15

This post by Stephen Walther talks about redirecting all requests in an MVC project by modifying the web.config system.webServer / rewrite section.

http://stephenwalther.com/archive/2015/01/16/asp-net-5-and-angularjs-part-3-adding-client-routing

However, it seems wrong to have to reintroduce a web.config xml file into an ASP.NET 5 project.

Is there another way to do this in ASP.NET 5? Maybe via the new config.json?

Chris Swain
  • 5,193
  • 5
  • 21
  • 22
  • Also, while my answer below solves your specific question, you might be happier if you allow your users to go to a specific page using the html 5 mode - for this, you'll want to add a route to your root route using the {*pathinfo} syntax. – Matt DeKrey May 15 '15 at 14:01
  • Is that in addition to your solution below? Or an alternative solution? – Chris Swain May 15 '15 at 17:50
  • Alternative. For example, if a user wants to bookmark http://example.com/albums, you won't want to redirect them to the root each time. – Matt DeKrey May 15 '15 at 17:52
  • That's a good alternative. Would you mind adding that to your solution below, too? – Chris Swain May 16 '15 at 15:46
  • Added for you. Mostly off the top of my head, as I don't have a compiler in front of me - I hope it works! – Matt DeKrey May 16 '15 at 16:15

4 Answers4

19

In your Startup's Configure method, where app is your IApplicationBuilder:

app.Run(context =>
{
    context.Response.Redirect("/");
    return Task.FromResult<object>(null);
});

This will send all otherwise unhandled requests to the root of your application. Place this last, after any UseStaticFiles() or other middleware registrations.

Note that this will not capture registrations above this; if you have other routes on your server (such as controller actions, etc.), they will not be captured. This should work well with the added benefit that you don't need to exclude patterns such as in the example.

Alternatively...

If you're doing this for a Single Page Application, you probably want to allow deep linking for your users. I use a simple attribute routing for that:

[Route("", Order = -1)]
[Route("{*pathInfo}", Order = 1000)]
public async Task<IActionResult> Index(string pathInfo = "", CancellationToken cancellationToken = default(CancellationToken))
{    
    return View("UiView");
}

This will map default requests (/) with priority while mapping all other requests (allowing default ordering, etc. to take priority) also to your "UiView".

If you don't want to use attribute routing, use the method as above with the following route mapping:

// Before all your routes
routeBuilder.MapRoute(
    "Root",
    "",
    defaults: new { controller = "Home", action = "Index" });

// Your routes here

// After all your routes
routeBuilder.MapRoute(
    "DeepLink",
    "{*pathInfo}",
    defaults: new { controller = "Home", action = "Index" });
Community
  • 1
  • 1
Matt DeKrey
  • 11,582
  • 5
  • 54
  • 69
  • Where does `CancellationToken` and `routeBuilder` come from? Which files? Code is not copy-and-paste friendly for those people new to C# – TetraDev May 26 '16 at 23:05
18

Found this very nice answer on SO: https://stackoverflow.com/a/34890926/990356

Context:

I have a SPA application and ASP.NET Core serves some REST APIs + 'wwwroot/index.html'.

I handle my routes inside the SPA application (client side) and it does not work when the user refresh the browser with a given route.

Example: refreshing the browser with URL http://localhost:5000/Account gives a 404 error because there is no file 'wwwroot/Account/index.html' on the server although this is valid route inside the client side app.

Solution:

public void Configure(IApplicationBuilder app)
{
    app.UseMvc();

    app.UseStatusCodePagesWithReExecute("/");

    app.UseDefaultFiles();
    app.UseStaticFiles();
}

Note that the order is important.

=> it means if a route does not match a file on the server or anything known (a REST API for example), the server serves "/" ('wwwroot/index.html') while keeping the URL intact.

Tested with a React/React Router app and ASP.NET MVC Core 1.1

tanguy_k
  • 11,307
  • 6
  • 54
  • 58
  • 1
    This would be absolutely perfect if there was a patter you could exclude all API routes from this as I do not want 404 errors where they gave me a bad primary key to load up my index.html page. – Buvy May 25 '17 at 19:17
  • @Buvy moving `app.UseMvc()` above `app.UseStatusCodePagesWithReExecute("/")` seems to fix the problem with APIs returning 404 – tanguy_k May 31 '17 at 00:42
  • @tanguy_k Do you have a GitHub repo to share this? I am trying to do the same thing , but reload of a route gives me 404 – Shaswat Rungta Jun 13 '17 at 15:22
  • @ShaswatRungta sorry, nothing public to share. You should setup a small example on GitHub that demonstrates the problem you have – tanguy_k Jun 13 '17 at 16:02
  • Actually got it working without nay MVC dependency by just using app.UseStatusCodePagesWithReExecute("/"). Thanks for your help – Shaswat Rungta Jun 13 '17 at 16:12
5

I am deploying an AngularJS app on Azure with vnext. Thanks to Matt I was able to make this snippet:

app.Run(context =>
            {
                if(context.Request.Path.Value != "/api") context.Response.Redirect("/");
                return Task.FromResult<object>(null);
            }
        );

It routes everything to the Angular App except my REST api calls.

Patrick Michalina
  • 1,279
  • 2
  • 12
  • 15
0

Based off @Matt DeKrey's answer and with the help of one of the developers here at my company, here is how I got ASP.NET Core 1 MVC to route all requests to a single view. Perfect for my Angular2 application with deep linking needed.

HomeController.cs

Inside public class HomeController : Controller

[Route("", Order = -1)]
[Route("{*pathInfo}", Order = 1000)]
public async Task<IActionResult> Index(string pathInfo = ""))
{    
    return View("View");
}

Startup.cs

Inside public void Configure

  app.UseMvc(routes =>
  {
    routes.MapRoute(
      "default",
      "{controller=Home}/{action=Index}/{id?}"
    );
  });
TetraDev
  • 16,074
  • 6
  • 60
  • 61