Search code examples
.net-corestatic-files

dotnet core UseStaticFiles index fallback


I have this Configure method in my Startup.cs. It does 3 things :

  • serve static files under wwwroot
  • add CSP header for index.html
  • serve parameters via /settings.json route
     public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
      {
         if (env.IsDevelopment())
         {
            app.UseDeveloperExceptionPage();
         }

         app.UseHttpsRedirection();

         // Defaults to index.html
         var defaultFilesOptions = new DefaultFilesOptions();
         defaultFilesOptions.DefaultFileNames.Clear();
         defaultFilesOptions.DefaultFileNames.Add("index.html");
         app.UseDefaultFiles(defaultFilesOptions);

         var staticFileOptions = new StaticFileOptions
         {
            OnPrepareResponse = ctx =>
            {
               // Add CSP for index.html
               if (ctx.File.Name == "index.html")
               {
                  ctx.Context.Response.Headers.Append(
                     "Content-Security-Policy", "default-src 'self'" // etc
                  );
               }
            }
         };

         app.UseStaticFiles(staticFileOptions); // wwwroot

         app.UseRouting();

         app.UseEndpoints(endpoints =>
         {
            // Settings.json endpoint
            endpoints.MapGet("/settings.json", async context =>
            {
               string json = $@"
                {{
                   ""myConfig"": ""{_configuration["myParameter"]}""
                }}";
               await context.Response.WriteAsync(json);
            });
         });
      }
   }

The files under wwwroot are in fact a vue.js app with routing. I need to return index.html for all inexistant request, for the client routing to take control of the page.

Currently it returns a 404 and does not pass in OnPrepareResponse hook.

How can I configure index fallback for the router to work in history mode ? I think it is acheivable by configuration in web.config, but I'd prefer to configure this in Startup.js so this is all in the same place.


Solution

  • I ended up writing a file provider that does the index fallback. It encapsulate a PhysicalFileProvider, and return index.html under certain conditions if the file is not found. In my case conditions are based on folders css, img or js.

    It is implemented this way :

    using Microsoft.Extensions.FileProviders;
    using Microsoft.Extensions.Primitives;
    using System.Linq;
    
    public class IndexFallbackFileProvider : IFileProvider
    {
       private readonly PhysicalFileProvider _innerProvider;
    
       public IndexFallbackFileProvider(PhysicalFileProvider physicalFileProvider)
       {
          _innerProvider = physicalFileProvider;    
       }
    
       public IDirectoryContents GetDirectoryContents(string subpath)
       {
          return _innerProvider.GetDirectoryContents(subpath);
       }
    
       public IFileInfo GetFileInfo(string subpath)
       {
          var fileInfo = _innerProvider.GetFileInfo(subpath);
          if(!fileInfo.Exists && MustFallbackToIndex(subpath))
          {
             if(!_staticFilesFolders.Any(f => subpath.Contains(f)))
             {
                fileInfo = _innerProvider.GetFileInfo("/index.html");
             }         
          }
    
          return fileInfo;
       }
    
       // Plain 404 are OK for css, img, js.
       private static string[] _staticFilesFolders = new string[] { "/css/", "/img/", "/js/" };
       private static bool MustFallbackToIndex(string subpath)
       {
          return !_staticFilesFolders.Any(f => subpath.Contains(f));
       }
    
       public IChangeToken Watch(string filter)
       {
          return _innerProvider.Watch(filter);
       }
    }
    

    Then, in startup.config, I use this provider. Plus, I had to set ServeUnknownFileTypes to true to respond to requests to a /path/without/extension.

    var physicalFileProvider = new PhysicalFileProvider(Path.Combine(env.ContentRootPath, "wwwroot"));
    var fileProvider = new IndexFallbackFileProvider(physicalFileProvider);
    
    var staticFileOptions = new StaticFileOptions
    {
       FileProvider = fileProvider,
       ServeUnknownFileTypes = true
    };
    
    app.UseStaticFiles(staticFileOptions);