MVC错误句柄与自定义错误消息

时间:2015-02-10 10:24:03

标签: c# asp.net-mvc error-handling exception-handling

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

  1. 捕捉错误
  2. 将详细信息记录在文件中
  3. 通过电子邮件发送
  4. 添加详细自定义信息(例如,Id 记录我正在努力阅读)
  5. 返回查看给用户的自定义消息
  6. 我发现了很多关于HandleErrorAttribute 的信息,但没有一个信息允许在错误中添加具体细节,我也找到了try catch的信息对于服务器,aproach 太重

    现在,我有:

    控制器:

    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"];
    }
    

6 个答案:

答案 0 :(得分:4)

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

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

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

  

ELMAH(错误记录模块和处理程序)是一个完全可插拔的应用程序范围的错误记录工具。它可以动态添加到正在运行的ASP.NET Web应用程序,甚至是机器上的所有ASP.NET Web应用程序,而无需重新编译或重新部署。

来自SCOTT HANSELMAN

的博客的参考资料

只需将ELMAH的二进制文件复制到应用程序的bin文件夹中,然后编辑 web.config 文件即可。那就是它!

您需要在web.config中添加以下内容,并进行以下链接中描述的其他更改。

<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>

以下是一些很好的参考链接(包含对项目安装ELMAH的详细参考)供您参考。

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

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

<强>更新

  

添加详细的自定义信息(例如我尝试阅读的记录的ID)

您可以构建自己的自定义异常,该异常派生自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中,您可以添加详细的自定义异常消息。

  

返回查看给用户的自定义消息

你只需要添加

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

并在Error.cshtml文件夹中添加~/View/Shared 然后,每当遇到异常时,它将在view / shared文件夹中找到Error.cshtml并呈现内容。所以你可以在那里呈现自定义信息。

答案 1 :(得分:3)

使用Elmah,其他人也建议使用。我是,并且没有回头!

它符合您的所有要求:

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

关于自定义代码(上面的第二点),AFAIK有两种选择:

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

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

覆盖默认错误日志数据存储。例如,获取SQL Server数据存储:

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

接下来,创建一个类“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类,您可以记录错误,而不必引发未处理的异常。

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

示例:自定义异常

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

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

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

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

答案 2 :(得分:2)

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

我过去有过很好的结果,因为他们具有属性&#34;指向&#34;感兴趣的请求的各个部分(我想你想要记录特定细节的位置) - 所以你可以使用这些标识符将请求拉到碎片上使用反射:

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

我已使用此方法来保护控制器操作(确保客户正在请求他们允许请求的内容) - 例如父母要求他们的孩子,而不是其他人的孩子。

或者,您可以设置一个配置,以便链接&#34;链接&#34;处理程序在一起 - 所以很多小的处理程序,都在做非常具体的位,都在处理相同的请求并请求指针(如上所述):

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

where&#34; emailAndLogFile&#34;创建一个从FilterAttribute继承的错误处理程序链,其中最后一个是标准的MVC HandleErrorAttribute。

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

HTH


EDITED TO ADD:继承自定义错误处理的示例:

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();
}

答案 3 :(得分:1)

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

注意:依赖性解析是可更改的。我正在使用Castle Windsor这个故事。您可以解决自己的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);
}

一个FilterConfig:

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

BusinessException:

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

因此,您可以使用OnException

访问ExceptionHandling班级的异常消息filterContext.Exception.Message

您应该在违反控制逻辑之后对此操作使用BusinessExceptionthrow new BusinessException("Message")

答案 4 :(得分:0)

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

答案 5 :(得分:-1)

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

在HandleErrorAttribute中你只是&#34;只有&#34;有httpContext / ExceptionContext和其他静态信息。