0
votes

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-->
1
What about the 500 error? Is there an error page? Use Telerik Fiddler to capture the conversation and you should clearly see the error page which tells how to fix it.Lex Li
I've install fiddler and browsed to the web app but i don't see any more details in Fiddler other than the generic 500 error code.Alex
docs.telerik.com/fiddler/Observe-Traffic/Tasks/… Learn the tool, and use Inspectors to check the response body. WebView should show you an error page.Lex Li
I did that. None of the views from inspector of the session show anything.Alex
Show a full screen shot of your Fiddler window.Lex Li

1 Answers

13
votes

Found the problem. The outputpath attribute in angular.json was set to:

"outputPath": "dist/ClientApp"

when it should be:

"outputPath": "dist"

That was causing all the angular files to be generated in clientapp/dist/clientapp instead of clientapp/dist

I found it by turning on logging for the page in the web.config. The log file showed the index.html file was not found which led me to go looking where the angular files where being generated.