如何在 ASP.NET MVC 中为错误页面指定 max-age
How to specify max-age for error pages in ASP.NET MVC
当我的 ASP.NET MVC 服务器(IIS 上的 运行)发生错误时,服务器当前提供静态页面。这是在我的 web.config 中的 httpErrors
元素中配置的,像这样:
<httpErrors errorMode="Custom" existingResponse="Replace">
<error statusCode="404" path="404.htm" responseMode="File" />
<error statusCode="500" path="500.htm" responseMode="File" />
</httpErrors>
检查来自服务器的响应时,我看到 cache-control: private
响应 header。这很好,但我想控制此页面的缓存时间。如何将 max-age=x
添加到此 cache-control
header?
我尝试了很多方法,但似乎只有当我为 404 错误设置自定义错误页面时它才有效。
<system.webServer>
<httpProtocol>
<customHeaders>
<add name="Cache-Control" value="max-age:12300" />
</customHeaders>
</httpProtocol>
</system.webServer>
如果我正确理解你的问题陈述,你的主要目标是控制 max-age
,而不是花哨的 <customErrors>
设置。尝试从动作过滤器控制 header 似乎合乎逻辑。
在web.config
我有这个 system.web
设置:
<system.web>
<compilation debug="true" targetFramework="4.6.1"/> <!-- framework version for reference -->
<httpRuntime targetFramework="4.6.1"/>
<customErrors mode="On">
</customErrors> <!-- I didn't try adding custom pages here, but this blog seem to have a solution: https://benfoster.io/blog/aspnet-mvc-custom-error-pages -->
</system.web>
在MaxAgeFilter.cs
public class MaxAgeFilter : ActionFilterAttribute, IResultFilter, IExceptionFilter
{
public void OnException(ExceptionContext filterContext)
{
if (filterContext.ExceptionHandled || filterContext.HttpContext.IsCustomErrorEnabled)
return;
var statusCode = (int)HttpStatusCode.InternalServerError;
if (filterContext.Exception is HttpException)
{
statusCode = (filterContext.Exception as HttpException).GetHttpCode();
}
else if (filterContext.Exception is UnauthorizedAccessException)
{
statusCode = (int)HttpStatusCode.Forbidden;
}
var result = CreateActionResult(filterContext, statusCode);
filterContext.Result = result;
// Prepare the response code.
filterContext.ExceptionHandled = true;
filterContext.HttpContext.Response.Clear();
filterContext.HttpContext.Response.StatusCode = statusCode;
filterContext.HttpContext.Response.TrySkipIisCustomErrors = true;
var cache = filterContext.HttpContext.Response.Cache;
cache.SetMaxAge(TimeSpan.FromSeconds(10)); // this requires a lot of extra plumbing which I suspect is necessary because if you were to rely on default error response - the cache will get overriden, see original SO answer: https://whosebug.com/questions/8144695/asp-net-mvc-custom-handleerror-filter-specify-view-based-on-exception-type
}
public override void OnResultExecuted(ResultExecutedContext filterContext)
{
var cache = filterContext.HttpContext.Response.Cache;
cache.SetMaxAge(TimeSpan.FromMinutes(10)); // this is easy - you just pass it to the current cache and magic works
base.OnResultExecuted(filterContext);
}
protected virtual ActionResult CreateActionResult(ExceptionContext filterContext, int statusCode)
{
var ctx = new ControllerContext(filterContext.RequestContext, filterContext.Controller);
var statusCodeName = ((HttpStatusCode)statusCode).ToString();
var viewName = SelectFirstView(ctx,
"~/Views/Shared/Error.cshtml",
"~/Views/Shared/Error.cshtml",
statusCodeName,
"Error");
var controllerName = (string)filterContext.RouteData.Values["controller"];
var actionName = (string)filterContext.RouteData.Values["action"];
var model = new HandleErrorInfo(filterContext.Exception, controllerName, actionName);
var result = new ViewResult
{
ViewName = viewName,
ViewData = new ViewDataDictionary<HandleErrorInfo>(model),
};
result.ViewBag.StatusCode = statusCode;
return result;
}
protected string SelectFirstView(ControllerContext ctx, params string[] viewNames)
{
return viewNames.First(view => ViewExists(ctx, view));
}
protected bool ViewExists(ControllerContext ctx, string name)
{
var result = ViewEngines.Engines.FindView(ctx, name, null);
return result.View != null;
}
}
如您所见,处理异常基本上需要重建整个Response
。为此,我几乎从 SO answer here
中获取了代码
最后,您决定是否要在您的控制器、操作或全局设置此属性:
App_Start/FilterConfig.cs
public class FilterConfig
{
public static void RegisterGlobalFilters(GlobalFilterCollection filters)
{
filters.Add(new MaxAgeFilter());
}
}
如果您使用的是自定义错误页面,则可以在视图中设置 headers。正如您所说,这样您就可以根据显示的错误页面设置不同的缓存长度。
@{
TimeSpan duration = new TimeSpan(0, 30, 0);
Response.Cache.SetMaxAge(duration);
}
当我的 ASP.NET MVC 服务器(IIS 上的 运行)发生错误时,服务器当前提供静态页面。这是在我的 web.config 中的 httpErrors
元素中配置的,像这样:
<httpErrors errorMode="Custom" existingResponse="Replace">
<error statusCode="404" path="404.htm" responseMode="File" />
<error statusCode="500" path="500.htm" responseMode="File" />
</httpErrors>
检查来自服务器的响应时,我看到 cache-control: private
响应 header。这很好,但我想控制此页面的缓存时间。如何将 max-age=x
添加到此 cache-control
header?
我尝试了很多方法,但似乎只有当我为 404 错误设置自定义错误页面时它才有效。
<system.webServer>
<httpProtocol>
<customHeaders>
<add name="Cache-Control" value="max-age:12300" />
</customHeaders>
</httpProtocol>
</system.webServer>
如果我正确理解你的问题陈述,你的主要目标是控制 max-age
,而不是花哨的 <customErrors>
设置。尝试从动作过滤器控制 header 似乎合乎逻辑。
在web.config
我有这个 system.web
设置:
<system.web>
<compilation debug="true" targetFramework="4.6.1"/> <!-- framework version for reference -->
<httpRuntime targetFramework="4.6.1"/>
<customErrors mode="On">
</customErrors> <!-- I didn't try adding custom pages here, but this blog seem to have a solution: https://benfoster.io/blog/aspnet-mvc-custom-error-pages -->
</system.web>
在MaxAgeFilter.cs
public class MaxAgeFilter : ActionFilterAttribute, IResultFilter, IExceptionFilter
{
public void OnException(ExceptionContext filterContext)
{
if (filterContext.ExceptionHandled || filterContext.HttpContext.IsCustomErrorEnabled)
return;
var statusCode = (int)HttpStatusCode.InternalServerError;
if (filterContext.Exception is HttpException)
{
statusCode = (filterContext.Exception as HttpException).GetHttpCode();
}
else if (filterContext.Exception is UnauthorizedAccessException)
{
statusCode = (int)HttpStatusCode.Forbidden;
}
var result = CreateActionResult(filterContext, statusCode);
filterContext.Result = result;
// Prepare the response code.
filterContext.ExceptionHandled = true;
filterContext.HttpContext.Response.Clear();
filterContext.HttpContext.Response.StatusCode = statusCode;
filterContext.HttpContext.Response.TrySkipIisCustomErrors = true;
var cache = filterContext.HttpContext.Response.Cache;
cache.SetMaxAge(TimeSpan.FromSeconds(10)); // this requires a lot of extra plumbing which I suspect is necessary because if you were to rely on default error response - the cache will get overriden, see original SO answer: https://whosebug.com/questions/8144695/asp-net-mvc-custom-handleerror-filter-specify-view-based-on-exception-type
}
public override void OnResultExecuted(ResultExecutedContext filterContext)
{
var cache = filterContext.HttpContext.Response.Cache;
cache.SetMaxAge(TimeSpan.FromMinutes(10)); // this is easy - you just pass it to the current cache and magic works
base.OnResultExecuted(filterContext);
}
protected virtual ActionResult CreateActionResult(ExceptionContext filterContext, int statusCode)
{
var ctx = new ControllerContext(filterContext.RequestContext, filterContext.Controller);
var statusCodeName = ((HttpStatusCode)statusCode).ToString();
var viewName = SelectFirstView(ctx,
"~/Views/Shared/Error.cshtml",
"~/Views/Shared/Error.cshtml",
statusCodeName,
"Error");
var controllerName = (string)filterContext.RouteData.Values["controller"];
var actionName = (string)filterContext.RouteData.Values["action"];
var model = new HandleErrorInfo(filterContext.Exception, controllerName, actionName);
var result = new ViewResult
{
ViewName = viewName,
ViewData = new ViewDataDictionary<HandleErrorInfo>(model),
};
result.ViewBag.StatusCode = statusCode;
return result;
}
protected string SelectFirstView(ControllerContext ctx, params string[] viewNames)
{
return viewNames.First(view => ViewExists(ctx, view));
}
protected bool ViewExists(ControllerContext ctx, string name)
{
var result = ViewEngines.Engines.FindView(ctx, name, null);
return result.View != null;
}
}
如您所见,处理异常基本上需要重建整个Response
。为此,我几乎从 SO answer here
最后,您决定是否要在您的控制器、操作或全局设置此属性:
App_Start/FilterConfig.cs
public class FilterConfig
{
public static void RegisterGlobalFilters(GlobalFilterCollection filters)
{
filters.Add(new MaxAgeFilter());
}
}
如果您使用的是自定义错误页面,则可以在视图中设置 headers。正如您所说,这样您就可以根据显示的错误页面设置不同的缓存长度。
@{
TimeSpan duration = new TimeSpan(0, 30, 0);
Response.Cache.SetMaxAge(duration);
}