it-roy-ru.com

заставить браузеры получать последние файлы 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, ваш браузер загрузит последние файлы, которые, как мы надеемся, избавят вас и ваших пользователей от разочарования.

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

92
kiev

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

Я сделал это с помощью метода расширения и использовал его в моих файлах 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>
69
Adam Tegen

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

Даже переполнение стека использует похожий метод:

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

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

26
Daniel Vassallo

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

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

Посмотреть здесь

18
jonesy827

В 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" />
17
metalheart

Для этого есть встроенный способ 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")
10
Alex Tkachuk

Я хотел простой вкладыш, чтобы сделать путь уникальным, чтобы разрушить кеш. Это сработало для меня:

<script src="scripts/main.js?bust_js_cache=<%=System.IO.File.GetLastWriteTime(Server.MapPath("scripts/main.js")).ToString("HH:mm:ss")%>" type="text/javascript"></script>

Если файл был изменен с момента последней загрузки на страницу, браузер извлечет обновленный файл.

Он генерирует штамп last modified из файла .js и помещает его туда вместо версии, к которой может быть нелегко получить доступ.

<script src="scripts/main.js?bust_js_cache=10:18:38" type="text/javascript"></script>

Другим вариантом может быть получение контрольной суммы файла.

4
sniperd

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

В коде класса .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>
4
Bryan

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

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

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

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

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

4
Pekka 웃

Вот подход, который работает с 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.

3
Ender2050

Существует более простой ответ на этот вопрос, чем ответ, данный операцией в вопросе (подход тот же):

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

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

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

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

Начиная с приведенного выше ответа я немного изменил код, чтобы помощник работал и с 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;
        }
    }
}
2
Sergi Mulà

Я использовал немного другую технику на своем сайте 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")
2
Anthony Wolfe

Упрощенные предыдущие предложения и предоставление кода для разработчиков .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>
1
Jason Ellingson

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

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

1
Mukesh Rai

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

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

Добавьте это с вводом в качестве строки запроса

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")
1
Lijo

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

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

Для решения этой проблемы в моем приложении ASP.Net Ajax я создал расширение, а затем вызвал его на главной странице. 

Для получения более подробной информации вы можете перейти по ссылке ссылка .

0
Kasim Husaini

Основываясь на ответе выше я написал небольшой класс расширения для работы с файлами 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 не является физическим файлом.

0
Uwe Keim

Основная проблема при этом заключается в том, что вам нужно помнить, чтобы обновлять номер версии в коде каждый раз, когда вы вносите какие-либо изменения в файлы 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" />

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

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

0
Tim S. Van Haren

Я использую аналогичный способ сделать то же самое, что вы делаете, не изменяя каждую страницу. Добавлено событие 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"]));
                    }
                }

            }

        }
    }
0
Jay