ASP.NET MVC HandleError

111

我该如何处理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”源代码的链接:http://mvccontrib.codeplex.com/SourceControl/changeset/view/4e8ad66c2a83#src%2fMVCContrib%2fFilters%2fRescueAttribute.cs返回。 - Peter
6个回答

158
[HandleError]

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

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

您还可以叠加其他特定信息的属性,以了解您要查找的异常类型。此时,您可以将错误定向到除默认的"Error"视图之外的特定视图。

有关更多信息,请参阅Scott Guthrie的博客文章


1
感谢提供的详细信息。 我不知道我做错了什么,但我创建了一个新项目,将所有现有的视图、控制器和模型移植到其中,现在它可以工作了。 不过我之前不知道有选择性视图这个功能。 - Boris Callens
如果需要记录这些异常,将代码放在视图的codebehind中是否可行? - Peter J
7
Iconic,这是我“宁迟勿缺”的回复:您可以继承HandleErrorAttribute类并重写其"OnException"方法:然后,插入任何您想要的日志记录或自定义操作。接下来,您可以完全处理异常(将context.ExceptionHandled设置为true),或者将其推迟到基类的OnException方法中处理。这里有一篇优秀的文章可能会对此有所帮助:http://blog.dantup.me.uk/2009/04/aspnet-mvc-handleerror-attribute-custom.html - Funka
我有很多控制器,所以我能否像这样global.asax中处理它,向用户显示一条消息? - Shaiju T
使用与PartialView相同的错误页面,并在异常发生后将其呈现在模态对话框中,这个想法怎么样?您能否在回答中提供一个示例?我想要实现的目标已经在使用MVC中的PartialView进行全局错误处理中解释过了。 - Jack

25
需要注意的是,未将HTTP错误代码设置为500(例如UnauthorizedAccessException)的错误将不会被HandleError过滤器处理。

2
真的,但是请查看MVC contrib中的RescueAttribute(链接在原帖中)。 - Boris Callens

15

将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
{
}

11

MVC中的属性在处理get和post方法以及跟踪ajax调用方面非常有用。

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

public class 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;
        }
    });
};

1
你在 AJAX 请求中泄露异常细节,但并非总是需要。你的日志记录代码不是线程安全的。你假设存在一个错误视图,并返回该视图而不更改响应代码。然后你去检查 JavaScript 中的某些错误字符串(本地化呢?)。基本上,你在重复现有答案所说的:“覆盖OnException来处理异常”,但展示了一个相当糟糕的实现方式。 - CodeCaster
@School.Resource.Messages.ReferanceExist参数是什么? - Jack
@CodeCaster,你知道在ASP.NET MVC中使用AJAX的这种错误处理方法有更好的方式吗?可以帮忙吗? - Jack
返回400或500,因为HTTP本意就是如此。不要在响应正文中挖掘特定的字符串。 - CodeCaster
@CodeCaster,您能否看一下关于在MVC中使用PartialView进行全局错误处理的问题? - Jack
@SandipPatel 非常感谢。我尝试了这种方法,但当控制器中出现异常时,我会将JSON返回给相关的AJAX调用,然后调用您的CheckAJAXError()方法。但是当我使用Firebug进行调试时,它在此JavaScript方法的第一行之后返回。我的控制器方法中是否有任何实现(我已经从BaseController继承了)?有什么想法吗? - Jack

4

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


谢谢回复,但我已经复制了Error.aspx页面。这可能是我通常会忘记的事情,但这次不会。 :P - Boris Callens

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

}


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接