【问题标题】:How to force browser to cache file in IIS?如何强制浏览器在 IIS 中缓存文件?
【发布时间】:2015-07-05 08:52:50
【问题描述】:

目前,我将应用程序版本附加到所有 JavaScript 和样式表文件,以防止在旧浏览器中缓存。它工作正常。但是,我想缓存所有 JavaScript 和样式表,而不向网络服务器发出任何请求。

使用当前设置,Web 服务器响应如下图所示。我不希望浏览器花时间检查所有 JavaScript 和样式表文件的 ETag。

这是 web.config 中的当前设置

<clientCache cacheControlMode="UseMaxAge" cacheControlMaxAge="365.00:00:00" />

【问题讨论】:

  • 你不能强制浏览器做任何事情。

标签: asp.net caching iis


【解决方案1】:

这是一个简单的解决方案,其中包含一个跨 ASP 实现工作的 HttpModule。我们在 SPA 应用程序中使用它。它会要求浏览器缓存某些资源一年。主页/登陆页面是一个例外,将始终使用 ETag 进行检查。

第 1 步: 您已经完成的第一步是在每个资源的 url 中添加版本号。我们将其作为构建过程中的一个自动化步骤来执行。

第 2 步:接下来将 CacheModule 类添加到您的应用中:

public class CacheModule : IHttpModule
    {
        // extensions to cache, e.g. ".css",".html",".js",".png",".jpg",".gif",".ico",".woff",".eot",".svg",".ttf"
        private readonly string[] _extensions = ConfigurationManager.AppSettings["CacheModule_Extensions"].Split(",");        


        private readonly string[] _exceptions = ConfigurationManager.AppSettings["CacheModule_Exceptions"].Split(",");  


        public void Dispose() {}

        public void Init(HttpApplication context)
        {
            context.EndRequest += (sender, args) =>
            {
                var ctx = HttpContext.Current;
                var path = ctx.Request.Url.GetComponents(UriComponents.Path, UriFormat.SafeUnescaped);

                var isExcept = _exceptions.Any(path.Contains);

                ctx.Response.AddHeader("Cache-Control", "private");

                if (_extensions.Any(path.Contains) && ! isExcept )
                {
                    ctx.Response.AddHeader("Expires", (DateTime.Now.ToUniversalTime().AddYears(1)).ToString("r"));
                }
                else if (isExcept)
                {
                    ctx.Response.AddHeader("Expires", (DateTime.Now.ToUniversalTime().AddHours(-1)).ToString("r"));
                    ctx.Response.AddHeader("Max-Age", "0");
                }

            };
        }


    }

第 3 步:最后你输入你的配置:

<?xml version="1.0"?>
    <configuration>
        <appSettings>
            <!-- resource extensions to cache -->
            <add key="CacheModule_Extensions" value=".css,.html,.js,.png,.jpg,.gif,.ico,.woff,.eot,.svg,.ttf" />
            <!-- exceptions to caching such as home/landing page e.g. "index.html" or any page/resource with known url that users may enter directly or may be redirected to -->
            <add key="CacheModule_Exceptions" value="index.html,buildinfo.html,unsupportedmobilebrowser.html, unsupportedbrowser.html" />
        </appSettings>
    <system.webServer>
        <modules>
            <add name="CacheModule" type="MyApp.Caching.CacheModule, MyApp"/>
        </modules>
    </system.webServer>
</configuration>

【讨论】:

    【解决方案2】:

    由于 IIS 服务文件并监视更改,IIS 将始终发送重新验证缓存标头,强制浏览器检查更改。为了解决这个问题,我们设计了CachedRoute,如下所示,但是这在 ASP.NET MVC 中运行良好,但只需稍作改动,您也可以在 ASP.NET WebForms 中实现相同的功能。

    此代码还为您提供将静态资源移动到 CDN 的好处。

    缓存版本网址前缀

    我们必须想出像/cached/version/ 这样的静态内容的版本控制,这只不过是静态资产的url 前缀。 version 可以是任何随机的字母数字字符串,完全没用,但标识不同的版本。

    最简单的方法之一是在 URL 中使用版本密钥。

    首先,在 AssemblyInfo.cs 中创建构建版本

     [assembly: AssemblyVersion("1.5.*.*")]
    

    保留,* 作为内部版本号替换,.net 编译器将随着每个版本自动递增。

    或在应用设置中定义版本如下

     <appSettings>
         <add key="Static-Content-Version" value="1.5.445.55565"/>
         <add key="CDNHost" value="cdn1111.cloudfront.net"/>
     </appSettings>
    
     // Route configuration
    
     // set CDN if you have
     string cdnHost = WebConfigrationManager.AppSettings["CDNHost"];
     if(!string.IsEmpty(cdnHost)){
         CachedRoute.CDNHost = cdnHost;
     }
    
     // get assembly build information
     string version = typeof(RouteConfig).Assembly.GetName().Version.ToString();
    
     CachedRoute.CORSOrigins = "*";
     CachedRoute.Register(routes, TimeSpam.FromDays(30), version);
    

    现在在每个页面上,将您的静态内容引用为,

     <script src="@CachedRoute.CachedUrl("/scripts/jquery-1.11.1.js")"></script>
    

    在渲染时,您的页面将被渲染为(没有 CDN)

     <script src="/cached/1.5.445.55565/scripts/jquery-1.11.1.js"></script>
    

    CDN 为

     <script 
          src="//cdn111.cloudfront.net/cached/1.5.445.55565/scripts/jquery-1.11.1.js">
     </script>
    

    将版本放在 URL 路径而不是查询字符串中可以使 CDN 性能更好,因为在 CDN 配置中可以忽略查询字符串(这通常是默认情况)。

    优势 如果您将版本设置为与组装版本相同,则很容易放置新版本。否则,您必须在每次版本更改时手动更改 web.config。

    CachedRoute 类来自 https://github.com/neurospeech/atoms-mvc.net/blob/master/src/Mvc/CachedRoute.cs

    public class CachedRoute : HttpTaskAsyncHandler, IRouteHandler
    {
    
        private CachedRoute()
        {
            // only one per app..
    
        }
    
        private string Prefix { get; set; }
    
        public static string Version { get; private set; }
    
        private TimeSpan MaxAge { get; set; }
    
        public static string CORSOrigins { get; set; }
        //private static CachedRoute Instance;
    
        public static void Register(
            RouteCollection routes,
            TimeSpan? maxAge = null,
            string version = null)
        {
            CachedRoute sc = new CachedRoute();
            sc.MaxAge = maxAge == null ? TimeSpan.FromDays(30) : maxAge.Value;
    
            if (string.IsNullOrWhiteSpace(version))
            {
                version = WebConfigurationManager.AppSettings["Static-Content-Version"];
                if (string.IsNullOrWhiteSpace(version))
                {
                    version = Assembly.GetCallingAssembly().GetName().Version.ToString();
                }
            }
    
            Version = version;
    
            var route = new Route("cached/{version}/{*name}", sc);
            route.Defaults = new RouteValueDictionary();
            route.Defaults["version"] = "1";
            routes.Add(route);
        }
    
        public override bool IsReusable
        {
            get
            {
                return true;
            }
        }
    
        public static string CDNHost { get; set; }
    
        public static HtmlString CachedUrl(string p)
        {
            if (!p.StartsWith("/"))
                throw new InvalidOperationException("Please provide full path starting with /");
            string cdnPrefix = string.IsNullOrWhiteSpace(CDNHost) ? "" : ("//" + CDNHost);
            return new HtmlString(cdnPrefix + "/cached/" + Version + p);
        }
    
        public override async Task ProcessRequestAsync(HttpContext context)
        {
            var Response = context.Response;
            Response.Cache.SetCacheability(HttpCacheability.Public);
            Response.Cache.SetMaxAge(MaxAge);
            Response.Cache.SetExpires(DateTime.Now.Add(MaxAge));
    
            if (CORSOrigins != null)
            {
                Response.Headers.Add("Access-Control-Allow-Origin", CORSOrigins);
            }
    
    
            string FilePath = context.Items["FilePath"] as string;
    
            var file = new FileInfo(context.Server.MapPath("/" + FilePath));
            if (!file.Exists)
            {
                throw new FileNotFoundException(file.FullName);
            }
    
            Response.ContentType = MimeMapping.GetMimeMapping(file.FullName);
    
            using (var fs = file.OpenRead())
            {
                await fs.CopyToAsync(Response.OutputStream);
            }
        }
    
        IHttpHandler IRouteHandler.GetHttpHandler(RequestContext requestContext)
        {
            //FilePath = requestContext.RouteData.GetRequiredString("name");
            requestContext.HttpContext.Items["FilePath"] = requestContext.RouteData.GetRequiredString("name");
            return (IHttpHandler)this;
        }
    }
    

    第一个请求的示例响应标头

    Access-Control-Allow-Origin:*
    Cache-Control:public
    Content-Length:453
    Content-Type:image/png
    Date:Sat, 04 Jul 2015 08:04:55 GMT
    Expires:Mon, 03 Aug 2015 00:46:43 GMT
    Server:Microsoft-IIS/8.5
    Via:1.1 ********************************
    X-Amz-Cf-Id: ******************************
    X-AspNet-Version:4.0.30319
    X-AspNetMvc-Version:5.2
    X-Cache:Miss from cloudfront
    X-Powered-By:ASP.NET
    

    看,没有 ETag、Vary by、Last Modified 或验证标头,并且还看到显式 Expires 标头,当您发送显式 Expires 标头时,浏览器将永远不会尝试验证缓存。

    【讨论】:

    • 你能告诉我第一个请求的响应头吗?
    猜你喜欢
    • 2014-07-18
    • 2015-02-18
    • 2011-10-11
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-02-18
    相关资源
    最近更新 更多