在 ASP.NET MVC 中自动版本化 CSS/JS?
Posted
技术标签:
【中文标题】在 ASP.NET MVC 中自动版本化 CSS/JS?【英文标题】:Autoversioning CSS/JS in ASP.NET MVC? 【发布时间】:2011-08-14 16:44:43 【问题描述】:所以我正在阅读 this *** post 关于 CSS/JS 文件的 ASP.NET MVC 中的“自动版本控制”,并且想知道执行此操作的“最佳”策略是什么。
提供的解决方案会插入一个程序集编号 - 这意味着每次发布时 - 它都会更改每个不理想的文件,因为如果您只对 1 个 *.css 或 *.js 进行修改,那么它会改变每一个文件。
1) 如何仅针对“单个文件”而不是使用修改日期或 IIS7 上的其他内容来使用站点范围的程序集?
2) 另外,如果我有某种“静态”资产,例如 - http://static.domain.com/js/123.js - 如果有人将此静态链接集成到他们的网站上,我如何使用重写来发送请求的最新文件?
即http://static.domain.com/js/123.js 是链接,当有此请求时 - 检查并发送最新文件?
【问题讨论】:
老实说,如果您要捆绑,那么每个文件都意味着 2-5 个缩小+捆绑的文件。我认为这是一个合理的解决方案。 【参考方案1】:ASP.NET 4.5+ 带有built-in bundling & minification framework 旨在解决这个问题。
如果您绝对需要一个简单的自己动手解决方案,您可以使用下面的答案,但我总是说正确的方法是使用捆绑和缩小框架。
您可以像这样修改 AssemblyInfo.cs 文件:
Change
[assembly: AssemblyVersion("1.0.0.0")]
to
[assembly: AssemblyVersion("1.0.*")]
这意味着每次构建项目时,都会有一个新的程序集版本,该版本高于之前的版本。现在您有了唯一的版本号。
创建一个 UrlHelperExtension 类,该类将在视图中需要时帮助获取此信息:
public static class UrlHelperExtensions
public static string ContentVersioned(this UrlHelper self, string contentPath)
string versionedContentPath = contentPath + "?v=" + Assembly.GetAssembly(typeof(UrlHelperExtensions)).GetName().Version.ToString();
return self.Content(versionedContentPath);
您现在可以通过以下方式轻松地将版本号添加到您的视图中:
<link href="@Url.ContentVersioned("style.css")" rel="stylesheet" type="text/css" />
当查看您的页面源代码时,您现在会看到类似的东西
<link href="style.css?v=1.0.4809.30029" rel="stylesheet" type="text/css" />
【讨论】:
我收到一个错误方法 'ContentVersioned' 没有重载需要 1 个参数 我正在做 很好的解决方案! @Anjyr 基于上述建议并假设您有 ContentVersioned 扩展,如上面将 src='~/Scripts/demoproject/@FileVersioning.ContentVersioned("custom.js")' 更改为 src ='@Url.ContentVersioned("~/full/path/to/custom.js")' @ANJYR 确保您使用的是 System.Web.Mvc.UrlHelper(不是 System.Web.Http.Routing.UrlHelper)【参考方案2】:更新:以前的版本在 Azure 上不起作用,我在下面进行了简化和更正。 (注意,要在 IIS Express 的开发模式下工作,您需要安装 Microsoft http://www.iis.net/downloads/microsoft/url-rewrite 的 URL Rewrite 2.0 - 它使用 WebPi 安装程序,请确保先关闭 Visual Studio)
如果您想更改文件的实际名称,而不是附加查询字符串(某些代理/浏览器会忽略静态文件),您可以按照以下步骤操作:(我知道这是一篇旧帖子,但我在开发解决方案时遇到了它:
怎么做: 每次构建项目时自动增加程序集版本,并将该数字用于特定资源上的路由静态文件喜欢保持清爽。 (所以 something.js 包含在 something.v1234.js 中,每次构建项目时 1234 都会自动更改) - 我还添加了一些额外的功能,以确保在生产中使用 .min.js 文件并使用 regular.js 文件调试时(我正在使用 WebGrease 来自动化缩小过程)这个解决方案的一个好处是它可以在本地/开发模式以及生产模式下工作。 (我使用的是 Visual Studio 2015 / Net 4.6,但我相信这也适用于早期版本。
第 1 步:在构建时启用程序集的自动增量 在 AssemblyInfo.cs 文件(在项目的“属性”部分下找到)更改以下几行:
[assembly: AssemblyVersion("1.0.0.0")]
[assembly: AssemblyFileVersion("1.0.0.0")]
到
[assembly: AssemblyVersion("1.0.*")]
//[assembly: AssemblyFileVersion("1.0.0.0")]
第 2 步:在 web.config 中为具有嵌入式版本 slug 的文件设置 url 重写(参见第 3 步)
在 web.config(项目的主要部分)中,在 <system.webServer>
部分添加以下规则,我将其直接放在 </httpProtocol>
结束标记之后。
<rewrite>
<rules>
<rule name="static-autoversion">
<match url="^(.*)([.]v[0-9]+)([.](js|css))$" />
<action type="Rewrite" url="R:1R:3" />
</rule>
<rule name="static-autoversion-min">
<match url="^(.*)([.]v[0-9]+)([.]min[.](js|css))$" />
<action type="Rewrite" url="R:1R:3" />
</rule>
</rules>
</rewrite>
第 3 步:设置应用程序变量以读取您当前的程序集版本并在您的 js 和 css 文件中创建版本 slug。
在 Global.asax.cs(在项目的根目录中找到)中,将以下代码添加到受保护的 void Application_Start()(在 Register 行之后)
// setup application variables to write versions in razor (including .min extension when not debugging)
string addMin = ".min";
if (System.Diagnostics.Debugger.IsAttached) addMin = ""; // don't use minified files when executing locally
Application["JSVer"] = "v" + System.Reflection.Assembly.GetExecutingAssembly().GetName().Version.ToString().Replace('.','0') + addMin + ".js";
Application["CSSVer"] = "v" + System.Reflection.Assembly.GetExecutingAssembly().GetName().Version.ToString().Replace('.', '0') + addMin + ".css";
第 4 步:使用我们在 Global.asax.cs 中设置的应用程序变量更改 Razor 视图中的 src 链接
@HttpContext.Current.Application["CSSVer"]
@HttpContext.Current.Application["JSVer"]
例如,在我的 _Layout.cshtml 中,在我的 head 部分,我有以下样式表代码块:
<!-- Load all stylesheets -->
<link rel='stylesheet' href='https://fontastic.s3.amazonaws.com/8NNKTYdfdJLQS3D4kHqhLT/icons.css' />
<link rel='stylesheet' href='/Content/css/main-small.@HttpContext.Current.Application["CSSVer"]' />
<link rel='stylesheet' media='(min-width: 700px)' href='/Content/css/medium.@HttpContext.Current.Application["CSSVer"]' />
<link rel='stylesheet' media='(min-width: 700px)' href='/Content/css/large.@HttpContext.Current.Application["CSSVer"]' />
@RenderSection("PageCSS", required: false)
这里有几点需要注意:1) 文件中没有扩展名。 2)也没有.min。这两个都由 Global.asax.cs 中的代码处理
同样,(也在 _Layout.cs 中)在我的 javascript 部分:我有以下代码:
<script src="~/Scripts/all3bnd100.min.js" type="text/javascript"></script>
<script src="~/Scripts/ui.@HttpContext.Current.Application["JSVer"]" type="text/javascript"></script>
@RenderSection("scripts", required: false)
第一个文件是我使用 WebGrease 手动创建的所有 3rd 方库的捆绑包。如果我添加或更改捆绑包中的任何文件(这种情况很少见),然后我手动将文件重命名为 all3bnd101.min.js、all3bnd102.min.js 等...此文件与重写处理程序不匹配,所以将一直缓存在客户端浏览器上,直到您手动重新捆绑/更改名称。
第二个文件是 ui.js(将被写为 ui.v12345123.js 或 ui.v12345123.min.js,具体取决于您是否在调试模式下运行)这将被处理/重写。 (你可以在 Global.asax.cs 的 Application_OnBeginRequest 中设置一个断点来观察它的工作)
对此的完整讨论:Simplified Auto-Versioning of Javascript / CSS in ASP.NET MVC 5 to stop caching issues (works in Azure and Locally) With or Without URL Rewrite(包括一种无需 URL 重写的方法)
【讨论】:
【参考方案3】:1) 改用文件修改时间。这是一个例子:
public static string GeneratePathWithTime(string cssFileName)
var serverFilePath = server.MapPath("~/static/" + cssFileName);
var version = File.GetLastWriteTime(serverFilePath).ToString("yyyyMMddhhmmss");
return string.Format("/static/0/1", version, cssFileName);
这将为“style.css
”生成类似“/static/201109231100/style.css
”的路径(假设您的style.css
位于static
目录中)。
然后,您将在 IIS 中添加一个重写规则,将“/static/201109231100/style.css
”重写为“/static/style.css
”。版本号只有在css文件被修改后才会改变,并且只适用于修改后的文件。
2) 您可以通过 HttpModule 处理对 123.js 的请求并发送它的最新内容,但我认为您不能保证请求获得最新版本。这取决于浏览器如何处理其缓存。您可以在响应标头中设置更早的过期时间(例如一分钟前),以告诉浏览器始终重新下载文件,但这完全取决于浏览器本身来决定是否重新下载文件.这就是为什么我们每次更新您的问题 1) 中的文件时都需要为修改后的文件生成不同的路径,如果之前从未访问过该 URL,浏览器将始终尝试下载该文件。
【讨论】:
你不需要在 IIS 中配置重写规则——MVC 原生支持 Routing! 当然。而不仅仅是 MVC。您还可以编写请求处理程序并将其注册到 web.config 中,或使用 Global.asax。它们也受到原生支持。 IIS 重写只是众多解决方案之一。我建议重写 IIS,因为它在 .net 运行时之前处理请求,并且可以在不更改代码的情况下轻松配置。 你说得对,从 ASP.NET 4 开始,路由也可以在 ASP.NET Core(MVC 之外)中使用。但是,我指的是 MVC,因为问题是用它标记的。【参考方案4】:我写了一个 Url Helper,它为我做 CacheBusting。
public static string CacheBustedContent(this UrlHelper helper, string contentPath)
var path = string.Empty;
if (helper.RequestContext.HttpContext.Cache["static-resource-" + contentPath] == null)
var fullpath = helper.RequestContext.HttpContext.Server.MapPath(contentPath);
var md5 = GetMD5HashFromFile(fullpath);
path = helper.Content(contentPath) + "?v=" + md5;
helper.RequestContext.HttpContext.Cache.Add("static-resource-" + contentPath, path, null, System.Web.Caching.Cache.NoAbsoluteExpiration, new TimeSpan(24, 0, 0), System.Web.Caching.CacheItemPriority.Default, null);
else
path = helper.RequestContext.HttpContext.Cache["static-resource-" + contentPath].ToString();
return path;
您可以将 GetMD5HashFromFile() 替换为 CRC 或任何其他类型的调用,该调用会根据文件的内容或最后修改日期生成唯一字符串。
缺点是每当缓存失效时都会调用它。如果您以某种方式实时更改文件,但不重置应用程序池,您可能需要触摸 web.config 以使其正确重新加载。
【讨论】:
也就是说,我真的很喜欢 SquishIt 来缩小、组合和缓存 javascript 和 css。 codethinked.com/…【参考方案5】:您可能想看看 Dean Hume 的博客帖子MVC and the HTML5 Application Cache。在那篇文章中,他指出了一种自动处理每个请求的版本控制的优雅方法,使用 @ShirtlessKirk 的类库:
@Url.Content("~/Content/Site.css").AppendHash(Request)
【讨论】:
对于偶然发现此答案的任何人,请注意 - 此技术依赖于似乎已弃用的应用程序缓存:developer.mozilla.org/en/docs/Web/HTML/…【参考方案6】:这个问题现在真的很老了,但如果有人偶然发现它,据我所知,这是当前的最新技术:
在 ASP.NET Core 中,您可以使用 TagHelpers 并将 asp-append-version
属性添加到任何 <link>
或 <script>
标签:
<script src="~/js/my.js" asp-append-version="true"></script>
对于 ASP.NET Core 和 Framework,都有一个名为 WebOptimizer (https://github.com/ligershark/WebOptimizer) 的 NuGet 包。它允许捆绑和缩小,并且还会将基于内容的版本字符串附加到您的文件中。
如果你想自己做,有方便的IFileVersionProvider
接口,你可以从 .NET Core 中的IServiceProvider
获得:
// this example assumes, you at least have a HttpContext
var fileVersionProvider = httpContext.RequestServices.GetRequiredService<IFileVersionProvider>();
string path = httpContext.Content("/css/site.css");
string pathWithVersionString = fileVersionProvider.AddFileVersionToPath(httpContext.Request.PathBase, path);
对于 .NET Framework,您可以从此处获取 FileVersionProvider
源:https://github.com/dotnet/aspnetcore/blob/main/src/Mvc/Mvc.Razor/src/Infrastructure/DefaultFileVersionProvider.cs
你将不得不做一些工作,比如用MemoryCache.Default
或ConcurrentDictionary
或其他东西替换缓存,但“肉”就在那里。
【讨论】:
以上是关于在 ASP.NET MVC 中自动版本化 CSS/JS?的主要内容,如果未能解决你的问题,请参考以下文章