Search code examples
c#.netangularasp.net-corenancy

.NET Core, Angular2, NancyFX - 404 changing URL in browser


When serving the application with .NET Core static files I'm unable to change the URL in the browser. When doing so I'm given the

404 - Not Found

Nancy error page.

The only URL that's working is the base URL, localhost:5000. Changing the URL to localhost:5000/reservations gives me 404. Pressing F5 when not on the base URL will also result in a 404.

If I use the browsersync url, localhost:4200, everything works and I can change the URL and press F5 without 404. This is what I'm doing when developing but not when it's deployed on the server using IIS.

Running the application in IIS Express

  1. ng build (files are copied over to wwwroot along with index.html)

  2. start IIS Express

Startup.cs

public class Startup
{
    public Startup(IHostingEnvironment env)
    {
        var builder = new ConfigurationBuilder()
            .SetBasePath(env.ContentRootPath)
            .AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
            .AddEnvironmentVariables();
        Configuration = builder.Build();

        env.ConfigureNLog("nlog.config");
    }

    public IConfiguration Configuration { get; }

    public void ConfigureServices(IServiceCollection services)
    {
        services.AddAuthentication(IISDefaults.AuthenticationScheme);

        services.AddMvc(config =>
        {
            var policy = new AuthorizationPolicyBuilder()
                .RequireAuthenticatedUser()
                .Build();
            config.Filters.Add(new AuthorizeFilter(policy));
        });

        services.AddMvc();

        services.InjectServices();

        services.AddOptions();

        services.Configure<Endpoints>(Configuration.GetSection("Endpoints"));

        services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();

        services.AddSingleton<IConfiguration>(Configuration);
    }

    public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
    {
        app.Use(async (context, next) =>
        {
            await next();
            if (!context.Request.Path.Value.StartsWith("/api/"))
            {
                context.Request.Path = "/index.html";
                await next();
            }
        });

        app.UseDefaultFiles();

        app.UseStaticFiles();

        loggerFactory.AddNLog();

        app.AddNLogWeb();

        app.UseOwin(x => x.UseNancy(new NancyOptions
        {
            Bootstrapper = new Bootstrapper(app)
        }));
    }
}

Bootstrapper.cs

public class Bootstrapper : DefaultNancyBootstrapper
{
    readonly IApplicationBuilder _app;

    public Bootstrapper(IApplicationBuilder app)
    {
        _app = app;
    }

    protected override void ConfigureApplicationContainer(TinyIoCContainer container)
    {
        base.ConfigureApplicationContainer(container);

        container.Register<IOptions<Endpoints>>(_app.ApplicationServices.GetService<IOptions<Endpoints>>());

        container.Register<IReservationService>(_app.ApplicationServices.GetService<IReservationService>());
    }
}

wwwroot

enter image description here

index.html

<!doctype html>
<html lang="en">
<head>
    <meta charset="utf-8">
    <title>XXX</title>
    <base href="/">
    <meta name="viewport" content="width=device-width,initial-scale=1">
    <link rel="icon" type="image/x-icon" href="XXX">
    <link href="styles.61cc257ef8131303860b.bundle.css" rel="stylesheet" />
</head>
<body>
    <app-root></app-root>
    <script type="text/javascript" src="inline.f68c652d205feae6e02a.bundle.js"></script>
    <script type="text/javascript" src="polyfills.756a44edc7e0a68ce65c.bundle.js"></script>
    <script type="text/javascript" src="vendor.940a3ce39a746f717f7b.bundle.js"></script>
    <script type="text/javascript" src="main.d5f9dd82a3600822cae2.bundle.js"></script>
</body>
</html>

I understand that there is some problem with the handling of static content from the server but I can't seem to figure it out.

How can I navigate around using the browser URL when hosting the Angular application in .NET Core?

Update 1:

After adding:

PerformPassThrough = (context => context.Response.StatusCode == HttpStatusCode.NotFound)

the 404 error message goes away but page is now blank without any error message except the 404 in the console..

enter image description here


Solution

    1. Modified Startup.cs
    2. Added module that returns index.html for all requests.
    3. Configured root path in bootstrapper.cs.

    Startup.cs

    public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
    {
        //For serving index.html on baseurl - this can we done in AppNancyModule aswell: Get("/", _ => View["index"]);
        app.UseDefaultFiles();
    
        app.UseStaticFiles();
    
        loggerFactory.AddNLog();
    
        app.AddNLogWeb();
    
        app.UseOwin(x => x.UseNancy(new NancyOptions
        {
            Bootstrapper = new Bootstrapper(app, env)
        }));
    }
    

    AppNancyModule

    public class AppNancyModule : NancyModule
    {
        public AppNancyModule()
        {
            //Get("/", _ => View["index"]);
    
            Get(@"^(.*)$", _ => View["index"]);
        }
    }
    

    Bootstrapper.cs

    public class AppRootPathProvider : IRootPathProvider
    {
        private readonly IHostingEnvironment _environment;
    
        public AppRootPathProvider(IHostingEnvironment environment)
        {
            _environment = environment;
        }
        public string GetRootPath()
        {
            return _environment.WebRootPath;
        }
    }
    
    public class Bootstrapper : DefaultNancyBootstrapper
    {
        readonly IApplicationBuilder _app;
    
        protected override IRootPathProvider RootPathProvider { get; }
    
        public Bootstrapper(IApplicationBuilder app, IHostingEnvironment environment)
        {
            RootPathProvider = new AppRootPathProvider(environment);
            _app = app;
        }
    
        protected override void ConfigureApplicationContainer(TinyIoCContainer container)
        {
            base.ConfigureApplicationContainer(container);
    
            container.Register<IOptions<Endpoints>>(_app.ApplicationServices.GetService<IOptions<Endpoints>>());
    
            container.Register<IReservationService>(_app.ApplicationServices.GetService<IReservationService>());
        }
    }