19

In server-side ASP.NET, we can do asp-append-version=true on static assets in a .cshtml file in order to automatically append a hash of the file to the filename. But, in Blazor WebAssembly, this doesn't work, which makes sense because I have a simple index.html file that bootstraps Blazor and references static files, not a server-modified file.

So is there a good way in Blazor WebAssembly's index.html file to append a hash to the URL of a static file, similar in outcome to the old asp-append-version=true? For example, to make <link href="css/site.css" rel="stylesheet" /> become <link href="css/site.css?v=1234abc..." rel="stylesheet" />, and thus changes to site.css on deployment will result in all clients GETting the newly changed static file, rather than relying on cache?

Patrick Szalapski
  • 8,738
  • 11
  • 67
  • 129
  • 1
    Did you consider replacing the index.html by a .cshtml page on your server ? – agua from mars May 12 '20 at 06:05
  • I thought there might be a client-only way of doing it, but I suppose I can do that if necessary. – Patrick Szalapski May 12 '20 at 12:21
  • I don't know other way with ASP.Net Core. This cannot be done in client side. – agua from mars May 12 '20 at 13:18
  • @PatrickSzalapski Where you able to try the .cshtml approach? Did it work? – Augusto Barreto Oct 22 '20 at 08:27
  • It seems that later versions of Blazor retrieve the .css file from WebAssembly runtime, so cache-busting is not required. Not exactly sure if I fully understand this, though. – Patrick Szalapski Oct 22 '20 at 19:07
  • This issue mentions there is a way to do it, but I haven't figured it out yet: https://github.com/dotnet/aspnetcore/issues/17036 – Eugenio De Hoyos Jan 05 '21 at 06:30
  • 1
    See https://learn.microsoft.com/en-us/aspnet/core/blazor/host-and-deploy/webassembly?view=aspnetcore-5.0#custom-boot-resource-loading As well as the section to change the filename extension of dll files. It's silly that this functionality is not available out-of-the box because it's a best practice in web programming. – Eugenio De Hoyos Jan 05 '21 at 06:41
  • 1
    the .cshtml approach breaks the client routing mechanism. it seems the index.html must be static. – Cesar Apr 01 '21 at 20:35

2 Answers2

4

I came up with a workaround for this. I have created a controller that will handle the request, read the index.html, search and replace a "{version}" placeholder and then return the content.

First step is to modify the Startup.cs:

            //endpoints.MapFallbackToFile("index.html");
            endpoints.MapFallbackToController("Index", "Home");

Then create a controller like this one:

public class HomeController : Controller
{
    private static string _processedIndexFile;
    private readonly IWebHostEnvironment _webHostEnvironment;

    public HomeController(IWebHostEnvironment webHostEnvironment)
    {
        _webHostEnvironment = webHostEnvironment;
    }

    [ResponseCache(CacheProfileName = CacheProfileNames.NoCache)]
    public IActionResult Index()
    {
        return ProcessAndReturnIndexFile();
    }

    private IActionResult ProcessAndReturnIndexFile()
    {
        if (_processedIndexFile == null)
        {
            IFileInfo file = _webHostEnvironment.WebRootFileProvider.GetFileInfo("index.html");
            _processedIndexFile = System.IO.File.ReadAllText(file.PhysicalPath);
            _processedIndexFile = _processedIndexFile.Replace("{version}", AppInfo.CompiledOn.ToString("yyyy'-'MM'-'dd'-'HH'-'mm'-'ss"));
        }
        return Content(_processedIndexFile, "text/html");
    }
}

And finally inside the index.html file, use urls like:

<link href="css/app.min.css?v={version}" rel="stylesheet" />

This also allows you to do some other things before returning the file inside the Index method, like checking if falling back to index.html is correct for the current request:

        // Check for incorrect fallback routes
        if (Request.Path.Value?.Contains("api/") == true)
            return NotFound();
Augusto Barreto
  • 3,637
  • 4
  • 29
  • 39
2

There is a quite easy solution to this if you are using ASP.NET Core hosted Blazor WASM:

Create a new empty Razor Page in your Server project for Blazor WASM. Call it BlazorIndex or something else you like.

It will look something like this:

@page
@model BlazorAppIndividualAccounts.Server.Pages.BlazorIndexModel
@{
}

Now add @addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers

Like this:

@page
@model BlazorAppIndividualAccounts.Server.Pages.BlazorIndexModel
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
@{
}

Then edit Program.cs and replace app.MapFallbackToFile("index.html"); with app.MapFallbackToPage("/BlazorIndex");

Now copy your values from Client wwwroot index.html to BlazorIndex.cshtml. Then remove index.html from Client wwwroot.

Complete example with asp-append-version="true" for css/app.css and AuthenticationService.js:

@page
@model BlazorAppIndividualAccounts.Server.Pages.BlazorIndexModel
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
@{
}

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
    <title>BlazorAppIndividualAccounts</title>
    <base href="/" />
    <link href="css/bootstrap/bootstrap.min.css" rel="stylesheet" />
    <link href="css/app.css" rel="stylesheet" asp-append-version="true" />
    <link rel="icon" type="image/png" href="favicon.png" />
    <link href="BlazorAppIndividualAccounts.Client.styles.css" rel="stylesheet" />
</head>

<body>
    <div id="app">
        <svg class="loading-progress">
            <circle r="40%" cx="50%" cy="50%" />
            <circle r="40%" cx="50%" cy="50%" />
        </svg>
        <div class="loading-progress-text"></div>
    </div>

    <div id="blazor-error-ui">
        An unhandled error has occurred.
        <a href="" class="reload">Reload</a>
        <a class="dismiss"></a>
    </div>
    <script src="_content/Microsoft.AspNetCore.Components.WebAssembly.Authentication/AuthenticationService.js" asp-append-version="true"></script>
    <script src="_framework/blazor.webassembly.js"></script>
</body>

</html>

Now you will append a hash to the URL of static files and you can also use any other feature from Razor Pages that you like.

enter image description here

Ogglas
  • 62,132
  • 37
  • 328
  • 418