2013-08-07 16 views
12

Występuje problem z wieloma formularzami w jednym widoku.Wiele formularzy w widoku MVC: ModelState zastosowany do wszystkich formularzy

Załóżmy, że mam następujący ViewModel:

public class ChangeBankAccountViewModel 
{ 
    public IEnumerable<BankInfo> BankInfos { get; set; } 
} 

public class BankInfo 
{ 
    [Required] 
    public string BankAccount { get; set; } 
    public long Id { get; set; } 
} 

W moim viewmodel, chcę wszystkie BankInfos być wyświetlane pod siebie, wewnątrz oddzielnych formularzach dla każdego.

Aby to osiągnąć, używam częściowego _EditBankInfo wyświetlania:

@model BankInfo 

@using (Html.BeginForm()) 
{ 
    @Html.HiddenFor(m => m.InvoiceStructureId) 
    @Html.TextBoxFor(m => m.IBANAccount) 

    <button type="submit">Update this stuff</button> 
} 

jak również mojego prawdziwego BankInfo wyświetlania:

foreach(var info in Model.BankInfos) 
{ 
    Html.RenderPartial("_EditBankInfo", info); 
} 

ostatni, tutaj są moje 2 METODY DZIAŁANIA:

[HttpGet] 
public ActionResult BankInfo() 
{ 
    return View(new ChangeBankAccountViewModel{BankInfos = new [] {new BankInfo...}); 
} 
[HttpPost] 
public ActionResult BankInfo(BankInfo model) 
{ 
    if(ModelState.IsValid) 
     ModelState.Clear(); 
    return BankInfo(); 
} 

Wszystko to działa hunky dory: Walidacja działa sprawnie, opublikowany model zostaje rozpoznany i zatwierdzony poprawnie ... Jednak, gdy strona zostanie wczytana ponownie, pojawia się problem. Ponieważ używam tej samej postaci wiele razy, mój ModelState zostanie zastosowany wiele razy. Tak więc podczas wykonywania aktualizacji w jednym formularzu, następna strona załaduje wszystkie z nich będzie miała zaksięgowane wartości.

Czy istnieje sposób, aby temu zapobiec?

Próbowałem zrobić to bez częściowych widoków, ale to trochę zawraca nazwę (są unikalne, ale modelowanie w oparciu o serwery nie rozpoznaje ich).

Dzięki za wszelkie odpowiedzi.

+0

Czy możesz pokazać działanie kontrolera, do którego formularz jest przesyłany? Szczególnie interesuje mnie model, który przyjmuje jako parametr i model, który przechodzi do widoku. –

+0

@DarinDimitrov, Dodano. Wiedz, że jest to uproszczony przykład, ale podstawowa konfiguracja powinna tam być. Poza tym prawdopodobnie używałbym tutaj jakiegoś scenariusza PRG. – Kippie

Odpowiedz

10

To trochę trudne. Oto, jak można go rozwiązać. Zacznij od przeniesienia części _EditBankInfo.cshtml do edytora szablonu ~/Views/Shared/EditorTemplates/BankInfo.cshtml, który wygląda tak (zauważ, że nazwa i lokalizacja szablonu jest ważna.) Powinna być umieszczona wewnątrz ~/Views/Shared/EditorTemplates i nazwana jako nazwa wpisanego używanego w Twojej kolekcji kolekcji IEnumerable<T>, która w twoim przypadku jest BankInfo.cshtml):

@model BankInfo 

<div> 
    @using (Html.BeginForm()) 
    { 
     <input type="hidden" name="model.prefix" value="@ViewData.TemplateInfo.HtmlFieldPrefix" /> 
     @Html.HiddenFor(m => m.Id) 
     @Html.TextBoxFor(m => m.BankAccount) 

     <button type="submit">Update this stuff</button> 
    } 
</div> 

a następnie w głównym widoku pozbyć pętli foreach i zastąpić go z prostego połączenia do EditorFor pomocnika:

@model ChangeBankAccountViewModel 

@Html.EditorFor(x => x.BankInfos) 

teraz dla każdego elementu szablonu edytora niestandardowego kolekcji BankInfos zostanie wyrenderowany. I w przeciwieństwie do częściowego szablon redaktor szanuje kontekst nawigacyjnej i wygeneruje następujące znaczniki:

<div> 
    <form action="/" method="post">  
     <input type="hidden" name="model.prefix" value="BankInfos[0]" /> 
     <input data-val="true" data-val-number="The field Id must be a number." data-val-required="The Id field is required." id="BankInfos_0__Id" name="BankInfos[0].Id" type="hidden" value="1" /> 
     <input data-val="true" data-val-required="The BankAccount field is required." id="BankInfos_0__BankAccount" name="BankInfos[0].BankAccount" type="text" value="account 1" />  
     <button type="submit">Update this stuff</button> 
    </form> 
</div> 

<div> 
    <form action="/" method="post">  
     <input type="hidden" name="model.prefix" value="BankInfos[1]" /> 
     <input data-val="true" data-val-number="The field Id must be a number." data-val-required="The Id field is required." id="BankInfos_1__Id" name="BankInfos[1].Id" type="hidden" value="2" /> 
     <input data-val="true" data-val-required="The BankAccount field is required." id="BankInfos_1__BankAccount" name="BankInfos[1].BankAccount" type="text" value="account 2" />  
     <button type="submit">Update this stuff</button> 
    </form> 
</div> 

... 

Teraz ponieważ każde pole ma konkretnej nazwy nie będzie już być żadnych konfliktów podczas wysyłania formularza. Zwróć uwagę na ukryte pole o nazwie model.prefix, które wyraźnie umieściłem w każdym formularzu.To będzie używany przez model niestandardowy spoiwem dla typu BankInfo:

public class BankInfoModelBinder: DefaultModelBinder 
{ 
    public override object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext) 
    { 
     bindingContext.ModelName = controllerContext.HttpContext.Request.Form["model.prefix"]; 
     return base.BindModel(controllerContext, bindingContext); 
    } 
} 

który zostanie zarejestrowany w swojej Application_Start:

ModelBinders.Binders.Add(typeof(BankInfo), new BankInfoModelBinder()); 

Dobra, teraz jesteśmy dobrze iść. Pozbądź się ModelState.Clear w akcji kontrolera, ponieważ już go nie potrzebujesz:

[HttpGet] 
public ActionResult BankInfo() 
{ 
    var model = new ChangeBankAccountViewModel 
    { 
     // This is probably populated from some data store 
     BankInfos = new [] { new BankInfo... }, 
    } 
    return View(model); 
} 

[HttpPost] 
public ActionResult BankInfo(BankInfo model) 
{ 
    if(ModelState.IsValid) 
    { 
     // TODO: the model is valid => update its value into your data store 
     // DO NOT CALL ModelState.Clear anymore. 
    } 

    return BankInfo(); 
} 
+0

Wielkie dzięki, Darin. Niesamowity przykład, który nauczył mnie kilku sztuczek (kto wiedział, że edytorowi pozwolił na kolekcje podlegające kolekcjom?). Po prostu mam niewielki problem z wykreśleniem prefiksu na wypadek, gdybym musiał ręcznie dodać błąd modelu do mojego modelu, ale jestem pewien, że wymyślę to. – Kippie

Powiązane problemy