Hi all,
I'm not sure if you're familiar with the NerdDinner application. It adds a method GetRuleViolations() and a property IsValid to the Dinner object. When the object is saved, it checks whether the object is valid. If it isn't, it throws an exception. In the controller that exception is caught, the ViewData's ModelState is filled with the rule violations and the view is redisplayed. The Html.Validation helpers highlight the errors.
What I'd like to do is create a HandleRuleViolationExceptionAttribute, similar to the HandleExceptionAttribute (which is part of the MVC Framework). The problem is that this attribute has to repopulate the View's Modelstate.
A view can have any object type for its model. The code that throws the RuleViolationException fills sets the RuleViolationException.Object to the View's Model.
I looked up the code for the HandleExceptionAttribute in the MVC source code and modified it:
<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
To fill the View's Model I iterate over the request's form keys and add the key and its value to the ViewData instance. It now works, however, I don't believe this is the way to do it.
In the Controller's Action method I could update the model with the UpdateModel-method. This also updates the viewStates ModelState. I can include an array of strings with the property names that must be updated, or, when having the model as an Action parameter, I could use the Bind-attribute to in- or exclude some properties (as I do in the create-action above). My method does not adhere to this, possibly resulting in security problems.
Is there a better way of constructing the ViewData object in the OnException method, that works similarly to the UpdateModel-method of the controller? Is there a way to invoke the UpdateModel-method from the ExceptionHandlerAttribute?
Thanks, Guillaume Hanique