500 error in Angular 6 .net core 2.1.1 application deployed to IIS
I have converted my project from the old Visual Studio SPA Angular template to the latest. I also converted the project from Angular 5 to Angular 6 and from webpack to angular-cli. The backend runs .Net Core 2.1.1.
This all works fine in development, but when deploying to IIS I get a 500 error. The latest hosting package is installed in IIS and works fine. I can deploy the old version of the app and it works fine, but the new version refuses to cooperate.
I also don't see the log file to try and track it down.
Any ideas?
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"
}
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");
}
});
}
}
}
this 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-->
Problem found. The outputpath property in angular.json is set to:
"outputPath": "dist/ClientApp"
When should it be:
"output path": "distribution"
This results in all angular files being generated in clientapp/dist/clientapp instead of clientapp/dist
I found it by turning on page logging in web.config. The log file showed that the index.html file was not found, which led me to see where the angular files were generated.