【问题标题】:ASP.NET MVC: How to create ViewData for an Exception FilterASP.NET MVC:如何为异常过滤器创建 ViewData
【发布时间】:2009-08-23 19:26:50
【问题描述】:

我不确定您是否熟悉 NerdDinner 应用程序。它将方法 GetRuleViolations() 和属性 IsValid 添加到 Dinner 对象。保存对象时,它会检查对象是否有效。如果不是,它会引发异常。在捕获到异常的控制器中,ViewData 的 ModelState 被规则违规填充并重新显示视图。 Html.Validation 帮助器突出显示错误。

我想做的是创建一个 HandleRuleViolationExceptionAttribute,类似于 HandleExceptionAttribute(它是 MVC 框架的一部分)。问题是这个属性必须重新填充视图的模型状态。

视图的模型可以有任何对象类型。引发 RuleViolationException 填充的代码将 RuleViolationException.Object 设置为 View 的 Model。

我在MVC源码中查找了HandleExceptionAttribute的代码并修改了:

    <AttributeUsage(AttributeTargets.Class Or AttributeTargets.Method, _
    Inherited:=True, AllowMultiple:=False)> _
    Public Class HandleRuleViolationExceptionAttribute
        Inherits FilterAttribute
        Implements IExceptionFilter

        Private m_View As String
        Private m_MasterPage As String

        Public Property View() As String
            Get
                Return m_View
            End Get
            Set(ByVal value As String)
                m_View = value
            End Set
        End Property

        Public Property MasterPage() As String
            Get
                Return If(m_MasterPage, String.Empty)
            End Get
            Set(ByVal value As String)
                m_MasterPage = value
            End Set
        End Property

        Public Sub OnException(ByVal filterContext As System.Web.Mvc.ExceptionContext) _
                Implements System.Web.Mvc.IExceptionFilter.OnException
            If filterContext Is Nothing Then 
                Throw New ArgumentException("filterContext is null")
            End If

            'Ignore if the error is already handled.
            If filterContext.ExceptionHandled Then Return

            'Handle only ObjectIsInvalidExceptions.
            If Not TypeOf filterContext.Exception Is ObjectIsInvalidException Then
                Return
            End If

            Dim ex As ObjectIsInvalidException = DirectCast(filterContext.Exception, ObjectIsInvalidException)

            'If this is not an HTTP 500 (for example, if somebody throws an HTTP 404 from an action method),
            'ignore it.
            If (New HttpException(Nothing, ex).GetHttpCode()) <> 500 Then Return

            Dim actionName As String = CStr(filterContext.RouteData.Values("action"))
            Dim viewName As String = If(String.IsNullOrEmpty(View), actionName, View)

            Dim viewData = filterContext.Controller.ViewData
            viewData.Model = ex.Object
            For Each item As String In filterContext.HttpContext.Request.Form.Keys
                viewData.Add(item, filterContext.HttpContext.Request.Form.Item(item))
            Next
            For Each ruleViolation In ex.Object.GetRuleViolations()
                viewData.ModelState.AddModelError(ruleViolation.PropertyName, ruleViolation.ErrorMessage)
            Next
            filterContext.Result = New ViewResult() With _
            { _
                    .ViewName = viewName, _
                    .MasterName = MasterPage, _
                    .ViewData = viewData, _
                    .TempData = filterContext.Controller.TempData _
            }
            filterContext.ExceptionHandled = True
            filterContext.HttpContext.Response.Clear()
            filterContext.HttpContext.Response.StatusCode = 500

            'Certain versions of IIS will sometimes use their own error page when
            'they detect a server error. Setting this property indicates that we
            'want it to try to render ASP.NET MVC's error page instead.
            filterContext.HttpContext.Response.TrySkipIisCustomErrors = True
        End Sub
    End Class

为了填充视图的模型,我遍历请求的表单键并将键及其值添加到 ViewData 实例。它现在有效,但是,我不相信这是这样做的方法。

在控制器的 Action 方法中,我可以使用 UpdateModel 方法更新模型。这也会更新 viewStates ModelState。我可以包含一个字符串数组,其中包含必须更新的属性名称,或者,当将模型作为 Action 参数时,我可以使用 Bind-attribute 来添加或排除某些属性(就像我在 create-action 中所做的那样以上)。我的方法不遵守这个,可能会导致安全问题。

有没有更好的方法在 OnException 方法中构造 ViewData 对象,其工作方式类似于控制器的 UpdateModel 方法?有没有办法从 ExceptionHandlerAttribute 调用 UpdateModel 方法?

谢谢, 纪尧姆·哈尼克

【问题讨论】:

    标签: asp.net-mvc


    【解决方案1】:

    几个快速点:
    1.您实际上想要更新Controller的ModelState(View可以作为属性访问) 2. 你想将结果设置为一个你传入模型对象的视图,即使它是无效的

    根据您的描述,您似乎应该调用控制器的 UpdateModel 方法。您可以通过执行以下操作从 OnException 方法中执行此操作:

    filterContext.Controller.UpdateModel(ex.Object)
    ...
    For Each ruleViolation In ex.Object.GetRuleViolations()
                filterContext.Controller.ModelState.AddModelError(ruleViolation.PropertyName, ruleViolation.ErrorMessage)
    Next
    ...
    filterContext.Result = filterContext.Controller.View(ex.Object)
    

    您可能会考虑在属性上公开一个名为“ViewName”的属性,以便用户可以指定备用视图以在异常情况下使用:

    <HandleRuleViolationException(ViewName:="SomeErrorViewForThisControllerOrAction")>
    

    这是一个非常巧妙的想法。请回来更新帖子,标记答案或评论结果。我很好奇这是怎么回事!

    【讨论】:

    • 感谢您的回复! Attribute 确实具有可以设置的 View 属性,但如果没有,则将 Action 名称用作 View 名称。我确实有与您完全相同的想法: filterContext.Controller.UpdateModel 但是 filterContext.Controller 属于 ControllerBase 类型,它没有 UpdateModel 方法。还有其他想法吗?我可以将真实的 Controller 实例传递给这个属性(Controller 的 Action 中的 this/Me)并使用它来更新模型吗?
    • 好点。我错过了。回顾代码,我实际上认为您只需要做最少的工作就可以在这里完成这项工作。通过将 viewData.Model 设置为 ex.Object,您可以获得同样的效果。我认为您会从 ex.Object 中获取所有表单数据?也许不是。
    • 我放弃了。我什至不确定 UpdateModel 是否是要调用的方法。即使是这样,我也不知道怎么做。有谁知道在设置视图的 Model 属性时如何更新 ModelState?
    【解决方案2】:

    知道了!

    Dim methodInfo = GetType(Controller).GetMethod("View", _
            Reflection.BindingFlags.NonPublic Or Reflection.BindingFlags.Instance, Nothing, _
            New Type() {GetType(Object)}, Nothing)
    Dim controller = DirectCast(filterContext.Controller, Controller)
    Dim viewResult As ViewResult = _
            CType(methodInfo.Invoke(controller, New Object() {ex.Object}), ViewResult)
    
    Dim viewData = viewResult.ViewData
    For Each ruleViolation In ex.Object.GetRuleViolations()
        viewData.ModelState.AddModelError( _
                ruleViolation.PropertyName, ruleViolation.ErrorMessage)
    Next
    filterContext.Result = viewResult
    

    在我的情况下,我知道 filterContext.Controller 在使用此 HandleRuleViolationsAttribute 时总是从 Controller 派生。在 Controller 中,ModelState 是通过调用 return View(theObject) 来设置的。不过,View 方法是受保护的,因此在 HandleRuleViolationsAttribute 中我使用反射调用它,这为我提供了一个 ViewResult 实例,其中 ModelState 已正确初始化。然后我可以使用 AddModelError 方法将 RuleViolations 添加到 ModelState。我将该 viewResult 分配给 filterContext.Result 以显示它。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2011-10-02
      • 1970-01-01
      • 1970-01-01
      • 2018-06-25
      • 2020-06-21
      • 2010-10-07
      • 2014-08-03
      相关资源
      最近更新 更多