我如何强制刷新(ctrl + F5)?
我们正在积极开发一个使用.Net和MVC的网站,我们的测试人员正试图获得最新的测试内容。 每次我们修改样式表或外部JavaScript文件时,测试人员都需要进行硬刷新(在IE中按Ctrl + F5)才能看到最新的东西。
我有可能强迫他们的浏览器获取这些文件的最新版本,而不是依赖于它们的缓存版本吗? 我们没有从IIS或任何其他方面进行任何特殊的缓存。
一旦投入生产,很难告诉客户他们需要硬刷新才能看到最新的变化。
谢谢!
您需要修改您引用的外部文件的名称。 例如,在每个文件末尾添加内部版本号,例如style-1423.css,并将编号作为构建自动化的一部分,以便每次都使用唯一的名称部署文件和引用。
我也遇到过这种情况,并发现我认为是非常令人满意的解决方案。
请注意,使用查询参数.../foo.js?v=1
可能意味着文件显然不会被某些代理服务器缓存。 直接修改路径会更好。
当内容发生变化时,我们需要浏览器强制重新加载。 所以,在我写的代码中,路径包含被引用文件的MD5哈希。 如果该文件重新发布到Web服务器,但具有相同的内容,则其URL是相同的。 更重要的是,使用无限期的缓存也是安全的,因为该URL的内容永远不会改变。
这个散列值是在运行时计算的(并且缓存在内存中用于性能),所以不需要修改构建过程。 事实上,自从将此代码添加到我的网站后,我不必多加考虑。
您可以在此网站上看到它的实际操作:潜水七 - 潜水员的在线潜水记录
在CSHTML / ASPX文件中
<head>
@Html.CssImportContent("~/Content/Styles/site.css");
@Html.ScriptImportContent("~/Content/Styles/site.js");
</head>
<img src="@Url.ImageContent("~/Content/Images/site.png")" />
这产生标记类似于:
<head>
<link rel="stylesheet" type="text/css"
href="/c/e2b2c827e84b676fa90a8ae88702aa5c" />
<script src="/c/240858026520292265e0834e5484b703"></script>
</head>
<img src="/c/4342b8790623f4bfeece676b8fe867a9" />
在Global.asax.cs中
我们需要创建一个路径来在此路径上提供内容:
routes.MapRoute(
"ContentHash",
"c/{hash}",
new { controller = "Content", action = "Get" },
new { hash = @"^[0-9a-zA-Z]+$" } // constraint
);
ContentController
这门课很长。 问题的关键很简单,但事实证明,您需要监视文件系统的变化,以强制重新计算缓存文件散列。 我通过FTP发布我的网站,例如, bin
文件夹被替换为Content
文件夹之前。 在此期间请求网站的任何人(人或蜘蛛)都将导致旧的散列被更新。
代码看起来比读/写锁定更复杂。
public sealed class ContentController : Controller
{
#region Hash calculation, caching and invalidation on file change
private static readonly Dictionary<string, string> _hashByContentUrl = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
private static readonly Dictionary<string, ContentData> _dataByHash = new Dictionary<string, ContentData>(StringComparer.Ordinal);
private static readonly ReaderWriterLockSlim _lock = new ReaderWriterLockSlim(LockRecursionPolicy.NoRecursion);
private static readonly object _watcherLock = new object();
private static FileSystemWatcher _watcher;
internal static string ContentHashUrl(string contentUrl, string contentType, HttpContextBase httpContext, UrlHelper urlHelper)
{
EnsureWatching(httpContext);
_lock.EnterUpgradeableReadLock();
try
{
string hash;
if (!_hashByContentUrl.TryGetValue(contentUrl, out hash))
{
var contentPath = httpContext.Server.MapPath(contentUrl);
// Calculate and combine the hash of both file content and path
byte[] contentHash;
byte[] urlHash;
using (var hashAlgorithm = MD5.Create())
{
using (var fileStream = System.IO.File.Open(contentPath, FileMode.Open, FileAccess.Read, FileShare.Read))
contentHash = hashAlgorithm.ComputeHash(fileStream);
urlHash = hashAlgorithm.ComputeHash(Encoding.ASCII.GetBytes(contentPath));
}
var sb = new StringBuilder(32);
for (var i = 0; i < contentHash.Length; i++)
sb.Append((contentHash[i] ^ urlHash[i]).ToString("x2"));
hash = sb.ToString();
_lock.EnterWriteLock();
try
{
_hashByContentUrl[contentUrl] = hash;
_dataByHash[hash] = new ContentData { ContentUrl = contentUrl, ContentType = contentType };
}
finally
{
_lock.ExitWriteLock();
}
}
return urlHelper.Action("Get", "Content", new { hash });
}
finally
{
_lock.ExitUpgradeableReadLock();
}
}
private static void EnsureWatching(HttpContextBase httpContext)
{
if (_watcher != null)
return;
lock (_watcherLock)
{
if (_watcher != null)
return;
var contentRoot = httpContext.Server.MapPath("/");
_watcher = new FileSystemWatcher(contentRoot) { IncludeSubdirectories = true, EnableRaisingEvents = true };
var handler = (FileSystemEventHandler)delegate(object sender, FileSystemEventArgs e)
{
// TODO would be nice to have an inverse function to MapPath. does it exist?
var changedContentUrl = "~" + e.FullPath.Substring(contentRoot.Length - 1).Replace("", "/");
_lock.EnterWriteLock();
try
{
// if there is a stored hash for the file that changed, remove it
string oldHash;
if (_hashByContentUrl.TryGetValue(changedContentUrl, out oldHash))
{
_dataByHash.Remove(oldHash);
_hashByContentUrl.Remove(changedContentUrl);
}
}
finally
{
_lock.ExitWriteLock();
}
};
_watcher.Changed += handler;
_watcher.Deleted += handler;
}
}
private sealed class ContentData
{
public string ContentUrl { get; set; }
public string ContentType { get; set; }
}
#endregion
public ActionResult Get(string hash)
{
_lock.EnterReadLock();
try
{
// set a very long expiry time
Response.Cache.SetExpires(DateTime.Now.AddYears(1));
Response.Cache.SetCacheability(HttpCacheability.Public);
// look up the resource that this hash applies to and serve it
ContentData data;
if (_dataByHash.TryGetValue(hash, out data))
return new FilePathResult(data.ContentUrl, data.ContentType);
// TODO replace this with however you handle 404 errors on your site
throw new Exception("Resource not found.");
}
finally
{
_lock.ExitReadLock();
}
}
}
助手方法
如果您不使用ReSharper,您可以删除这些属性。
public static class ContentHelpers
{
[Pure]
public static MvcHtmlString ScriptImportContent(this HtmlHelper htmlHelper, [NotNull, PathReference] string contentPath, [CanBeNull, PathReference] string minimisedContentPath = null)
{
if (contentPath == null)
throw new ArgumentNullException("contentPath");
#if DEBUG
var path = contentPath;
#else
var path = minimisedContentPath ?? contentPath;
#endif
var url = ContentController.ContentHashUrl(contentPath, "text/javascript", htmlHelper.ViewContext.HttpContext, new UrlHelper(htmlHelper.ViewContext.RequestContext));
return new MvcHtmlString(string.Format(@"<script src=""{0}""></script>", url));
}
[Pure]
public static MvcHtmlString CssImportContent(this HtmlHelper htmlHelper, [NotNull, PathReference] string contentPath)
{
// TODO optional 'media' param? as enum?
if (contentPath == null)
throw new ArgumentNullException("contentPath");
var url = ContentController.ContentHashUrl(contentPath, "text/css", htmlHelper.ViewContext.HttpContext, new UrlHelper(htmlHelper.ViewContext.RequestContext));
return new MvcHtmlString(String.Format(@"<link rel=""stylesheet"" type=""text/css"" href=""{0}"" />", url));
}
[Pure]
public static string ImageContent(this UrlHelper urlHelper, [NotNull, PathReference] string contentPath)
{
if (contentPath == null)
throw new ArgumentNullException("contentPath");
string mime;
if (contentPath.EndsWith(".png", StringComparison.OrdinalIgnoreCase))
mime = "image/png";
else if (contentPath.EndsWith(".jpg", StringComparison.OrdinalIgnoreCase) || contentPath.EndsWith(".jpeg", StringComparison.OrdinalIgnoreCase))
mime = "image/jpeg";
else if (contentPath.EndsWith(".gif", StringComparison.OrdinalIgnoreCase))
mime = "image/gif";
else
throw new NotSupportedException("Unexpected image extension. Please add code to support it: " + contentPath);
return ContentController.ContentHashUrl(contentPath, mime, urlHelper.RequestContext.HttpContext, urlHelper);
}
}
反馈赞赏!
而不是内部编号或随机数,以编程方式将文件的上次修改日期作为querystring追加到URL中。 这可以防止任何你忘记手动修改查询字符串的事故,并且允许浏览器在文件没有改变时缓存它。
示例输出可能如下所示:
<script src="../../Scripts/site.js?v=20090503114351" type="text/javascript"></script>
链接地址: http://www.djcxy.com/p/55769.html
上一篇: How can I force a hard refresh (ctrl+F5)?
下一篇: How to add an apple delegate to a list of fruit delegates?