【问题标题】:How to access HTTP StatusDescription in custom error page如何在自定义错误页面中访问 HTTP StatusDescription
【发布时间】:2014-11-11 07:24:54
【问题描述】:

当一个操作(asp.net mvc 5)在数据库中找不到某些东西时,用户必须看到一个带有简短自定义错误消息的页面,例如"Invoice 5 does not exist"。此外,响应必须有一个404 HTTP 代码。

另一个例子:当动作调用不正确时,用户必须看到例如"Parameter 'invoiceId' is required"。此外,响应必须有一个400 HTTP 代码。

我尝试将自定义消息存储在 HTTP 状态描述中并在自定义错误页面中显示它们。有两种方法(afaik):

一个

mvc action:
return HttpNotFound("Invoice 5 does not exist"); OR
return new HttpStatusCodeResult(HttpStatusCode.BadRequest, "Parameter 'invoiceId' is required");

web.config:
<httpErrors errorMode="Custom">
  <remove statusCode="404" />
  <error statusCode="404" path="/Error/NotFound" responseMode="ExecuteURL" />
  <remove statusCode="400" />
  <error statusCode="400" path="/Error/BadRequest" responseMode="ExecuteURL" />
</httpErrors>

B

mvc action:
throw new HttpException(404, "Invoice 5 does not exist"); OR
throw new HttpException(400, "Parameter 'invoiceId' is required");

web.config:
<customErrors mode="On">
  <error statusCode="404" redirect="~/Error/NotFound" />
  <error statusCode="400" redirect="~/Error/BadRequest" />
</customErrors>

无论哪种方式,HTTP状态描述如何显示在自定义页面中?如何在 NotFound/BadRequest 操作或视图中访问它?

如果这是不可能的,4xx 响应如何包含数据驱动的消息,并很好地呈现给用户?

更新:

我沉迷于使用配置,但似乎不可能将自定义字符串推送到 web.config 中声明的错误页面。 @Carl 和 @V2Solutions 都只回答了我的后备问题(从“如果这是不可能的......”开始)。我会这样总结他们的方法:

C (@Carl)

不要使用 web.config 或 HttpStatusCodeResult。

抛出异常,在 Application_Error 中捕获它们并以编程方式呈现自定义错误页面。

D (@V2Solutions)

不要使用 web.config、异常或 HttpStatusCodeResult。

直接在 Response 中设置状态代码/描述,然后返回你喜欢的任何视图。

【问题讨论】:

    标签: asp.net asp.net-mvc asp.net-mvc-5 http-status-codes custom-error-pages


    【解决方案1】:

    创建一个 ErrorController - 这允许您定制您的最终用户错误页面和状态代码。每个操作结果都接受一个异常,您可以在 global.asax 中的 application_error 方法中将其添加到您的路由数据中。它不必是异常对象,它可以是任何你喜欢的对象 - 只需将它添加到 application_error 中的 routedata 即可。

    [AllowAnonymous]
    public class ErrorController : Controller
    {
        public ActionResult PageNotFound(Exception ex)
        {
            Response.StatusCode = 404;
            return View("Error", ex);
        }
    
        public ActionResult ServerError(Exception ex)
        {
            Response.StatusCode = 500;
            return View("Error", ex);
        }
    
        public ActionResult UnauthorisedRequest(Exception ex)
        {
            Response.StatusCode = 403;
            return View("Error", ex);
        }
    
        //Any other errors you want to specifically handle here.
    
        public ActionResult CatchAllUrls()
        {
            //throwing an exception here pushes the error through the Application_Error method for centralised handling/logging
            throw new HttpException(404, "The requested url " + Request.Url.ToString() + " was not found");
        }
    }
    

    您的错误视图:

    @model Exception
    @{
        ViewBag.Title = "Error";
    }
    
    <h2>Error</h2>
    
    @Model.Message
    

    添加一个路由以捕获所有 url 到您的路由配置的末尾 - 这将捕获所有通过匹配现有路由尚未捕获的 404:

    routes.MapRoute("CatchAllUrls", "{*url}", new { controller = "Error", action = "CatchAllUrls" });
    

    在您的 global.asax 中:

    protected void Application_Error(object sender, EventArgs e)
        {
            Exception exception = Server.GetLastError();
    
            //Error logging omitted
    
            HttpException httpException = exception as HttpException;
            RouteData routeData = new RouteData();
            IController errorController = new Controllers.ErrorController();
            routeData.Values.Add("controller", "Error");
            routeData.Values.Add("area", "");
            routeData.Values.Add("ex", exception);
    
            if (httpException != null)
            {
                //this is a basic example of how you can choose to handle your errors based on http status codes.
                switch (httpException.GetHttpCode())
                {
                    case 404:
                        Response.Clear();
    
                        // page not found
                        routeData.Values.Add("action", "PageNotFound");
    
                        Server.ClearError();
                        // Call the controller with the route
                        errorController.Execute(new RequestContext(new HttpContextWrapper(Context), routeData));
    
                        break;
                    case 500:
                        // server error
                        routeData.Values.Add("action", "ServerError");
    
                        Server.ClearError();
                        // Call the controller with the route
                        errorController.Execute(new RequestContext(new HttpContextWrapper(Context), routeData));
                        break;
                     case 403:
                        // server error
                        routeData.Values.Add("action", "UnauthorisedRequest");
    
                        Server.ClearError();
                        // Call the controller with the route
                        errorController.Execute(new RequestContext(new HttpContextWrapper(Context), routeData));
                        break;
                     //add cases for other http errors you want to handle, otherwise HTTP500 will be returned as the default.
                    default:
                        // server error
                        routeData.Values.Add("action", "ServerError");
    
                        Server.ClearError();
                        // Call the controller with the route
                        errorController.Execute(new RequestContext(new HttpContextWrapper(Context), routeData));
                        break;
                }
            }
            //All other exceptions should result in a 500 error as they are issues with unhandled exceptions in the code
            else
            {
                routeData.Values.Add("action", "ServerError");
                Server.ClearError();
                // Call the controller with the route
                errorController.Execute(new RequestContext(new HttpContextWrapper(Context), routeData));
            }
        }
    

    然后当你抛出

    throw new HttpException(404, "Invoice 5 does not exist");
    

    您的消息将被传递并显示给用户。此时您可以指定要使用哪个状态码,并在 application_error 中扩展 switch 语句。

    【讨论】:

    • 我知道这是一篇旧帖子,但是当代码命中时,我在 Global.asax.cs 中收到此消息:`errorController.Execute(new RequestContext(new HttpContextWrapper(Context), routeData) )` 这是错误代码:System.InvalidOperationException: 'A single instance of controller 'CMP.ArbetsplanWeb.Controllers.ErrorController' cannot be used to handle multiple requests. If a custom controller factory is in use, make sure that it creates a new instance of the controller for each request.
    【解决方案2】:

    基础控制器:

    using System.Web;
    using System.Web.Mvc;
    
    namespace YourNamespace.Controllers
    {
        public class BaseController : Controller
        {
            public BaseController()
            {
                ViewBag.MetaDescription = Settings.metaDescription;
                ViewBag.MetaKeywords = Settings.metaKeywords;
            }
    
            protected new HttpNotFoundResult HttpNotFound(string statusDescription = null)
            {
                return new HttpNotFoundResult(statusDescription);
            }
    
            protected HttpUnauthorizedResult HttpUnauthorized(string statusDescription = null)
            {
                return new HttpUnauthorizedResult(statusDescription);
            }
    
            protected class HttpNotFoundResult : HttpStatusCodeResult
            {
                public HttpNotFoundResult() : this(null) { }
    
                public HttpNotFoundResult(string statusDescription) : base(404, statusDescription) { }
    
            }
    
            protected class HttpUnauthorizedResult : HttpStatusCodeResult
            {
                public HttpUnauthorizedResult(string statusDescription) : base(401, statusDescription) { }
            }
    
            protected class HttpStatusCodeResult : ViewResult
            {
                public int StatusCode { get; private set; }
                public string StatusDescription { get; private set; }
    
                public HttpStatusCodeResult(int statusCode) : this(statusCode, null) { }
    
                public HttpStatusCodeResult(int statusCode, string statusDescription)
                {
                    this.StatusCode = statusCode;
                    this.StatusDescription = statusDescription;
                }
    
                public override void ExecuteResult(ControllerContext context)
                {
                    if (context == null)
                    {
                        throw new ArgumentNullException("context");
                    }
    
                    context.HttpContext.Response.StatusCode = this.StatusCode;
                    if (this.StatusDescription != null)
                    {
                        context.HttpContext.Response.StatusDescription = this.StatusDescription;
                    }
                    // 1. Uncomment this to use the existing Error.ascx / Error.cshtml to view as an error or
                    // 2. Uncomment this and change to any custom view and set the name here or simply
                    // 3. (Recommended) Let it commented and the ViewName will be the current controller view action and on your view (or layout view even better) show the @ViewBag.Message to produce an inline message that tell the Not Found or Unauthorized
                    //this.ViewName = "Error";
                    this.ViewBag.Message = context.HttpContext.Response.StatusDescription;
                    base.ExecuteResult(context);
                }
            }
        }
    }
    

    要像这样在您的操作中使用:

    public ActionResult Index()
    {
        // Some processing
        if (...)
            return HttpNotFound();
        // Other processing
    }
    

    【讨论】:

    • 感谢@CallMeLaNN,他最初在linklink 发布了这种方法
    猜你喜欢
    • 2011-03-26
    • 2017-06-05
    • 1970-01-01
    • 2011-01-17
    • 1970-01-01
    • 2023-03-21
    • 2013-08-31
    • 2016-01-17
    • 1970-01-01
    相关资源
    最近更新 更多