37

Is there a way to make a catch all route serve a static file?

Looking at this http://blog.nbellocam.me/2016/03/21/routing-angular-2-asp-net-core/

I basically want something like this:

        app.UseMvc(routes =>
        {
            routes.MapRoute("default", "{controller}/{action=Index}");

            routes.MapRoute("spa", "{*url}"); // This should serve SPA index.html
        });

So any route that doesn't match an MVC controller will serve up wwwroot/index.html

4
  • 2
    If you're already in the routing element, you've gone past the point where static files are served in the pipeline. You can create a catch-all controller action that will return the content of the file instead. Commented Feb 23, 2017 at 11:32
  • Is there a recommended way of doing that? Commented Feb 23, 2017 at 11:34
  • Not as far as I know. Commented Feb 23, 2017 at 11:34
  • 1
    return File("~/index.html", "text/html"); in the action seems to work fine Commented Feb 23, 2017 at 11:36

8 Answers 8

40

I had to make some additions to @DavidG answer. Here is what I ended up with

Startup.cs

app.UseStaticFiles();

app.UseMvc(routes =>
{
   routes.MapRoute("default", "{controller}/{action}");

   routes.MapRoute("Spa", "{*url}", defaults: new { controller = "Home", action = "Spa" });
});

HomeController.cs

public class HomeController : Controller
{
  public IActionResult Spa()
  {
      return File("~/index.html", "text/html");
  }
}
Sign up to request clarification or add additional context in comments.

1 Comment

This seems to work perfectly running locally, but when I publish to Azure app service, ALL the requests for static files like *.js files always return the index.html file instead of the actual file being requested. Any idea why?
29

ASP.NET Core catch all routes for Web API and MVC are configured differently

With Web API (if you're using prefix "api" for all server-side controllers eg. Route("api/[controller"]):

app.Use(async (context, next) => 
{ 
    await next(); 
    var path = context.Request.Path.Value;

    if (!path.StartsWith("/api") && !Path.HasExtension(path)) 
    { 
        context.Request.Path = "/index.html"; 
        await next(); 
    } 
});            

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

app.UseMvc();

With MVC (dotnet add package Microsoft.AspNetCore.SpaServices -Version x.y.z):

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

app.UseMvc(routes => 
{ 
    routes.MapRoute( 
        name: "default", 
        template: "{controller=Home}/{action=Index}"); 

    routes.MapSpaFallbackRoute("spa", new { controller = "Home", action = "Index" }); 
});  

1 Comment

Wonderful! I used your first solution, although I put UseStaticFiles before your logic so that static files will be served first and I didn't need UseDefaultFiles. More importantly, I had to set path as context.Request.Path = "/"; and in my HomeController's Index action, serve File("~/index.html", "text/html");.
25

If you're already in the routing stage, you've gone past the point where static files are served in the pipeline. Your startup will look something like this:

app.UseStaticFiles();

...

app.UseMvc(...);

The order here is important. So your app will look for static files first, which makes sense from a performance standpoint - no need to run through MVC pipeline if you just want to throw out a static file.

You can create a catch-all controller action that will return the content of the file instead. For example (stealing the code in your comment):

public IActionResult Spa()
{
    return File("~/index.html", "text/html");
}

4 Comments

Which File class is that you are using? It cannot possibly be System.IO.FileSystem.File. Cannot find a good match.
@Marcus It's not a file class, it's the File method of the controller class.
I was a jackass, forgot to inherit from Controller, that´s why I couldn´t see it. Thx!
I get No file provider has been configured to process the supplied file
6

In case you don't want manually specify which routes are for api:

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

app.UseMvc() // suggestion: you can move all the SPA requests to for example /app/<endpoints_for_the_Spa> and let the mvc return 404 in case <endpoints_for_the_Spa> is not recognized by the backend. This way SPA will not receive index.html

// at this point the request did not hit the api nor any of the files

// return index instead of 404 and let the SPA to take care of displaying the "not found" message
app.Use(async (context, next) => {
    context.Request.Path = "/index.html";
    await next();
});
app.UseStaticFiles(); // this will return index.html

5 Comments

This is working fine also in my asp.net-core-3.1 app. I just added the app.Use and 2nd app.UseStaticFiles after app.UseEndpoints.
Thank for this answer! This was the solution for my SPA when I converted from core 2->3.1 and my oidc callback redirects ended up with 404. With this trick everything is back to normal!
What is the point of calling app.UseStaticFiles() twice? I don't understand that part.
@flipdoubt I guess it's because he's serving the index page once the static pipeline has already run, so he needs to re-call the static pipeline for it to serve the index page.
@flipdoubt it's just a way to return index.html at all times, as Maxime Morin said.
5

What I'm using that works well is Microsoft.AspNetCore.Builder.SpaRouteExtensions.MapSpaFallbackRoute:

app.UseMvc(routes =>
{
    // Default route for SPA components, excluding paths which appear to be static files (have an extension)
    routes.MapSpaFallbackRoute(
        "spaFallback",
        new { controller = "Home", action = "Index" });
});

HomeController.Index has the equivalent of your index.html. You can probably route to a static page also.

A bit off topic, but if you also have an API in the same project under an api folder you can set a default 404 response for any API routes that don't match:

routes.MapRoute(
    "apiDefault",
    "api/{*url}",
    new { controller = "Home", action = "ApiNotFound" });

You would end up with the following behavior:

  • /controller => No extension, so serve SPA default page from HomeController.Index and let SPA handle routing
  • /file.txt => Extension detected, serve static file
  • /api/controller => proper API response (use attribute routing or set up another map for the API controllers)
  • /api/non-existent-route => 404 NotFound() returned from HomeController.ApiNotFound

In many cases you'll want an API in a separate project, but this is a viable alternative.

Comments

2

In order to serve index.html from wwwroot folder the following directives should be added (.Net Core 2).

This allows to serve static files:

app.UseStaticFiles();

This allows to get default files, e.g. index.html:

app.UseDefaultFiles();

1 Comment

The question was not about service any static file.
2

In ASP.NET Core 3.1, I've used the following:

Startup.cs

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
    app.UseRouting();

    app.UseCors();

    app.UseEndpoints(endpoints =>
    {
        endpoints.MapControllers();
    });

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

MyController.cs

[HttpGet("{anything}")]
public IActionResult GetSPA()
{
    return File("~/index.html", "text/html");
}

Comments

1

In .NET Core 6, add app.MapFallbackToFile("index.html"); in your Program.cs file.

Here is example code from the ASP.NET Core with React project template:

app.MapControllerRoute(
    name: "default",
    pattern: "{controller}/{action=Index}/{id?}");

app.MapFallbackToFile("index.html");  

app.Run();

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.