Do MVC3 non-sequential hidden input indexes need to come first? - asp.net-mvc

MVC3 non-sequential index hidden inputs for model binding..
<input type="hidden" name="Index" value="whatever" />
Does it matter if they go before, after, in the middle of the other related inputs to be posted?
Does it matter at all where they end up in the posted data?
For example, can they all be lumped together and it still works?
<input type="text" name="[A].Id" value="1" />
<input type="text" name="[B].Id" value="2" />
<input type="hidden" name="Index" value="A" />
<input type="hidden" name="Index" value="B" />

No, the order of your form fields does not matter, nore where they appear on the html page.
The most important factor for MVC3 is the name of the fields must match to the name of your controller/action parameter.
If you have two fields with the same name however, only one value will be returned into your action.

As long as the hidden fields are located inside of the form it should not matter the order in which they are placed. Please see code sample below. Notice how the hidden fields are put anywhere inside of the form.
#using (Html.BeginForm())
{
#Html.ValidationSummary(false, "Please correct the following errors")
#Html.HiddenFor(m => m.CoolStuffId)
#Html.Partial("_EditCoolStuff", Model)
<fieldset class="ui-grid-a">
<div class="ui-block-a"><a data-role="button" href="#Url.Action("ActionPlan", "Store", new { id = Model.StoreID })">Cancel</a></div>
<div class="ui-block-b"><button type="submit" data-theme="a">Submit</button></div>
</fieldset>
#Html.HiddenFor(m => m.TypeId)
}

Related

Get method : form input not in url

I am doing my first ASP.NET mvc project, on the home page, Index.cshtml, I have a small form:
<form action="ChoixFormulaire" method="get">
<fieldset>
<label>NAS</label>
<input id="nas" type="text" placeholder="###"/>
<br />
<label>Date of birth</label>
<input id="date" type="text" placeholder="AAAA-MM-JJ"/>
<br />
<label>Employee number</label>
<input id="numEmployee" type="text" placeholder="######"/>
<br />
</fieldset>
<input type="submit" value="Soumettre" onclick="return VerifierFormulaire()" />
</form>
When the button is clicked, there is some verification made in the 'VerifierFormulaire()' method, which is defined in the same Index.cshtml file. Then the ChoixFormulaire.cshtml is displayed (called from the ChoixFormulaire() method in my HomeController, which returns View()).
I was expecting the form inputs to be in the URL as parameters. For example, If I enter '123' for NAS, '1989-01-01' for date of birth and '123456' for employee number, I am redirected to http://localhost:15778/Home/ChoixFormulaire? but I would expect to be redirected to http://localhost:15778/Home/ChoixFormulaire?nas=123&dateBirth=1989-01-01&numEmployee=123456
Try adding the name attribute:
<input id="nas" name="nas" />

asp.net - show phone number as link or not

I've got a real simple ASP.NET MVC4 app that uses JQuery Mobile and displays a list of users and their information. 3 fields off the model are phone numbers. These fields can contain a null value, so I'd like to link the phone number using <a href="tel:" if it exists, and nothing if not. I came up with this:
<div data-role="fieldcontain">
<label for="textinput1"><strong>Office Phone:</strong></label>
if(!#String.IsNullOrEmpty(user.OfficePhone)){
<input name="" id="textinput1" value="#user.OfficePhone" type="text" readonly="true"/>
} else {
<input name="" id="textinput" value="#user.OfficePhone" type="text" readonly="true"/>
}
</div>
But would think there's a cleaner, better way to do this. Are there any other options or am I stuck with writing out the <input> tag twice in both conditions?
This is untested code. Hopefully it gets the idea across:
#{string pref="tel:"}
#if(String.IsNullOrEmpty(user.OfficePhone))
{pref="";
}
<div data-role="fieldcontain">
<label for="textinput1"><strong>Office Phone:</strong></label>
<input name="" id="textinput1" value="#(pref)#(user.OfficePhone)" type="text" readonly="true"/>
</div>

Steve Sanderson's BeginCollectionItem helper won't bind correctly

I am using Steve Sanderson's BeginCollectionItem helper and ran into a problem. I have a form that has an option to add unlimited reward fields. I am using his helper since it solved this problem with how to keep generating the fields and not have to worry about how to bind it when the form gets submitted.
I have in this same form some checkboxes that there is an unknown amount. The difference with this one versus the rewards is the unknown amount will become known after a database call and will be known by the time the code gets to the view.
So my code looks like this
public class FrmVm
{
public Guid Id { get; set; }
public string Name { get; set; }
public bool Active { get; set; }
public IList<WarrantyFeaturesVm> WarrantyFeaturesVm { get; set; } // this is the checkbox ones.
public IList<RewardVms> RewardVms { get; set; } // this is the dyanmic one that I needed the helper for
public CbCreditCardFrmVm()
{
Active = true;
WarrantyFeaturesVm = new List<WarrantyFeaturesVm>();
RewardVms = new List<RewardVms>();
}
}
// view
#foreach (var tier in Model.RewardVms)
{
#Html.Partial("GenerateReward", tier) // in this partial view in the BeginCollectionItem
}
#foreach (var warranties in Model.WarrantyFeaturesVm)
{
using (Html.BeginCollectionItem("WarrantyFeaturesVm"))
{
<span>#warranties.Name:</span>
#Html.TextBoxFor(x => warranties.FeatureId)
#Html.CheckBoxFor(x => warranties.HasFeature)
}
}
I am using jquery to submit the data by using serializeArray(). When it goes to the server it bind all the dynamic ones correctly and even binds the Warranty to the Collection(the collection count is 1). Yet it never binds anything insides the WarrantyFeaturesVm, everything is left as default.
if I remove using (Html.BeginCollectionItem("WarrantyFeaturesVm")) then it won't even bind the collection.
Anyone know why it is not binding anything in the collection?
Edit
// for loop (works)
<form method="post" id="" action="" class="ui-formwizard ui-helper-reset ui-widget ui-widget-content ui-corner-all" novalidate="novalidate">
<span id="" class="step ui-formwizard-content ui-helper-reset ui-corner-all" style="display: none;">
<input type="hidden" value="6aa20677-d367-4e2a-84f0-9fbe00deb191" name="WarrantyFeaturesVm[0].FeatureId" id="WarrantyFeaturesVm_0__FeatureId" data-val-required="The FeatureId field is required." data-val="true" class="ui-wizard-content ui-helper-reset ui-state-default"> <span>Purchase</span>
<input type="checkbox" value="true" name="WarrantyFeaturesVm[0].HasFeature" id="WarrantyFeaturesVm_0__HasFeature" data-val-required="The HasFeature field is required." data-val="true" class="ui-wizard-content ui-helper-reset ui-state-default"><input type="hidden" value="false" name="WarrantyFeaturesVm[0].HasFeature" class="ui-wizard-content ui-helper-reset ui-state-default">
</form>
//foreach loop beginItemCollection(does not work)
<form method="post" id="" action="" class="ui-formwizard ui-helper-reset ui-widget ui-widget-content ui-corner-all" novalidate="novalidate">
<span id="" class="step ui-formwizard-content ui-helper-reset ui-corner-all" style="display: inline;">
<input type="hidden" value="68ba9241-c409-4f4b-96da-cce13b127c1e" autocomplete="off" name="WarrantyFeaturesVm.index" class="ui-wizard-content ui-helper-reset ui-state-default">
<input type="hidden" value="6aa20677-d367-4e2a-84f0-9fbe00deb191" name="WarrantyFeaturesVm[68ba9241-c409-4f4b-96da-cce13b127c1e].war.FeatureId" id="WarrantyFeaturesVm_68ba9241-c409-4f4b-96da-cce13b127c1e__war_FeatureId" data-val-required="The FeatureId field is required." data-val="true" class="ui-wizard-content ui-helper-reset ui-state-default"> <span>Purchase</span>
<input type="checkbox" value="true" name="WarrantyFeaturesVm[68ba9241-c409-4f4b-96da-cce13b127c1e].war.HasFeature" id="WarrantyFeaturesVm_68ba9241-c409-4f4b-96da-cce13b127c1e__war_HasFeature" data-val-required="The HasFeature field is required." data-val="true" class="ui-wizard-content ui-helper-reset ui-state-default"><input type="hidden" value="false" name="WarrantyFeaturesVm[68ba9241-c409-4f4b-96da-cce13b127c1e].war.HasFeature" class="ui-wizard-content ui-helper-reset ui-state-default">
</span>
</form>
//for loop beginItemCollection (does not work)
<form method="post" id="" action="" class="ui-formwizard ui-helper-reset ui-widget ui-widget-content ui-corner-all" novalidate="novalidate">
<span id="" class="step ui-formwizard-content ui-helper-reset ui-corner-all" style="display: none;">
<input type="hidden" value="fe3fbc82-a2df-476d-a15a-dacd841df97e" autocomplete="off" name="WarrantyFeaturesVm.index" class="ui-wizard-content ui-helper-reset ui-state-default">
<input type="hidden" value="6aa20677-d367-4e2a-84f0-9fbe00deb191" name="WarrantyFeaturesVm[fe3fbc82-a2df-476d-a15a-dacd841df97e].WarrantyFeaturesVm[0].FeatureId" id="WarrantyFeaturesVm_fe3fbc82-a2df-476d-a15a-dacd841df97e__WarrantyFeaturesVm_0__FeatureId" data-val-required="The FeatureId field is required." data-val="true" class="ui-wizard-content ui-helper-reset ui-state-default"> <span>Purchase</span>
<input type="checkbox" value="true" name="WarrantyFeaturesVm[fe3fbc82-a2df-476d-a15a-dacd841df97e].WarrantyFeaturesVm[0].HasFeature" id="WarrantyFeaturesVm_fe3fbc82-a2df-476d-a15a-dacd841df97e__WarrantyFeaturesVm_0__HasFeature" data-val-required="The HasFeature field is required." data-val="true" class="ui-wizard-content ui-helper-reset ui-state-default"><input type="hidden" value="false" name="WarrantyFeaturesVm[fe3fbc82-a2df-476d-a15a-dacd841df97e].WarrantyFeaturesVm[0].HasFeature" class="ui-wizard-content ui-helper-reset ui-state-default">
</span>
<span id="adminSettings" class="step ui-formwizard-content ui-helper-reset ui-corner-all" style="display: inline;">
</form>
Ok I think I see what is going on here.
In the second sample, where you did the foreach, it looks like your cshtml was something like this (# symbols may be incorrect):
foreach (var war in Model.WarrantyFeaturesVm) {
using (Html.BeginCollectionItem("WarrantyFeaturesVm")) {
Html.HiddenFor(m => war.FeatureId)
<span>#Html.DisplayFor(m => war.Name)</span>
Html.HiddenFor(m => war.HasFeature)
}
}
Because BeginCollectionItem uses its context to derive the HTML names and id's, this is why you end up with "war" in the id's and names. The model binder is looking for a collection property named "WarrantyFeaturesVm", which it finds. However it is then looking for a property named "war" on the WarrantyFeaturesVm viewmodel, which it cannot find, and thus does not bind.
<input type="hidden" value="6aa20677-d367-4e2a-84f0-9fbe00deb191"
name="WarrantyFeaturesVm[68ba9241-c409-4f4b-96da-cce13b127c1e].war.FeatureId"
id="WarrantyFeaturesVm_68ba9241-c409-4f4b-96da-cce13b127c1e__war_FeatureId" .../>
In the 3rd scenario, it is similar. It is looking for the WarranyFeaturesVm collection property, which it finds. It however looks for another collection item.
<input type="hidden" value="6aa20677-d367-4e2a-84f0-9fbe00deb191"
name="WarrantyFeaturesVm[fe3fbc82-a2df-476d-a15a-dacd841df97e].WarrantyFeaturesVm[0].FeatureId"
id="WarrantyFeaturesVm_fe3fbc82-a2df-476d-a15a-dacd841df97e__WarrantyFeaturesVm_0__FeatureId" .../>
In order to bind correctly, your HTML has to look similar to your first HTML example:
<input type="hidden" value="68ba9241-c409-4f4b-96da-cce13b127c1e"
name="WarrantyFeaturesVm.index" .../>
<input type="hidden" value="6aa20677-d367-4e2a-84f0-9fbe00deb191"
name="WarrantyFeaturesVm[68ba9241-c409-4f4b-96da-cce13b127c1e].FeatureId"
id="WarrantyFeaturesVm_68ba9241-c409-4f4b-96da-cce13b127c1e__FeatureId" .../>
Like I hinted in my comment, you can achieve this by putting the BeginCollectionItem and everything it wraps into a partial view. The partial view will then receive its own context, since your helpers will use the view's #Model property with the stongly-typed helpers like so: #Html.WidgetFor(m => m.PropertyName).
On the other hand, if you really need the collection to be rendered in the outer view, I don't see any problem using normal indexing (integer-based) with a for loop and without BeginCollectionItem.
Update
I dug up this old post from Phil Haack. An excerpt:
...by introducing an extra hidden input, you can allow for arbitrary
indices. In the example below, we provide a hidden input with the
.Index suffix for each item we need to bind to the list. The name of
each of these hidden inputs are the same, so as described earlier,
this will give the model binder a nice collection of indices to look
for when binding to the list.
<form method="post" action="/Home/Create">
<input type="hidden" name="products.Index" value="cold" />
<input type="text" name="products[cold].Name" value="Beer" />
<input type="text" name="products[cold].Price" value="7.32" />
<input type="hidden" name="products.Index" value="123" />
<input type="text" name="products[123].Name" value="Chips" />
<input type="text" name="products[123].Price" value="2.23" />
<input type="hidden" name="products.Index" value="caliente" />
<input type="text" name="products[caliente].Name" value="Salsa" />
<input type="text" name="products[caliente].Price" value="1.23" />
<input type="submit" />
</form>
BeginCollectionItem uses this indexing method to make sure the model binding happens. The only difference is it uses Guids instead of ints as the indexer. But you could manually set any indexer like in Phil's example above.

Get posted values from dynamically added controls in ASP.NET MVC

I have a form in which i'm dynamically addding controls through jQuery. I need to access the values in those controls (textboxes) when posting back the form to the server. I'm sure this is a trivial problem but i just can't get my head around it.
Any help would be greatly apreciated.
When adding a multiple controls to the page, give them all the same name attribute so you can do the following in your action:
public ActionResult MyAction(string[] items)
{
// items will contain all the values in the text boxes
return View();
}
So your HTML would like like this
<input type="text" name="items" />
<input type="text" name="items" />
<input type="text" name="items" />
<input type="text" name="items" />
<input type="text" name="items" />
<input type="text" name="items" />

Lists as mvc controller method arguments?

I have 2 tables containing checkboxes in a MVC form. The names of the checkboxes are currently more or less random.
Can I name the items in any smart way so that I can retrieve them as two named lists as controller method arguments? Preferably if I could do with prefixing the names.
<div>
<input type="checkbox" name="xyz" />
<input type="checkbox" name="foo" />
<input type="checkbox" name="123" />
</div>
<div>
<input type="checkbox" name="bar" />
<input type="checkbox" name="456" />
<input type="checkbox" name="baz" />
</div>
Can I somehow get it as arguments, similar to this?
public ActionResult DoThis(BlablahViewModel model, string[] firstList, string[] secondList)
{
Currently I just check for their existence roughly like this:
Request.Form["xyz"].Contains("t")
Thanks!
You'll need to use the List Model Binding features of MVC:
http://haacked.com/archive/2008/10/23/model-binding-to-a-list.aspx
For a good guide of how this can all work together:
http://blog.stevensanderson.com/2010/01/28/editing-a-variable-length-list-aspnet-mvc-2-style/

Resources