2009-04-05 13 views
12

Das Problem: Aktualisieren von ModelState im Buchungs + Validierungsszenario.Aktualisieren von ModelState mit Modellobjekt

Ich habe eine einfache Form bekommt:

<%= Html.ValidationSummary() %> 
<% using(Html.BeginForm())%> 
<%{ %> 
    <%=Html.TextBox("m.Value") %> 
    <input type="submit" /> 
<%} %> 

Wenn Benutzer senden I-Eingang überprüfen möge und in einigen Fällen mag ich den Fehler für den Benutzer zu beheben, um ihn wissen zu lassen, dass er einen Fehler gemacht, das ist bereits fest:

[AcceptVerbs(HttpVerbs.Post)] 
public ActionResult Index(M m) 
{ 
    if (m.Value != "a") 
    { 
     ModelState.AddModelError("m.Value", "should be \"a\""); 
     m.Value = "a"; 
     return View(m); 
    } 
    return View("About");    
} 

Nun das Problem ist, wird MVC einfach das Modell zu der Ansicht geführt ignorieren und wieder machen, was der Benutzer eingegeben - und nicht meinen Wert („a“). Dies passiert, weil der TextBox-Renderer prüft, ob ein ModelState vorhanden ist und ob er nicht null ist - der Wert von ModelState wird verwendet. Dieser Wert ist natürlich derjenige, der vor dem Posten eingegeben wurde.

Da ich das Verhalten des TextBox-Renderers nicht ändern kann, wäre die einzige Lösung, die ich gefunden habe, die Aktualisierung des ModelState durch mich selbst. Der Quick'n'dirty-Weg besteht darin, den DefaultModelBinder (ab) zu verwenden und die Methode zu überschreiben, die die Werte von Formularen dem Modell zuweist, indem einfach die Zuweisungsrichtung geändert wird;). Mit DefaultModelBinder muss ich die IDs nicht analysieren. Der folgende Code (basierend auf Original Implementierung von Default) ist meine Lösung für dieses Problem:

/// <summary> 
    /// Updates ModelState using values from <paramref name="order"/> 
    /// </summary> 
    /// <param name="order">Source</param> 
    /// <param name="prefix">Prefix used by Binder. Argument name in Action (if not explicitly specified).</param> 
    protected void UpdateModelState(object model, string prefix) 
    { 
     new ReversedBinder().BindModel(this.ControllerContext, 
      new ModelBindingContext() 
      { 
       Model = model, 
       ModelName = prefix, 
       ModelState = ModelState, 
       ModelType = model.GetType(), 
       ValueProvider = ValueProvider 
      }); 
    } 

    private class ReversedBinder : DefaultModelBinder 
    { 
     protected override void BindProperty(ControllerContext controllerContext, ModelBindingContext bindingContext, System.ComponentModel.PropertyDescriptor propertyDescriptor) 
     { 
      string prefix = CreateSubPropertyName(bindingContext.ModelName, propertyDescriptor.Name); 
      object val = typeof(Controller) 
       .Assembly.GetType("System.Web.Mvc.DictionaryHelpers") 
       .GetMethod("DoesAnyKeyHavePrefix") 
       .MakeGenericMethod(typeof(ValueProviderResult)) 
       .Invoke(null, new object[] { bindingContext.ValueProvider, prefix }); 
      bool res = (bool)val; 
      if (res) 
      { 

       IModelBinder binder = new ReversedBinder();//this.Binders.GetBinder(propertyDescriptor.PropertyType); 
       object obj2 = propertyDescriptor.GetValue(bindingContext.Model); 

       ModelBindingContext context2 = new ModelBindingContext(); 
       context2.Model = obj2; 
       context2.ModelName = prefix; 
       context2.ModelState = bindingContext.ModelState; 
       context2.ModelType = propertyDescriptor.PropertyType; 
       context2.ValueProvider = bindingContext.ValueProvider; 
       ModelBindingContext context = context2; 
       object obj3 = binder.BindModel(controllerContext, context); 

       if (bindingContext.ModelState.Keys.Contains<string>(prefix)) 
       { 
        var prefixKey = bindingContext.ModelState.Keys.First<string>(x => x == prefix); 
        bindingContext.ModelState[prefixKey].Value 
            = new ValueProviderResult(obj2, obj2.ToString(), 
                   bindingContext.ModelState[prefixKey].Value.Culture); 
       } 
      } 
     } 
    } 

So bleibt die Frage: bin ich etwas extrem selten tun oder bin ich etwas fehlt? Wenn Ersteres, wie könnte ich solche Funktionalität besser implementieren (unter Verwendung der bestehenden MVC-Infrastruktur)?

Antwort

4

Sie könnten eine Formularsammlung als Parameter anstelle Ihres Modellobjekts in Ihrem Controller akzeptieren, so: public ActionResult Index(FormCollection Form).

Daher wird der Standardmodellbinder den Modellstatus nicht aktualisieren, und Sie erhalten das gewünschte Verhalten.

Bearbeiten: Oder Sie können nur das ModelStateDictionary aktualisieren, um Ihre Änderungen am Modell widerzuspiegeln.


[AcceptVerbs(HttpVerbs.Post)] 
public ActionResult Index(M m) 
{ 
    if (m.Value != "a") 
    { 
     ModelState["m.Value"].Value = new ValueProviderResult("a", m.Name, 
        CultureInfo.CurrentCulture); 
     ModelState.AddModelError("m.Value", "should be \"a\""); 
     m.Value = "a"; 
     return View(m); 
    } 
    return View("About");    
} 

Hinweis: Ich bin mir nicht sicher, ob dies der beste Weg ist. Aber es scheint zu funktionieren und es sollte das Verhalten sein, das du willst.

+0

Aber ich möchte verbindlich den Standard erhalten. Ich möchte es, weil ich ModelState verwenden möchte. Ich möchte nur den ModelState aktualisieren, um Änderungen in meinem Modellobjekt widerzuspiegeln. – user87338

+0

Bitte sehen Sie meine Bearbeitung. –

+0

Ihr Kommentar ist genau das, was ich tue, aber Sie tun es selbst, und ich benutze Te-Mappe, so dass ich etwas mehr "generische" habe. Die Änderung des Wertes ("a") passiert in einer niedrigeren Reihe, also weiß ich nicht wirklich, welche Stützen sich geändert haben. Und auch Sie möchten nicht ModelState ["m.Value"]. Wert = neu ValueProviderResult ("a", m.Name, CultureInfo.CurrentCulture); für jedes Objekt Eigenschaft, tun Sie :). – user87338

22

Ich weiß, dass dieser Beitrag ziemlich alt ist, aber es ist ein Problem, das ich vorher hatte und ich dachte nur an eine einfache Lösung, die ich mag - einfach den ModelState löschen, nachdem Sie die gebuchten Werte haben.

UpdateModel(viewModel); 
ModelState.Clear(); 

viewModel.SomeProperty = "a new value"; 
return View(viewModel); 

und die Ansicht muss das (möglicherweise geänderte) View-Modellobjekt anstelle von ModelState verwenden.

Vielleicht ist das wirklich offensichtlich. Es scheint so im Nachhinein!

0

mache ich etwas sehr ungewöhnlich oder fehle ich etwas?

Ich denke, das ist ziemlich selten. Ich denke, MVC geht davon aus, dass Validierungsfehler eine Ja/Nein-Affäre sind, und in diesem Fall verwenden Sie einen Validierungsfehler, um allgemeines Benutzerfeedback zu geben.

denke ich MVC scheint auch am glücklichsten, wenn POSTs entweder aufgrund Validierungsfehler fehlschlagen, oder eine Aktion ausführen und umleiten oder etwas ganz anderes machen. Außerhalb von Modellvalidierungsfehlern ist es ziemlich selten, dieselbe Eingabe erneut zu rendern.

Ich habe seit etwa einem Jahr mit MVC jetzt und lief nur in diesen in einem anderen Kontext, in dem nach einem POST ich eine neue Form als Antwort auf machen wollte.

[HttpPost] 
public ActionResult Upload(DocumentView data) { 
    if(!ModelState.IsValid) return View(data); 
    ProcessUpload(data); 
    return View(new DocumentView()); 
} 

MVC rendert die ModelState von data, nicht mein neues Objekt. Sehr überraschend.

Wenn der ehemalige, dann, wie könnte ich eine solche Funktionalität in einer besseren Art und Weise

  1. auf automatische Korrekturen in JavaScript implementieren implementieren (vielleicht nicht möglich sein)
  2. halten eine Liste von automatischen Korrekturen gemacht, wenn das Objekt nach all denen gültig ist, dann übergebe es an die "About" -Ansicht und wird als eine Nachricht wie "M gespeichert, mit folgenden Korrekturen: ..." angezeigt.
Verwandte Themen