如何在自定义错误页面中访问HTTP StatusDescription

时间:2014-09-17 09:58:16

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

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

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

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

A

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>

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,exception或HttpStatusCodeResult。

     

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

2 个答案:

答案 0 :(得分:2)

创建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

添加一条路线以捕获路线配置末尾的所有网址 - 这会捕获所有尚未通过匹配现有路线捕获的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语句。

答案 1 :(得分:0)

BaseController:

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
}