How to route EVERYTHING other than Web API to /index.html

35,376

Solution 1

Use a wildcard segment:

routes.MapRoute(
    name: "Default",
    url: "{*anything}",
    defaults: new { controller = "Home", action = "Index" }
);

Solution 2

Suggest more native approach

<system.webServer>
    <httpErrors errorMode="Custom">
        <remove statusCode="404" subStatusCode="-1"/>
        <error statusCode="404" prefixLanguageFilePath="" path="/index.cshtml" responseMode="ExecuteURL"/>
    </httpErrors>
</system.webServer>

Solution 3

in my case none of these approaches worked. i was stuck in 2 error message hell. either this type of page is not served or some sort of 404.

url rewrite worked:

<system.webServer>
    <rewrite>
      <rules>
        <rule name="AngularJS" stopProcessing="true">
          <match url="[a-zA-Z]*" />

          <conditions logicalGrouping="MatchAll">
            <add input="{REQUEST_FILENAME}" matchType="IsFile" negate="true" />
            <add input="{REQUEST_FILENAME}" matchType="IsDirectory" negate="true" />
            <add input="{REQUEST_URI}" pattern="^/(api)" negate="true" />
          </conditions>
          <action type="Rewrite" url="/" />
        </rule>
      </rules>
    </rewrite>
    ...

notice i matched on [a-zA-Z] because i don't want to rewrite any of the .js .map etc urls.

this worked in VS out of hte box, but in IIS you may need to install a url-rewrite module https://www.iis.net/downloads/microsoft/url-rewrite

Solution 4

I had a similar approach as the top few answers, but the downside is if someone is making an API call incorrectly, they'll end up getting that index page back instead of something more useful.

So I've updated mine such that it will return my index page for any request not starting with /api:

        //Web Api
        GlobalConfiguration.Configure(config =>
        {
            config.MapHttpAttributeRoutes();
        });

        //MVC
        RouteTable.Routes.Ignore("api/{*anything}");
        RouteTable.Routes.MapPageRoute("AnythingNonApi", "{*url}", "~/wwwroot/index.html");

Solution 5

I've been working with OWIN Self-Host and React Router few days ago, and incurred to probably similar issue. Here is my solution.

My workaround is simple; check if it's a file in the system; otherwise return index.html. Since you don't always want to return index.html if some other static files are requested.

In your Web API config file:

config.Routes.MapHttpRoute(
    name: "DefaultApi",
    routeTemplate: "api/{controller}/{id}",
    defaults: new { id = RouteParameter.Optional }
);

config.Routes.MapHttpRoute(
    name: "Default",
    routeTemplate: "{*anything}",
    defaults: new { controller = "Home", action = "Index" }
);

and then create a HomeController as follwing...

public class HomeController: ApiController
{
    [HttpGet]
    [ActionName("Index")]
    public HttpResponseMessage Index()
    {
        var requestPath = Request.RequestUri.AbsolutePath;
        var filepath = "/path/to/your/directory" + requestPath;

        // if the requested file exists in the system
        if (File.Exists(filepath))
        {
            var mime = MimeMapping.GetMimeMapping(filepath);
            var response = new HttpResponseMessage();
            response.Content = new ByteArrayContent(File.ReadAllBytes(filepath));
            response.Content.Headers.ContentType = new MediaTypeHeaderValue(mime);
            return response;
        }
        else
        {
            var path = "/path/to/your/directory/index.html";
            var response = new HttpResponseMessage();
            response.Content = new StringContent(File.ReadAllText(path));
            response.Content.Headers.ContentType = new MediaTypeHeaderValue("text/html");
            return response;
        }
    }
}
Share:
35,376
Scott R. Frost
Author by

Scott R. Frost

.NET Software Developer with over 10 years of experience in ASP.NET, Windows Forms, C#, VB.NET, MS SQL (including lots of SSRS), Oracle, Crystal Reports, and even some other buzzwords!

Updated on July 24, 2022

Comments

  • Scott R. Frost
    Scott R. Frost almost 2 years

    I've been working on an AngularJS project, inside of ASP.NET MVC using Web API. It works great except when you try to go directly to an angular routed URL or refresh the page. Rather than monkeying with server config, I thought this would be something I could handle with MVC's routing engine.

    Current WebAPIConfig:

    public static class WebApiConfig
    {
        public static void Register(HttpConfiguration config)
        {
            // Web API routes
            config.MapHttpAttributeRoutes();
    
            config.Routes.MapHttpRoute(
                name: "DefaultApi",
                routeTemplate: "api/{controller}/{id}",
                defaults: new { id = RouteParameter.Optional },
                constraints: new { id = @"^[0-9]+$" }
            );
    
            config.Routes.MapHttpRoute(
                name: "ApiWithActionAndName",
                routeTemplate: "api/{controller}/{action}/{name}",
                defaults: null,
                constraints: new { name = @"^[a-z]+$" }
            );
    
            config.Routes.MapHttpRoute(
                name: "ApiWithAction",
                routeTemplate: "api/{controller}/{action}",
                defaults: new { action = "Get" }
            );
        }
    }
    

    Current RouteConfig:

    public class RouteConfig
    {
        public static void RegisterRoutes(RouteCollection routes)
        {
            routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
            routes.IgnoreRoute(""); //Allow index.html to load
            routes.IgnoreRoute("partials/*"); 
            routes.IgnoreRoute("assets/*");
        }
    }
    

    Current Global.asax.cs:

    public class WebApiApplication : HttpApplication
    {
        protected void Application_Start()
        {
            AreaRegistration.RegisterAllAreas();
            GlobalConfiguration.Configure(WebApiConfig.Register);
            FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
            RouteConfig.RegisterRoutes(RouteTable.Routes);
            var formatters = GlobalConfiguration.Configuration.Formatters;
            formatters.Remove(formatters.XmlFormatter);
            GlobalConfiguration.Configuration.Formatters.JsonFormatter.SerializerSettings = new JsonSerializerSettings
            {
                Formatting = Formatting.Indented,
                PreserveReferencesHandling = PreserveReferencesHandling.None,
                ReferenceLoopHandling = ReferenceLoopHandling.Ignore,
            };
        }
    }
    

    GOAL:

    /api/* continues to go to WebAPI, /partials/, and /assets/ all go to file system, absolutely anything else gets routed to /index.html, which is my Angular single page app.

    --EDIT--

    I seem to have gotten it working. Added this to the BOTTOM OF RouteConfig.cs:

     routes.MapPageRoute("Default", "{*anything}", "~/index.html");
    

    And this change to the root web.config:

    <system.web>
    ...
      <compilation debug="true" targetFramework="4.5.1">
        <buildProviders>
          ...
          <add extension=".html" type="System.Web.Compilation.PageBuildProvider" /> <!-- Allows for routing everything to ~/index.html -->
          ...
        </buildProviders>
      </compilation>
    ...
    </system.web>
    

    However, it smells like a hack. Any better way to do this?

  • Scott R. Frost
    Scott R. Frost over 10 years
    A wildcard might work, can I make it go to a flat file rather than a MVC controller? Alternatively, how would I make a Home controller with an Index action that passed execution to index.html while maintaining whatever they entered as the URL (for example /something/edit/123)?
  • Trevor Elliott
    Trevor Elliott over 10 years
    In ASP.NET MVC a request always goes to a controller and a controller uses a view engine to generate a view. This can be a simple HTML file with nothing special, see the Controller.View. Just rename the file to .cshtml, place it in the Views/Home folder, and set the Layout to null at the top of the file.
  • Trevor Elliott
    Trevor Elliott over 10 years
    The route in my answer will maintain whatever URL the user requested.
  • parliament
    parliament almost 9 years
    worked perfect and is more in line with how other web servers would be configured. Thanks!
  • Silencer
    Silencer over 8 years
    Not sure if this is "the right way", but after a long search for other methods, I came back to this one. Best one so far.
  • codea
    codea over 8 years
    Worked for me as well, Since I am using only ASP.NET API, I don't have the route config available, as it is only available in System.Web.Mvc. this works perfectly. Thank you!
  • jonnybot
    jonnybot about 8 years
    The behavior you describe is expected, but not what the question asks for. Sure, you can serve static resources. The question is, how to map arbitrary routes to a specific static resource (index.html).
  • Sonic Soul
    Sonic Soul over 7 years
    i'm getting "this type of page is not served" error after using this approach
  • Sonic Soul
    Sonic Soul over 7 years
    sorry made the edits to wrong answer, and now removed them :)
  • Hawk
    Hawk almost 7 years
    This answer worked for me. I added the 2x RouteTable configurations to my global.asax after the web api configuration and I registered the .html PageBuilderProvider and it worked. Didn't need to register any additional web api routes apart from the default /api route. My angular 4 app deployed in the root of the application works perfectly with routing, all bundles, images and other files also behave as expected. Thumbs up!
  • Kris Coleman
    Kris Coleman over 6 years
    for me, this worked, but I had to change the pattern to .*(api)
  • Zach Smith
    Zach Smith about 6 years
    This isn't web api... is it?
  • Erutan409
    Erutan409 over 2 years
    Commenting in hopes that it will help for search engine indexing if anyone is looking for a solution for VueJS Router. This worked absolutely perfectly for how I'm implementing this in C# w/ ApiController.