I've converted my project from the old Visual Studio SPA Angular template to the latest one. I've also converted the project from Angular 5 to Angular 6 and from webpack to angular-cli. The back end is running .Net Core 2.1.1.
It all works great in dev but I'm getting a 500 error when deployed to IIS. The latest hosting bundle is installed in IIS and running ok. I can deploy the old version of the application and it runs fine but the new one refuses to cooperate.
I'm also not seeing the log files to try to track it down.
Any thoughts?
Here is my angular.json
{
"$schema": "./node_modules/@angular/cli/lib/config/schema.json",
"version": 1,
"newProjectRoot": "projects",
"projects": {
"ClientApp": {
"root": "",
"sourceRoot": "src",
"projectType": "application",
"prefix": "app",
"schematics": {
"@schematics/angular:component": {
"styleext": "scss"
}
},
"architect": {
"build": {
"builder": "@angular-devkit/build-angular:browser",
"options": {
"outputPath": "dist/ClientApp",
"index": "src/index.html",
"main": "src/main.ts",
"polyfills": "src/polyfills.ts",
"tsConfig": "src/tsconfig.app.json",
"assets": [
"src/favicon.ico",
"src/assets"
],
"styles": [
"node_modules/font-awesome/css/font-awesome.min.css",
"node_modules/primeng/resources/primeng.min.css",
"node_modules/primeicons/primeicons.css",
"node_modules/videogular2/fonts/videogular.css",
"src/assets/theme/theme-blue.scss",
"src/styles.scss"
],
"scripts": []
},
"configurations": {
"production": {
"fileReplacements": [
{
"replace": "src/environments/environment.ts",
"with": "src/environments/environment.prod.ts"
}
],
"optimization": true,
"outputHashing": "all",
"sourceMap": false,
"extractCss": true,
"namedChunks": false,
"aot": true,
"extractLicenses": true,
"vendorChunk": false,
"buildOptimizer": true
}
}
},
"serve": {
"builder": "@angular-devkit/build-angular:dev-server",
"options": {
"browserTarget": "ClientApp:build"
},
"configurations": {
"production": {
"browserTarget": "ClientApp:build:production"
}
}
},
"extract-i18n": {
"builder": "@angular-devkit/build-angular:extract-i18n",
"options": {
"browserTarget": "ClientApp:build"
}
},
"test": {
"builder": "@angular-devkit/build-angular:karma",
"options": {
"main": "src/test.ts",
"polyfills": "src/polyfills.ts",
"tsConfig": "src/tsconfig.spec.json",
"karmaConfig": "src/karma.conf.js",
"styles": [
"src/styles.scss"
],
"scripts": [],
"assets": [
"src/favicon.ico",
"src/assets"
]
}
},
"lint": {
"builder": "@angular-devkit/build-angular:tslint",
"options": {
"tsConfig": [
"src/tsconfig.app.json",
"src/tsconfig.spec.json"
],
"exclude": [
"**/node_modules/**"
]
}
}
}
},
"ClientApp-e2e": {
"root": "e2e/",
"projectType": "application",
"architect": {
"e2e": {
"builder": "@angular-devkit/build-angular:protractor",
"options": {
"protractorConfig": "e2e/protractor.conf.js",
"devServerTarget": "ClientApp:serve"
},
"configurations": {
"production": {
"devServerTarget": "ClientApp:serve:production"
}
}
},
"lint": {
"builder": "@angular-devkit/build-angular:tslint",
"options": {
"tsConfig": "e2e/tsconfig.e2e.json",
"exclude": [
"**/node_modules/**"
]
}
}
}
}
},
"defaultProject": "ClientApp"
}
And here is my startup.cs
using Driver.Server.DBContext;
using Driver.Server.Repositories;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.HttpsPolicy;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.SpaServices.AngularCli;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using AutoMapper;
using Driver.Server.Models.Entities;
using System;
using Microsoft.AspNetCore.Identity;
using System.Net;
using Microsoft.AspNetCore.Diagnostics;
using Driver.Server.Helpers;
using Microsoft.AspNetCore.Http;
using Driver.Server.Auth;
using Microsoft.Extensions.DependencyInjection.Extensions;
using Microsoft.IdentityModel.Tokens;
using System.Text;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Driver.Server.Services;
using Microsoft.AspNetCore.Antiforgery;
using Driver.Server.Models;
using Microsoft.Extensions.Logging;
using Driver.Server.Repositories.Interfaces;
namespace Driver {
public class Startup {
private readonly IConfiguration _config;
public Startup(IConfiguration config) {
_config = config;
}
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) {
services.AddDbContext<DriverDbContext>(options => options.UseSqlServer(_config.GetConnectionString("DriverDBConnection")));
services.AddSingleton<IJwtFactory, JwtFactory>();
services.TryAddTransient<IHttpContextAccessor, HttpContextAccessor>();
services.AddIdentity<AppUser, AppRole>()
.AddEntityFrameworkStores<DriverDbContext>()
.AddDefaultTokenProviders();
var settings = _config.GetSection("AuthenticationSettings").Get<AuthenticationSettings>();
services.AddScoped<IMailService, MailService>();
var signingKey = new SymmetricSecurityKey(Encoding.ASCII.GetBytes(settings.SecretKey));
// Configure JwtIssuerOptions
services.Configure<JwtIssuerOptions>(options => {
options.Issuer = settings.JwtIssuerOpts.Issuer;
options.Audience = settings.JwtIssuerOpts.Audience;
options.SigningCredentials = new SigningCredentials(signingKey, SecurityAlgorithms.HmacSha256);
});
// Specify the validation parameters to dictate how we want received tokens validated
var tokenValidationParameters = new TokenValidationParameters {
ValidateIssuer = true,
ValidIssuer = settings.JwtIssuerOpts.Issuer,
ValidateAudience = true,
ValidAudience = settings.JwtIssuerOpts.Audience,
ValidateIssuerSigningKey = true,
IssuerSigningKey = signingKey,
RequireExpirationTime = false,
ValidateLifetime = true,
ClockSkew = TimeSpan.Zero
};
services.AddAuthentication(options => {
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
}).AddJwtBearer(options => {
options.ClaimsIssuer = settings.JwtIssuerOpts.Issuer;
options.TokenValidationParameters = tokenValidationParameters;
options.SaveToken = true;
});
// Create an authorization claim policy to guard API controllers and actions
services.AddAuthorization(options => {
options.AddPolicy(
Constants.PolicyNames.ApiUser,
policy => policy.RequireClaim(
Constants.JwtClaimIdentifiers.Rol,
Constants.Claims.ApiAccess));
});
services.Configure<IdentityOptions>(options => {
// Password settings
options.Password.RequireDigit = settings.PasswordSettings.RequiredDigit;
options.Password.RequiredLength = settings.PasswordSettings.RequiredLength;
options.Password.RequireNonAlphanumeric = settings.PasswordSettings.RequireNonAlphanumeric;
options.Password.RequireUppercase = settings.PasswordSettings.RequireUppercase;
options.Password.RequireLowercase = settings.PasswordSettings.RequireLowercase;
options.Password.RequiredUniqueChars = settings.PasswordSettings.RequiredUniqueChars;
// Lockout settings
options.Lockout.DefaultLockoutTimeSpan = TimeSpan.FromMinutes(settings.LockoutSettings.DefaultLockoutTimeSpan);
options.Lockout.MaxFailedAccessAttempts = settings.LockoutSettings.MaxFailedAccessAttempts;
options.Lockout.AllowedForNewUsers = settings.LockoutSettings.AllowedForNewUsers;
// User settings
options.User.RequireUniqueEmail = settings.UserSettings.RequireUniqueEmail;
});
services.AddAntiforgery(options => options.HeaderName = "X-XSRF-TOKEN");
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
// In production, the Angular files will be served from this directory
services.AddSpaStaticFiles(configuration => {
configuration.RootPath = "ClientApp/dist";
});
services.AddAutoMapper(typeof(Startup));
services.AddSingleton(_config);
services.AddScoped<ICompanyRepository, CompanyRepository>();
services.AddScoped<ILookupRepository, LookupRepository>();
services.AddScoped<IManagerRepository, ManagerRepository>();
services.AddScoped<IAppUserRepository, AppUserRepository>();
services.AddScoped<IAppRoleRepository, AppRoleRepository>();
services.AddScoped<IRefreshTokenRepository, RefreshTokenRepository>();
services.AddScoped<ILoginAttemptRepository, LoginAttemptRepository>();
services.AddScoped<INavigationTraceRepository, NavigationTraceRepository>();
services.AddScoped<IVideoRepository, VideoRepository>();
services.AddScoped<IJobRequisitionRepository, JobRequisitionRepository>();
services.AddScoped<IJobOrderRepository, JobOrderRepository>();
services.AddScoped<IMailService, MailService>();
services.AddScoped<IEmailLogRepository, EmailLogRepository>();
services.AddScoped<ITrackingRepository, TrackingRepository>();
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(
IApplicationBuilder app,
IHostingEnvironment env,
IAntiforgery antiforgery) {
if (env.IsDevelopment()) {
app.UseDeveloperExceptionPage();
}
else {
app.UseHsts();
}
app.UseExceptionHandler(
builder => {
builder.Run(
async context => {
context.Response.StatusCode = (int)HttpStatusCode.InternalServerError;
context.Response.Headers.Add("Access-Control-Allow-Origin", "*");
var ex = context.Features.Get<IExceptionHandlerFeature>();
if (ex != null) {
var message = string.Join(
Environment.NewLine,
"MESSAGE:",
ex.Error.Message,
ex.Error.InnerException != null ? "INNER EXCEPTION MESSAGE:" + Environment.NewLine + ex.Error.InnerException.Message : ""
);
context.Response.AddApplicationError(ex.Error.Message);
await context.Response.WriteAsync(message).ConfigureAwait(false);
}
});
});
//Todo: Turn on Authentication
app.UseAuthentication();
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseSpaStaticFiles();
//If request is first entry point to server (not an api call)
//Generate the anti-forgery cookies
app.Use(async (context, next) => {
string path = context.Request.Path.Value;
if (path != null && !path.Contains("/api", StringComparison.OrdinalIgnoreCase)) {
// XSRF-TOKEN used by angular in the $http if provided
context.Response.Cookies.Append(
"XSRF-TOKEN",
antiforgery.GetAndStoreTokens(context).RequestToken,
new CookieOptions { HttpOnly = false, Secure = true }
);
}
await next();
});
app.UseMvc(routes => {
routes.MapRoute(
name: "default",
template: "{controller}/{action=Index}/{id?}");
});
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.UseAngularCliServer(npmScript: "start");
}
});
}
}
}
Here is my web.config
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<system.webServer>
<security>
<requestFiltering>
<requestLimits maxAllowedContentLength="209715200" />
</requestFiltering>
</security>
<rewrite>
<rules>
<rule name="HTTP/S to HTTPS Redirect" enabled="true" stopProcessing="true">
<match url="(.*)" />
<conditions logicalGrouping="MatchAny">
<add input="{SERVER_PORT_SECURE}" pattern="^0$" />
</conditions>
<action type="Redirect" url="https://{HTTP_HOST}{REQUEST_URI}" redirectType="Permanent" />
</rule>
</rules>
</rewrite>
<modules runAllManagedModulesForAllRequests="false">
<remove name="WebDAVModule" />
</modules>
<handlers>
<add name="aspNetCore" path="*" verb="*" modules="AspNetCoreModule" resourceType="Unspecified" />
</handlers>
<aspNetCore processPath="dotnet" arguments=".\Driver.dll" stdoutLogEnabled="false" stdoutLogFile=".\logs\stdout" />
</system.webServer>
</configuration>
<!--ProjectGuid: 3bb90a77-cfdb-4435-bad5-3895ee14f1d3-->