Как получить доступ к HTTP StatusDescription на странице пользовательских ошибок
Когда действие (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>
В
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.
Установите код состояния / описание непосредственно в ответе и верните любой вид, который вам нравится.
2 ответа
Создать ErrorController - это позволяет настроить страницы ошибок конечного пользователя и коды состояния. Каждый результат действия принимает исключение, которое вы можете добавить к данным вашего маршрута в вашем методе application_error в вашем global.asax. Это не обязательно должен быть объект исключения, это может быть что угодно - просто добавьте его к routedata в вашем application_error.
[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");
Ваше сообщение будет передано и показано пользователю. На этом этапе вы можете указать, какой код состояния вы хотите использовать, и расширить оператор switch в application_error.
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
}