In ASP.NET MVC what is the best show unhandled exceptions in my view?

℡╲_俬逩灬. 提交于 2019-11-28 18:22:41
Dave Alperovich

I can see the misconception. You want to do the MVC thing and redirect to a controller action.

But defaultRedirect is itself a Web Form convention and thereby limited. The moment you redirect to another controller, you will lose your HttpContext, and thereby lose your HandleErrorInfo Object

Your [HandleError] Attribute requires a View to direct its error message to. Going by your example above, I assume that you have a Views/Error Folder for your ErrorController, and in it you have an Index View. If you want to your Filter Context to send a HandleErrorInfo object to that view,

Try this syntax:

[HandleError(View="~/Views/Error/Index")]
Public class HomeController : Controller

But what about Logging?!?!?

I suspect your intention is more than just displaying error stack to users. In fact, I suspect you have no such intention at all. I suspect what your real aim is to log your error (probably to db) and to display some bland message to your user.

What I've explained so far was "what is best [way to] show unhandled exceptions in my view". The [HandleError] attribute is good for that.

But when you want to move to the next step (logging the error) you have a few options:

1) Override your base controller's On Exception method; create your own Controller inheriting from the MVC Controller class but override the On Exception Method. This approach can be used in conjunction with [HandleError] attribute

2) Create a custom exception handler Create your own Exception Handler that logs the error. Your exception handler can then call a View of choice or can work in conjunction with [HandleError(order=2)] since filter attributes can take an order argument applying precedence.


Nitin Sawant asks what an error view would look like. The

@model System.Web.Mvc.HandleErrorInfo
<h2>Exception details</h2>
<p> Controller: @Model.ControllerName </p>
<p> Action: @Model.ActionName </p>
<p> Exception: @Model.Exception </p>

I do something similar to maxlego which handles all errors (not just those occurring in controllers with the HandleError attribute).

My MvcApplication class (in global.asax.cs) has this:

public class MvcApplication : HttpApplication
{
    // usual stuff here...

    protected void Application_Error(object sender, EventArgs e)
    {
        Server.HandleError(((MvcApplication)sender).Context);
    }
}

The above code uses an extension method from my MVC library of useful stuff. With this in place I don't need any error handling attributes, customErrors config or custom filters. Instead the extension method will log the error details then invoke an appropriate view, either:

  • AccessDenied
  • NotFound
  • InternalServerError

The extension method code to make this work is:

public static class HttpServerUtilityExtensions
{
    private static readonly Logger Logger = LogManager.GetCurrentClassLogger();

    public static void HandleError(this HttpServerUtility server, HttpContext httpContext)
    {
        var currentController = " ";
        var currentAction = " ";
        var currentRouteData = RouteTable.Routes.GetRouteData(new HttpContextWrapper(httpContext));

        if (currentRouteData != null)
        {
            if (currentRouteData.Values["controller"] != null && !String.IsNullOrEmpty(currentRouteData.Values["controller"].ToString()))
                currentController = currentRouteData.Values["controller"].ToString();

            if (currentRouteData.Values["action"] != null && !String.IsNullOrEmpty(currentRouteData.Values["action"].ToString()))
                currentAction = currentRouteData.Values["action"].ToString();
        }

        var exception = server.GetLastError();
        Logger.ErrorException(exception.Message, exception);

        var controller = DependencyResolver.Current.GetService<ErrorController>();
        var routeData = new RouteData();
        var action = "InternalServerError";

        if (exception is HttpException)
        {
            var httpEx = exception as HttpException;

            switch (httpEx.GetHttpCode())
            {
                case 404:
                    action = "NotFound";
                    break;

                case 401:
                    action = "AccessDenied";
                    break;
            }
        }

        httpContext.ClearError();
        httpContext.Response.Clear();
        httpContext.Response.StatusCode = exception is HttpException ? ((HttpException)exception).GetHttpCode() : 500;
        httpContext.Response.TrySkipIisCustomErrors = true;

        routeData.Values["controller"] = "Error";
        routeData.Values["action"] = action;

        controller.ViewData.Model = new HandleErrorInfo(exception, currentController, currentAction);
        ((IController)controller).Execute(new RequestContext(new HttpContextWrapper(httpContext), routeData));
    }
}

Note, the above uses NLog for logging the error details but could easily be changed to support something else. Also, this method respects your IoC container when resolving the ErrorController.

I have used this little piece of code to show users handled errors page. Whether page was not found or some other error occurred.

    void Application_Error(object sender, EventArgs e)
    {
        // this value can be fetched from config or depend on DEBUG smybol
        if (!handleErrors)
            return;

        var error = Server.GetLastError();
        var code = (error is HttpException) ? (error as HttpException).GetHttpCode() : 500;

        if (code == 404)
        {
            // do something if page was not found. log for instance
        }
        else
        {
            // collect request info and log exception
        }

        // pass exception to ErrorsController
        Request.RequestContext.RouteData.Values["ex"] = error;

        // execute controller action
        IController errorController = new ErrorsController();
        errorController.Execute(new RequestContext(new HttpContextWrapper(Context), Request.RequestContext.RouteData));
    }

And errors controller look something like this. If you need detailed exception, it is accessible via RouteData

public class ErrorsController : Controller
{
    /// <summary>
    /// Page not found
    /// </summary>
    /// <returns></returns>
    public ActionResult Http404()
    {
        return View();
    }

    /// <summary>
    /// All other errors
    /// </summary>
    /// <param name="actionName"></param>
    protected override void HandleUnknownAction(string actionName)
    {
        // in case detailed exception is required.
        var ex = (Exception) RouteData.Values["ex"];
        return View();
    }
}

You can add different view for each http code. Just implement action Http{Code}

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!