ASP.NET MVC - handling multiple objects in one form - asp.net-mvc

I have a scenario I'm stuck on - I have a domain object that has a collection of objects attached to it. Something like this:
public class Person
{
public string Name { get; set; }
public IList<PhoneNumber> PhoneNumbers {get; set; }
public IList<Address> Addresses { get; set; }
}
The UI the client wants has a single input form for adding and editing. A user could enter 0 to many phones/addresses for each person. How do I handle posting the collection of values back to the controller?
I can think of a couple of approaches, but they all seem brute-force and not very elegant. Is there a best practice for handling this sort of problem?

It is supported by the framework by using a special "form layout". Phil Haack has an article on this, check this out
Edit Scott Hanselman (http://www.hanselman.com/blog/ASPNETWireFormatForModelBindingToArraysListsCollectionsDictionaries.aspx) just posted an update on this. Furthermore in RC1 it seems (ran into this mysel last night) that the indexes needs to be 0-based and steadily increasing (at least if you are "binding" against IList)
Edit2 Link didn't seem to work

In the past I've used the railsy convention for this.
<input name="Parent[childObjectType][serial_number]" type="textbox" value="" />
So for your situation this
<input name="Person[PhoneNumber][1]" type="TextBox" value="555-1212" />
<input name="Person[PhoneNumber][2]" type="TextBox" value="555-555-1212" />
and you can increment the serial number and replicate the element in javascript to get multiple, and use a formCollection in your action which will give you a way to get these elements.
The default from the mvc team is to used a ModelBinder, and dot syntax for this operation.
However this makes jquery and other javascript frameworks baulk.

I have been starting to use json and jQuery to post complex types to a controller action using JSON.NET and a JsonFilter on the server side which automatically takes your json object and serializes it to the equivalent C# type. I have found this to be a very clean solution and easier to test. You can check out this post with the sample code to download to get you started on how to do it. It is pretty straight forward.
http://blogger.forgottenskies.com/?p=252

In mentioned Hanselman's post he writes that you don't need indexes, you just have the same name for input boxes and have a array parameter in action and it works.

I've done this several times and have used Phil Haack's post as a guide. This last time I figured out how to make it work with Editor Templates. Posting this here in hopes it helps someone else down the road (or me if I ever forget).
My example here is using Addresses (using only one property out for brevity).
AddressViewModel.cs
public class AddressViewModel
{
public string Address1 { get; set; }
}
AddressViewModels.cs
public class AddressViewModels : List<AddressViewModel>
{
}
PersonViewModel.cs
public class PersonViewModel
{
public AddressViewModels HomeAddresses { get; set; }
}
AddressViewModel.cshtml (Editor Template)
#model AddressViewModel
<div>
#Html.LabelFor(m => m.Address1)
#Html.TextBoxFor(m => m.Address1)
</div>
AddressViewModels.cshtml (Editor Template)
#model AddressViewModels
#for (var i = 0; i < Model.Count; i++)
{
#Html.Hidden("Index", i)
#Html.EditorFor(m => Model[i])
}
Person.cshtml
#model Person
<h3>Edit Addresses</h3>
#Html.EditorFor(m => m.HomeAddresses)
<button type="submit">Save</button>
Rendered HTML
<input id="HomeAddresses_Index" name="HomeAddresses.Index" type="hidden" value="0">
<label for="HomeAddresses_1__Address1">Address 1</label>
<input id="HomeAddresses_1__Address1" name="HomeAddresses[1].Address1" type="text" value="P.O.Box 123" >
<button type="submit">Save</button>

Related

ASP.NET MVC: unable to post array of values

I'm working at an ASP.NET MVC website using C#.
On a page I try to send back a complete model to the controller. In this model most inputs are unique variables, and there is an array.
In the model, the array is defined like this:
public class SimulazioneModelComplete
...
public SoaValueModel[] GareSoaSec { get; set; }
The razor / html code (very simplified!) is like this
#model SimulazioneModelComplete
#using (Html.BeginForm("CreaEsito", "Simulazioni", FormMethod.Post, new { id = "CreaEsito", name = "CreaEsito", enctype = "multipart/form-data" }))
{
...
#foreach (SoaModel soa in ViewBag.Categorie)
<input type="checkbox" name="GareSoaSec[#counter].IdSoa" id="GareSoaSec[#counter].IdSoa" value="#soa.id" />
...
}
The user posts data clicking a button:
<button onclick="Save()">SAVE</button>
The onclick javascript is like this:
function Save()
{
$.post("/Abbonamenti/Simulazioni/CreaEsito", $("CreaEsito").serialize(), function (data) { });
};
The CreaEsito method is like this:
[HttpPost]
[AuthorizedOnly(Roles = "Administrator, Agent, Esiti")]
public ActionResult CreaEsito(SimulazioneModelComplete model, SoaValueModel[] GareSoaSec)
I try in a lot of ways, but I never have a complete model, with both the simple variables and the array of objects. It seems like the "link" made by "name" doesn't work properly, and I don't understand why.
It should work well (I do the same in other pages, and everything works well), but this time it doesn't work. When I look the model sent back to the controller, the unique variables are ok, the array is empty, length is zero.
I've lost all Friday and Saturday, now is 10PM and I'm still fighting, without any solution.
Can someone help?

MVC client side validation for a Model that has a list of models

I have this model that has a list of another model. Then in my view, I have my form that fills a couple of fields for my main Model. But I want this form to also be able to add X models of the other type and to be all wired up and I'm wondering how to properly do this.
So here are my two models:
public class MyMainModel
{
public int MyMainId { get; set; }
[Required(ErrorMessage = "Groovy name required")]
[Display(Name = "MyMainModel's groovy name:")]
public string Name { get; set; }
public List<MySubModel> MySubModels { get; set; }
}
public class MySubModel
{
public int MySubId { get; set; }
[Required(ErrorMessage = "Cool name required")]
[Display(Name = "MySubModel's cool name:")]
public string Name { get; set; }
}
When I hit my controller for my "create" view, I go through this action:
public ActionResult SomePageAboutCreating()
{
// [...] Some other stuff
return View(new MyMainModel());
}
Now this sends to my strongly typed view:
#model myFunProject.WebModels.MyMainModel
<div>
<form id="my-create-form" onsubmit="CreateMyMainModel(this); return false;">
#Html.AntiForgeryToken()
#Html.ValidationSummary()
<div class="result" style="background-color: silver;">This is the operation result box</div>
<img class="loading" src="/Images/ajax-loader.gif" alt="Loading..." width="16" height="16" style="display: none;" />
<section>
#Html.LabelFor(m => m.Name)
#Html.TextBoxFor(m => m.Name)
#Html.ValidationMessageFor(m => m.Name)
</section>
<!-- Here begins the fields for my list of "MySubModel" -->
#Html.EditorFor(x => x.MySubModels)
<!-- Here I'm thinking about a javascript that will add the previous fields X times so that the user can create "MyMainModel" as well as x times "MySubModel"... -->
<input type="submit" class="btn-send" id="my-create-form-submit" value="Send" />
</form>
</div>
So I think I have to use the EditorTemplates here... So I setup in my /Views/EditorTemplates/MySubModels.cshtml (Named against my "MyMainModel"'s property) and then when I write my form in there, I'm confused...
#model myFunProject.WebModels.MyMainModel
#*<section>
#Html.LabelFor(m => m.Name)
#Html.TextBoxFor(m => m.Name)
#Html.ValidationMessageFor(m => m.Name)
</section>*#
So here I'm not sure what to put in here... I want my Name property there to be the one of "MySubModel". And as the user sees this form, say for example he'll go through this scenario:
Enters a name for the "MyMainModel".
Goes to the other name box and fills in a name of the first instance of "MySubModel.
Then he would click a special button that would manipulate dom to append another MySubModel.Name field.
He would write in the second "MySubModel" name.
He would click submit.
The ajax call I put in there I'm ok doing the wiring up, but my confusion comes with the code I have to write for the editor template and then I'm kind of also wondering about how I'm going to create a new field (for that second "MySubModel" for example...).
Any help would be appreciated, I've gone through many articles about subjects close to this, but have not found this case yet. Thanks!
EDIT:
I'll add the action (an overly simplified version hehe) that is called by my ajax when the form is submitted.
public ActionResult CreateMyMainModel(MyMainModel myMainModel) {
// [...] Do stuff like save to database...
// var aGroovyNAme = myMainModel.Name;
foreach(var mySubModel in myMainModel.MySubModels) {
// Here I would have the sub models available to manipulate...
// var aCoolName = mySubModel.Name;
}
return Content("ok");
}
I've gone through many articles about subjects close to this, but have not found this case yet.
I would really recommend you reading the editing a variable length list article from Steven Sanderson which illustrates a very nice approach to handle this scenario. He presents a custom Html.BeginCollectionItem helper which could be used to generate non-sequential indexes (guids) for the input field names and thus allowing for easily removing elements dynamically without leaving holes in the indexes. When the user decides to add another item, an AJAX call is made to a controller action which simply returns an empty template (partial).
You could also do this purely on the client side with javascript only. Steven Sanderson illustrated this approach using knockoutjs in this similar article.
Those two articles are really the best approach in terms of dynamically editing a variable length list of items in ASP.NET MVC. Reading them will really be helpful for better understanding some core concepts in model binding in ASP.NET MVC.
I had a similar issue on a project where I wanted to validate some fields on some occasions
and not on others (ie on save don't validate but on submit validate everything.
I ended up doing everything manually in javascript and posting back a json object.
On reflection I would rather have manipulated the validation javascript file(MicrosoftMVCValidation.js).
For model binding issues, I recommended looking at custom model binding.
I found using EditorTemplates a bit fiddly especially with partial views.
I find asp.net mvc 3 a bit weak in model binding. I hoped that some of the issues would be fixed in mvc 4 but from what I have looked at so far MVC4 is primarily an upgrade for creating windows phone applications.
Here's an example of a custom model binder for decimal properties in the model.
You can use the same logic with your own custom models.
I found there are occasions when I want to bind a model with a collection of entities on large pages, rather than just binding a basic collection of properties for example.
public class DecimalModelBinder : DefaultModelBinder
{
public override object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
var valueProviderResult = bindingContext.ValueProvider.GetValue(bindingContext.ModelName);
//return valueProviderResult == null ? base.BindModel(controllerContext, bindingContext) : Convert.ToDecimal(valueProviderResult.AttemptedValue);
if (valueProviderResult == null)
return base.BindModel(controllerContext, bindingContext);
else if (valueProviderResult.AttemptedValue == "")
return base.BindModel(controllerContext, bindingContext);
else
return Convert.ToDecimal(valueProviderResult.AttemptedValue);
}
}
I've recently answered pretty much this exact question on another thread:
ASP.Net MVC4 bind a "create view" to a model that contains List

How to persist a complex object in the View while updating just a small portion

I have a feeling this could be a basic question!
I have a complex object ie a document object which contains properties that are lists of other objects. It has been created by deserializing some XML.
I would like to pass the entire Model to the View
Return ViewResult(MyDoc)
In the View some properties are edited. Control is then returned back to the Post Controller:
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Index(Document myDoc)
"myDoc" now just represents my Form's fields. I suspect this is ModelBinding at work. So I guess I need to persist my complex object in a hidden field(example would be great) or as a session object. However I am a bit confused how my updated field gets merged back into the persisted object(hidden or session).
Is storing that large object in the view entirely necessary?
The View seems to have no control over that content, so you probably don't want to send all that data to the View. It seems you are applying a mind-set based on ViewState, which doesn't really fit very well with MVC, IMHO.
Communication between the View and the Controller is done through ViewModels, and there's usually no need to store any large serialized data and making that go back and forth when interacting with the server.
Can't you create a ViewModel that represents only the useful data for the view (the fields), and get that data back in your Action by receiving the ViewModel during POST, and afterwards synchronize the information you obtained from the View with what you load from your XML only at that time?
You are right, this is model binding at work.
The binding occurs almost automatically when you use HtmlHelpers such as :
#Html.TextboxFor(model => model.PropertyName)
This line actually creates something a little bit like this :
<input type="textbox" id="Modelobject_PropertyName" name="ModelObject.PropertyName" />
Then when you submit your form, the DefaultModelBinder is able to deserialize the POST value and create and object of the given type (at least it will try), if it can't find a corresponding entry, the property will be null and if an entry have no corresponding property, it will be ignored (unless you have other parameters).
You can read this article it's a little bit old, but it's still pretty accurate.
As an exemple:
Let's say you have a simple object :
public class IndexModel
{
public string MyProperty { get; set; }
public bool MyCheckbox { get; set; }
}
A simple controler :
public class TestingController : Controller
{
[OutputCache(Duration=0, NoStore = true)]
public ActionResult Index()
{
return View(new IndexModel { MyProperty = "hi" });
}
[HttpPost]
[OutputCache(Duration=0, NoStore = true)]
public ActionResult Index(IndexModel model)
{
model.MyProperty += "!";
ModelState.Clear();
return View(model);
}
}
And a simple view :
#model MvcApp.Models.IndexModel
#using (Html.BeginForm())
{
<div>
#Html.LabelFor(model => model.MyProperty)<p />
#Html.TextBoxFor(model => model.MyProperty)
</div>
<div>
#Html.LabelFor(model => model.MyCheckbox)<p />
#Html.CheckBoxFor(model => model.MyCheckbox)
</div>
<input type="submit" />
}
You will see, when you submit the form, that the model is recreated completely.
If you don't want to display the actual value of a property, but still need it persisted:
#Html.HiddenFor(model => model.MyProperty)
This will generate a hidden field that will be posted back and, therefor, persisted.
Happy coding !

Binding contained collections from Edit Action #model view?

Can somebody please provide code to allow [] to be used so as to save contained collection within a #model?
Edit View:
#model MVC3.Models.A
// I need to save collection values but can't use [] here to setup model binding.
#Html.EditorFor(model => model.Bs[0].Val)
Models:
public class A
{
public A()
{
this.Bs = new HashSet<B>();
}
public int Name { get; set; }
public virtual ICollection<B> Bs { get; set; }
}
public class B
{
public int Val { get; set; }
public virtual A A { get; set; }
}
The problem is that your property is an ICollection<T>, which doesn't provide indexed access to elements.
Change it to an IList<T> or just a List<T> and you can write your view code as it is in your first example.
If you can't change the property type for some reason, then you'll need to create a specialized view model for your particular view and then map it to the A model in the controller POST action.
Alternatively, it is possible (albeit a little awkward) to bind to simple collection types directly as long as you follow the proper HTML conventions, which means using multiple input elements with the same name, as described by Phil Haack, i.e.:
<input type="text" name="ints" value="1" />
<input type="text" name="ints" value="4" />
<input type="text" name="ints" value="2" />
<input type="text" name="ints" value="8" />
But this only works for simple types, i.e. primitives or strings, not complex types like whatever B is in this context.
One final comment: If you're using editor templates, you can usually just bind to the entire collection and the MVC framework will figure out how to put it back together, as in just:
#Html.EditorFor(m => m.Bs);
Of course this gives you no control over what goes between the editor templates for each item, so if you're trying to generate <li> elements for them or something like that, then you'll have to embed it directly into the editor template which possibly means creating a custom editor template specifically for this collection binding and using the corresponding override of EditorFor (the one that takes a template name).
It's a bit of a pain, but could be less work than trying to switch to a parallel-model system, if you've been sharing classes between your UI and data layers up 'til now.

Custom Model Binder for Complex composite objects HELP

I am trying to write a custom model binder but I'm having great difficulty trying to figure how to bind complex composite objects.
this is the class I'm trying to bind to:
public class Fund
{
public int Id { get; set; }
public string Name { get; set; }
public List<FundAllocation> FundAllocations { get; set; }
}
and this is how my attempt at writing the custom binder looks like:
public class FundModelBinder : IModelBinder
{
public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
throw new NotImplementedException();
}
public object GetValue(ControllerContext controllerContext, string modelName, Type modelType, ModelStateDictionary modelState)
{
var fund = new Fund();
fund.Id = int.Parse(controllerContext.HttpContext.Request.Form["Id"]);
fund.Name = controllerContext.HttpContext.Request.Form["Name"];
//i don't know how to bind to the list property :(
fund.FundItems[0].Catalogue.Id = controllerContext.HttpContext.Request.Form["FundItem.Catalogue.Id"];
return fund;
}
}
Any Ideas
thanks
Tony
Do you really need to implement a custom ModelBinder here? The default binder may do what you need (as it can populate collections and complex objects):
Lets say your controller action looks like this:
public ActionResult SomeAction(Fund fund)
{
//do some stuff
return View();
}
And you html contains this:
<input type="text" name="fund.Id" value="1" />
<input type="text" name="fund.Name" value="SomeName" />
<input type="text" name="fund.FundAllocations.Index" value="0" />
<input type="text" name="fund.FundAllocations[0].SomeProperty" value="abc" />
<input type="text" name="fund.FundAllocations.Index" value="1" />
<input type="text" name="fund.FundAllocations[1].SomeProperty" value="xyz" />
The default model binder should initialise your fund object with 2 items in the FundAllocations List (I don't know what your FundAllocation class looks like, so I made up a single property "SomeProperty"). Just be sure to include those "fund.FundAllocations.Index" elements (which the default binder looks at for it's own use), that got me when I tried to get this working).
I have been spending too much on this exact same thing lately!
Without seeing your HTML form, I am guessing that it is just returning the results of selection from a multi select list or something? If so, your form is just returning a bunch of integers rather than returning your hydrated FundAllocations object. If you want to do that then, in your custom ModelBinder, you're going to need to do your own lookup and hydrate the object yourself.
Something like:
fund.FundAllocations =
repository.Where(f =>
controllerContext.HttpContext.Request.Form["FundItem.Catalogue.Id"].Contains(f.Id.ToString());
Of course, my LINQ is only for example and you obviously can retrieve the data anyway that you want. Incidentally, and I know that it doesn't answer your question but after much faffing around I have decided that for complex objects, I am best to use a ViewModel and have the default ModelBinder bind to that and then, if I need to, hydrate the model which represents my entity. There are a number of issues that I ran into which made this the best choice, I won't bore you with them now but am happy to extrapolate if you wish.
The latest Herding Code podcast is a great discussion of this as are K Scott Allen's Putting the M in MVC blog posts.

Resources