首页 > 解决方案 > 如何在 ASP.NET MVC 中为错误页面指定 max-age

问题描述

当我的 ASP.NET MVC 服务器(在 IIS 上运行)发生错误时,服务器当前提供静态页面。这是httpErrors在我的 web.config 中的元素中配置的,如下所示:

<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响应标头。这很好,虽然我想控制这个页面被缓存的时间。如何添加max-age=x到此cache-control标题?

标签: asp.net-mvciiscache-controliis-10

解决方案


如果我正确理解您的问题陈述,您的主要目标是控制max-age,而不是花哨的<customErrors>设置。尝试从动作过滤器控制标题似乎是合乎逻辑的。

在 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://stackoverflow.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. 为此,我几乎从this SO answer here中获取了代码

最后,您决定是否要在控制器、操作或全局设置此属性:

App_Start/FilterConfig.cs

public class FilterConfig
{
    public static void RegisterGlobalFilters(GlobalFilterCollection filters)
    {
        filters.Add(new MaxAgeFilter());
    }
}

推荐阅读