MVC RedirectToAction in a catch block - asp.net-mvc

I´m trying to redirect to an action from one controller to another if something in a try-block goes wrong. What I want to achieve is a general way of presenting a view to the user if something goes wrong in different controllers by directing all errors to an errorhandling ActionResult in my Homecontroller. This is basically what the code looks like:
try
{
Code that may go wrong
}
catch (Exception e)
{
set the errorcode (integer)
Logg the error (write a simple textfile)
RedirectToAction("ErrorHandling", "Home", errorcode);
}
And in the Homecontroller i would like to generate a view, telling the user that something went wrong:
public ActionResult ErrorHandling(int errorcode)
{
do something with the errorcode
return View(different view depending on errorcode);
}
My problem is that if i manipulate the code so that an exception is thrown every step in the catcblock is executed except for the RedirectToAction whic is being ignored. What am i missing? I´m kind of new to this, so hopefully there is a simple answer that i haven´t been able to find...

In your catch block try
return new RedirectToRouteResult(new RouteValueDictionary
{
{"Controller", "Home"},
{"Action", "ErrorHandling"},
{"errorcode", errorcode}
});
Maybe you simply forgot the return in your code:
return RedirectToAction("ErrorHandling", "Home", errorcode);

Related

Return to original view from MVC action filter

Im working on a asp.net core website and im trying to make som global validation exception handling using Filters. The backend can at random places throw fluentapi ValidationException and I want to catch these and show the error messages to the user. This filter only cares about ValidationExceptions. All other exceptions will be handled later..
Instead of using a try/catch in every post action in all my controllers, I want to use a filter that catches only ValidationExceptions, add the errors to the ModelState and then return to the original view with the updated ModelState.
I have tried many things but every time I just get a blank page after the filter finishes. I can easily set a new RedirectToRouteResult witht the controller and action from the context. But then I dont have the ModelState and values the user entered..
public class PostExceptionFilter : ExceptionFilterAttribute
{
public override void OnException(ExceptionContext context)
{
if (context.Exception is FluentValidation.ValidationException)
{
var ex = context.Exception as FluentValidation.ValidationException;
context.Exception = null;
context.HttpContext.Response.StatusCode = 200;
context.ExceptionHandled = true;
foreach (var item in ex.Errors.ToList())
{
context.ModelState.AddModelError(item.PropertyName, item.ErrorMessage);
}
// Done with the stuff I want.
// Now please go back to the original view with the updated modelstate and values
}
else if (context.Exception is UnauthorizedAccessException)
{
// Do something else...
}
else
{
// Do something else...
}
base.OnException(context);
}
}
You cannot access the particlar Model(related to Action Method) in Exception Filters. So you have to handle the error at Controller level if you want to add Errors to model.
try
{
//Do something
}
Catch(Exception e)
{
ModelState.AddModelError(string key, string errorMessage);
Return View(model)
}
The error message will present itself in the <%: Html.ValidationSummary() %> in your View
Without try-catch blocks you won't know if exception occured in Action Method, So that you can add Custom Errors to Model.

Error propagation in controllers MVC

When I try to propagate an exception and pass it as parameter into my ErrorController, it is always null.
Controller
public ActionResult Test()
{
try
{
throw new Exception("ALGO");
//
return View();
}
catch (Exception ex)
{
return RedirectToAction("Error", "Error",
new
{
exception = ex,
controller = this.ControllerContext.RouteData.Values["controller"],
action = this.ControllerContext.RouteData.Values["action"]
});
}
}
ErrorController
public ActionResult Error(Exception exception, string controller, string action)
{
// exception is always null...
Response.StatusCode = 500;
ViewBag.exception = new HandleErrorInfo(exception, controller, action);
return View();
}
Any idea how to get the exception properly?
Is there a better approach for error handling?
I also tried this one but I got several errors because of parameteless constructor for handleerrorinfo
Whenever you use RedirectToAction, it performs an HTTP redirect. Any of the values you pass have to be primitive types, since they will be appended to the redirect URL. That means that you cannot pass an entire object, like you are trying to do with the exception. The easiest thing that you can do is to replace the RedirectToAction with
return Error(ex, this.ControllerContext.RouteData.Values["controller"], this.ControllerContext.RouteData.Values["action"]);
This approach will still call your Error method and display the View properly, but it will not change the URL like a redirect would. If you wanted to use this method, then you could try using javascript to change the URL.
Also, do you really want to display all of the error details to your end user? If you are just using this to display a plain error page without details then you could look into simply using the customErrors attribute in your web config to redirect to an error page. That way all that your end user knows is that some error occured.

Catching Exceptions in the Controller

It seems like if an Exception occurs inside a controller, the view-engine won't go to the intended view, even if the "exception" is caught inside a try-catch block?
or I'm missing something here:
public ActionResult MyController(int? param1, DateTime? param2)
{
MyModel model = new MyModel();
try
{
model = new MyModel();
//AN ERROR HAPPENS HERE (so the code goes to catch the Exception):
model.Connection.Initialize();
}
catch (Exception ex)
{
ViewBag.ErrorMessage = ex.Message;
}
//when I put a break point I get to this following line, however, "MyView" is never displayed!?
return PartialView("MyView", model);
}
You might be getting another exception afterwards, somewhere in your view. Look at the stack trace that displays on the browser page and fix that.

Asp.Net MVC3 Redirect

I have an action like shown below. In GetAvailableBookList, I get the list and if there is not any available book redirect to a message page. But in action part code continues to execute and gets an exception and I find myself in error page.
I don't want to use return RedirectToAction or something like that because there are a lot of places where we use this redirect logic in our application.
public ActionResult ActionName()
{
List<BookType> bookList = GetAvailableBookList();
// some code
return View("RelatedView");
}
private List<BookType> GetAvailableBookList()
{
....
list = GetList();
if(list.Count == 0)
{
System.Web.HttpContext.Current.Response.Redirect(messagePageUrl, true);
}
else return list;
}
Unfortunately, Response.Redirect() isn't really friendly with ASP.NET MVC. My rule of thumb is if it comes from HttpContext I don't want to touch it in the controller (of course there are many exceptions to that rule) -- especially since it improves testability.
My suggestion is to use RedirectToAction, but since you don't want to repeat code you can do it in such a way that you don't have to repeat code (although in this case I don't see a problem with repeating code).
public ActionResult LoadBookListAndContinue(
Func<List<BookType>, ActionResult> continuation)
{
var list = LoadBooklist();
if(list.Any())
{
return action(continuation);
}
return new RedirectResult(messagePageUrl);
}
// in your controller
public ActionResult ActionName()
{
return LoadBookListAndContinue(
list => {
// some code
return View("RelatedView");
});
}
Is it pretty? No, but it works better than the Redirect exception.
Use
return RedirectToAction("NoListAvailable");
if you have a specific action you would like to execute. The NoListAvailable action can return a view indicating the problem.
Alternatively, you could return the view directly
return View("NoListAvailable");
The exception you are getting is probably ThreadAbortException and this is something you cannot avoid unless you allow the thread to continue (2nd argument in Response.Redirect).
On a side note your current solution is generally flawed. You should use RedirectToAction in each action when your method returns an empty list.
Throwing a specific exception and redirect where you catch it may be solution
Try to write
System.Web.HttpContext.Current.Response.Redirect(messagePageUrl, false);

How to use HandleError with model state errors

I want to use a custom action filter to handle specific exceptions from my service classes to populate the model state and then return the view.
For example, take my previous code:
public ActionResult SomeAction(SomeViewModel model)
{
try
{
_someService.SomeMethod(model);
}
catch (ServiceException ex)
{
ModelState.AddModelError(ex.Key, ex.ErrorMessage);
}
return View();
}
Basically, it would call a service, and if a ServiceException was thrown, it would know that there was an issue w/ the model data, and add the error to the ModelState, then just return the view. But I noticed some very un-DRY-like patterns, because I had this same try/catch code in every action method.
So, to DRY it up a bit, I basically created a new HandleServiceError action filter:
public class HandleServiceErrorAttribute : HandleErrorAttribute
{
public override void OnException(ExceptionContext context)
{
((Controller)context.Controller)
.ModelState
.AddModelError(
((ServiceException)context.Exception).Key,
((ServiceException)context.Exception).ErrorMessage
);
context.ExceptionHandled = true;
}
}
Then simplified my action methods like so:
public ActionResult SomeAction(SomeViewModel model)
{
_someService.SomeMethod(model);
return View();
}
Problem is, once the action filter handles the error, it doesn't return to my action method. I sort of understand, under the hood, why this is happening. But I would still like to figure out a way to do what I'm trying to do.
Is this possible?
Thanks in advance.
UPDATE:
I tried the suggestions from the article Darin provided in his answer, but ran into issues trying to use constructor injection with the controller's model state.
For example, if you look at their Controllers\ProductController.cs code, they have the controller's empty constructor using a service locator to create the service, passing in the controller's ModelState at that point:
public ProductController()
{
_service = new ProductService(new ModelStateWrapper(this.ModelState),
new ProductRepository());
}
But if you look at the injected constructor, it assumes the ModelState will be injected into the constructor for the service:
public ProductController(IProductService service)
{
_service = service;
}
I don't know how to get CI to work with the current controller's ModelState. If I could figure this out, then this approach may work.
You could still return the corresponding view:
context.Result = new ViewResult
{
ViewName = context.RouteData.GetRequiredString("action")
};
You may also take a look at the following article for an alternative about how to perform validation at the service layer.

Resources