asp.net mvc - Multiple forms in MVC view: ModelState applied to all forms -
running trouble multiple forms on single view.
suppose have following viewmodel:
public class changebankaccountviewmodel { public ienumerable<bankinfo> bankinfos { get; set; } } public class bankinfo { [required] public string bankaccount { get; set; } public long id { get; set; } }
in viewmodel, want bankinfos displayed underneath eachother, inside separate forms each.
to achieve this, i'm using partial view _editbankinfo:
@model bankinfo @using (html.beginform()) { @html.hiddenfor(m => m.invoicestructureid) @html.textboxfor(m => m.ibanaccount) <button type="submit">update stuff</button> }
as actual view bankinfo:
foreach(var info in model.bankinfos) { html.renderpartial("_editbankinfo", info); }
last, here 2 action methods:
[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(); }
all of working hunky dory: validation works smooth, posted model gets recognized , validated correctly... however, when page reloads when problem arises. because i'm using same form multiple times, modelstate applied multiple times. when performing update on 1 form, next page load of them have posted values.
is there way prevent happening?
i've tried doing without partial views, screws naming bit (they're unique, serverside modelbinding won't recognize them).
thanks answers.
this bit tricky. here's how can solved. start moving _editbankinfo.cshtml
partial editor template ~/views/shared/editortemplates/bankinfo.cshtml
looks (notice name , location of template important. should placed inside ~/views/shared/editortemplates
, named name of typed used in ienumerable<t>
collection property, in case 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 stuff</button> } </div>
and in main view rid of foreach
loop , replace simple call editorfor
helper:
@model changebankaccountviewmodel @html.editorfor(x => x.bankinfos)
now each element of bankinfos
collection custom editor template rendered. , contrary partial, editor template respects navigational context , generate following markup:
<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 number." data-val-required="the id field required." id="bankinfos_0__id" name="bankinfos[0].id" type="hidden" value="1" /> <input data-val="true" data-val-required="the bankaccount field required." id="bankinfos_0__bankaccount" name="bankinfos[0].bankaccount" type="text" value="account 1" /> <button type="submit">update 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 number." data-val-required="the id field required." id="bankinfos_1__id" name="bankinfos[1].id" type="hidden" value="2" /> <input data-val="true" data-val-required="the bankaccount field required." id="bankinfos_1__bankaccount" name="bankinfos[1].bankaccount" type="text" value="account 2" /> <button type="submit">update stuff</button> </form> </div> ...
now since every field has specific name there no longer conflicts when posting form. notice hidden field named model.prefix
explicitly placed inside each form. used custom model binder bankinfo
type:
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); } }
which registered in application_start
:
modelbinders.binders.add(typeof(bankinfo), new bankinfomodelbinder());
alright, go. rid of modelstate.clear
in controller action no longer need it:
[httpget] public actionresult bankinfo() { var model = new changebankaccountviewmodel { // populated data store bankinfos = new [] { new bankinfo... }, } return view(model); } [httppost] public actionresult bankinfo(bankinfo model) { if(modelstate.isvalid) { // todo: model valid => update value data store // not call modelstate.clear anymore. } return bankinfo(); }
Comments
Post a Comment