MVC 6 路由,SPA 回退 + 404 错误页面

MVC 6 Routing, SPA fallback + 404 Error Page

使用 ASP.NET Core 1.0 的 MVC 6RC1,您可以在调用 Startup.Configure 函数时映射路由 app.UseMvc。我已经映射了一个 "spa-fallback" 路由,它将确保 HomeControllerIndex 视图是默认值,如下所示:

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?}");
    });
}

我希望回退,以便我的 Angular2 应用程序的路由不会导致 HTTP 状态代码 404,未找到 。但是当用户无意中尝试导航到不存在的页面视图时,我还需要正确处理。您可能会注意到我还调用了 app.UseStatusCodePagesWithRedirects("/Home/Error/{0}");.

使用状态代码重定向到我的错误页面的调用和 "spa-fallback" 路由似乎相互排斥——这意味着我似乎只能拥有一个 (但遗憾的是不是两者)。有谁知道我怎样才能做到两全其美?

我花了一些时间才弄清楚如何在不使用 MVC 为我的索引提供服务的情况下执行此操作,并且仍然收到丢失文件的 404。这是我的 http 管道:

    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();
        });            
    }

我想出了两个可能 workarounds/solutions。

ASP.NET 核心 1.0, RC1

特别是 Angular2,我们可以简单地换掉 LocationStrategy。例如,在我的 wwwroot/app/boot.ts 中,我有以下内容:

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 })
    ]);

请注意,我正在使用 provide 函数用 HashLocationStrategy 覆盖标准 LocationStrategy。这会在 URL 中添加 # 并防止 MVC 错误地抛出 404's,但也会正确处理响应 404's当用户手动输入不存在的 URL 时。

ASP.NET 核心 1.0, RC2

只需使用Microsoft.AspNet.NodeServices. Within this library there are both AngularServices and ReactServices which allow for the mapping of SPA routes specifically, via the MapSpaFallbackRoute函数。我声明我们需要等待 RC2,但要了解当前的实现并未完全按预期运行。

试试这个:

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() 将捕获所有与之前定义的任何路由都不匹配的请求。通过这种方式,您可以获得自定义水疗后备并同时使用 app.UseStatusCodePagesWithRedirects()。