Search code examples
asp.net-mvchttp-redirecterror-handlingdeclarative

ASP.NET MVC - Declarative Redirect On Error


I have been capturing exceptions and redirecting users to an error page. I pass the exception message and a return URL to tell the user what happened and allow them to return another page.

        try
        {
            return action(parameters);
        }
        catch (Exception exception)
        {
            ErrorViewModel errorModel = new ErrorViewModel();
            errorModel.ErrorMessage = "An error occured while doing something.";
            errorModel.ErrorDetails = exception.Message;
            errorModel.ReturnUrl = Url.Action("Controller", "Action");
            return RedirectToAction("Index", "Error", errorModel);
        }

This seems like way too much code to wrap around every action. I am using a global filter for errors:

    public static void RegisterGlobalFilters(GlobalFilterCollection filters)
    {
        HandleErrorAttribute attribute = new HandleErrorAttribute();
        filters.Add(attribute);
    }

and I have my web.config setup like this:

<customErrors mode="On" defaultRedirect="~/Error/Unknown">

But, this only works for unhandled exceptions.

I want exceptions to cause a redirect to an error controller/action taking a parameter holding the exception details. It would be nice if I could indicate the return URL on a action-by-action basis, or to have a default if none is provided.


Solution

  • Here is a little RedirectOnErrorAttribute class I created:

    using System;
    using System.Web.Mvc;
    using MyApp.Web.Models;
    
    namespace MyApp.Web.Utilities
    {
        public class RedirectOnErrorAttribute : ActionFilterAttribute
        {
            /// <summary>
            /// Initializes a new instance of a RedirectOnErrorAttribute.
            /// </summary>
            public RedirectOnErrorAttribute()
            {
                ErrorMessage = "An error occurred while processing your request.";
            }
    
            /// <summary>
            /// Gets or sets the controller to redirect to.
            /// </summary>
            public string ReturnController { get; set; }
    
            /// <summary>
            /// Gets or sets the action to redirect to.
            /// </summary>
            public string ReturnAction { get; set; }
    
            /// <summary>
            /// Gets or sets the error message.
            /// </summary>
            public string ErrorMessage { get; set; }
    
            /// <summary>
            /// Redirects the user to an error screen if an exception is thrown.
            /// </summary>
            /// <param name="filterContext">The filter context.</param>
            public override void OnActionExecuted(ActionExecutedContext filterContext)
            {
                if (filterContext.Exception != null && !filterContext.ExceptionHandled)
                {
                    ErrorViewModel viewModel = new ErrorViewModel();
                    viewModel.ErrorMessage = ErrorMessage;
                    viewModel.ErrorDetails = filterContext.Exception.Message;
                    string controller = ReturnController;
                    string action = ReturnAction;
                    if (String.IsNullOrWhiteSpace(controller))
                    {
                        controller = (string)filterContext.RequestContext.RouteData.Values["controller"];
                    }
                    if (String.IsNullOrWhiteSpace(action))
                    {
                        action = "Index";
                    }
                    UrlHelper helper = new UrlHelper(filterContext.RequestContext);
                    viewModel.ReturnUrl = helper.Action(action, controller);
                    string url = helper.Action("Index", "Error", viewModel);
                    filterContext.Result = new RedirectResult(url);
                    filterContext.ExceptionHandled = true;
                }
                base.OnActionExecuted(filterContext);
            }
        }
    }
    

    Now, I can decorate all of my actions like this:

    [RedirectOnError(ErrorMessage="An error occurred while doing something.", ReturnController="Controller", ReturnAction="Action")]