MVC 6 Routing, SPA fallback + 404 Error Page

2020-02-28 06:06发布

问题:

With RC1 of ASP.NET Core 1.0's MVC 6 you can map routes from within your Startup.Configure function when invoking app.UseMvc. I have mapped a "spa-fallback" route that will ensure that the HomeController and Index view are the defaults like so:

public void Configure(IApplicationBuilder app, 
                      IHostingEnvironment env, 
                      ILoggerFactory loggerFactory)
{
    // ... omitted for brevity
    app.UseExceptionHandler("/Home/Error");
    app.UseStatusCodePagesWithRedirects("/Home/Error/{0}");

    app.UseMvc(routes =>
    {
        routes.MapRoute("default", "{controller=Home}/{action=Index}/{id?}");
        routes.MapRoute("spa-fallback", "{*anything}", new { controller = "Home", action = "Index" });
        routes.MapWebApiRoute("defaultApi", "api/{controller}/{id?}");
    });
}

I desire the fallback so that my Angular2 app's routes will not result in an HTTP Status Code of 404, Not Found. But I also need to correctly handle when a user does inadvertently attempt to navigate to a page view that doesn't exist. You might notice that I have also called app.UseStatusCodePagesWithRedirects("/Home/Error/{0}");.

The call to redirect to my error page with the status code and the "spa-fallback" route seem mutually exclusive -- meaning it appears that I can only have one or the other (but sadly not both). Does anyone know how I could manage to have the best of both worlds?

回答1:

It took me some time to figure out how to do this without serving my index using MVC and to still receive 404s for missing files. Here's my http pipeline:

    public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
    {
        app.UseDefaultFiles();
        app.UseStaticFiles();

        app.UseMvc(                
            routes => {
                routes.MapRoute(
                    name: "api",
                    template: "api/{controller}/{action}/{id?}");

                // ## commented out since I don't want to use MVC to serve my index.    
                // routes.MapRoute(
                //     name:"spa-fallback", 
                //     template: "{*anything}", 
                //     defaults: new { controller = "Home", action = "Index" });
        });

        // ## this serves my index.html from the wwwroot folder when 
        // ## a route not containing a file extension is not handled by MVC.  
        // ## If the route contains a ".", a 404 will be returned instead.
        app.MapWhen(context => context.Response.StatusCode == 404 &&
                               !Path.HasExtension(context.Request.Path.Value),
                    branch => {
                           branch.Use((context, next) => {
                               context.Request.Path = new PathString("/index.html");
                               Console.WriteLine("Path changed to:" + context.Request.Path.Value);
                               return next();});

                    branch.UseStaticFiles();
        });            
    }


回答2:

I came up with two possible workarounds/solutions.

ASP.NET Core 1.0, RC1

With Angular2 specifically, we can simply swap out the LocationStrategy. For example in my wwwroot/app/boot.ts I have the following:

import {provide} from "angular2/core";
import {bootstrap} from "angular2/platform/browser";
import {LocationStrategy, HashLocationStrategy, ROUTER_PROVIDERS} from "angular2/router";
import {HTTP_PROVIDERS} from "angular2/http";

import {AppComponent} from "./app.component"

bootstrap(AppComponent,
    [
        ROUTER_PROVIDERS,
        HTTP_PROVIDERS,
        provide(LocationStrategy, { useClass: HashLocationStrategy })
    ]);

Note that I am utilizing the provide function to override the standard LocationStrategy with the HashLocationStrategy. This adds the # in the URL and prevents MVC from incorrectly throwing 404's, but also correctly handles responding with 404's when the user manually types in a URL that doesn't exist.

ASP.NET Core 1.0, RC2

Simply use Microsoft.AspNet.NodeServices. Within this library there are both AngularServices and ReactServices which allow for the mapping of SPA routes specifically, via the MapSpaFallbackRoute function. I state that we need to wait for RC2 with the understanding that the current implementations are not fully functioning as desired.



回答3:

Try this:

public void Configure(IApplicationBuilder app,
    IHostingEnvironment env,
    ILoggerFactory loggerFactory)
{
    app.UseExceptionHandler("/Home/Error");
    app.UseStatusCodePagesWithRedirects("/Home/Error/{0}");

    app.UseMvc(routes =>
    {
        routes.MapRoute("default", "{controller=Home}/{action=Index}/{id?}");
        routes.MapWebApiRoute("defaultApi", "api/{controller}/{id?}");
    });

    app.Run(async context =>
    {
        context.Response.Redirect("/Home/Index");
        await Task.CompletedTask;
    });
}

app.Run() will catch all requests that doesn't match any of the routes defined earlier. This way you can get your custom spa fallback and use app.UseStatusCodePagesWithRedirects() at the same time.