带有自定义错误消息的 MVC 错误处理

MVC Error Handle with custom error Messages

我正在使用 MVC5 构建一个新的 Web 应用程序,我需要以下内容:

  1. 捕获错误
  2. 在文件中记录详细信息
  3. 通过电子邮件发送
  4. 添加到详细自定义信息(例如 Id 记录我正在尝试阅读)
  5. Return 查看给用户的自定义消息

我找到了很多关于 HandleErrorAttribute 的信息,但其中 none 允许向错误添加具体细节,我也找到了信息说 try catch 方法对于服务器来说太重

目前,我有:

控制器:

public partial class HomeController : Controller
{
    private static Logger logger = LogManager.GetCurrentClassLogger();

    public virtual ActionResult Index()
    {
        try
        {
            return View();
        }
        catch (Exception e)
        {
            logger.Error("Error in Index: " + e);
            return MVC.Error.Index("Error in Home Controller");
        }
    }
}

我发现这个扩展 HandleErrorAttribute 看起来很完整,但没有做我需要的一切:

private bool IsAjax(ExceptionContext filterContext)
{
    return filterContext.HttpContext.Request.Headers["X-Requested-With"] == "XMLHttpRequest";
}

public override void OnException(ExceptionContext filterContext)
{
    if (filterContext.ExceptionHandled || !filterContext.HttpContext.IsCustomErrorEnabled)
    {
        return;
    }


    // if the request is AJAX return JSON else view.
    if (IsAjax(filterContext))
    {
        //Because its a exception raised after ajax invocation
        //Lets return Json
        filterContext.Result = new JsonResult(){Data=filterContext.Exception.Message,
            JsonRequestBehavior=JsonRequestBehavior.AllowGet};

        filterContext.ExceptionHandled = true;
        filterContext.HttpContext.Response.Clear();    
    }
    else
    {
        //Normal Exception
        //So let it handle by its default ways.
        base.OnException(filterContext);

    }

    // Write error logging code here if you wish.

    //if want to get different of the request
    //var currentController = (string)filterContext.RouteData.Values["controller"];
    //var currentActionName = (string)filterContext.RouteData.Values["action"];
}

您可以使用包含所需信息(id、表名等)的客户异常来使用特殊信息捕获全局错误。

在 HandleErrorAttribute 中,您 "only" 有 httpContext/ExceptionContext 和其他静态信息。

为什么不创建包含所需错误信息的模型并在需要时将数据绑定到模型?它还将允许您create/return从中查看

您的要求最符合 Elmah。非常好的记录错误的插件。

ELMAH 代表 错误记录模块和处理程序

ELMAH 提供了如此高度的 可插拔性 ,以至于即使安装 ELMAH 也不需要 编译 您的应用程序。

ELMAH (Error Logging Modules and Handlers) is an application-wide error logging facility that is completely pluggable. It can be dynamically added to a running ASP.NET web application, or even all ASP.NET web applications on a machine, without any need for re-compilation or re-deployment.

引用自SCOTT HANSELMAN

的博客

只需将 ELMAH 的二进制文件复制到应用程序的 bin 文件夹并编辑 web.config 文件。就是这样!

您需要将以下内容添加到您的 web.config 并进行以下 link 中描述的一些其他更改。

<sectionGroup name="elmah">
  <section name="security" requirePermission="false" type="Elmah.SecuritySectionHandler, Elmah" />
  <section name="errorLog" requirePermission="false" type="Elmah.ErrorLogSectionHandler, Elmah" />
  <section name="errorMail" requirePermission="false" type="Elmah.ErrorMailSectionHandler, Elmah" />
  <section name="errorFilter" requirePermission="false" type="Elmah.ErrorFilterSectionHandler, Elmah" />
</sectionGroup>

例如设置邮件帐户

<configuration>
    <configSections>
        <sectionGroup name="elmah">
            <section name="errorLog" requirePermission="false" type="Elmah.ErrorLogSectionHandler, Elmah"/>
            <section name="errorMail" requirePermission="false" type="Elmah.ErrorMailSectionHandler, Elmah"/>
            <section name="errorFilter" requirePermission="false" type="Elmah.ErrorFilterSectionHandler, Elmah"/>
        </sectionGroup>
    </configSections>
    <elmah>
    <errorMail from="test@test.com" to="test@test.com"
       subject="Application Exception" async="false"
       smtpPort="25" smtpServer="***"
       userName="***" password="***">
    </errorMail>
    </elmah>
<system.web>        
    <customErrors mode="RemoteOnly" defaultRedirect="CustomError.aspx">
        <error statusCode="403" redirect="NotAuthorized.aspx" />
        <!--<error statusCode="404" redirect="FileNotFound.htm" />-->
    </customErrors>
    <httpHandlers>
        <remove verb="*" path="*.asmx"/>
        <add verb="*" path="*.asmx" validate="false" type="System.Web.Script.Services.ScriptHandlerFactory, System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35"/>
        <add verb="*" path="*_AppService.axd" validate="false" type="System.Web.Script.Services.ScriptHandlerFactory, System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35"/>
        <add verb="GET,HEAD" path="ScriptResource.axd" type="System.Web.Handlers.ScriptResourceHandler, System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" validate="false"/>
        <add verb="POST,GET,HEAD" path="elmah.axd" type="Elmah.ErrorLogPageFactory, Elmah" />
    </httpHandlers>
    <httpModules>
        <add name="ScriptModule" type="System.Web.Handlers.ScriptModule, System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35"/>
        <add name="ErrorLog" type="Elmah.ErrorLogModule, Elmah"/>
        <add name="ErrorMail" type="Elmah.ErrorMailModule, Elmah" />
    </httpModules>
</system.web>
</configuration>

这里有一些很好的参考资料link(其中包含有关将 ELMAH 安装到您的项目的详细参考资料)供您参考。

https://msdn.microsoft.com/en-us/library/aa479332.aspx

https://code.google.com/p/elmah/wiki/MVC

更新

Add to the detail custom information (for example the Id of the record I'm trying to read)

您可以构建自己的派生自 Exception 的自定义异常。

public class MyException : Exception
{
    public MyException(string message, Exception ex) : base(ex.Message, ex)
    {

    }
}

然后像

一样使用它
public virtual ActionResult Index()
{
    try
    {
        return View();
    }
    catch (Exception e)
    {
        throw new MyException("detailed exception", e);
    }
}

通过这种方式,主要异常将被包裹在 myexception 中,您可以添加详细的自定义异常消息。

Return to the view custom messages to the user

您只需添加

<system.web>
    <customErrors mode="On">
    </customErrors>
<sytem.web>

并在 ~/View/Shared 文件夹中添加 Error.cshtml 然后每当遇到异常时,它都会在 view/shared 文件夹中找到 Error.cshtml 并呈现内容。这样您就可以在那里呈现您的自定义消息。

按照其他人的建议使用 Elmah。我是,而且没有回头!

它满足您的所有要求:

  • 捕获所有错误,例如400 秒、500 秒...
  • 记录到文件,以及您能想到的任何其他数据存储,例如数据库、内存、Azure、更多文件格式(XML、CSV)、RSS 提要...
  • 电子邮件错误:在 Web.config 中启用和配置邮件设置 - 非常简单。您甚至可以异步发送电子邮件!
  • 添加自定义代码 - 在您的情况下向错误添加额外的详细信息
  • 使用您自己的自定义错误页面 - web.config 中的自定义错误节点(400 秒、500 秒)和您自己的错误控制器

进一步了解自定义代码(上面的最后一点),据我所知,您有两个选择:

1。创建自定义错误日志实现。

这并不难。我就是这么做的!

覆盖默认的错误日志数据存储。例如,采用 SQL 服务器数据存储:

In Web.config
<elmah>
   <errorLog type="Elmah.SqlErrorLog, Elmah" connectionStringName="myCn" applicationName="myAppName" />
</elmah>

接下来,创建一个 class "MySQLServerErrorLog" 并派生自 Elmah.ErrorLog

然后所需要做的就是覆盖 Log() 方法。

public override string Log(Error error)
        {   

        // You have access to all the error details - joy!      
        = error.HostName,
        = error.Type,
        = error.Message,
        = error.StatusCode
        = error.User,
        = error.Source,

        // Call the base implementation
    }

在 Web.config 中,将默认(以上)条目替换为您的实现:

<elmah>
   <errorLog type="myProjectName.MySQLServerErrorLog, myProjectName" />
</elmah>

2。您可以以编程方式记录错误

使用 ErrorSignal class,您可以记录错误而不必引发未处理的异常。

语法: ErrorSignal.FromCurrentContext().Raise(new NotSupportedException());

示例:自定义异常

var customException = new Exception("My error", new NotSupportedException()); 
ErrorSignal.FromCurrentContext().Raise(customException);

这使您可以选择使用自定义逻辑以编程方式记录您需要的任何内容。

我已经为我的 Elmah 实例编写了将错误记录到 Azure 云存储 Table 和 Blob(错误堆栈跟踪详细信息)的功能。

FWIW 在我使用 Elmah 之前,我已经为使用 HandleErrorAttribute 和 Application_Error(在 Global.asax 中)的 MVC 编写了自己的异常处理机制。它有效,但在我看来太笨重了。

如果是我,我会创建自己的异常处理属性,它将所需的行为添加到 HandleErrorAttribute 的基本实现中。

我在过去取得了很好的结果,具有属性 "pointed at" 感兴趣的请求的各个部分(我在想你说你想要记录具体细节的地方) - 所以你可以使用这些标识符通过反射将请求拉成碎片:

CustomHandleErrorAttribute(["id", "name", "model.lastUpdatedDate"])

我已经使用这种方法来保护控制器操作(确保客户正在请求他们被允许请求的东西)——例如parent 正在请求有关他们 children 的信息,而不是其他人的 children。

或者,您可以设置一个配置,从而将 "chain" 处理程序放在一起 - 很多小处理程序,都在做非常具体的工作,都处理相同的请求和请求指针(如上所示) ):

ChainedErrorHandling("emailAndLogFile", ["id", "name", "model.lastUpdatedDate"])

其中 "emailAndLogFile" 创建了一个继承自 FilterAttribute 的错误处理程序链,链中的最后一个错误处理程序是标准 MVC HandleErrorAttribute。

但到目前为止,最简单的方法是这两种方法中的前者。

HTH


编辑添加:继承自定义错误处理的示例:

public class CustomErrorAttribute : HandleErrorAttribute
{
    public CustomErrorAttribute(string[] requestPropertiesToLog)
    {
        this.requestPropertiesToLog = requestPropertiesToLog;
    }

    public string[] requestPropertiesToLog { get; set; }

    public override void OnException(ExceptionContext filterContext)
    {
        var requestDetails = this.GetPropertiesFromRequest(filterContext);

        // do custom logging / handling
        LogExceptionToEmail(requestDetails, filterContext);
        LogExceptionToFile(requestDetails, filterContext);
        LogExceptionToElseWhere(requestDetails, filterContext);// you get the idea

        // even better - you could use DI (as you're in MVC at this point) to resolve the custom logging and log from there.
        //var logger = DependencyResolver.Current.GetService<IMyCustomErrorLoggingHandler>();
        // logger.HandleException(requestDetails, filterContext);

        // then let the base error handling do it's thang.
        base.OnException(filterContext);
    }

    private IEnumerable<KeyValuePair<string, string>> GetPropertiesFromRequest(ExceptionContext filterContext)
    {
        // in requestContext is the queryString, form, user, route data - cherry pick bits out using the this.requestPropertiesToLog and some simple mechanism you like
        var requestContext = filterContext.RequestContext;
        var qs = requestContext.HttpContext.Request.QueryString;
        var form = requestContext.HttpContext.Request.Form;
        var user = requestContext.HttpContext.User;
        var routeDataOfActionThatThrew = requestContext.RouteData;

        yield break;// just break as I'm not implementing it.
    }

    private void LogExceptionToEmail(IEnumerable<KeyValuePair<string, string>> requestDetails, ExceptionContext filterContext)
    {
        // send emails here
    }

    private void LogExceptionToFile(IEnumerable<KeyValuePair<string, string>> requestDetails, ExceptionContext filterContext)
    {
        // log to files
    }

    private void LogExceptionToElseWhere(IEnumerable<KeyValuePair<string, string>> requestDetails, ExceptionContext filterContext)
    {
        // send cash to me via paypal everytime you get an exception ;)
    }
}

然后在控制器操作中添加如下内容:

[CustomErrorAttribute(new[] { "formProperty1", "formProperty2" })]
public ActionResult Index(){
    return View();
}

首先,您可以定义一个过滤器属性,您可以在 global.asax 中的 MVC 应用程序中启动时注册它,这样您就可以捕获在调用操作时发生的任何类型的错误。

注意:依赖关系解析是可变的。我正在为这个故事使用温莎城堡。您可以解决自己的 IOC 容器的依赖关系。例如,ILogger 依赖项。我在动作调用时使用了这个 属性 注入。 Windsor Action Invoker

例如过滤器:

public class ExceptionHandling : FilterAttribute, IExceptionFilter
{
    public ILogger Logger { get; set; }

    public void OnException(ExceptionContext filterContext)
    {
        Logger.Log("On Exception !", LogType.Debug, filterContext.Exception);

        if (filterContext.Exception is UnauthorizedAccessException)
        {
            filterContext.Result = UnauthorizedAccessExceptionResult(filterContext);
        }
        else if (filterContext.Exception is BusinessException)
        {
            filterContext.Result = BusinessExceptionResult(filterContext);
        }
        else
        {
            // Unhandled Exception
            Logger.Log("Unhandled Exception ", LogType.Error, filterContext.Exception);
            filterContext.Result = UnhandledExceptionResult(filterContext);
        }
    } 
}

这样你什么都能抓到。

所以:

private static ActionResult UnauthorizedAccessExceptionResult(ExceptionContext filterContext)
{
    // Send email, fire event, add error messages 
    // for example handle error messages
    // You can seperate the behaviour by: if (filterContext.HttpContext.Request.IsAjaxRequest())
    filterContext.ExceptionHandled = true;
    filterContext.HttpContext.Response.TrySkipIisCustomErrors = true;
    filterContext.Controller.TempData.Add(MessageType.Danger.ToString(), filterContext.Exception.Message);

    // So you can show messages using with TempData["Key"] on your action or views
    var lRoutes = new RouteValueDictionary(
        new
        {
            action = filterContext.RouteData.Values["action"],
            controller = filterContext.RouteData.Values["controller"]
        });
    return new RedirectToRouteResult(lRoutes);
}

在Global.asax中:

protected void Application_Start()
{
    FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
}

过滤器配置:

public static void RegisterGlobalFilters(GlobalFilterCollection filters)
{
    filters.Add(new ExceptionHandling());
}

业务异常:

public class BusinessException : Exception, ISerializable
{
    public BusinessException(string message)
        : base(message)
    {
        // Add implemenation (if required)
    }
}

因此您可以使用 filterContext.Exception.Message

ExceptionHandling class 访问异常消息 OnException

你应该在任何违反控制逻辑之后的动作上使用 BusinessException 这样:throw new BusinessException("Message").