ASP.NET MVC 处理错误

Posted

技术标签:

【中文标题】ASP.NET MVC 处理错误【英文标题】:ASP.NET MVC HandleError 【发布时间】:2010-09-16 01:08:39 【问题描述】:

如何处理 asp.net MVC Preview 5 中的 [HandleError] 过滤器? 我在我的 Web.config 文件中设置了 customErrors

<customErrors mode="On" defaultRedirect="Error.aspx">
  <error statusCode="403" redirect="NoAccess.htm"/>
  <error statusCode="404" redirect="FileNotFound.htm"/>
</customErrors>

并将 [HandleError] 放在我的控制器类上方,如下所示:

[HandleError]
public class DSWebsiteController: Controller

    [snip]
    public ActionResult CrashTest()
    
        throw new Exception("Oh Noes!");
    

然后我让我的控制器从这个类继承并在它们上调用 CrashTest()。 Visual Studio 在错误处停止,按 f5 继续后,我被重新路由到 Error.aspx?aspxerrorpath=/sxi.mvc/CrashTest (其中 sxi 是使用的控制器的名称。 当然找不到路径,我得到“'/'应用程序中的服务器错误。” 404.

此网站已从预览版 3 移植到 5 版。 除了错误处理之外,一切都运行(移植的工作并不多)。 当我创建一个完整的新项目时,错误处理似乎起作用了。

想法?

--注意-- 由于这个问题现在有超过 3K 的视图,我认为放入我目前正在使用的(ASP.NET MVC 1.0)将是有益的。 在mvc contrib project 中有一个名为“RescueAttribute”的出色属性 你可能也应该检查一下;)

【问题讨论】:

链接到RescueAttribute来源:mvccontrib.codeplex.com/SourceControl/changeset/view/… 【参考方案1】:
[HandleError]

当您只向您的类(或您的操作方法)提供 HandleError 属性时,当发生未处理的异常时,MVC 将首先在控制器的视图文件夹中查找名为“错误”的相应视图。如果在那里找不到它,它将继续查看共享视图文件夹(默认情况下应该有一个 Error.aspx 文件)

[HandleError(ExceptionType = typeof(SqlException), View = "DatabaseError")]
[HandleError(ExceptionType = typeof(NullReferenceException), View = "LameErrorHandling")]

您还可以使用有关您正在寻找的异常类型的特定信息来堆叠其他属性。此时,您可以将错误定向到默认“错误”视图以外的特定视图。

欲了解更多信息,请查看Scott Guthrie's blog post。

【讨论】:

感谢您提供的扩展信息。我不知道我做错了什么,但我创建了一个新项目,将所有现有的视图、控制器和模型移植到其中,现在它可以工作了。虽然不知道选择性视图。 如果需要记录这些异常,在视图中添加代码隐藏是否可以接受? 标志性的,这是我对您的评论的“迟到总比没有好”的回复:您可以改为将 HandleErrorAttribute 子类化并覆盖其“OnException”方法:然后,插入您想要的任何日志记录或自定义操作。然后,您可以完全处理异常(将 context.ExceptionHandled 设置为 true),或者为此推迟到基类自己的 OnException 方法。这是一篇可能对此有所帮助的优秀文章:blog.dantup.me.uk/2009/04/… 我有很多控制器,所以我可以在global.asax 内部处理这个问题,比如this 向用户显示消息吗? @和PartialView一样的错误页面,出现异常后在模态对话框中渲染怎么办?您能否在答案中提供一个示例? Global Error handling using PartialView in MVC 解释了我想要实现的目标。【参考方案2】:

还要注意的是没有设置http错误码为500的错误

(例如 UnauthorizedAccessException)

不会被 HandleError 过滤器处理。

【讨论】:

是的,但请查看 MVC 贡献中的 RescueAttribute(OP 中的链接)【参考方案3】:

http错误码为500的解决方法 这是一个名为 [ERROR] 的属性,把它放在一个动作上

public class Error: System.Web.Mvc.HandleErrorAttribute

    public override void OnException(System.Web.Mvc.ExceptionContext filterContext)
    

            if (filterContext.HttpContext.IsCustomErrorEnabled)
            
                filterContext.ExceptionHandled = true;

            
            base.OnException(filterContext);
            //OVERRIDE THE 500 ERROR  
           filterContext.HttpContext.Response.StatusCode = 200;
    

    private static void RaiseErrorSignal(Exception e)
    
        var context = HttpContext.Current;
      // using.Elmah.ErrorSignal.FromContext(context).Raise(e, context);
     


//示例:

[Error]
[HandleError]
[PopulateSiteMap(SiteMapName="Mifel1", ViewDataKey="Mifel1")]
public class ApplicationController : Controller


【讨论】:

【参考方案4】:

MVC 中的属性在 get 和 post 方法的错误处理中非常有用,它还跟踪 ajax 调用

在您的应用程序中创建一个基本控制器并在您的主控制器(EmployeeController)中继承它。

公共类 EmployeeController : BaseController

在基本控制器中添加以下代码。

/// <summary>
/// Base Controller
/// </summary>
public class BaseController : Controller
       
    protected override void OnException(ExceptionContext filterContext)
    
        Exception ex = filterContext.Exception;

        //Save error log in file
        if (ConfigurationManager.AppSettings["SaveErrorLog"].ToString().Trim().ToUpper() == "TRUE")
        
            SaveErrorLog(ex, filterContext);
        

        // 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 = Convert.ToString(filterContext.Exception),
                JsonRequestBehavior = JsonRequestBehavior.AllowGet
            ;
        
        else
        
            filterContext.ExceptionHandled = true;
            filterContext.HttpContext.Response.Clear();

            filterContext.Result = new ViewResult()
            
                //Error page to load
                ViewName = "Error",
                ViewData = new ViewDataDictionary()
            ;

            base.OnException(filterContext);
        
    

    /// <summary>
    /// Determines whether the specified filter context is ajax.
    /// </summary>
    /// <param name="filterContext">The filter context.</param>
    private bool IsAjax(ExceptionContext filterContext)
    
        return filterContext.HttpContext.Request.Headers["X-Requested-With"] == "XMLHttpRequest";
    

    /// <summary>
    /// Saves the error log.
    /// </summary>
    /// <param name="ex">The ex.</param>
    /// <param name="filterContext">The filter context.</param>
    void SaveErrorLog(Exception ex, ExceptionContext filterContext)
    
        string logMessage = ex.ToString();

        string logDirectory = Server.MapPath(Url.Content("~/ErrorLog/"));

        DateTime currentDateTime = DateTime.Now;
        string currentDateTimeString = currentDateTime.ToString();
        CheckCreateLogDirectory(logDirectory);
        string logLine = BuildLogLine(currentDateTime, logMessage, filterContext);
        logDirectory = (logDirectory + "\\Log_" + LogFileName(DateTime.Now) + ".txt");

        StreamWriter streamWriter = null;
        try
        
            streamWriter = new StreamWriter(logDirectory, true);
            streamWriter.WriteLine(logLine);
        
        catch
        
        
        finally
        
            if (streamWriter != null)
            
                streamWriter.Close();
            
        
    

    /// <summary>
    /// Checks the create log directory.
    /// </summary>
    /// <param name="logPath">The log path.</param>
    bool CheckCreateLogDirectory(string logPath)
    
        bool loggingDirectoryExists = false;
        DirectoryInfo directoryInfo = new DirectoryInfo(logPath);
        if (directoryInfo.Exists)
        
            loggingDirectoryExists = true;
        
        else
        
            try
            
                Directory.CreateDirectory(logPath);
                loggingDirectoryExists = true;
            
            catch
            
            
        

        return loggingDirectoryExists;
    

    /// <summary>
    /// Builds the log line.
    /// </summary>
    /// <param name="currentDateTime">The current date time.</param>
    /// <param name="logMessage">The log message.</param>
    /// <param name="filterContext">The filter context.</param>       
    string BuildLogLine(DateTime currentDateTime, string logMessage, ExceptionContext filterContext)
    
        string controllerName = filterContext.RouteData.Values["Controller"].ToString();
        string actionName = filterContext.RouteData.Values["Action"].ToString();

        RouteValueDictionary paramList = ((System.Web.Routing.Route)(filterContext.RouteData.Route)).Defaults;
        if (paramList != null)
        
            paramList.Remove("Controller");
            paramList.Remove("Action");
        

        StringBuilder loglineStringBuilder = new StringBuilder();

        loglineStringBuilder.Append("Log Time : ");
        loglineStringBuilder.Append(LogFileEntryDateTime(currentDateTime));
        loglineStringBuilder.Append(System.Environment.NewLine);

        loglineStringBuilder.Append("Username : ");
        loglineStringBuilder.Append(Session["LogedInUserName"]);
        loglineStringBuilder.Append(System.Environment.NewLine);

        loglineStringBuilder.Append("ControllerName : ");
        loglineStringBuilder.Append(controllerName);
        loglineStringBuilder.Append(System.Environment.NewLine);

        loglineStringBuilder.Append("ActionName : ");
        loglineStringBuilder.Append(actionName);
        loglineStringBuilder.Append(System.Environment.NewLine);

        loglineStringBuilder.Append("----------------------------------------------------------------------------------------------------------");
        loglineStringBuilder.Append(System.Environment.NewLine);

        loglineStringBuilder.Append(logMessage);
        loglineStringBuilder.Append(System.Environment.NewLine);
        loglineStringBuilder.Append("==========================================================================================================");

        return loglineStringBuilder.ToString();
    

    /// <summary>
    /// Logs the file entry date time.
    /// </summary>
    /// <param name="currentDateTime">The current date time.</param>
    string LogFileEntryDateTime(DateTime currentDateTime)
    
        return currentDateTime.ToString("dd-MMM-yyyy HH:mm:ss");
    

    /// <summary>
    /// Logs the name of the file.
    /// </summary>
    /// <param name="currentDateTime">The current date time.</param>
    string LogFileName(DateTime currentDateTime)
    
        return currentDateTime.ToString("dd_MMM_yyyy");
    


================================================ =

查找目录:Root/App_Start/FilterConfig.cs

添加以下代码:

/// <summary>
/// Filter Config
/// </summary>
public class FilterConfig

    /// <summary>
    /// Registers the global filters.
    /// </summary>
    /// <param name="filters">The filters.</param>
    public static void RegisterGlobalFilters(GlobalFilterCollection filters)
    
        filters.Add(new HandleErrorAttribute());
    

跟踪 AJAX 错误:

在布局页面加载时调用 CheckAJAXError 函数。

function CheckAJAXError() 
    $(document).ajaxError(function (event, jqXHR, ajaxSettings, thrownError) 

        var ex;
        if (String(thrownError).toUpperCase() == "LOGIN") 
            var url = '@Url.Action("Login", "Login")';
            window.location = url;
        
        else if (String(jqXHR.responseText).toUpperCase().indexOf("THE DELETE STATEMENT CONFLICTED WITH THE REFERENCE CONSTRAINT") >= 0) 

            toastr.error('ReferanceExistMessage');
        
        else if (String(thrownError).toUpperCase() == "INTERNAL SERVER ERROR") 
            ex = ajaxSettings.url;
            //var url = '@Url.Action("ErrorLog", "Home")?exurl=' + ex;
            var url = '@Url.Action("ErrorLog", "Home")';
            window.location = url;
        
    );
;

【讨论】:

您正在泄露 AJAX 请求的异常详细信息,您并不总是希望这样。您的日志记录代码不是线程安全的。您假设有一个错误视图并返回它,而不更改响应代码。然后你去检查 javascript 中的某些错误字符串(本地化呢?)。基本上,您是在重复现有答案已经说过的内容:“覆盖 OnException 以处理异常”,但它的实现非常糟糕。 @School.Resource.Messages.ReferenceExist 参数是什么? @CodeCaster 你知道在 ASP.NET MVC 中使用 AJAX 这种错误处理方法的更好方法吗?有什么帮助吗? 返回 400 或 500,正如 HTTP 所期望的那样。不要去挖掘响应正文中的特定字符串。 @CodeCaster 你能看看Global Error handling using PartialView in MVC关于这个问题吗?【参考方案5】:

您缺少 Error.aspx :) 在预览版 5 中,它位于您的 Views/Shared 文件夹中。只需从新的 Preview 5 项目中复制它即可。

【讨论】:

感谢您的回复,但我已经复制了 Error.aspx 页面。确实可能是我通常会忘记的事情,但这次不会。 :P【参考方案6】:
    [HandleError]
    public class ErrorController : Controller
            
        [AcceptVerbs(HttpVerbs.Get)]
        public ViewResult NotAuthorized()
        
            //401
            Response.StatusCode = (int)HttpStatusCode.Unauthorized;

        return View();
    

    [AcceptVerbs(HttpVerbs.Get)]
    public ViewResult Forbidden()
    
        //403
        Response.StatusCode = (int)HttpStatusCode.Forbidden;

        return View();
    

    [AcceptVerbs(HttpVerbs.Get)]
    public ViewResult NotFound()
    
        //404
        Response.StatusCode = (int)HttpStatusCode.NotFound;
        return View();
    

    public ViewResult ServerError()
    
        //500
        Response.StatusCode = (int)HttpStatusCode.NotFound;
        return View();
    

【讨论】:

以上是关于ASP.NET MVC 处理错误的主要内容,如果未能解决你的问题,请参考以下文章

ASP.NET MVC 处理错误

ASP.NET MVC中的错误处理

asp.net mvc 3 中的错误处理

ASP.NET MVC 404 错误处理 [重复]

ASP.NET MVC Ajax 错误处理

Asp.Net MVC (RC 5) 中的 404 Http 错误处理程序