ti-enxame.com

MVC4 StyleBundle: Você pode adicionar uma string de consulta que impede o cache no modo Debug?

Eu tenho um aplicativo MVC e estou usando a classe StyleBundle para renderizar arquivos CSS como este:

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

O problema que tenho é que, no modo Debug, os URLs do CSS são renderizados individualmente e eu tenho um proxy da Web que armazena esses URLs em cache. No modo Release, eu sei que uma string de consulta é adicionada ao URL final para invalidar os caches de cada release.

É possível configurar StyleBundle para adicionar uma string de consulta aleatória no modo Debug, bem como produzir a seguinte saída para solucionar o problema de armazenamento em cache?

<link href="/stylesheet.css?random=some_random_string" rel="stylesheet"/>
46
growse

Você pode criar uma classe IBundleTransform personalizada para fazer isso. Aqui está um exemplo que acrescentará um parâmetro v = [filehash] usando um hash do conteúdo do arquivo.

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);
            }                
        }
    }
}

Você pode registrar a classe adicionando-a à coleção Transforms de seus pacotes configuráveis.

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

Agora, o número da versão será alterado apenas se o conteúdo do arquivo for alterado.

45
bingles

Você só precisa de uma string única. Não precisa ser Hash. Usamos a data LastModified do arquivo e obtemos os Ticks a partir daí. Abrir e ler o arquivo é caro, como observou o @Todd. Os tiques são suficientes para gerar um número único que muda quando o arquivo é alterado.

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);
            }
        }
    }
}

e como usá-lo:

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

e é isso que o MVC escreve:

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

também funciona bem com pacotes de scripts.

41
H Dog

Essa biblioteca pode adicionar o hash de impedimento de cache aos arquivos do pacote configurável no modo de depuração, bem como algumas outras coisas de impedimento de cache: https://github.com/kemmis/System.Web.Optimization.HashCache

Você pode aplicar o HashCache a todos os pacotes configuráveis ​​em um BundlesCollection

Execute o método de extensão ApplyHashCache () na Instância BundlesCollection após todos os pacotes configuráveis ​​terem sido adicionados à coleção .

BundleTable.Bundles.ApplyHashCache();

Ou você pode aplicar o HashCache a um único pacote

Crie uma instância do HashCacheTransform e adicione-a à instância do pacote configurável à qual você deseja aplicar o HashCache.

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

Eu tive o mesmo problema, mas com versões em cache nos navegadores clientes após uma atualização. Minha solução é encapsular a chamada para @Styles.Render("~/Content/css") no meu próprio renderizador que acrescenta nosso número de versão na string de consulta da seguinte maneira:

    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);
    }

E então, na exibição, eu faço assim:

@RenderHelpers.RenderCacheSafe("~/Content/css")
8
Johan Gov

No momento, mas está programado para ser adicionado em breve (agora agendado para a versão estável 1.1, você pode acompanhar esse problema aqui: Codeplex

2
Hao Kung

Observe que isso foi escrito para scripts, mas também funciona para estilos (basta alterar essas palavras-chave)

Com base na resposta de @ 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);
}

Uso:

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

Substituindo

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

Benefícios:

  • Funciona para v1.0.0.0 de System.Web.Optimizations
  • Funciona em vários arquivos no pacote
  • Obtém a data de modificação do arquivo, em vez de hash, de cada arquivo, em vez de um grupo

Além disso, quando você precisar solucionar rapidamente o 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);
}

Uso:

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

Substituindo:

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

(Também substitui muitas outras pesquisas alternativas)

1
Todd