我有一个MVC应用程序,我正在使用StyleBundle
类来渲染这样的CSS文件:
bundles.Add(new StyleBundle("~/bundles/css").Include("~/Content/*.css"));
我遇到的问题是,在Debug
模式下,CSS网址是单独呈现的,我有一个网络代理,可以积极地缓存这些网址。在Release
模式下,我知道在最终URL中添加了一个查询字符串,以使每个版本的任何缓存无效。
是否可以将StyleBundle
配置为在Debug
模式下添加随机查询字符串以生成以下输出以解决缓存问题?
<link href="/stylesheet.css?random=some_random_string" rel="stylesheet"/>
答案 0 :(得分:45)
您可以创建自定义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());
现在版本号只会在文件内容发生变化时发生变化。
答案 1 :(得分:38)
你只需要一个独特的字符串。它不一定是哈希。我们使用文件的LastModified日期并从那里获取Ticks。正如@Todd所说,打开和阅读文件很昂贵。刻度足以输出在文件更改时更改的唯一编号。
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 bundle。
答案 2 :(得分:13)
此库可以在调试模式下将缓存清除哈希添加到您的包文件中,以及其他一些缓存破坏的内容:https://github.com/kemmis/System.Web.Optimization.HashCache
在BundlesCollection实例上执行ApplyHashCache()扩展方法 将所有捆绑包添加到集合后。
BundleTable.Bundles.ApplyHashCache();
创建HashCacheTransform的实例并将其添加到所需的包实例中 将HashCache应用于。
var myBundle = new ScriptBundle("~/bundle_virtual_path").Include("~/scripts/jsfile.js");
myBundle.Transforms.Add(new HashCacheTransform());
答案 3 :(得分:8)
我遇到了同样的问题,但升级后在客户端浏览器中使用了缓存版本。我的解决方案是在我自己的渲染器中将调用包装到@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")
答案 4 :(得分:2)
目前还没有,但很快就会添加(现在计划在1.1稳定版中发布,您可以在此处跟踪此问题:Codeplex
答案 5 :(得分:1)
请注意,这是为脚本编写的,但也适用于样式(只需更改这些关键字)
以@ 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("...")
优点:
此外,当您需要快速解决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>
(也取代了许多其他替代查找)