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