Static files are NotFound in ASP NET 6 with Angular 12 and IndividualAccounts Visual Studio template

5.5k views Asked by At

We have a project that has been started 5 years ago in ASP .NET MVC 4. When DOTNET Core was released, we migrated the entire application in the Visual Studio with the Angular template. Since at that time Individual User Accounts were not supported in the Visual Studio project template, we created the Authentication with JWT Tokens.

We have been incrementally increasing both the DOTNET Core version and the Angular version when new steady versions were released.

Currently, the project is at DOTNET 5 and Angular 12. I have committed all my change to GitHub, if I start the application without changing anything, everything works. However, if I want to update the DOTNET Version to 6 and the only change I make is to right-click the project -> Properties and then increase the version to DOTNET 6, the application starts, but when I click on login, something is broken and it does not work, in Console I get:

POST https://localhost:44315/api/account/login 404
scheduleTask    @   zone.js:3382
push../node_modules/zone.js/dist/zone.js.ZoneDelegate.scheduleTask  @   zone.js:421
onScheduleTask  @   zone.js:311
push../node_modules/zone.js/dist/zone.js.ZoneDelegate.scheduleTask  @   zone.js:414
push../node_modules/zone.js/dist/zone.js.Zone.scheduleTask  @   zone.js:248
push../node_modules/zone.js/dist/zone.js.Zone.scheduleMacroTask @   zone.js:271
scheduleMacroTaskWithCurrentZone    @   zone.js:716
(anonymous) @   zone.js:3415
proto.<computed>
Ku.s    @   :44398/38b1e82a62a44…db/browserLink:2843
(anonymous) @   http.js:1919
push../node_modules/rxjs/dist/esm5/internal/Observable.js.Observable._trySubscribe  @   Observable.js:38
(anonymous) @   Observable.js:32
errorContext    @   errorContext.js:19
push../node_modules/rxjs/dist/esm5/internal/Observable.js.Observable.subscribe  @   Observable.js:23
doInnerSub  @   mergeInternals.js:18
outerNext   @   mergeInternals.js:13
OperatorSubscriber._this._next  @   OperatorSubscriber.js:11
push../node_modules/rxjs/dist/esm5/internal/Subscriber.js.Subscriber.next   @   Subscriber.js:34
(anonymous) @   from.js:55
push../node_modules/rxjs/dist/esm5/internal/Observable.js.Observable._trySubscribe  @   Observable.js:38
(anonymous) @   Observable.js:32
errorContext    @   errorContext.js:19
push../node_modules/rxjs/dist/esm5/internal/Observable.js.Observable.subscribe  @   Observable.js:23
mergeInternals  @   mergeInternals.js:47
(anonymous) @   mergeMap.js:14
(anonymous) @   lift.js:10
(anonymous) @   Observable.js:27
errorContext    @   errorContext.js:19
push../node_modules/rxjs/dist/esm5/internal/Observable.js.Observable.subscribe  @   Observable.js:23
(anonymous) @   filter.js:6
(anonymous) @   lift.js:10
(anonymous) @   Observable.js:27
errorContext    @   errorContext.js:19
push../node_modules/rxjs/dist/esm5/internal/Observable.js.Observable.subscribe  @   Observable.js:23
(anonymous) @   map.js:6
(anonymous) @   lift.js:10
(anonymous) @   Observable.js:27
errorContext    @   errorContext.js:19
push../node_modules/rxjs/dist/esm5/internal/Observable.js.Observable.subscribe  @   Observable.js:23
(anonymous) @   map.js:6
(anonymous) @   lift.js:10
(anonymous) @   Observable.js:27
errorContext    @   errorContext.js:19
push../node_modules/rxjs/dist/esm5/internal/Observable.js.Observable.subscribe  @   Observable.js:23
push../src/app/components/Account/login-form/login-form.component.ts.LoginFormComponent.login   @   login-form.component.ts:54
LoginFormComponent_Template_form_ngSubmit_6_listener    @   template.html:8
executeListenerWithErrorHandling    @   core.js:15308
wrapListenerIn_markDirtyAndPreventDefault   @   core.js:15346
(anonymous) @   Subscriber.js:123
push../node_modules/rxjs/dist/esm5/internal/Subscriber.js.Subscriber._next  @   Subscriber.js:63
push../node_modules/rxjs/dist/esm5/internal/Subscriber.js.Subscriber.next   @   Subscriber.js:34
(anonymous) @   Subject.js:38
errorContext    @   errorContext.js:19
push../node_modules/rxjs/dist/esm5/internal/Subject.js.Subject.next @   Subject.js:30
emit    @   core.js:25935
onSubmit    @   forms.js:4113
NgForm_submit_HostBindingHandler    @   forms.js:4146
executeListenerWithErrorHandling    @   core.js:15308
wrapListenerIn_markDirtyAndPreventDefault   @   core.js:15346
(anonymous) @   platform-browser.js:560
push../node_modules/zone.js/dist/zone.js.ZoneDelegate.invokeTask    @   zone.js:434
onInvokeTask    @   core.js:28659
push../node_modules/zone.js/dist/zone.js.ZoneDelegate.invokeTask    @   zone.js:433
push../node_modules/zone.js/dist/zone.js.Zone.runTask   @   zone.js:205
push../node_modules/zone.js/dist/zone.js.ZoneTask.invokeTask    @   zone.js:516
invokeTask  @   zone.js:1656
globalZoneAwareCallback

And I get 404 for all my API calls.

My Startup.cs is:

public class Startup
    {
        private static string databaseConnection = "";
        private const string SecretKey = ""; 
        private readonly SymmetricSecurityKey _signingKey = new SymmetricSecurityKey(Encoding.ASCII.GetBytes(SecretKey));
        private readonly IHostEnvironment hostingEnvironment;

        public Startup(IConfiguration configuration, IHostEnvironment host)
        {
            Configuration = configuration;
            hostingEnvironment = host;
        }

        public IConfiguration Configuration { get; }

        // This method gets called by the runtime. Use this method to add services to the container.
        public void ConfigureServices(IServiceCollection services)
        {
            // Auto Mapper Configurations
            var mappingConfig = new MapperConfiguration(mc =>
            {
                mc.AddProfile(new MappingProfile());
            }); 

            IMapper mapper = mappingConfig.CreateMapper();
            services.AddSingleton(mapper);

            databaseConnection = Configuration.GetConnectionString("DefaultConnection");
            if (databaseConnection == null) databaseConnection = "";

            services.AddDbContext<ApplicationDbContext>(options =>
                options.UseSqlServer(databaseConnection));

            DatabaseConnection.ConnectionString = databaseConnection;

            services.AddSingleton<IJwtFactory, JwtFactory>();

            services.AddIdentity<ApplicationUser, IdentityRole>()
                .AddEntityFrameworkStores<ApplicationDbContext>()
                .AddDefaultTokenProviders();

            // jwt wire up
            // Get options from app settings
            var jwtAppSettingOptions = Configuration.GetSection(nameof(JwtIssuerOptions));

            // Configure JwtIssuerOptions
            services.Configure<JwtIssuerOptions>(options =>
            {
                options.Issuer = jwtAppSettingOptions[nameof(JwtIssuerOptions.Issuer)];
                options.Audience = jwtAppSettingOptions[nameof(JwtIssuerOptions.Audience)];
                options.SigningCredentials = new SigningCredentials(_signingKey, SecurityAlgorithms.HmacSha256);
            });

            services.AddAuthorization(options =>
            {
                options.AddPolicy("ApiUser", policy => policy.RequireClaim(Constants.Strings.JwtClaimIdentifiers.Rol, Constants.Strings.JwtClaims.ApiAccess));
            });


            var authenticationType = Configuration["Authentication:Type"];

            if (authenticationType == "ActiveDirectory")
            {
                services.AddAuthentication(IISDefaults.AuthenticationScheme);
            }
            else if (authenticationType == "AzureActiveDirectory")
            {
                services.AddAuthentication(defaultScheme: AzureADDefaults.AuthenticationScheme).AddAzureAD(options => Configuration.Bind("AzureAd", options));
            }

            services.Configure<OpenIdConnectOptions>(AzureADDefaults.OpenIdScheme, options =>
            {
                options.Authority = options.Authority + "/v2.0/";         // Microsoft identity platform

                options.TokenValidationParameters.ValidateIssuer = false; // accept several tenants (here simplified)
            });

            services.AddCors(c =>
            {
                c.AddPolicy("AllowOrigin",
                    options => options
                            .AllowAnyOrigin()
                            .AllowAnyMethod()
                            .AllowAnyHeader()
                            .AllowCredentials()//Add this line
                        );
            });

            // Add application services.
            services.AddTransient<IEmailSender, EmailSender>();
            services.AddNodeServices();

            services.AddSingleton(Configuration);

            services.Configure<CookiePolicyOptions>(options =>
            {
                // This lambda determines whether user consent for non-essential cookies is needed for a given request.
                options.CheckConsentNeeded = context => true;
                options.MinimumSameSitePolicy = SameSiteMode.None;
            });

            services.AddRazorPages();
            services.AddControllers().AddNewtonsoftJson();

            // In production, the Angular files will be served from this directory
            services.AddSpaStaticFiles(configuration =>
            {
                configuration.RootPath = "ClientApp/dist";
            });

            // If using IIS:
            services.Configure<IISServerOptions>(options =>
            {
                options.AllowSynchronousIO = true;
            });
        }

        // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
        public void Configure(IApplicationBuilder app, IHostEnvironment env, IServiceProvider serviceProvider, ILoggerFactory loggerFactory)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }
            else
            {
                app.UseExceptionHandler("/Error");
                app.UseHsts();
            }

            var importsPath = Path.Combine(new string[] { hostingEnvironment.ContentRootPath, "wwwroot", "imports" });
            if (Directory.Exists(importsPath))
            {
                Directory.Delete(importsPath, true);
            }

            app.UseHttpsRedirection();
            app.UseStaticFiles();
            app.UseSpaStaticFiles();

            var jwtAppSettingOptions = Configuration.GetSection(nameof(JwtIssuerOptions));
            var tokenValidationParameters = new TokenValidationParameters
            {
                ValidateIssuer = true,
                ValidIssuer = jwtAppSettingOptions[nameof(JwtIssuerOptions.Issuer)],

                ValidateAudience = true,
                ValidAudience = jwtAppSettingOptions[nameof(JwtIssuerOptions.Audience)],

                ValidateIssuerSigningKey = true,
                IssuerSigningKey = _signingKey,


                RequireExpirationTime = false,
                ValidateLifetime = false,
                ClockSkew = TimeSpan.Zero
            };

            app.UseRouting();

            app.UseAuthentication();
            app.UseAuthorization();

            var authenticationType = Configuration["Authentication:Type"];

            if (authenticationType == "ActiveDirectory")
            {

            }
            else if (authenticationType == "AzureActiveDirectory")
            {

            }

            app.UseEndpoints(endpoints =>
            {
                endpoints.MapControllerRoute("default", "{controller=Home}/{action=Index}/{id?}");
            });

            loggerFactory.AddLog4Net();
            
            app.UseSpa(spa =>
            {
                // To learn more about options for serving an Angular SPA from ASP.NET Core,
                // see https://go.microsoft.com/fwlink/?linkid=864501

                spa.Options.SourcePath = "ClientApp";

                if (env.IsDevelopment())
                {
                    spa.Options.StartupTimeout = new TimeSpan(0, 5, 0);
                    spa.UseAngularCliServer(npmScript: "start");
                }
            });

            CreateRoles(serviceProvider);
            
            if (Thread.CurrentThread.CurrentUICulture.Name == "en-US")
                SessionConstants.CultureInfo = CultureInfo.CreateSpecificCulture("ro-RO");
            else
                SessionConstants.CultureInfo = CultureInfo.CreateSpecificCulture("ro-RO");

            Thread.CurrentThread.CurrentCulture = SessionConstants.CultureInfo;
            Thread.CurrentThread.CurrentUICulture = SessionConstants.CultureInfo;

            Directory.CreateDirectory(importsPath);
        }

        private void CreateRoles(IServiceProvider serviceProvider)
        {
            var roleManager = serviceProvider.GetRequiredService<RoleManager<IdentityRole>>();
            var userManager = serviceProvider.GetRequiredService<UserManager<ApplicationUser>>();
            Task<IdentityResult> roleResult;
            
            //Check that there is an Administrator role and create if not

            foreach (var roleName in RoleNames.Build())
            {
                Task<bool> hasAdminRole = roleManager.RoleExistsAsync(roleName);
                hasAdminRole.Wait();
                if (!hasAdminRole.Result)
                {
                    roleResult = roleManager.CreateAsync(new IdentityRole(roleName));
                    roleResult.Wait();
                }
            }

        }

        public static void AddErrorToDatabase(Exception e)
        {
            // Get stack trace for the exception with source file information
            var st = new StackTrace(e, true);
            // Get the stack frames

            string file = "";
            string fileTemp = "";
            string method = "";
            string lineNumber = "";

            foreach (StackFrame frame in st.GetFrames())
            {
                // Get the file name from the stack frame
                fileTemp = frame.GetFileName() ?? "";
                fileTemp = fileTemp.Replace('\\', '-').Split('-').Last().Trim();

                int line = frame.GetFileLineNumber();

                if (line > 0)
                {
                    file += "-> " + fileTemp + "\n";

                    // Get the method from the stack frame
                    method = "-> " + frame.GetMethod().ToString().Substring(frame.GetMethod().ToString().IndexOf(' '), frame.GetMethod().ToString().IndexOf('(') - frame.GetMethod().ToString().IndexOf(' ')) + "\n";

                    // Get the line number from the stack frame
                    lineNumber += "-> " + frame.GetFileLineNumber().ToString() + "\n";
                }
            }

            string destails = e.Message;

            if (e.InnerException != null)
            {
                var innerException = e;

                Exception realerror = e;
                while (realerror.InnerException != null)
                {
                    realerror = realerror.InnerException;
                    destails += "\n" + realerror.Message;
                }
            }

            if (databaseConnection != null)
            {
                string QueryString = "INSERT INTO Error(Description,Moment,[File],Method,Line) VALUES (@description,@moment,@file,@method,@line)";

                System.Type tipProdus = e.GetType();

                SqlConnection myConnection = new SqlConnection(databaseConnection);
                SqlDataAdapter myCommand = new SqlDataAdapter(QueryString, myConnection);
                DataSet ds = new DataSet();

                // add the elements to the list
                using (SqlConnection con = new SqlConnection(databaseConnection))
                {
                    con.Open();
                    using (SqlCommand cmd = new SqlCommand(QueryString, con))
                    {
                        cmd.Parameters.AddWithValue("@description", destails);
                        cmd.Parameters.AddWithValue("@moment", DateTime.Now);
                        cmd.Parameters.AddWithValue("@file", file);
                        cmd.Parameters.AddWithValue("@method", method);
                        cmd.Parameters.AddWithValue("@line", lineNumber);
                        cmd.ExecuteNonQuery();
                    }
                    con.Close();
                }

            }
            else
            {
                //string folderPath = System.IO.Directory.GetCurrentDirectory();
                string path = "errors" + DateTime.Now.ToString("ddMMyyyyhhmmss") + ".txt";
                if (!File.Exists(path))
                {
                    // Create a file to write to.
                    using (StreamWriter sw = File.CreateText(path))
                    {
                        sw.WriteLine(destails);
                        sw.WriteLine(DateTime.Now);
                        sw.WriteLine(file);
                        sw.WriteLine(method);
                        sw.WriteLine(lineNumber);
                    }
                }
            }
        }

        public static void WriteToFile(string root, string message)
        {
            string path = root;

            path = Path.Combine(path, DateTime.Now.ToString("ddMMyyyyHHmmss") + ImportReturn.CleanFileName("log.txt"));
            
            System.IO.File.WriteAllText(path, message);
            
        }
    }

And I do not understand why. It is probably something that needs to be added to Startup file I believe, but I have been looking everywhere for the cause and I cannot find it.

Since this was not working, I tried creating the project from scratch in Visual Studio because now the template does support Individual User Accounts and I believe it can be better than the implementation I integrated.

The problem is, after I created the project and I tried to include the CSS and JS files like they were included in the old project like so:

<!DOCTYPE html>
<html lang="ro">
<head>
  <meta charset="utf-8" />
  <title>SIGAD</title>
  <base href="/" />

  <meta name="viewport" content="width=device-width, initial-scale=1" />
  <link rel="icon" type="image/x-icon" href="favicon.ico" />

  <link href="/js/libs/bootstrap/dist/css/bootstrap.min.css" rel="stylesheet" />

  <link href="/css/superhero.css" rel="stylesheet">

  <link href="/ss/lobipanel.min.css" rel="stylesheet" />

  <!--This page css - Morris CSS -->
  <link href="/plugins/c3-master/c3.min.css" rel="stylesheet">
  <!-- Custom CSS -->
  <!--alerts CSS -->
  <link href="/plugins/sweetalert/sweetalert.css" rel="stylesheet" type="text/css">

  <link href="/css/icons/font-awesome/css/font-awesome.min.css" rel="stylesheet">
  <link href="/css/icons/simple-line-icons/css/simple-line-icons.css" rel="stylesheet">
  <link href="/css/icons/weather-icons/css/weather-icons.min.css" rel="stylesheet">
  <link href="/css/icons/linea-icons/linea.css" rel="stylesheet">
  <link href="/css/icons/themify-icons/themify-icons.css" rel="stylesheet">
  <link href="/css/icons/flag-icon-css/flag-icon.min.css" rel="stylesheet">
  <link href="/css/icons/material-design-iconic-font/css/materialdesignicons.min.css" rel="stylesheet">
  <link href="/css/spinners.css" rel="stylesheet">
  <link href="/css/animate.css" rel="stylesheet">

  <!--Range slider CSS -->
  <link href="/plugins/ion-rangeslider/css/ion.rangeSlider.css" rel="stylesheet">
  <link href="/plugins/ion-rangeslider/css/ion.rangeSlider.skinModern.css" rel="stylesheet">

  <link href="/css/style.css" rel="stylesheet">

  <link href="/css/picker.min.css" rel="stylesheet">

  <!-- You can change the theme colors from here -->
  <link href="/css/colors/green-dark.css" id="theme" rel="stylesheet">
  <link href="/css/font-awesome.min.css" id="theme" rel="stylesheet">

</head>
<body>

  <app-root></app-root>

  <!--<script src="/js/jquery-2.2.4.min.js" type="text/javascript"></script>
  <script src="/js/crm/jquery-ui.min.js" asp-append-version="true"></script>-->
  <script src="/js/libs/jquery/dist/jquery.min.js"></script>
  <!-- ============================================================== -->
  <!-- All Jquery -->
  <!-- ============================================================== -->
  <!-- Bootstrap tether Core JavaScript -->
  <!--<script src="/plugins/bootstrap/js/popper.min.js"></script>
  <script src="/plugins/bootstrap/js/bootstrap.min.js"></script>-->

  <script src="/js/libs/popper.js/dist/umd/popper.min.js"></script>
  <script src="/js/libs/bootstrap/dist/js/bootstrap.min.js"></script>

  <!--<script src="/js/crm/lobipanel.min.js" asp-append-version="true"></script>

  <script src="/js/tableExport.js" asp-append-version="true"></script>-->
  <!-- slimscrollbar scrollbar JavaScript -->
  <script src="/js/crm/jquery.slimscroll.min.js" asp-append-version="true"></script>
  <!--<script src="/js/crm/fastclick.min.js" asp-append-version="true"></script>-->
  <!--Wave Effects -->
  <script src="/js/waves.js"></script>
  <!--Menu sidebar -->
  <script src="/js/sidebarmenu.js"></script>
  <!--stickey kit -->
  <script src="/plugins/sticky-kit-master/dist/sticky-kit.min.js"></script>
  <script src="/plugins/sparkline/jquery.sparkline.min.js"></script>
  <!--Custom JavaScript -->
  <script src="/js/custom.min.js"></script>
  <!-- ============================================================== -->
  <!-- This page plugins -->
  <!-- ============================================================== -->
  <!-- Plugin JavaScript -->
  <script src="/plugins/moment/moment.js"></script>
  <!-- Clock Plugin JavaScript -->
  <script src="/plugins/sticky-kit-master/dist/sticky-kit.min.js"></script>
  <script src="/plugins/sparkline/jquery.sparkline.min.js"></script>
  <script src="/plugins/chartJs/Chart.min.js"></script>
  <!--Custom JavaScript -->
  <!--<script src="/js/jasny-bootstrap.js"></script>-->

  <script src="/plugins/d3/d3.min.js"></script>
  <script src="/plugins/c3-master/c3.min.js"></script>
  <script src="/js/site.js"></script>

  <script src="/js/jquery.floatThead.min.js"></script>

  <!-- Range slider  -->
  <script src="/plugins/ion-rangeslider/js/ion-rangeSlider/ion.rangeSlider.min.js"></script>
</body>
</html>

I keep getting:

Refused to apply style from 'https://localhost:44467/wwwroot/css/superhero.css' because its MIME type ('text/html') is not a supported stylesheet MIME type, and strict MIME checking is enabled.

for all the CSS files and

Failed to load resource: the server responded with a status of 404 (Not Found) jquery.min.js:1

for all the JS files.

This means that the static files are not added.

I have tried all the possible paths: with ~, with "/wwwroot" at the begging, nothing seems to work.

Program.cs (since Stratup.cs does not exists anymore) does include app.UseStaticFiles();

The full configuration is:

var builder = WebApplication.CreateBuilder(args);

// Add services to the container.
var connectionString = builder.Configuration.GetConnectionString("DefaultConnection");
builder.Services.AddDbContext<ApplicationDbContext>(options =>
    options.UseSqlServer(connectionString));
builder.Services.AddDatabaseDeveloperPageExceptionFilter();

builder.Services.AddDefaultIdentity<ApplicationUser>(options => options.SignIn.RequireConfirmedAccount = false)
    .AddEntityFrameworkStores<ApplicationDbContext>();

builder.Services.AddIdentityServer()
    .AddApiAuthorization<ApplicationUser, ApplicationDbContext>();

builder.Services.AddAuthentication()
    .AddIdentityServerJwt();

builder.Services.AddControllersWithViews();
builder.Services.AddRazorPages();

var app = builder.Build();

// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
    app.UseMigrationsEndPoint();
}
else
{
    // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
    app.UseHsts();
}

app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();

app.UseAuthentication();
app.UseIdentityServer();
app.UseAuthorization();

app.MapControllerRoute(
    name: "default",
    pattern: "{controller}/{action=Index}/{id?}");
app.MapRazorPages();

app.MapFallbackToFile("index.html"); ;

app.Run();

var loggerFactory = LoggerFactory.Create(builder => builder.AddConsole());

I need help to understand why I cannot include my static files please.

The first problem I wrote just for the whole story to be fully understood and maybe somebody else needs help with that too.

UPDATE:

I managed to load the files, but only if I move them in the src/assets folder and load them from the main Angular style.css and angular.json files. They do work.

The more weird problem is that neither the images are loaded, I also get 404 for them if they are in the wwwroot/images folder, but not if I copy them in the src/assets/images folder (if I add "/assets" path before the older "/images/...").

I really do not understantd why the files in the wwwroot folder are not loaded, not even if I put "../../wwwroot/" before the old path.

Thank you

3

There are 3 answers

2
Цветан Иванов On

Your problem is related to the package Microsoft.AspNetCore.SpaProxy. It's used to start Angular CLI server in the background when the ASP.NET Core app starts.

You can confirm this by checking that once you start the project there are two consoles opened. Your asp.net core app can access the wwwroot and angular the ClientApp.

In your project file you have a bunch of settings:

  1. SpaRoot - The root of your angular project. From here the angular app is serving it's files.
  2. SpaProxyServerUrl - The URL of the angular app. It's different then the ASP.NET core URL. You are trying to access the wwwroot files from this URL which is not working.
  3. SpaProxyLaunchCommand

The package Microsoft.AspNetCore.SpaProxy is being started as a hosting startup assembly in the launchSettings.json. https://learn.microsoft.com/en-us/aspnet/core/fundamentals/host/platform-specific-configuration?view=aspnetcore-6.0


You can read more about the angular template here: https://learn.microsoft.com/en-us/aspnet/core/client-side/spa/angular

The source code of the package https://github.com/dotnet/aspnetcore/tree/main/src/Middleware/Spa/SpaProxy

4
Shadi Alnamrouti On

My suggestion is to separate the backend from frontend: 1- To create a new .net 6 web API which has no startup in the latest template. Not just to target .net6 2- To create a new Angular 13 project using cmd or code which is separate from the backend 3- Fix the interaction between them without the hybrid codes from Microsoft

5
Miloš On

OK, based on Cvetan's answer, I figured out that you can add all routes you want to proxy to the proxy.conf.js in your ClientApp folder. So, I added my controllers paths, css folder, js folder and favicon path from wwwroot. Now all works. Thanks Cvetan.

P.S. This change from Microsoft with starting the proxy was announced earlier for VS 2022 when timeouts started to appear with UseAngularCliServer method used in ASP.NET Core with Angular template in VS 2019.

I am updating the answer with the example of the code that Andrei requested. Consider that css and js folders are under the wwwroot and those folders contain site.css and site.js respectively (for example)

const { env } = require('process');

const target = env.ASPNETCORE_HTTPS_PORT ? `https://localhost:${env.ASPNETCORE_HTTPS_PORT}` :
  env.ASPNETCORE_URLS ? env.ASPNETCORE_URLS.split(';')[0] : 'http://localhost:10992';

const PROXY_CONFIG = [
  {
    context: [
      "/api/user-profiles",      
      "/_configuration",
      "/css",
      "/js",
      "/favicon.ico",
      "/.well-known",
      "/Identity",
      "/connect",
      "/ApplyDatabaseMigrations",
      "/_framework"
   ],
    target: target,
    secure: false
  }
]

module.exports = PROXY_CONFIG;

If this helps, please upvote the answer. Thanks.