使用Server.TransferRequest()传递视图模型
我正在尝试微调我的MVC应用程序中的error handling。
我在web.config中启用了自定义错误,并将以下代码添加到Application_Error
。
Global.asax中
protected void Application_Error(object sender, EventArgs e) { Exception exception = Server.GetLastError() as Exception; if (exception != null) { Context.ClearError(); Context.Response.TrySkipIisCustomErrors = true; string path = (exception is HttpException && (exception as HttpException).GetHttpCode() == 404) ? "~/Error/NotFound" : "~/Error/Index"; Context.Server.TransferRequest(path, false); } }
ErrorController.cs
[AllowAnonymous] public ActionResult Index() { Response.Clear(); Response.StatusCode = 503; Response.TrySkipIisCustomErrors = true; return View(); } [AllowAnonymous] public ActionResult NotFound() { Response.Clear(); Response.StatusCode = 404; Response.TrySkipIisCustomErrors = true; return View(); }
Web.config文件
这似乎运作得相当好。 但是如何将一些错误细节传递给我的错误控制器?
此外,还提供了有关向我的错误控制器获取exception详细信息以获取控制器内发生的exception的提示。
注意:我不想在这里使用重定向。 这样做会告诉Google这样的抓取工具有关URL的错误信息。
如果要在错误控制器中获取错误详细信息,而不是在Application_Error函数中清除错误详细信息(Context.ClearError())。
一旦进入ErrorController Action再次获取最后一个错误,然后清除它。
HttpContext.Server.GetLastError()
如果要获取发生exception的控制器和操作名称,可以使用下面的代码来获取详细信息
Request.RequestContext.RouteData.Values["controller"] Request.RequestContext.RouteData.Values["action"]
添加参数会有帮助吗?
- public ActionResult Index(string errorMessage)
- public ActionResult NotFound(string errorMessage)
然后在Application_Error中可以看起来像 –
protected void Application_Error(object sender, EventArgs e) { Exception exception = Server.GetLastError() as Exception; if (exception != null) { Context.ClearError(); Context.Response.TrySkipIisCustomErrors = true; string path = (exception is HttpException && (exception as HttpException).GetHttpCode() == 404) ? "~/Error/NotFound?errorMessage="+exception.Message : "~/Error/Index?errorMessage="+exception.Message; Context.Server.TransferRequest(path, false); } }
您可以根据需要添加其他参数。 虽然不是最好的方法。
一个简单的方法是传递Exception或ViewModel,如下所示:
在你的application_error中:
HttpContext.Current.Items["Exception"] = exception;
在您的错误控制器中:
var exception = HttpContext.Current.Items["Exception"] as Exception;
警告:我不喜欢使用HttpContext。
这是我的设置。 它不会重定向,它会在同一个地方处理应用程序和一些已配置的IIS错误。 您还可以将所需的任何信息传递给Error控制器。
在Web.config中 :
... ...
在ErrorController中 (仅为简洁起见显示方法签名):
// This one gets called from Application_Error // You can add additional parameters to this action if needed public ActionResult Index(Exception exception) { ... } // This one gets called by IIS (see Web.config) public ActionResult Display([Bind(Prefix = "id")] HttpStatusCode statusCode) { ... }
另外,我有一个ErrorViewModel
和一个Index
视图。
在Application_Error中 :
protected void Application_Error(object sender, EventArgs e) { var exception = Server.GetLastError(); var httpContext = new HttpContextWrapper(Context); httpContext.ClearError(); var routeData = new RouteData(); routeData.Values["controller"] = "Error"; routeData.Values["action"] = "Index"; routeData.Values["exception"] = exception; // Here you can add additional route values as necessary. // Make sure you add them as parameters to the action you're executing IController errorController = DependencyResolver.Current.GetService(); var context = new RequestContext(httpContext, routeData); errorController.Execute(context); }
到目前为止,这是我的基本设置。 这不会执行重定向(错误控制器操作从Application_Error执行),它处理控制器exception以及IIS 404(例如yourwebsite.com/blah.html)。
从这一点开始, ErrorController
发生的任何事情都将基于您的需求。
作为一个例子,我将添加一些我的实现的额外细节。 正如我所说,我有一个ErrorViewModel
。
我的ErrorViewModel :
public class ErrorViewModel { public string Title { get; set; } public string Text { get; set; } // This is only relevant to my business needs public string ContentResourceKey { get; set; } // I am including the actual exception in here so that in the view, // when the request is local, I am displaying the exception for // debugging purposes. public Exception Exception { get; set; } }
我的ErrorController (相关部分):
public ActionResult Index(Exception exception) { ErrorViewModel model; var statusCode = HttpStatusCode.InternalServerError; if (exception is HttpException) { statusCode = (HttpStatusCode)(exception as HttpException).GetHttpCode(); // More details on this below if (exception is DisplayableException) { model = CreateErrorModel(exception as DisplayableException); } else { model = CreateErrorModel(statusCode); model.Exception = exception; } } else { model = new ErrorViewModel { Exception = exception }; } return ErrorResult(model, statusCode); } public ActionResult Display([Bind(Prefix = "id")] HttpStatusCode statusCode) { var model = CreateErrorModel(statusCode); return ErrorResult(model, statusCode); } private ErrorViewModel CreateErrorModel(HttpStatusCode statusCode) { var model = new ErrorViewModel(); switch (statusCode) { case HttpStatusCode.NotFound: // Again, this is only relevant to my business logic. // You can do whatever you want here model.ContentResourceKey = "error-page-404"; break; case HttpStatusCode.Forbidden: model.Title = "Unauthorised."; model.Text = "Your are not authorised to access this resource."; break; // etc... } return model; } private ErrorViewModel CreateErrorModel(DisplayableException exception) { if (exception == null) { return new ErrorViewModel(); } return new ErrorViewModel { Title = exception.DisplayTitle, Text = exception.DisplayDescription, Exception = exception.InnerException }; } private ActionResult ErrorResult(ErrorViewModel model, HttpStatusCode statusCode) { HttpContext.Response.Clear(); HttpContext.Response.StatusCode = (int)statusCode; HttpContext.Response.TrySkipIisCustomErrors = true; return View("Index", model); }
在某些情况下,我需要在发生错误时显示自定义消息。 我为此目的有一个自定义exception:
[Serializable] public class DisplayableException : HttpException { public string DisplayTitle { get; set; } public string DisplayDescription { get; set; } public DisplayableException(string title, string description) : this(title, description, HttpStatusCode.InternalServerError, null, null) { } public DisplayableException(string title, string description, Exception exception) : this(title, description, HttpStatusCode.InternalServerError, null, exception) { } public DisplayableException(string title, string description, string message, Exception exception) : this(title, description, HttpStatusCode.InternalServerError, message, exception) { } public DisplayableException(string title, string description, HttpStatusCode statusCode, string message, Exception inner) : base((int)statusCode, message, inner) { DisplayTitle = title; DisplayDescription = description; } }
然后我像这样使用它:
catch(SomeException ex) { throw new DisplayableException("My Title", "My custom display message", "An error occurred and I must display something", ex) }
在我的ErrorController
我单独处理此exception,从此DisplayableException
设置ErrorViewModel
的Title
和Text
属性。
你可以使用Session对象,如Session["AppError"]=exception
。 然后,您可以在错误控制器中检索它。 请记住,exception不是可序列化的,但您可以使用其他技巧。 他们几个在这里: 如何在C#中序列化Exception对象? 或者使自定义.NETexception可序列化的正确方法是什么?
试试这样;
protected void Application_Error(Object sender, EventArgs e) { var exception = Server.GetLastError(); var statusCode = exception.GetType() == typeof (HttpException) ? ((HttpException) exception).GetHttpCode() : 500; var routeData = new RouteData { Values = { {"controller", "Error"}, {"action", "Index"}, {"statusCode", statusCode}, {"exception", exception} } }; Server.ClearError(); Response.TrySkipIisCustomErrors = true; IController errorController = new ErrorController(); errorController.Execute(new RequestContext(new HttpContextWrapper(Context), routeData)); Response.End(); } }
然后在ErrorController中的Index方法中编写一些业务代码。 (如果StatusCode == 400 ……其他……)