.NET MVC global exception handling (2)

Posted Jun 16, 20204 min read

Added the content of the previous section

MVCfilter Filter

There are four kinds of filters in MVC:Authorization, Exception, Action, Result, what we want to use is Exception filter

When we create a new MVC project, the exception filter has been automatically registered in the program, first from the global configuration file mentioned in the previous section, the Application_Start method in the Global.asax file will run when the program starts , Of which the global filter is registered by default, as shown in the figure

We can enter the RegisterGlobalFilters method to view. This method registers an exception handling filter by default, which means that the MVC program in the default state will be caught and processed by the program when the exception occurs. The processing method is to jump to the error page, which is the previous article. The article says the Error page under the Layout folder

However, there is a big premise for using exception filters is to open the custom error handling setting in Web.config. The customErrors node must be set to "On". This setting is turned off by default, which means that it must be added manually.

<system.web>
  <compilation debug="true" targetFramework="4.6.1"/>
  <httpRuntime targetFramework="4.6.1"/>
  <customErrors mode="On">
  </customErrors>
</system.web>

It should be noted that 404 errors, this type of exception will not be caught by the filter

But you can add a node in web.config for custom configuration and jump to the corresponding page

<customErrors mode="On">
  <error redirect="~/Error/NotFound" statusCode="404" />
</customErrors>

public class ErrorController:Controller
{
    //GET:Error
    public ActionResult Index()
    {
        return View();
    }

    public ActionResult CustomHttpError()
    {
        ViewBag.Message = "There is an error";
        //HandleErrorInfo
        //ExceptionContext

        return View();
    }
    public ActionResult NotFound()
    {
        return View();
    }
}

Custom filter

The default exception filter of MVC can meet the basic needs, but if you want to deal with some exceptions, you need to customize the content of the filter. You can achieve this by rewriting the OnException method.

public class CustomHandleErrorAttribute:HandleErrorAttribute
{
    public override void OnException(ExceptionContext filterContext)
    {
        /* Call the OnException method of the base class to implement basic functions.
         * If you want to completely customize, you do not need to call the method of the base class
         */
        base.OnException(filterContext);

        /* Here you can record error logs, send error notifications, etc.
         * Relevant exception information can be obtained through Exception object and HttpException object.
         * Exception exception = filterContext.Exception;
         * HttpException httpException = new HttpException(null, exception);
         */
    }
}

Sample code

public class MyErrorHandler:FilterAttribute, IExceptionFilter
{
    public void OnException(ExceptionContext filterContext)
    {
        if(filterContext.ExceptionHandled || !filterContext.HttpContext.IsCustomErrorEnabled)
            return;

        var statusCode =(int) HttpStatusCode.InternalServerError;
        if(filterContext.Exception is HttpException)
        {
            statusCode = filterContext.Exception.As<HttpException>().GetHttpCode();
        }
        else if(filterContext.Exception is UnauthorizedAccessException)
        {
            //to prevent login prompt in IIS
            //which will appear when returning 401.
            statusCode =(int)HttpStatusCode.Forbidden;
        }
        _logger.Error("Uncaught exception", filterContext.Exception);

        var result = CreateActionResult(filterContext, statusCode);
        filterContext.Result = result;

        //Prepare the response code.
        filterContext.ExceptionHandled = true;
        filterContext.HttpContext.Response.Clear();
        filterContext.HttpContext.Response.StatusCode = statusCode;
        filterContext.HttpContext.Response.TrySkipIisCustomErrors = true;
    }

    protected virtual ActionResult CreateActionResult(ExceptionContext filterContext, int statusCode)
    {
        var ctx = new ControllerContext(filterContext.RequestContext, filterContext.Controller);
        var statusCodeName =((HttpStatusCode) statusCode).ToString();

        var viewName = SelectFirstView(ctx,
                                       "~/Views/Error/{0}.cshtml".FormatWith(statusCodeName),
                                       "~/Views/Error/General.cshtml",
                                       statusCodeName,
                                       "Error");

        var controllerName =(string) filterContext.RouteData.Values["controller"];
        var actionName =(string) filterContext.RouteData.Values["action"];
        var model = new HandleErrorInfo(filterContext.Exception, controllerName, actionName);
        var result = new ViewResult
                         {
                             ViewName = viewName,
                             ViewData = new ViewDataDictionary<HandleErrorInfo>(model),
                         };
        result.ViewBag.StatusCode = statusCode;
        return result;
    }

    protected string SelectFirstView(ControllerContext ctx, params string[]viewNames)
    {
        return viewNames.First(view => ViewExists(ctx, view));
    }

    protected bool ViewExists(ControllerContext ctx, string name)
    {
        var result = ViewEngines.Engines.FindView(ctx, name, null);
        return result.View != null;
    }
}