How does the model binder figure out which user input to bind? - asp.net-mvc

If I use the MVC designer to create a strongly typed view, it will automatically write code to model bind UI input with model properties. But assume my view is not strongly typed with the model. Assume my model has 5 properties that can be bound, and the UI is sending 10 field inputs.
How does MVC cleverly figure out which field should be bound with which property?

Your inputs should have a name attribute, and those names should match what's in your MVC post action's model properties. Say for example:
Your Model:
public class Person
{
public string Address { get; set; }
}
HTML:
<input type="text" name="Address" /> or #Html.TextBox("Address")
MVC Action:
public ActionResult SubmitPerson(Person p)
{
//p.Address will have the value from the input with the name="Address"
}

Related

ASP.Net MVC Model binding for empty input string creates empty model object

I've been trying to get my head around how to solve this problem in the cleanest way.
How to get the model binder to NOT create empty objects when submitting empty strings for complex types?
Say I've got this model:
public class CreateCaseModel
{
[Required]
public UserModel Contact { get; set; }
public UserModel Assigned {get; set; }
}
and the user-model:
public class UserModel {
[Required]
public string Id {get;set;}
public string Name {get;set;
}
And in the view i have these inputs:
<input type="hidden" name="Assigned.Id" value="" />
<input type="hidden" name="Contact.Id" value="userId1" />
I post this to a controller something like:
public ActionResult Create(CreateCaseModel model){
if (!ModelState.IsValid)
{
//handle error
}else {
//create
}
}
In the controller now my model.Contact and model.Assigned will never be null.
Only the model.Contact will however contain anything (the Idwill be set)
At this point the modelstate will also be invalid due to the fact that the UserModel.Id field is marked as required.
What I want to happen is for the binder to not create the UserModel object at all if the input is empty.
The entire reason for me needing this is that I'm using the UserModel.Name field elsewhere in the view, and I'd like to group the two fields together. And I want to use the ModelState.IsValidcheck for serverside validation.
I could of course go with extracting the Id and Name fields of each userobjects and put them directly in the CreateCaseModel, but I want to explore if what I describe is even possible.
Grateful for any tips or pointers!
How to get the model binder to NOT create empty objects when submitting empty strings for complex types?
Write your own custom ModelBinder. The default model binder will always attempt to create complex types for recursive modelbinding.

MVC - Manually set form submit value from ViewData

In a web app I'm creating, I have a list of clients. And each client has a sub-list of projects.
This is my Client model:
Public Class ppClient
<Key()>
Public Property ClientID As Integer
Public Property Name As String
Public Overridable Property Projects As ICollection(Of ppProject)
End Class
And here is my Project model:
Public Class ppProject
<Key()>
Public Property ProjectID As Integer
Public Property ClientID As Integer
Public Property ProjectTitle As String
Public Overridable Property Client As ppClient
End Class
The problem I am encountering is that I am trying to manually add a form value when a new project is being created. I have the ClientID saved in ViewData, and so I don't want to ask the user for it. This is my view and form submission:
ModelType MVCnewDB.ppProject
#Using Html.BeginForm()
#Html.ValidationSummary(True)
#* Needs to submit ClientID *#
Html.EditorFor(Function(model) model.ProjectTitle)
Html.ValidationMessageFor(Function(model) model.ProjectTitle)
#<input type="submit" value="Create Project" />
End Using
<div>
#Html.ActionLink("Back", "Index")
</div>
So how do I get the form to submit ClientID as my ViewData value? Can I make a call to Html.HiddenFor and stuff a value into it or something?
Any help would be appreciated.
Ah okay - I've found what I was looking for. I was looking for #Html.Hidden("ClientID", ViewData("key")) which submits the given ViewData value to the form as the ClientID field.
You just need to add a hidden field. I don't know how you would do it in VB, but in C# it would be like this:
#Html.HiddenFor(x => ViewBag.ClientID)
or
#Html.HiddenFor(x => ViewData["ClientID"])
This is how I interpret your question:
You are on a page with a model bound to your ppProject class
You have a ClientID somewhere and you want to know where to put it on the form so that...
You want the form submission to include the ClientID so you can relate the ppProject to a client.
Is this correct?
If so, you have a couple options. If you've included your ClientID in a ViewBag, you can use a hidden field to store the value and submit it. On your action, you can actually specify more than one parameter. The first can be the ppProject class you want the model binder to bind your fields to, and another string parameter with the same name as the hidden field containing your ClientID value. As long as the parameter matches the hidden field name exactly, the model binder will match the two up and you'll get the ClientID.
(In C# because my VB.NET skills are too rusty for it to make sense:)
public ActionResult NewProject(ppProject projectInfo, string ClientID)
{
...
}
The above assumes you keep your view as-is but you add something like this:
<input type="hidden" name="ClientID" value="#ViewBag.ClientID" />
Or possibly (just a guess!):
Html.HiddenFor(Function(model) ViewBag.ClientID)
This assumes you've matched the ViewBag property name to the parameter name on your ActionResult in your controller. (Just replace the ViewBag with whatever you're using to store the ClientID value.)

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

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.

mvc.net how to edit member list variables

Given the following model which has a name, url, and an arbitrary list of keywords (I want the user to add a series of keywords) ...
public class Picture
{
public Picture()
{
keywords = new List<string>();
}
public string name {get;set:}
public string url {get;set;}
public List<string> keywords{get;set;}
}
... and the following action in my controller ...
[HttpPost]
public ActionResult Edit(FormCollection fc)
{
if (ModelState.IsValid)
{
// do stuff
}
return View(ModelManager.Picture);
}
In the FormCollection I have the following field
fc["keywords"] = "keyword1,keyword2,keyword3"
And I then create a Picture object based on the form collection.
However, I would prefer to use a strongly-typed action such as
[HttpPost]
public ActionResult Edit(Picture p)
But in this approach, my p.keywords property is always empty. Is there some way to help the framework recreate my p.keywords property before it hits my controller's action method?
I thought an Editor Template might work here, but I don't think there is a way to model bind a nested IEnumerable view model member. Your fastest bet may be handling it directly with FormCollection and some string parsing magic. Otherwise, if you have to strongly-type this, maybe a custom model binder like this could help if you can control your keyword element id's:
public class PictureKeywordBinder : IModelBinder
{
public object GetValue(ControllerContext controllerContext,
string modelName, Type modelType,
ModelStateDictionary modelState)
{
Picture picture = new Picture();
//set name, url, other paramaters here
foreach(var item in Request.Form.Keys)
{
if (item.StartsWith("keyword"))
{
picture.keywords.Add(Request.Form[item]);
}
}
//add any errors to model here
return picture;
}
}
Maybe the keyword id's could be setup in a partial view passed the sub model from your parent view:
<% Html.RenderPartial("PictureKeywords", Model.keywords);
Are your keywords seperate text boxes? If so, create an inputs like this and they will be populated by the model binder.
<input name="keywords[0]" type="text">
<input name="keywords[1]" type="text">
<input name="keywords[2]" type="text">
The way I got around this, is to use a hidden input to store the csv string of items, in your case, keywords.
I then hooked into the form submit event (using jQuery) and appended the inputs to form the csv string, which is then stored in the hidden input. This hidden input was strongly typed to a property on my model.
It's a little clunky, but if you have a dynamic number of possible keywords then this works quite well (except if JS is disabled of course)
In what way you are expecting the user to add more keywords? In the form comma separated values(CSV) or by dynamically adding textboxes?
Based on your requirement, i have two solutions with me.

Resources