2009-08-20 22 views
1

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

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

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

我抬头代码为HandleExceptionAttribute MVC中的源代码,并修改了它:

<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 

来填充视图的Model我遍历请求的形式键和添加键值并将其值的ViewData的实例。它现在可以工作,但是,我不认为这是做到这一点的方法。

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

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

感谢, 纪尧姆Hanique

回答

0

Got it!

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中,ModelState通过调用return View(theObject)来设置。虽然View方法是受保护的,但是在HandleRuleViolationsAttribute中,我使用反射来调用它,这给了我一个正确初始化ModelState的ViewResult实例。然后,我可以使用AddModelError方法将RuleViolations添加到ModelState中。我将该viewResult指派给filterContext.Result以显示它。

1

夫妇快速点:
1.实际上,你要更新的控制器的ModelState中(其中查看访问的属性) 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) 

你可能会考虑暴露了一个名为“视图名”的属性,属性,以便用户可以指定一个不同的观点在例外的情况下使用:

<HandleRuleViolationException(ViewName:="SomeErrorViewForThisControllerOrAction")> 

这是一个非常整洁的想法。请回来更新帖子,标记答案或评论结果。我很好奇这是如何解决的!

+0

谢谢您的回复! 该属性确实有一个可以设置的View属性,但如果不是,Action名称将用作View名称。 我确实有和你一样的想法:filterContext.Controller.UpdateModel但是filterContext.Controller的类型是ControllerBase,它没有UpdateModel方法。任何其他想法? 我可以将真实的控制器实例传递给此属性(控制器的Action中的this/Me)并使用它来更新模型? – 2009-08-21 08:47:06

+0

好点。我错过了。通过代码回顾,我实际上认为在这里完成这项工作所需的最少工作量非常大。通过将viewData.Model设置为ex.Object,您可以达到同样的效果。我会认为你会从ex.Object获取所有表单数据?也许不会。 – 2009-08-21 14:30:09

+0

我放弃了。如果UpdateModel是调用的方法,我甚至不确定。即使是这样,我也搞不明白。有没有人知道如何在设置视图的Model属性时更新ModelState? – 2009-08-23 09:33:27