MVC3 ModelBinding to a collection posted back with index gaps - asp.net-mvc

I have a collection of objects on my Model that I'm rendering in a View by using EditFor function, and I have an EditorTemplate which is responsible for actually rendering each object.
#Html.EditorFor(model => model.MyObjects)
This has worked well for a while now, and when you check the html, my text boxes are prefixed with the model property, followed by the index of the collection they came from.
<input class="text-box single-line" id="MyObjects_2__SomeProperty"
name="MyObjects[2].SomeProperty" type="Text" value="" />
However I've recently started using the ShowForEdit and ShowForDisplay properties in the model metadata for the collection, and in the first line of my editor template if the ShowForEdit is not true, I just skip it.
#if (!ViewData.ModelMetadata.ShowForEdit)
{
return;
}
But because these are all indexed in the html, when I try to save this collection back to the viewmodel via a postback, it fails because of a reliance on the indexing numbers. Every item in the collection after the missing index is missing from my view model when I check it's value.
In this case it's actually my first item in the collection that I'm skipping since I don't want it to be visible on the edit view, but because of this when I postback the first index in the html is 1 (instead of 0 like it normally would be), but this is a problem when you try to save the changes. This is also a problem when altering the DOM using javascript.
Has anyone else encountered a problem with the default model binder's ability to read data posted back when one or more indexes in the html represented collection are not present?
Are there model binders that handle this problem?

Ran into this issue recently and solved it by converting the List to a Dictionary<string, model> with GUIDs as the key.
#foreach (var index in Model.EmailAddresses.Keys)
{
<label asp-for="#Model.EmailAddresses[index].Email">Email</label>
<input asp-for="#Model.EmailAddresses[index].Email" type="text" />
}
This avoided having to include hidden inputs that map to the index value.

There are some very good blog posts that allow you to modelbind to a list without the need to provide zero based contiguous index. plz have a look at
http://blog.stevensanderson.com/2010/01/28/editing-a-variable-length-list-aspnet-mvc-2-style/
http://zahidadeel.blogspot.com/2011/05/master-detail-form-in-aspnet-mvc-3-ii.html
Furthermore, if you are interested in MVVM pattern and knockout js you can check this great work by steve sanderson
For more reading put "editing varibale length list mvc style" in google and it will give u a dozen useful links

Related

MVC 5 - Returning an unordered list to the server, along with a form

I have an MVC 5 / Bootstrap application. On one of the pages, I have a number of fields all bound to the model associated with the page. However, I also have a simple unordered list which always starts out empty and the user can then add items to it. They do this by entering some text into a type ahead field. Once the user finds what he/she is looking for, he/she can click a button and have it added to the unordered list. Any number of items can be added to the list. All of this works fine.
My question is how I can get the contents of the unordered list posted back to the server along with the rest of the form contents, since the unordered list isn't part of the model?
Here is one way to skin this cat:
A) Add a collection to your model (which really should be a ViewModel, and not a domain model) to hold those items
B) In your button's click handler, create a hidden input field that conforms to the ASP.Net Wire Format: http://www.hanselman.com/blog/ASPNETWireFormatForModelBindingToArraysListsCollectionsDictionaries.aspx
If you had a collection of orders, you should end up generating controls like this:
<input type="hidden" name="Orders[0].Id" value="1" />
<input type="hidden" name="Orders[1].Id" value="2" />
Note sequential ordering is important, if you start removing items, you'll need to re-sequence your name values.
There couple of ways to work it out.
If you don't want to add to model, what I would prefer to do you can:
Directly access item that were posted via Controller.Request property;
You can post this items separately via Ajax request, and handle them in different controller action.

Multiple input fields for the same model data in ASP.NET MVC

I have a very large model for user profiles. There are about 15 different new user categories (e.g., young traveler, experienced traveler, captain, helmsman, ...). Depending on the user category, some data fields of the model are not applicable and remain null in the database.
I'm trying to populate the model through one big sign-up form. JavaScript determines the user category based on the user's previous answers and shows/hides applicable/non-applicable input fields in the form.
The following problem arose:
Depending on the user category, I need to show some input fields in a slightly different order in the form. I have solved this by creating multiple input fields for the same data field in the view (e.g., #Html.TextBoxFor(x => x.Age)) and then using JavaScript to show only one depending on the user category.
My plan was to submit only the visible input fields so that the server is not confused about which input fields to use to create the model.
Unfortunately, Visual Studio doesn't quite play along: First, there's there problem of having multiple input field with the same id; I could maybe live with that. But then, validation attributes in the HTML-source are generated only for the first occurrence of each data field in the view (e.g., #Html.TextBoxFor(x => x.Age)), not for any later occurrences.
E.g., the first occurrence generates
<input data-val="true" data-val-number="The field Age must be a number."
data-val-range="The field Age must be between 10 and 99." data-val-range-max="99"
data-val-range-min="10" id="Age" name="Age" type="text" value="">
The second occurrence generates merely
<input id="Age" name="Age" type="text" value="">
How can I solve this? My thinking:
Create all input fields hidden once at the beginning of the view and use Javascript to generate at the form dynamically. Downside: A lot of work, all page structure is now in JavaScript instead of view.
Use partial views for different user categories. Downside: When the form page is opened (in the controller), I retrieve several lists from the database and put them in the model in order to populate the form DropDown input fields and RadioButton input lists in the view. Is there any way to reuse this information in the partial views so that there are not many repetitive database queries?
Any thoughts on the situation are very much appreciated. I'm somewhat new to ASP.NET MVC.
Your view model should include a property (pehaps an enum) that defines you user category,
then in your view just render the html applicable to that category
/// Some common html
#if (model.UserCategory == "captain")
{
// controls specific to captain
}
else if (model.UserCategory = "helmsman")
{
// controls specific to helmsman
}
If you need to include this html on the same page you are determining the 'previous answers', put this in a partial view and all it using ajax (passing the user category).
Another option would be to create only one control for each property and then in the javascript that hides the control, also disable the control - disabled controls don't post back so the property will remain null
$('#MyProperty').prop('disabled', true);
Honestly, I would drop the idea of building such a beast in server technology in favor of using plain JS, KnockoutJS or AngularJS which seem to be built exact for this needs.
However, answering your questions:
You have to remember, that browser submits to the server only those fileds which are enabled. If you don wan't to "confuse" server just disable unneeded fields before submit.
Model binder of asp.net mvc finds values by name attribute not by id. You can have form with couple fields with different id attributes and one name for all of them. It will be just a bit more complex to declare them in the Razor View, but it should work.

Dynamic Form in ASP.NET MVC3 Razor

I have a small problem.
I want to have dropdown list with some objects. After clicking on one I want to add it to list with textfield for naming it. I don't want to limit quantity of this fields. I want to receive in controller ID (stored in dropdown list) and name (given by user) for each selected item. How can I do it?
I was thinking about storing it in some fields as a text, and parsing in cotroller but I think it's not elegant.
EDIT.
Ok, Thansk for your help, but it's not working for me correctly.
I generate html like this:
<input type="hidden" value="96" name="Inputs[0].Key">
<input type="text" name="Inputs[0].Value">
In my controller I'm receiving this dictionary. The problem is that quantity of elements is correct, but all values are null. What is wrong here?
The best way to go about this is by using array-style model binding.
So, for each element you wish to name you create a hidden field to store the drop down value plus a text field to store the user-given name. You name them as follows:
<input type="hidden" name="element[0].Key" /><input type="text" name="name[0].Value" />
increasing the index value each time. This is easily achieved with a bit of JavaScript. You then create an action method which takes a KeyValuePair<string, string>[] as a parameter. You will then be able to parse through your values no problem with a loop or LINQ expression.
Use IEnumerable<KeyPairValue<string,string>> MySelectedItem = new List<KeyPairValue<string,string>>(); on model, and when adding it to the list, name it like an array:
MySelectedItem[1].Key, MySelectedItem[1].Value, MySelectedItem[2].Key...
(I haven't tested this, but it should work)
Edit: check out this blog post with better explanation on how to do it: http://haacked.com/archive/2008/10/23/model-binding-to-a-list.aspx

How to bind list of dynamic objects to the Model

I have a strongly typed view where one of the properties of the model that is passed in is a list of objects. I am handling dynamic binding of inputs to this list by using the BeginCollectionItem helper which works fine. However, I also want to initially have a group of inputs visible on the view that will bind to the 'first' object in the list.
I've tried just copying the inputs that BeginCollectionItem generates but that doesn't seem to work.
It generates inputs with name attributes like
invoiceItems[ef43a8f2-c6b7-4791-bf7f-6764c8e3fc9b].Description
invoiceItems[ef43a8f2-c6b7-4791-bf7f-6764c8e3fc9b].Cost
so I manually put 2 inputs on the view with the name attributes
invoiceItems[firstOne].Description
invoiceItems[firstOne].Cost
but it didn't show up in the model on the controller after submitting the form.
I can't just insert one the BeginCollectionItem way when the view loads because the initial item has to be displayed differently.
Not exactly answer to your question, but I hope it will solve your problem.
Instead of changing the name in the view, you may try changing the model. Add a boolean property IsFirst. I believe you know in the controller(or wherever the model is being instantiated) which one is first. Set the first items IsFirst to true.
I figured it out. I was on the right track with manually putting in the stuff that BeginCollectionItem did, I was just missing a part. You need another hidden input that tells the binding what the index you're using is.
So the following works perfectly
<input type="hidden" name="invoiceItems.index" autocomplete="off" value="firstOne" />
<input type="text" placeholder = "ex. Labour" id="invoiceItems_firstOne__Description" name="invoiceItems[firstOne].Description" />
<input type="text" placeholder = "ex. $15" id="invoiceItems_firstOne__Cost" name="invoiceItems[firstOne].Cost" />
I just added the top hidden input to what I had before and it's all good.

MVC4 Razor - #Html.DisplayFor not binding to model

I am trying to find me feet with MVC4 Razor and I'm stuck with this simple problem.
When I use #Html.DisplayFor the model is always sent back as NULL, but when I use #Html.TextBoxFor this model is fully populated, what am I missing?
Thanks in advance
This is a common issue that many people miss in the asp.net mvc framework. Not just the difference in the helpers such as HiddenFor, DisplayFor, TextBoxFor - but how exactly the framework sets up automatically collecting and validating these inputs. The magic is all done with HTML5's data-* attributes. You will notice when looking at the input tag generated that there are going to be some extra properties in the form of data-val, data-val-required, and perhaps some additional data properties for types, for example numerics would be data-val-number.
These data attributes allow the jQuery extension jquery.validate.unobtrusive.js to parse the DOM and then decide which fields to validate or generate error messages.
The actual collection of posted data is reflected from the name property. This is what should map up to the model that is in the c# or vb [HttpPost] method.
Use HiddenFor when you want to provide posted data that the user does not need to be aware of.
Use DisplayFor when you want to show records but not allow them to be editted.
Use TextBoxFor when you want to allow user input or allow the user to edit a field.
EDIT
"the purpose of this view is to enable the user to view the data before submitting it to the database. Any ideas how I can achieve this?"
You could accomplish this with a duo of HiddenFor and DisplayFor. Use HiddenFor to have the values ready to be posted, and DisplayFor to show those values.
DisplayFor will not do the Model binding. TextBoxFor will do because it creates a input element in the form and the form can handle it when it is being posted. If you want to get some data in the HttpPost action and you dont want to use the TextBoxFor, you can keep that pirticulare model proeprty in a hidden variable inside the form using the HiddenFor HTML helper method like this.
#using(Html.BeginForm())
{
<p>The Type Name is</p> #Html.DisplayFor(x=>x.TypeName)
#Html.HiddenFor(x=>x.TypeName)
<input type="submit" value="Save" />
}
Use both DisplayFor and HiddenFor. DisplayFor simply displays the text and is not an input field, thus, it is not posted back. HiddenFor actually creates <input type="hidden" value="xxxx"/>
DisplayFor builds out a HTML label, not an input. Labels are not POSTed to the server, but inputs are.
I know this is a bit of an old question but you can roll your own, custom combined display control as shown below. This renders the model value followed by a hidden field for that value
#Html.DisplayExFor(model => Model.ItemCode)
Simply use what the framework already has in place
public static MvcHtmlString DisplayExFor<TModel, TProperty>(this HtmlHelper<TModel> htmlHelper, Expression<Func<TModel, TProperty>> ex)
{
var sb = new StringBuilder();
sb.Append(htmlHelper.DisplayFor(ex));
sb.Append(htmlHelper.HiddenFor(ex));
return MvcHtmlString.Create(sb.ToString());
}
Do you mean during a form post? If you use DisplayFor, this creates a element which does not contain any form values. Typically you use these in conjunction with each other to create a label for your textbox, then using the Html.TextBoxFor to allow users to modify the data element.
Example:
#Html.DisplayFor(x=>x.Item)
#Html.TextBoxFor(x=>x.Item)
Will Render
Item <a text input field following>
Or in HTML
<label for="Item">Item</label><input type="Text" id="Item" name="Item"/>

Resources