我不确定您是否熟悉NerdDinner应用程序。它将一个方法GetRuleViolations()和一个属性IsValid添加到晚餐对象。当对象被保存时,它检查对象是否有效。如果不是,则抛出异常。在控制器中,异常被捕获,视图数据的ModelState中填充了违反规则的情况,视图被重新显示。Html.Validation辅助对象将高亮显示错误。
我想要做的是创建一个类似于HandleExceptionAttribute (它是MVC框架的一部分)的HandleRuleViolationExceptionAttribute。问题是这个属性必须重新填充视图的Modelstate。
视图可以具有其模型的任何对象类型。抛出RuleViolationException填充的代码将RuleViolationException.Object设置为视图的模型。
我在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
为了填充视图的Model,我遍历请求的表单键,并将键及其值添加到ViewData实例中。它现在可以工作了,然而,我不相信这是做这件事的方式。
在控制器的Action方法中,我可以使用UpdateModel-method更新模型。这也会更新viewStates ModelState。我可以包含一个带有必须更新的属性名称的字符串数组,或者,当将模型作为Action参数时,我可以使用Bind-attribute来加入或排除一些属性(就像我在上面的create-action中所做的那样)。我的方法没有遵守这一点,可能会导致安全问题。
有没有更好的方法在OnException方法中构造ViewData对象,它的工作方式类似于控制器的UpdateModel-方法?有没有办法从ExceptionHandlerAttribute调用UpdateModel-method?
谢谢,纪梵希·哈尼克
发布于 2009-08-23 03:26:50
明白了!
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
在我的例子中,我知道当使用这个HandleRuleViolationsAttribute时,filterContext.Controller总是派生自控制器。在控制器中,通过调用return View(theObject)来设置ModelState。但是视图方法是受保护的,所以在HandleRuleViolationsAttribute中我使用反射调用它,这给了我一个正确初始化了ModelState的ViewResult实例。然后,我可以使用AddModelError- RuleViolations将该方法添加到ModelState中。我将该viewResult赋值给filterContext.Result以显示它。
发布于 2009-08-20 19:12:15
几个快速要点:
从您的描述来看,您似乎应该调用控制器的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")>
这是一个相当巧妙的想法。请回来更新帖子,标记答案,或对结果发表评论。我很好奇这是怎么回事!
https://stackoverflow.com/questions/1308433
复制相似问题