Home > database >  StaticFileOptions.OnPrepareResponse never called for ASP.NET Core SPA web app
StaticFileOptions.OnPrepareResponse never called for ASP.NET Core SPA web app

Time:01-27

I've created a React-based SPA that runs on ASP.NET Core 5.0 but recently encountered an issue after pushing an update with breaking changes to production - users are often seeing errors when trying to access the site because their browser is loading a stale version of the SPA that's incompatible with the changes made to the backend.

Investigation

Digging into this, it seems like the problem is that index.html is being cached for longer than it should and that I should be configuring it to never be cached, based on what I read in these posts:

  • response cache control headers

    However, I'd prefer to achieve this via SpaStaticFiles and I'd like to understand why those callbacks aren't being called.

    Update

    Reviewing the SPA Static Files Extension source code step-into SPA static files extension

    I'll play around and see if there's a way to get the web server to serve the files using the SPA static file middleware while I'm debugging the app locally.

    CodePudding user response:

    I've managed to find a way to get the OnPrepareResponse callbacks to work.

    My understanding is that these weren't being invoked before because my application is configured to serve static files using a React development server when running in development mode, rather than serving them from the file system. The SPA Static Files Extension is programmed to detect this case and disable static file serving, as shown in the update section of my question.

    To workaround this so that I could get these callbacks to fire while debugging locally, I disabled the code to run the React development server and built my SPA separately so that it generated the static files on disk.

    Specifically, I updated my Startup.Configure method to the following:

    public void Configure(IApplicationBuilder app)
    {
        app.UseStaticFiles();
    
        app.UseSpaStaticFiles(new StaticFileOptions
        {
            OnPrepareResponse = ctx =>
            {
                // Cache all static resources for 1 year (versioned filenames)
                if (ctx.Context.Request.Path.StartsWithSegments("/static"))
                {
                    var headers = ctx.Context.Response.GetTypedHeaders();
                    headers.CacheControl = new CacheControlHeaderValue
                    {
                        Public = true,
                        MaxAge = TimeSpan.FromDays(365),
                    };
                }
            },
        });
    
        app.UseRouting();
        
        app.UseSpa(spa =>
        {
            spa.Options.SourcePath = "spa";
            spa.Options.DefaultPageStaticFileOptions = new StaticFileOptions
            {
                OnPrepareResponse = ctx => {
                    // Do not cache implicit `/index.html`.
                    var headers = ctx.Context.Response.GetTypedHeaders();
                    headers.CacheControl = new CacheControlHeaderValue
                    {
                        NoCache = true,
                        NoStore = true,
                        MustRevalidate = true,
                    };
                },
            };
    
            if (Environment.GetEnvironmentVariable("LOCAL_DEVELOPMENT") == "1")
            {
                spa.UseReactDevelopmentServer(npmScript: "start");
            }
        });
    }
    

    I then temporarily commented out the last three lines:

        //if (Environment.GetEnvironmentVariable("LOCAL_DEVELOPMENT") == "1")
        //{
        //    spa.UseReactDevelopmentServer(npmScript: "start");
        //}
    

    I opened a PowerShell console in the ClientApp folder and ran yarn && yarn build.

    Launching the ASP.NET Core web app, I was then able to see that the index page is loaded with cache-control headers that completely disable caching, and will cause the browser to request a fresh response from the server each time the page is reloaded: index.html cache-control header

    Meanwhile, the versioned static assets are configured to be cached for a year, and the browser loads them from its internal cache on page reload: static asset cache-control header

    It seems like this would have worked in production just fine, but at least this way I was able to find a way to confirm these callbacks would work properly while running the application locally, and now I have a better idea of why these callbacks weren't being called before.

  •  Tags:  
  • Related