MVC 4 捆绑包是否解决了陈旧 .js 文件的问题?这些 .js 文件缓存在客户端的计算机上,因此它们有时不会随新部署进行更新。
捆绑并让框架确定 etag 是否匹配可以解决 MVC 4 中的问题?
同样,使用 MVC 3 时有哪些替代方案?
MVC 4 捆绑包是否解决了陈旧 .js 文件的问题?这些 .js 文件缓存在客户端的计算机上,因此它们有时不会随新部署进行更新。
捆绑并让框架确定 etag 是否匹配可以解决 MVC 4 中的问题?
同样,使用 MVC 3 时有哪些替代方案?
MVC 4 捆绑发出捆绑资源的散列。
例如。
<link href="@System.Web.Optimization.BundleTable.
Bundles.ResolveBundleUrl("~/Content/css")"
rel="stylesheet"
type="text/css" />
结果如下:
<link href="/Content/css?v=ji3nO1pdg6VLv3CVUWntxgZNf1z"
rel="stylesheet" type="text/css" />
如果文件更改,v
参数将更改,强制客户端重新下载资源。
您可以将它与 MVC 3 一起使用。它位于System.Web.Optimization.dll
. 您可以下载并使用 .
欲了解更多信息: http: //nuget.org/packages/microsoft.web.optimization
例如,在您的 global.asax 中,添加以下内容:
bundles.Add(new ScriptBundle("~/bundles/jquery").Include(
"~/Scripts/jquery-{version}.js"));
bundles.Add(new ScriptBundle("~/bundles/jqueryui").Include(
"~/Scripts/jquery-ui-{version}.js"));
bundles.Add(new ScriptBundle("~/bundles/jqueryval").Include(
"~/Scripts/jquery.unobtrusive*",
"~/Scripts/jquery.validate*"));
bundles.Add(new ScriptBundle("~/bundles/customjs").Include(
"~/Scripts/jquery.custom.js"));
// or what you want to add different js files.
我最近遇到了脚本缓存的问题。新浏览器(尤其是 Chrome)正在缓存脚本,有时它们不会向服务器发送请求以检查是否有新版本。
在 MVC3 应用程序中,我决定使用自定义路由处理程序来处理它。在 html 中,我将修订附加到每个脚本链接。然后在我的处理程序中,我从 url 中删除修订号,然后在服务器上搜索实际文件(例如,Path/Script.rev1000.js 指向 Path/Script.js)。
这是我的代码:
public class ContentRouteHandler : IRouteHandler
{
private OzirRouteProvider _routeProvider;
public ContentRouteHandler(OzirRouteProvider routeProvider)
{
this._routeProvider = routeProvider;
}
public IHttpHandler GetHttpHandler(RequestContext requestContext)
{
return new ContentHttpHandler(this._routeProvider, this, requestContext);
}
}
internal class ContentHttpHandler : IHttpHandler, IRequiresSessionState
{
private OzirRouteProvider _routeProvider;
private ContentRouteHandler _routeHandler;
private RequestContext _requestContext;
public bool IsReusable { get { return false; } }
public ContentHttpHandler(OzirRouteProvider routeProvider, ContentRouteHandler routeHandler, RequestContext requestContext)
{
this._routeProvider = routeProvider;
this._routeHandler = routeHandler;
this._requestContext = requestContext;
}
public void ProcessRequest(HttpContext context)
{
string contentPath = context.Request.PhysicalPath;
string fileName = Path.GetFileNameWithoutExtension(contentPath);
string extension = Path.GetExtension(contentPath);
string path = Path.GetDirectoryName(contentPath);
bool minify = false;
// Here i get fileName like Script.rev1000.min.js
// I strip revision and .min from it so I'll have Script.js
var match = Regex.Match(fileName, "(\\.rev\\d+)?(\\.min)?$");
if (match.Groups[2].Success)
{
minify = true;
fileName = fileName.Remove(match.Groups[2].Index, match.Groups[2].Length);
contentPath = Path.Combine(path, fileName + extension);
}
if (match.Groups[1].Success)
{
fileName = fileName.Remove(match.Groups[1].Index, match.Groups[1].Length);
contentPath = Path.Combine(path, fileName + extension);
}
if (!File.Exists(contentPath)) // 404
{
throw new HttpException(404, "Not found");
}
DateTime lastModified = this.GetModificationDate(contentPath);
string eTag = this.GetETag(context.Request.RawUrl, contentPath, lastModified);
// Check for modification
string requestETag = context.Request.Headers["If-None-Match"];
string requestLastModified = context.Request.Headers["If-Modified-Since"];
DateTime? requestLastModifiedDate = requestLastModified == null ? null : (DateTime?)DateTime.Parse(requestLastModified).ToUniversalTime().TruncMiliseconds();
// Compare e-tag and modification date
if ((requestLastModified != null || requestETag != null) &&
(requestLastModified == null || requestLastModifiedDate == lastModified) &&
(requestETag == null || requestETag == eTag))
{
context.Response.StatusCode = 304;
context.Response.SuppressContent = true;
context.Response.Flush();
return;
}
switch (extension)
{
case ".js":
context.Response.ContentType = "application/x-javascript";
if (minify) // minify file?
{
string minContentPath = Path.Combine(path, fileName + ".min" + extension);
this.MinifyJs(contentPath, minContentPath);
contentPath = minContentPath;
}
break;
default:
throw new NotSupportedException(string.Format("Extension {0} is not supported yet", extension));
}
// g-zip and deflate support
string acceptEncoding = context.Request.Headers["Accept-Encoding"];
if (!string.IsNullOrEmpty(acceptEncoding) && acceptEncoding.Contains("gzip"))
{
context.Response.Filter = new System.IO.Compression.GZipStream(context.Response.Filter, System.IO.Compression.CompressionMode.Compress);
context.Response.AppendHeader("Content-Encoding", "gzip");
}
else if (!string.IsNullOrEmpty(acceptEncoding) && acceptEncoding.Contains("deflate"))
{
context.Response.Filter = new System.IO.Compression.DeflateStream(context.Response.Filter, System.IO.Compression.CompressionMode.Compress);
context.Response.AppendHeader("Content-Encoding", "deflate");
}
context.Response.AddCacheDependency(new CacheDependency(contentPath));
context.Response.AddFileDependency(contentPath);
context.Response.Cache.SetCacheability(HttpCacheability.ServerAndPrivate);
context.Response.Cache.SetETag(eTag);
context.Response.Cache.SetExpires(DateTime.Now.AddDays(7));
context.Response.Cache.SetLastModified(lastModified);
context.Response.Cache.SetMaxAge(TimeSpan.FromDays(7));
context.Response.TransmitFile(contentPath);
context.Response.Flush();
}
private void MinifyJs(string contentPath, string minContentPath)
{
this._log.DebugFormat("Minifying JS {0} into {1}", contentPath, minContentPath);
if (!File.Exists(minContentPath) || File.GetLastWriteTime(contentPath) > File.GetLastWriteTime(minContentPath))
{
string content = File.ReadAllText(contentPath, Encoding.UTF8);
JavaScriptCompressor compressor = new JavaScriptCompressor();
compressor.Encoding = Encoding.UTF8;
compressor.ErrorReporter = new CustomErrorReporter(LoggingType.Debug);
content = compressor.Compress(content);
File.WriteAllText(minContentPath, content, Encoding.UTF8);
}
}
private DateTime GetModificationDate(string contentPath)
{
DateTime lastModified = File.GetLastWriteTimeUtc(contentPath).TruncMiliseconds();
return lastModified;
}
private string GetETag(string url, string contentPath, DateTime lastModified)
{
string eTag = string.Format("url={0},path={1},lm={2},rev={3}", url, contentPath, lastModified, AppInfo.Revision);
return Quote(GetHash(eTag));
}
private static string GetHash(string value)
{
byte[] data = MD5.Create().ComputeHash(Encoding.UTF8.GetBytes(value));
StringBuilder hex = new StringBuilder(data.Length * 2);
foreach (byte b in data)
{
hex.AppendFormat("{0:x2}", b);
}
return hex.ToString();
}
private static string Quote(string value)
{
return string.Format("\"{0}\"", value);
}
}
要使用它,您必须打开RouteExistingFiles
并注册路由,例如:
routes.Add(new Route("Content/{*resource}", new RouteValueDictionary(), new RouteValueDictionary { { "resource", @".*(\.css)$" } }, contentHandler));
routes.Add(new Route("Scripts/{*resource}", new RouteValueDictionary(), new RouteValueDictionary { { "resource", @".*(\.js)$" } }, contentHandler));