2009-08-20 11 views
1

Je ne suis pas sûr si vous êtes familier avec l'application NerdDinner. Il ajoute une méthode GetRuleViolations() et une propriété IsValid à l'objet Dinner. Lorsque l'objet est enregistré, il vérifie si l'objet est valide. Si ce n'est pas le cas, il déclenche une exception. Dans le contrôleur, cette exception est interceptée, ModelState de ViewData est rempli avec les violations de règles et la vue est réaffichée. Les aides Html.Validation mettent en évidence les erreurs.ASP.NET MVC: Comment créer ViewData pour un filtre d'exception

Ce que je voudrais faire est de créer un HandleRuleViolationExceptionAttribute, semblable à HandleExceptionAttribute (qui fait partie de MVC Framework). Le problème est que cet attribut doit repeupler le Modelstate de View.

Une vue peut avoir n'importe quel type d'objet pour son modèle. Le code qui génère les remplissages RuleViolationException définit RuleViolationException.Object sur le modèle de la vue.

Je regardai le code pour le HandleExceptionAttribute dans le code source MVC et modifia:

<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 

Pour remplir le modèle I itérer sur les clés de formulaire de la demande de la vue et ajoutez la clé et sa valeur à la ViewData exemple. Cela fonctionne maintenant, cependant, je ne crois pas que ce soit la façon de le faire.

Dans la méthode Action du contrôleur, je pouvais mettre à jour le modèle avec la méthode UpdateModel. Cela met également à jour le viewStates ModelState. Je peux inclure un tableau de chaînes avec les noms de propriétés qui doivent être mis à jour ou, lorsque le modèle est un paramètre Action, je peux utiliser l'attribut Bind pour inclure ou exclure certaines propriétés (comme je le fais dans create-action au dessus). Ma méthode n'adhère pas à cela, ce qui peut entraîner des problèmes de sécurité.

Existe-t-il une meilleure façon de construire l'objet ViewData dans la méthode OnException, qui fonctionne de manière similaire à la méthode UpdateModel du contrôleur? Existe-t-il un moyen d'invoquer la méthode UpdateModel à partir de ExceptionHandlerAttribute?

Merci, Guillaume Hanique

Répondre

0

L'a reçu!

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 

Dans mon cas, je sais que filterContext.Controller dérive toujours du contrôleur lorsque ce HandleRuleViolationsAttribute est utilisé. Dans le contrôleur, ModelState est défini en appelant return View (theObject).La méthode View est protégée, cependant, dans le HandleRuleViolationsAttribute, je l'appelle en utilisant la réflexion, ce qui me donne une instance de ViewResult avec le ModelState correctement initialisé. Je peux ensuite ajouter les RuleViolations à ModelState en utilisant la méthode AddModelError. J'affecte ce viewResult à filterContext.Result pour l'afficher.

1

Couple points rapides:
1. Vous voulez vraiment mettre à jour ModelState (où la vue a accès à une propriété) du contrôleur 2. Vous souhaitez définir le résultat a Vue où vous passez l'objet modèle même s'il est invalide

D'après ce que vous décrivez, il semble que vous devriez appeler la méthode UpdateModel du contrôleur. Vous pouvez le faire à partir de votre méthode OnException en faisant ceci:

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) 

Vous pourriez envisager d'exposer une propriété appelée « ViewName » sur l'attribut afin que l'utilisateur peut spécifier un autre point de vue à utiliser dans le cas d'une exception:

<HandleRuleViolationException(ViewName:="SomeErrorViewForThisControllerOrAction")> 

Ceci est une idée assez soignée. Veuillez revenir et mettre à jour le post, marquer la réponse ou commenter le résultat. Je suis très curieux de savoir comment cela fonctionne!

+0

Merci pour votre réponse! L'attribut a en effet une propriété View qui peut être définie, mais si ce n'est pas le cas, le nom de l'action est utilisé comme nom de vue. J'ai eu exactement la même idée que vous: filterContext.Controller.UpdateModel HOWEVER filterContext.Controller est de type ControllerBase qui n'a pas la méthode UpdateModel. D'autres idées? Puis-je transmettre l'instance réelle du contrôleur à cet attribut (le ceci/moi dans l'action du contrôleur) et l'utiliser pour mettre à jour le modèle? –

+0

Bon point. J'avais raté ça. En repensant au code, je pense que vous avez à peu près le minimum de travail requis pour y parvenir. En définissant viewData.Model sur ex.Object, vous obtenez la même chose. Je pense que vous obtiendrez toutes les données de formulaire de l'ex.Object? Peut être pas. –

+0

J'abandonne. Je ne suis même plus sûr si UpdateModel est la méthode à appeler. Et même si c'est le cas, je n'arrive pas à comprendre comment. Est-ce que quelqu'un sait comment mettre à jour le ModelState lors de la définition de la propriété Model de la vue? –

Questions connexes