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
パラメータが変更され、クライアントはリソースを再ダウンロードする必要があります。
出典: http: //bit.ly/xT8ZM5
MVC3で使用できます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));