Подтвердить что ты не робот

MVC4 StyleBundle: можете ли вы добавить строку запроса на удаление кеша в режиме отладки?

У меня есть приложение MVC, и я использую класс StyleBundle для рендеринга CSS файлов следующим образом:

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

Проблема заключается в том, что в режиме Debug URL-адреса CSS выводятся отдельно, и у меня есть веб-прокси, который агрессивно кэширует эти URL-адреса. В режиме Release я знаю, что строка запроса добавляется к окончательному URL-адресу, чтобы аннулировать любые кеши для каждой версии.

Можно ли настроить StyleBundle для добавления случайной последовательности в режиме Debug, а также для создания следующего вывода, чтобы обойти проблему кэширования?

<link href="/stylesheet.css?random=some_random_string" rel="stylesheet"/>
4b9b3361

Ответ 1

Для этого вы можете создать собственный класс IBundleTransform. Вот пример, который добавит параметр v = [filehash], используя хэш содержимого файла.

public class FileHashVersionBundleTransform: IBundleTransform
{
    public void Process(BundleContext context, BundleResponse response)
    {
        foreach(var file in response.Files)
        {
            using(FileStream fs = File.OpenRead(HostingEnvironment.MapPath(file.IncludedVirtualPath)))
            {
                //get hash of file contents
                byte[] fileHash = new SHA256Managed().ComputeHash(fs);

                //encode file hash as a query string param
                string version = HttpServerUtility.UrlTokenEncode(fileHash);
                file.IncludedVirtualPath = string.Concat(file.IncludedVirtualPath, "?v=", version);
            }                
        }
    }
}

Затем вы можете зарегистрировать класс, добавив его в коллекцию Transforms ваших пакетов.

new StyleBundle("...").Transforms.Add(new FileHashVersionBundleTransform());

Теперь номер версии изменится только при изменении содержимого файла.

Ответ 2

Вам просто нужна уникальная строка. Это не должно быть Хэшем. Мы используем дату LastModified файла и получаем Ticks оттуда. Открытие и чтение файла дорого, как отметил @Todd. Ticks достаточно для вывода уникального числа, которое изменяется при изменении файла.

internal static class BundleExtensions
{
    public static Bundle WithLastModifiedToken(this Bundle sb)
    {
        sb.Transforms.Add(new LastModifiedBundleTransform());
        return sb;
    }
    public class LastModifiedBundleTransform : IBundleTransform
    {
        public void Process(BundleContext context, BundleResponse response)
        {
            foreach (var file in response.Files)
            {
                var lastWrite = File.GetLastWriteTime(HostingEnvironment.MapPath(file.IncludedVirtualPath)).Ticks.ToString();
                file.IncludedVirtualPath = string.Concat(file.IncludedVirtualPath, "?v=", lastWrite);
            }
        }
    }
}

и как его использовать:

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

и это то, что пишет MVC:

<link href="bundles/css/site.css?v=635983900813469054" rel="stylesheet"/>

отлично работает с пакетами Script.

Ответ 3

Эта библиотека может добавить хэш кеша к вашим файлам пакетов в режиме отладки, а также несколько других вещей, связанных с кэшированием: https://github.com/kemmis/System.Web.Optimization.HashCache p >

Вы можете применить HashCache ко всем пакетам в BundlesCollection

Выполнить метод расширения ApplyHashCache() на экземпляре BundlesCollection после того, как все пакеты были добавлены в коллекцию.

BundleTable.Bundles.ApplyHashCache();

Или вы можете применить HashCache к одному Bundle

Создайте экземпляр HashCacheTransform и добавьте его в экземпляр пакета, который вы хотите для применения HashCache к.

var myBundle = new ScriptBundle("~/bundle_virtual_path").Include("~/scripts/jsfile.js");
myBundle.Transforms.Add(new HashCacheTransform());

Ответ 4

У меня была такая же проблема, но с кешированными версиями в клиентских браузерах после обновления. Мое решение состоит в том, чтобы обернуть вызов @Styles.Render("~/Content/css") в моем собственном рендерере, который добавляет наш номер версии в строку запроса следующим образом:

    public static IHtmlString RenderCacheSafe(string path)
    {
        var html = Styles.Render(path);
        var version = VersionHelper.GetVersion();
        var stringContent = html.ToString();

        // The version should be inserted just before the closing quotation mark of the href attribute.
        var versionedHtml = stringContent.Replace("\" rel=", string.Format("?v={0}\" rel=", version));
        return new HtmlString(versionedHtml);
    }

И тогда в представлении мне это нравится:

@RenderHelpers.RenderCacheSafe("~/Content/css")

Ответ 5

В настоящее время, но это скоро будет добавлено (сейчас запланировано для версии с стабильной версией 1.1, вы можете отслеживать эту проблему здесь: Codeplex

Ответ 6

Обратите внимание, что это написано для скриптов, но также работает для стилей (просто измените эти ключевые слова)

Основываясь на ответе @Johan:

public static IHtmlString RenderBundle(this HtmlHelper htmlHelper, string path)
{
    var context = new BundleContext(htmlHelper.ViewContext.HttpContext, BundleTable.Bundles, string.Empty);
    var bundle = System.Web.Optimization.BundleTable.Bundles.GetBundleFor(path);
    var html = System.Web.Optimization.Scripts.Render(path).ToString();
    foreach (var item in bundle.EnumerateFiles(context))
    {
        if (!html.Contains(item.Name))
            continue;

        html = html.Replace(item.Name, item.Name + "?" + item.LastWriteTimeUtc.ToString("yyyyMMddHHmmss"));
    }

    return new HtmlString(html);
}

public static IHtmlString RenderStylesBundle(this HtmlHelper htmlHelper, string path)
{
    var context = new BundleContext(htmlHelper.ViewContext.HttpContext, BundleTable.Bundles, string.Empty);
    var bundle = System.Web.Optimization.BundleTable.Bundles.GetBundleFor(path);
    var html = System.Web.Optimization.Styles.Render(path).ToString();
    foreach (var item in bundle.EnumerateFiles(context))
    {
        if (!html.Contains(item.Name))
            continue;

        html = html.Replace(item.Name, item.Name + "?" + item.LastWriteTimeUtc.ToString("yyyyMMddHHmmss"));
    }

    return new HtmlString(html);
}

Использование:

@Html.RenderBundle("...")
@Html.RenderStylesBundle("...")

Замена

@Scripts.Render("...")
@Styles.Render("...")

Преимущества:

  • Работает для v1.0.0.0 System.Web.Optimizations
  • Работает с несколькими файлами в комплекте
  • Возвращает дату изменения файла, а не хэширование каждого файла, а не группу

Кроме того, когда вам нужно быстро обходиться с Bundler:

public static MvcHtmlString ResolveUrl(this HtmlHelper htmlHelper, string url)
{
    var urlHelper = new UrlHelper(htmlHelper.ViewContext.RequestContext);
    var resolvedUrl = urlHelper.Content(url);

    if (resolvedUrl.ToLower().EndsWith(".js") || resolvedUrl.ToLower().EndsWith(".css"))
    {
        var localPath = HostingEnvironment.MapPath(resolvedUrl);
        var fileInfo = new FileInfo(localPath);
        resolvedUrl += "?" + fileInfo.LastWriteTimeUtc.ToString("yyyyMMddHHmmss");
    }

    return MvcHtmlString.Create(resolvedUrl);
}

Использование:

<script type="text/javascript" src="@Html.ResolveUrl("~/Scripts/jquery-1.9.1.min.js")"></script>

Замена:

<script type="text/javascript" src="@Url.Content("~/Scripts/jquery-1.9.1.min.js")"></script>

(Также заменяет многие другие альтернативные запросы)