代码之家  ›  专栏  ›  技术社区  ›  Guillaume Hanique

ASP.NET MVC:如何为异常筛选器创建ViewData

  •  1
  • Guillaume Hanique  · 技术社区  · 17 年前

    我不确定您是否熟悉NerdDinner应用程序。它添加了一个方法GetRuleViolations()和一个属性IsValid到晚餐对象。保存对象时,它会检查对象是否有效。如果不是,则抛出异常。在捕获异常的控制器中,ViewData的ModelState充满了违反规则的情况,视图将重新显示。验证帮助程序突出显示错误。

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

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

    我在MVC源代码中查找HandleException属性的代码,并对其进行了修改:

        <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模型状态。我可以包含一个字符串数组,其中包含必须更新的属性名,或者,当将模型作为操作参数时,我可以使用Bind属性插入或排除某些属性(就像我在上面的create操作中所做的那样)。我的方法不符合这一点,可能导致安全问题。

    在OneException方法中构造ViewData对象是否有更好的方法,其工作原理与控制器的UpdateModel方法类似?是否有方法从ExceptionHandlerAttribute调用UpdateModel方法?

    纪尧姆·哈尼克

    2 回复  |  直到 17 年前
        1
  •  1
  •   Tony Heupel    17 年前

    几个要点:
    1.实际上,您希望更新控制器的ModelState(视图可以将其作为属性访问) 2.您希望将结果设置为传入模型对象的视图,即使该视图无效

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

    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)
    

    <HandleRuleViolationException(ViewName:="SomeErrorViewForThisControllerOrAction")>
    

    这是一个很好的主意。请回来更新帖子,标记答案,或对结果发表评论。我很好奇这是怎么回事!

        2
  •  0
  •   Guillaume Hanique    17 年前

    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
    

    在我的例子中,我知道当使用这个HandlerRuleViolationsAttribute时,filterContext.Controller总是从Controller派生。在控制器中,通过调用返回视图(对象)来设置模型状态。但是View方法是受保护的,因此在HandlerRuleViolationsAttribute中,我使用反射调用它,这将为我提供一个正确初始化了ModelState的ViewResult实例。然后,我可以使用AddModelError方法将规则冲突添加到ModelState。我将viewResult指定给filterContext.Result以显示它。