ASP.NET MVC: как создать ViewData для фильтра исключений - PullRequest
1 голос
/ 20 августа 2009

Я не уверен, знакомы ли вы с приложением NerdDinner. Он добавляет метод GetRuleViolations () и свойство IsValid к объекту Dinner. Когда объект сохраняется, он проверяет, является ли объект действительным. Если это не так, это вызывает исключение. В контроллере, в котором обнаружено это исключение, ModelState объекта ViewData заполняется нарушениями правил, и представление отображается повторно. Помощники Html.Validation выделяют ошибки.

Что я хотел бы сделать, это создать атрибут HandleRuleViolationExceptionAttribute, аналогичный атрибуту HandleExceptionAttribute (который является частью MVC Framework). Проблема заключается в том, что этот атрибут должен заполнять состояние модели представления.

Вид может иметь любой тип объекта для своей модели. Код, который генерирует исключения RuleViolationException, устанавливает RuleViolationException.Object для модели представления.

Я посмотрел код для 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

Чтобы заполнить модель представления, я перебираю ключи формы запроса и добавляю ключ и его значение в экземпляр ViewData. Теперь это работает, однако я не верю, что это способ сделать это.

В методе Controller's Action я мог бы обновить модель с помощью метода UpdateModel. Это также обновляет viewStates ModelState. Я могу включить массив строк с именами свойств, которые должны быть обновлены, или, имея модель в качестве параметра Action, я мог бы использовать атрибут Bind для включения или исключения некоторых свойств (как я делаю в create-action). выше). Мой метод не придерживается этого, что может привести к проблемам с безопасностью.

Есть ли лучший способ создания объекта ViewData в методе OnException, который работает аналогично методу UpdateModel контроллера? Есть ли способ вызвать метод UpdateModel из ExceptionHandlerAttribute?

Спасибо, Гийом Ханик

Ответы [ 2 ]

1 голос
/ 21 августа 2009

Пара быстрых очков:
1. Вы действительно хотите обновить ModelState контроллера (к которому у представления есть доступ как свойство) 2. Вы хотите установить результат в View, где вы передаете объект модели, даже если он недействителен

Из того, что вы описываете, кажется, что вы должны вызывать метод 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")>

Это довольно изящная идея. Пожалуйста, вернитесь и обновите сообщение, отметьте ответ или комментарий относительно результата. Мне очень любопытно, как это работает!

0 голосов
/ 23 августа 2009

Понял!

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 всегда наследуется от Controller, когда используется этот атрибут HandleRuleViolationsAttribute. В контроллере ModelState устанавливается путем вызова return View (theObject). Однако метод View защищен, поэтому в атрибуте HandleRuleViolationsAttribute я вызываю его с помощью отражения, что дает мне экземпляр ViewResult с правильно инициализированной ModelState. Затем я могу добавить RuleViolations в ModelState, используя метод AddModelError. Я присваиваю этот viewResult для filterContext.Result, чтобы он отображался.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...