Passing a view model using Server.TransferRequest ()

I am trying to fine tune the error handling in my MVC application.

I included custom errors in my web.config, and I added the following code to 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

 <system.web> <customErrors mode="RemoteOnly" defaultRedirect="~/Error"> <error statusCode="404" redirect="~/Error/NotFound"/> <error statusCode="500" redirect="~/Error" /> </customErrors> </system.web> 

This seems to work quite well. But how can I pass some error information along with my error controller?

Also, additional points for tips on getting exception information for my error controller for exceptions that occur in the controller.

Note. I do not want to use redirection here. This will tell the crawlers how wrong the Google url information is.

+5
source share
6 answers

If you want to get error data in the error controller, then do not clear the error information (Context.ClearError ()) inside the Application_Error function.

Once you are in the ErrorController action, run the last error again and clear it.

 HttpContext.Server.GetLastError() 

If you want to get the controller and the name of the action in which the exception occurred, you can use the code below to get details

 Request.RequestContext.RouteData.Values["controller"] Request.RequestContext.RouteData.Values["action"] 

Also, if you want to run ErrorController and Specific Action from Application_Error, you can do something like below

  protected void Application_Error() { Exception exception = Server.GetLastError(); var httpException = exception as HttpException; Response.Clear(); Server.ClearError(); var routeData = new RouteData(); routeData.Values["controller"] = "Errors"; routeData.Values["action"] = "Common"; routeData.Values["exception"] = exception; Response.StatusCode = 500; if (httpException != null) { Response.StatusCode = httpException.GetHttpCode(); switch (Response.StatusCode) { case 403: routeData.Values["action"] = "Http403"; break; case 404: routeData.Values["action"] = "Http404"; break; case 400: routeData.Values["action"] = "Http400"; break; } } Response.TrySkipIisCustomErrors = true; IController errorsController = new ErrorsController(); var rc = new RequestContext(new HttpContextWrapper(Context), routeData); /* This will run specific action without redirecting */ errorsController.Execute(rc); } 

If you want to pass the error as an object to the error controller, you can add additional route data, as shown below

 routeData.Values["errorDetail"] = httpException; 
+4
source

Did adding parameters help?

  • public ActionResult Index (string errorMessage)
  • public ActionResult NotFound (errorMessage string)

And then in Application_Error it might look something like this:

 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); } } 

You can enable an additional parameter as per your requirement. Not the best approach though.

+3
source

A simple approach would be to pass an Exception or ViewModel as follows:

In application application_error:

 HttpContext.Current.Items["Exception"] = exception; 

in your error controller:

 var exception = HttpContext.Current.Items["Exception"] as Exception; 

Caution: I am not a fan of using HttpContext.

+2
source

This is the setting that I have. It will not redirect and processes both the application and some configured IIS errors in the same place. You can also pass on any information needed for your error controller.

In Web.config :

 <system.web> <customErrors mode="Off" /> ... </system.web> <system.webServer> <httpErrors errorMode="Custom" existingResponse="Auto"> <remove statusCode="403" /> <remove statusCode="404" /> <remove statusCode="500" /> <error statusCode="403" responseMode="ExecuteURL" path="/Error/Display/403" /> <error statusCode="404" responseMode="ExecuteURL" path="/Error/Display/404" /> <error statusCode="500" responseMode="ExecuteURL" path="/Error/Display/500" /> </httpErrors> ... </system.webServer> 

In ErrorController (showing method signatures for brevity only):

 // 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) { ... } 

In addition, I have an ErrorViewModel and Index ErrorViewModel .

In 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<ErrorController>(); var context = new RequestContext(httpContext, routeData); errorController.Execute(context); } 

So far this is the basic setup that I have. This will not lead to redirection (the error controller action is executed from Application_Error), and it handles controller exceptions, as well as, for example, IIS 404 (for example, yourwebsite.com/blah.html).

From now on, everything that happens inside your ErrorController will be based on your needs.


As an example, I will add some additional information about my implementation. As I said, I have an ErrorViewModel .

My 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; } } 

My ErrorController (relevant parts):

 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); } 

In some cases, I need to display a custom message when an error occurs. I have a special exception for this purpose:

 [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; } } 

Then I use it as follows:

 catch(SomeException ex) { throw new DisplayableException("My Title", "My custom display message", "An error occurred and I must display something", ex) } 

In my ErrorController I handle this exception separately, setting the ErrorViewModel Title and Text properties from this DisplayableException .

+1
source

Can you use a Session object, for example Session["AppError"]=exception . Then you can get it in your error controller. Keep in mind that the exception is not Serializable, but you can use other tricks. A couple of them are here: How to serialize an Exception object in C #? or What is the correct way to create a custom .NET exception serialized?

0
source

Try it;

  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(); } } 

Then write a small business code in the Index method in ErrorController. (if StatusCode == 400 ... else ...)

0
source

Source: https://habr.com/ru/post/1258578/


All Articles