4

I have a js module, let's call it A. It uses versioning by appending ?v=xxxxxxxxxxxx into its URL (like <script src="/Scripts/A.js?v=637082108844148373"></script>). v changes everytime we make changes in the file.

Here is the code:

public static class UrlHelperExtensions
{
    public static string Content(this UrlHelper helper, string filename, bool versioned)
    {
        var result = filename;

        if (versioned)
        {
            var lastWriteTimeToken = CalculateToken(helper.RequestContext.HttpContext, filename);
            result = filename + "?v=" + lastWriteTimeToken;
        }

        return helper.Content(result);
    }
}

And then we can use it in Razor views as this:

// Sample.cshtml
// ... code omitted for the sake of brevity ...

@section scripts {
    <script type="module" src="@Url.Content("~/Scripts/A.js", true)"></script>
}

// ... code omitted for the sake of brevity ...

The module imports modules B.js and C.js:

// A.js

import {Foo} from "./B.js";
import {Bar} from "./C.js";

If I change something in module A.js, client browser's cache is busted since we have ?v parameter which is changing every time we make any changes in A.js. But if I change something in module B.js or C.js, its version remains the same and I have to clear cache manually (CTRL + F5) to see the changes.

In other words, we can't use ?v parameter for the lines:

import {Foo} from "./B.js";
import {Bar} from "./C.js";

How to solve this problem of cache busting for imported files in MVC 5?

2
  • Why not create a ScriptBundle with A, B and C in one instead of importing them in the js file A itself? In MVC you can normally create a bundle in /App_Start/BundleConfig.cs. And then if you update one file it will be directly available (and is automatically minimized), Commented Mar 31, 2022 at 12:38
  • @VDWWD A, B and C are modules, not just regular JS files. We use export and import to expose only what we need to and use only what we need in the module. Commented Mar 31, 2022 at 12:51

3 Answers 3

1

With ASP.NET Core, you may use the TagHelper attribute: asp-append-version to perform cache busting.

<script src="/Scripts/A.js" asp-append-version="true"></script>
Sign up to request clarification or add additional context in comments.

Comments

0

I would work backwards from the desired final deployed state of your index.html file. My Demo SPA produces this output, but the application code does not need to deal with any cache related concerns.

<!DOCTYPE html>
<html lang='en'>
    <head>
        <meta charset='utf-8'>
        <meta name='viewport' content='width=device-width, initial-scale=1, shrink-to-fit=no'>

        <base href='/spa/' />
        <title>OAuth Demo App</title>

        <link rel='stylesheet' href='bootstrap.min.css?t=1648582395628' integrity='sha256-YvdLHPgkqJ8DVUxjjnGVlMMJtNimJ6dYkowFFvp4kKs='>
        <link rel='stylesheet' href='app.css?t=1648582395628' integrity='sha256-B7pu+gcFspulW4zXfgczVtPcEuZ81tZRFYeRciEzWro='>
    <body>
        <div id='root' class='container'></div>

        <script type='module' src='vendor.bundle.js?t=1648582395628' integrity='sha256-g0/+kYJcpXM7K5tvtIwBx//nKV3mCR8Y6NktYGHgpW0='></script>
        <script type='module' src='app.bundle.js?t=1648582395628' integrity='sha256-YY15iWJ0R9wnYkc1BP9yBYMlPNCeGFJBWFhio6z8Y1Q='></script>
    </body>
</html>

BUILD STEP

In my case I am using a Webpack build step, which keeps the application code simple. Meanwhile application modules depend on each other in a simple way.

SERVER SIDE TECH

This will be harder if you are mixing server side and client side code to manage Javascript, and personally I am not a fan of such tech stacks. The same principles apply though, so I would look into bundling options and a build step to solve your problem. The application code should not know anything about cache busting URLs.

Comments

0

For simplicity, if verbose in the script tag, you could include a list of files to check.

<script type="module" src="@Url.Content("~/Scripts/A.js", true, new string[]{ "~/Scripts/B.js","~/Scripts/C.js"})"></script>

Then amend your helper to include those in the token.

public static class UrlHelperExtensions
{
    public static string Content(this UrlHelper helper, string filename, bool versioned, string[] cacheCheck)
    {
        var result = filename;

        if (versioned)
        {
            var cacheChecks = cacheCheck.ToList();
            cacheChecks.Add(filename);
            var lastWriteTimeToken = new List<string>();
            foreach (var item in cacheChecks)
            {
                lastWriteTimeToken.Add(CalculateToken(helper.RequestContext.HttpContext, item));
            }
            result = filename + "?v=" + String.Join("", lastWriteTimeToken.ToArray());
        }

        return helper.Content(result);
    }
}

Untested :)

2 Comments

But those files aren't included using <script> tag but imported in JS sources of other modules
Yes, exactly. So you have to tell the path builder to check other files. It’s not an ideal situation, but neither is the problem at hand.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.