ASP.net core MVC catch all route serve static file
Asked Answered
G

8

35

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

Germinant answered 23/2, 2017 at 11:24 Comment(4)
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.Seller
Is there a recommended way of doing that?Germinant
Not as far as I know.Seller
return File("~/index.html", "text/html"); in the action seems to work fineGerminant
S
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");
}
Seller answered 23/2, 2017 at 11:42 Comment(4)
Which File class is that you are using? It cannot possibly be System.IO.FileSystem.File. Cannot find a good match.Odawa
@Odawa It's not a file class, it's the File method of the controller class.Seller
I was a jackass, forgot to inherit from Controller, that´s why I couldn´t see it. Thx!Odawa
I get No file provider has been configured to process the supplied filePointenoire
M
39

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");
  }
}
Minaret answered 22/5, 2017 at 13:29 Comment(1)
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?Abdicate
F
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" }); 
});  
Flak answered 27/12, 2017 at 8:22 Comment(1)
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");.Typography
S
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");
}
Seller answered 23/2, 2017 at 11:42 Comment(4)
Which File class is that you are using? It cannot possibly be System.IO.FileSystem.File. Cannot find a good match.Odawa
@Odawa It's not a file class, it's the File method of the controller class.Seller
I was a jackass, forgot to inherit from Controller, that´s why I couldn´t see it. Thx!Odawa
I get No file provider has been configured to process the supplied filePointenoire
O
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
Outdistance answered 22/3, 2019 at 7:52 Comment(5)
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.Solatium
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!Periodate
What is the point of calling app.UseStaticFiles() twice? I don't understand that part.Swollen
@Swollen 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.Lentil
@Swollen it's just a way to return index.html at all times, as Maxime Morin said.Outdistance
E
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.

Equity answered 16/9, 2017 at 21:14 Comment(0)
B
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();
Blevins answered 3/10, 2017 at 12:40 Comment(1)
The question was not about service any static file.Masoretic
H
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");
}
Hanshansard answered 22/4, 2020 at 15:19 Comment(0)
P
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();
Paphlagonia answered 17/1, 2023 at 20:41 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.