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
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:
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