Заставить браузеры получать последние js и css файлы в приложении asp.net

Некоторые браузеры кэшируют файлы js и css, не обновляя их, если вы их не заставляете. Самый простой способ.

Я только что реализовал это решение, которое, похоже, работает.

Объявите переменную версии на своей странице

  public string version { get; set; }

Получить номер версии из ключа web.config

 version = ConfigurationManager.AppSettings["versionNumber"];

На странице aspx выполните вызовы javascript и таблиц стилей, подобных этому

<script src="scripts/myjavascript.js?v=<%=version %>" type="text/javascript"></script>
<link href="styles/mystyle.css?v=<%=version %>" rel="stylesheet" type="text/css" />

Итак, если вы установите версию = 1.1 из 1.0 в свой web.config, ваш браузер будет загружать последние файлы, которые, как мы надеемся, избавят вас и ваших пользователей от разочарования.

Есть ли другое решение, которое работает лучше, или это вызовет какие-либо непредвиденные проблемы для веб-сайта?

Ответ 1

Я решил это, применив последнюю модифицированную временную метку в качестве параметра запроса к скриптам.

Я сделал это с помощью метода расширения и использовал его в своих файлах CSHTML. Примечание: эта реализация кэширует временную метку в течение 1 минуты, поэтому мы не так сильно зацикливаем диск.

Вот способ расширения:

public static class JavascriptExtension {
    public static MvcHtmlString IncludeVersionedJs(this HtmlHelper helper, string filename) {
        string version = GetVersion(helper, filename);
        return MvcHtmlString.Create("<script type='text/javascript' src='" + filename + version + "'></script>");
    }

    private static string GetVersion(this HtmlHelper helper, string filename)
    {
        var context = helper.ViewContext.RequestContext.HttpContext;

        if (context.Cache[filename] == null)
        {
            var physicalPath = context.Server.MapPath(filename);
            var version = $"?v={new System.IO.FileInfo(physicalPath).LastWriteTime.ToString("MMddHHmmss")}";
            context.Cache.Add(filename, version, null,
              DateTime.Now.AddMinutes(5), TimeSpan.Zero,
              CacheItemPriority.Normal, null);
            return version;
        }
        else
        {
            return context.Cache[filename] as string;
        }
    }
}

А затем на странице CSHTML:

 @Html.IncludeVersionedJs("/MyJavascriptFile.js")

В отображаемом HTML это выглядит как:

 <script type='text/javascript' src='/MyJavascriptFile.js?20111129120000'></script>

Ответ 2

Ваше решение работает. Это довольно популярно.

Даже Qaru использует аналогичный метод:

<link rel="stylesheet" href="http://sstatic.net/so/all.css?v=6184"> 

Где v=6184, вероятно, номер версии SVN.

Ответ 3

ASP.NET MVC будет обрабатывать это для вас, если вы используете пакеты для вашего JS/CSS. Он автоматически добавит номер версии в виде GUID к вашим пакетам и обновит этот GUID только тогда, когда пакет будет обновлен (например, у любого из исходных файлов есть изменения).

Это также помогает, если у вас есть тонна файлов JS/CSS, так как это может значительно увеличить время загрузки контента!

См. здесь

Ответ 4

В ASP.NET Core (MVC 6) это работает из коробки с помощью помощника тега asp-append-version:

<script src="scripts/myjavascript.js" asp-append-version="true"></script>
<link href="styles/mystyle.css rel="stylesheet" asp-append-version="true" />

Ответ 5

В asp.net есть встроенный способ: связывание. Просто используйте его. Каждая новая версия будет иметь уникальный суффикс "? V = XXXXXXX". В режиме отладки отключено, для включения make make в web.config:

<system.web>
    <compilation debug="false" />
</system.web>

Или добавьте в метод RegisterBundles (BundleCollection bundles):

BundleTable.EnableOptimizations = true;

Например:

BundleConfig.cs:

bundles.Add(new ScriptBundle("~/Scripts/myjavascript.js")
                .Include("~/Scripts/myjavascript.js"));

bundles.Add(new StyleBundle("~/Content/mystyle.css")
                .Include("~/Content/mystyle.css"));

_Layout.cshtml:

@Scripts.Render("~/Scripts/myjavascript.js")
@Styles.Render("~/Content/mystyle.css")

Ответ 6

Интересно, что на этом самом сайте есть проблемы с описанным вами подходом в связи с некоторыми настройками прокси-сервера, хотя он должен быть отказоустойчивым.

Проверьте это обсуждение Meta Stack Overflow.

Таким образом, в свете этого может иметь смысл не использовать параметр GET для обновления, но фактическое имя файла:

href="/css/scriptname/versionNumber.css" 

хотя это больше нужно сделать, так как вам нужно будет создать файл или создать для него переписывание URL.

Ответ 7

На основе ответа Адама Тегана, измененного для использования в приложении веб-форм.

В коде класса .cs:

public static class FileUtility
{
    public static string SetJsVersion(HttpContext context, string filename) {
        string version = GetJsFileVersion(context, filename);
        return filename + version;
    }

    private static string GetJsFileVersion(HttpContext context, string filename)
    {
        if (context.Cache[filename] == null)
        {
            string filePhysicalPath = context.Server.MapPath(filename);

            string version = "?v=" + GetFileLastModifiedDateTime(context, filePhysicalPath, "yyyyMMddhhmmss");

            return version;
        }
        else
        {
            return string.Empty;
        }
    }

    public static string GetFileLastModifiedDateTime(HttpContext context, string filePath, string dateFormat)
    {
        return new System.IO.FileInfo(filePath).LastWriteTime.ToString(dateFormat);
    }
}

В разметке aspx:

<script type="text/javascript" src='<%= FileUtility.SetJsVersion(Context,"/js/exampleJavaScriptFile.js") %>'></script>

И в отображаемом HTML он выглядит как

<script type="text/javascript" src='/js/exampleJavaScriptFile.js?v=20150402021544'></script>

Ответ 8

Здесь используется подход, который работает с ASP.NET 5/MVC 6/vNext.

Шаг 1: Создайте класс, чтобы вернуть последнее время записи файла, аналогично другим ответам в этом потоке. Примечание. Для этого требуется инъекция зависимостей ASP.NET 5 (или другой).

public class FileVersionService
{
    private IHostingEnvironment _hostingEnvironment;
    public FileVersionService(IHostingEnvironment hostingEnvironment)
    {
        _hostingEnvironment = hostingEnvironment;
    }

    public string GetFileVersion(string filename)
    {
       var path = string.Format("{0}{1}", _hostingEnvironment.WebRootPath, filename);
       var fileInfo = new FileInfo(path);
       var version = fileInfo.LastWriteTimeUtc.ToString("yyyyMMddhhmmssfff");
       return version;
     }
}

Шаг 2: Зарегистрируйте службу, которую нужно ввести внутри startup.cs:

public void ConfigureServices(IServiceCollection services)
{
    ...
    services.AddScoped<FileVersionService>();
    ...
}

Шаг 3: Затем, в ASP.NET 5, можно вставлять службу непосредственно в представление макета, например _Layout.cshtml, как это:

@inject Namespace.Here.FileVersionService fileVersionService
<!DOCTYPE html>
<html lang="en" class="@ViewBag.HtmlClass">
<head>
    ...
    <link href="/css/[email protected]("\\css\\styles.css")" rel="stylesheet" />
    ...
</head>
<body>
    ...
</body>

Есть некоторые последние штрихи, которые можно было бы сделать, чтобы лучше комбинировать физические пути и обрабатывать имя файла в стиле, более совместимом с синтаксисом, но это отправная точка. Надеюсь, что это поможет людям перейти на ASP.NET 5.

Ответ 9

Ответ на этот вопрос проще, чем ответ, заданный оп в вопросе (подход один и тот же):

Определите ключ в файле web.config:

<add key="VersionNumber" value="06032014"/>

Сделать вызов appsettings непосредственно со страницы aspx:

<link href="styles/navigation.css?v=<%=ConfigurationManager.AppSettings("VersionNumber")%>" rel="stylesheet" type="text/css" />

Ответ 10

Начиная с выше ответ, я немного изменил код, чтобы помощник работал с файлами CSS и добавлял версию каждый раз, когда вы делаете некоторые изменения в файлы, а не только при создании сборки

public static class HtmlHelperExtensions
{
    public static MvcHtmlString IncludeVersionedJs(this HtmlHelper helper, string filename)
    {
        string version = GetVersion(helper, filename);
        return MvcHtmlString.Create("<script type='text/javascript' src='" + filename + version + "'></script>");
    }

    public static MvcHtmlString IncludeVersionedCss(this HtmlHelper helper, string filename)
    {
        string version = GetVersion(helper, filename);
        return MvcHtmlString.Create("<link href='" + filename + version + "' type ='text/css' rel='stylesheet'/>");
    }

    private static string GetVersion(this HtmlHelper helper, string filename)
    {
        var context = helper.ViewContext.RequestContext.HttpContext;
        var physicalPath = context.Server.MapPath(filename);
        var version = "?v=" +
        new System.IO.FileInfo(physicalPath).LastWriteTime
        .ToString("yyyyMMddHHmmss");
        context.Cache.Add(physicalPath, version, null,
          DateTime.Now.AddMinutes(1), TimeSpan.Zero,
          CacheItemPriority.Normal, null);

        if (context.Cache[filename] == null)
        {
            context.Cache[filename] = version;
            return version;
        }
        else
        {
            if (version != context.Cache[filename].ToString())
            {
                context.Cache[filename] = version;
                return version;
            }
            return context.Cache[filename] as string;
        }
    }
}

Ответ 11

Я использовал несколько другую технику на моем сайте aspnet MVC 4:

_ViewStart.cshtml:

@using System.Web.Caching
@using System.Web.Hosting
@{
    Layout = "~/Views/Shared/_Layout.cshtml";
    PageData.Add("scriptFormat", string.Format("<script src=\"{{0}}?_={0}\"></script>", GetDeployTicks()));
}

@functions
{

    private static string GetDeployTicks()
    {
        const string cacheKey = "DeployTicks";
        var returnValue = HttpRuntime.Cache[cacheKey] as string;
        if (null == returnValue)
        {
            var absolute = HostingEnvironment.MapPath("~/Web.config");
            returnValue = File.GetLastWriteTime(absolute).Ticks.ToString();
            HttpRuntime.Cache.Insert(cacheKey, returnValue, new CacheDependency(absolute));
        }
        return returnValue;
    }
}

Затем в действительных представлениях:

 @Scripts.RenderFormat(PageData["scriptFormat"], "~/Scripts/Search/javascriptFile.min.js")

Ответ 12

Получить время изменения файла, как показано ниже

private static string GetLastWriteTimeForFile(string pathVal)
    {
        return System.IO.File.GetLastWriteTime(HostingEnvironment.MapPath(pathVal)).ToFileTime().ToString();
    }

Добавьте это с помощью ввода как querystring

public static string AppendDateInFile(string pathVal)
    {
        var patheWithDate = new StringBuilder(pathVal);
        patheWithDate.AppendFormat("{0}x={1}",
                               pathVal.IndexOf('?') >= 0 ? '&' : '?',
                               GetLastWriteTimeForFile(pathVal));
        return patheWithDate.ToString();
    }

Вызовите это из разметки.

Подход помощника MVC

Добавить метод расширения

namespace TNS.Portal.Helpers
{
    public static class ScriptExtensions
    {
        public static HtmlString QueryStringScript<T>(this HtmlHelper<T> html, string path)
        {
            var file = html.ViewContext.HttpContext.Server.MapPath(path);
            DateTime lastModified = File.GetLastWriteTime(file);
            TagBuilder builder = new TagBuilder("script");
            builder.Attributes["src"] = path + "?modified=" + lastModified.ToString("yyyyMMddhhmmss");
            return new HtmlString(builder.ToString());
        }

       public static HtmlString QueryStringStylesheet<T>(this HtmlHelper<T> html, string path)
       {
        var file = html.ViewContext.HttpContext.Server.MapPath(path);
        DateTime lastModified = File.GetLastWriteTime(file);
        TagBuilder builder = new TagBuilder("link");
        builder.Attributes["href"] = path + "?modified=" + lastModified.ToString("yyyyMMddhhmmss");
        builder.Attributes["rel"] = "stylesheet";
        return new HtmlString(builder.ToString());
      }

    }
}

Добавьте это пространство имен в web.config

<system.web.webPages.razor>
    <host factoryType="System.Web.Mvc.MvcWebRazorHostFactory, System.Web.Mvc, Version=5.2.3.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" />
    <pages pageBaseType="System.Web.Mvc.WebViewPage">
      <namespaces>
        <add namespace="System.Web.Mvc" />
        <add namespace="System.Web.Mvc.Ajax" />
        <add namespace="System.Web.Mvc.Html" />
        <add namespace="System.Web.Optimization"/>
        <add namespace="System.Web.Routing" />
        <add namespace="TNS.Portal" />
        <add namespace="TNS.Portal.Helpers" />
      </namespaces>
    </pages>
  </system.web.webPages.razor>

Используйте его в поле зрения

@Html.QueryStringScript("/Scripts/NPIAjaxCalls.js")
@Html.QueryStringStylesheet("/Content/StyledRadio.css")

Ответ 13

Упрощенные предварительные предложения и предоставление кода для разработчиков .NET Web Forms.

Это приведет к относительным ( "~/" ) и абсолютным URL-адресам в пути к ресурсу.

Поместите в файл класса статических расширений следующее:

public static string VersionedContent(this HttpContext httpContext, string virtualFilePath)
{
    var physicalFilePath = httpContext.Server.MapPath(virtualFilePath);
    if (httpContext.Cache[physicalFilePath] == null)
    {
        httpContext.Cache[physicalFilePath] = ((Page)httpContext.CurrentHandler).ResolveUrl(virtualFilePath) + (virtualFilePath.Contains("?") ? "&" : "?") + "v=" + File.GetLastWriteTime(physicalFilePath).ToString("yyyyMMddHHmmss");
    }
    return (string)httpContext.Cache[physicalFilePath];
}

И затем назовите его на главной странице как таковой:

<link type="text/css" rel="stylesheet" href="<%= Context.VersionedContent("~/styles/mystyle.css") %>" />
<script type="text/javascript" src="<%= Context.VersionedContent("~/scripts/myjavascript.js") %>"></script>

Ответ 14

Основная проблема с этим в основном заключается в том, что вам нужно будет не забывать обновлять свой номер версии в коде каждый раз, когда вы вносите какие-либо изменения в ваши файлы css или js.

Возможно, лучший способ сделать это - установить гарантированный уникальный параметр с каждым из ваших файлов css или js, например:

<script src="scripts/myjavascript.js?_=<%=DateTime.Now.Ticks%>" type="text/javascript"></script>
<link href="styles/mystyle.css?_=<%=DateTime.Now.Ticks%>" rel="stylesheet" type="text/css" />

Это заставляет файлы запрашивать с сервера каждый раз, что также означает, что ваш сайт не будет таким же эффективным при загрузке страницы, поскольку эти файлы никогда не будут кэшироваться и будут использовать ненужную пропускную способность каждый раз.

По существу, если вы не можете обновлять номер версии каждый раз, когда вы делаете изменение, вы можете уйти от того, как вы это делаете.

Ответ 15

Основываясь на вышеприведенном ответе, я написал небольшой класс расширения для работы с CSS и JS файлами:

public static class TimestampedContentExtensions
{
    public static string VersionedContent(this UrlHelper helper, string contentPath)
    {
        var context = helper.RequestContext.HttpContext;

        if (context.Cache[contentPath] == null)
        {
            var physicalPath = context.Server.MapPath(contentPath);
            var version = @"v=" + new FileInfo(physicalPath).LastWriteTime.ToString(@"yyyyMMddHHmmss");

            var translatedContentPath = helper.Content(contentPath);

            var versionedContentPath =
                contentPath.Contains(@"?")
                    ? translatedContentPath + @"&" + version
                    : translatedContentPath + @"?" + version;

            context.Cache.Add(physicalPath, version, null, DateTime.Now.AddMinutes(1), TimeSpan.Zero,
                CacheItemPriority.Normal, null);

            context.Cache[contentPath] = versionedContentPath;
            return versionedContentPath;
        }
        else
        {
            return context.Cache[contentPath] as string;
        }
    }
}

Вместо того, чтобы писать что-то вроде:

<link href="@Url.Content(@"~/Content/bootstrap.min.css")" rel="stylesheet" type="text/css" />
<script src="@Url.Content(@"~/Scripts/bootstrap.min.js")"></script>

Теперь вы можете написать:

<link href="@Url.VersionedContent(@"~/Content/bootstrap.min.css")" rel="stylesheet" type="text/css" />
<script src="@Url.VersionedContent(@"~/Scripts/bootstrap.min.js")"></script>

т.е. просто замените Url.Content на Url.VersionedContent.

Сгенерированные URL-адреса выглядят примерно так:

<link href="/Content/bootstrap.min.css?v=20151104105858" rel="stylesheet" type="text/css" />
<script src="/Scripts/bootstrap.min.js?v=20151029213517"></script>

Если вы используете класс расширения, вы можете добавить обработку ошибок, если вызов MapPath не работает, поскольку contentPath не является физическим файлом.

Ответ 16

Я использую аналогичный способ сделать то же самое, что и вы, без изменения каждой страницы. Добавлено событие PreRender - это главный файл. Он сохраняет мою логику в одном месте и применим к файлам js и css.

protected void Page_PreRender(object sender, EventArgs e)
    {
        HtmlLink link = null;
        LiteralControl script = null;


        foreach (Control c in Header.Controls)
        {
            //StyleSheet add version
            if (c is HtmlLink)
            {
                link = c as HtmlLink;


                if (link.Href.EndsWith(".css", StringComparison.InvariantCultureIgnoreCase))
                {
                    link.Href += string.Format("?v={0}", ConfigurationManager.AppSettings["agVersion"]);
                }

            }

            //Js add version
            if (c is LiteralControl)
            {
                script = c as LiteralControl;

                if (script.Text.Contains(".js"))
                {
                    var foundIndexes = new List<int>();


                    for (int i = script.Text.IndexOf(".js\""); i > -1; i = script.Text.IndexOf(".js\"", i + 1))
                    {

                        foundIndexes.Add(i);
                    }

                    for (int i = foundIndexes.Count - 1; i >= 0; i--)
                    {

                        script.Text = script.Text.Insert(foundIndexes[i] + 3, string.Format("?v={0}", ConfigurationManager.AppSettings["agVersion"]));
                    }
                }

            }

        }
    }

Ответ 17

<?php $rand_no = rand(10000000, 99999999)?> <script src="scripts/myjavascript.js?v=<?=$rand_no"></script>

Это работает для меня во всех браузерах. Здесь я использовал PHP для генерации случайных чисел. Вы можете использовать свой собственный язык на стороне сервера.

Ответ 18

Вы можете переопределить свойство DefaultTagFormat скриптов или стилей.

Scripts.DefaultTagFormat = @"<script src=""{0}?v=" + ConfigurationManager.AppSettings["pubversion"] + @"""></script>";
Styles.DefaultTagFormat = @"<link href=""{0}?v=" + ConfigurationManager.AppSettings["pubversion"] + @""" rel=""stylesheet""/>";

Ответ 19

Для страниц ASP.NET я использую следующие

перед

<script src="/Scripts/pages/common.js" type="text/javascript"></script>

ПОСЛЕ (принудительная перезагрузка)

 <script src="/Scripts/pages/common.js?ver<%=DateTime.Now.Ticks.ToString()%>" type="text/javascript"></script>

Добавление DateTime.Now.Ticks работает очень хорошо.