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
I am a long time backend .Net developer who is new to web application development. I am using MVC 4, Razor, EF 5, and I have a basic understanding on how to make a routine DB driven MVC 4 site with these tools.
I need to create a custom form capability for a workflow scenario. I have the entire code-first schema designed and classes for different formfield types, and formvalue type.
The model that will be passed to a view will be a form class with a list of formvalues, which contain form field specifications. So the view will have to iterate through the form fields and dynamically choose what editors to use and so on.
My problem is that this prevents me from using any data annotation for client side validation. And every place I find advice on custom validation assumes (not surprisingly) that all the fields are members of a class. Whereas, in this case, all the fields are on a list.
My goal is to end up with validation messages on each field and a validation summary as you would normally have if the view was simply bound to an annotated class.
Any suggestions on how to approach this? Thanks in advance.
Imagine view logic that has the following:
#foreach (var fieldvalue in Model.FormFieldValues) {
// Logic that chooses the appropriate editor based on typeof(fieldvalue.FormField)
// Binds the editor to fieldvalue.Value
// Validation specs are contained in the Formfield obtained by fieldValue.FormField
// And my problem is setting up the validation without the aid of the normal method
// of data annotation or class level custom validation.
}
and the fieldvalue class looks like this:
public class FormFieldValue : EboEntity
{
public string Value { get; set; }
[Required]
public int FormFieldId { get; set; }
[Required]
public virtual FormField FormField { get; set; }
[Required]
public int FormId { get; set; }
[Required]
public virtual Form Form { get; set; }
}
And imagine that the FormField object has fields such as Required, Maxlength, and so on, depending on the kind of field it is (i. e. FormFieldText would be a subclass of FormField)
ANSWER:
Ok, you can tell I am new at MVC.
The answer to my question is that the specific editors take htmlAttributes which can control validation. So in the case where one of my formfields is a required textfield of stringlenth(10), I can invoke the editor as follows:
<div class="editor-field">
#Html.TextBoxFor(model => model.NoTexty, new {
required = true,
maxlength = 10,
placeholder = "Texty"})
#Html.ValidationMessageFor(model => model.NoTexty)
</div>
but in my case the htmlAddtributes won't be hard coded, but rather they will come from the fields in the formvalue.FormField object.
Sorry if I wasted anyone's time on this. But I will leave this here for any other newbies like me.
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 !
I’m using the a service layer with a ModelStateWrapper as shown here.
What’s he proper way to control the error keys to use when validating in the service layer so they correspond to the input names in my views?
e.g. I have the following ViewModel for a customer:
public class CustomerViewModel
{
public string Name { get; set; }
/*…*/
}
And the following ViewModel for some use case’s View (the user can create two customers on the fly in this use case by entering their names):
public class SomeOtherUseCaseViewModel
{
public CustomerViewModel BuyingCustomer { get; set; }
public CustomerViewModel SellingCustomer { get; set; }
/*… */
}
Which I use in the View like this:
<!-- … -->
<%: Html.EditorFor(model => model.BuyingCustomer.Name) %>
<%: Html.ValidationMessageFor(model => model.BuyingCustomer.Name) %>
<!-- … -->
<%: Html.EditorFor(model => model.SellingCustomer.Name) %>
<%: Html.ValidationMessageFor(model => model.SellingCustomer.Name) %>
In my customer service I’ve the following validation method which gets called when creating the customer (note that this is a different Customer class, this is a domain class):
public bool ValidateCustomer(Customer customer)
{
/* Check if there is another customer with the same name */
if (/*…*/)
{
_validationDictionary.addError(“Name”, “There is another customer with the same name”); //the _validationDictionary holds the ModelStateWrapper
return false;
}
}
Do you see my problem?
Depending on the View, the error should be added with the key “BuyingCustomer.Name” or “SellingCustomer.Name” (or eventually there will be two errors, one with each key) instead of just “Name”; otherwise the framework won’t properly highlight the fields and show the errors.
What is the proper way to solve this situation neatly? I don't want to pass the "error key prefix" to the service layer, because that's a UI concern and not a service layer concern, (right?).
I'd take it later you query the service for any validation errors?
Either query service immediately after validation then reset the errors, or have the method call the return validation errors. If you do either of those you can prefix them with( prefix(this validationDictionaryObject, string pfx) ) an extension method for the dictionary, the api wouldn't be too bad that way. A second extension method (HasErrors()) for the dictionary would do the same as your boolean return
For the problem you are having alone, i tend to eschew querying the service for errors.
It's convenient to have the service collect them all, but then you are adding error state to a service that doesn't really belong, its not that the service is broken, but one model it checked was. If you need to call that service again (like you are doing) it has old state. You have to manage that. Either that or return errors because
after all, its a validation method. Have it return validation results. :)
var errs = service.ValidateCustomer(sellinCustomer);
if (errs.HasErrors()){
errs.Prefix("SellingCustomer");
//add to model state dictionary for view here.
}
I have the following code in my view:
<%= Html.ListBoxFor(c => c.Project.Categories,
new MultiSelectList(Model.Categories, "Id", "Name", new List<int> { 1, 2 }))%>
<%= Html.ListBox("MultiSelectList",
new MultiSelectList(Model.Categories, "Id", "Name", new List<int> { 1, 2 }))%>
The only difference is that the first helper is strongly typed (ListBoxFor), and it fails to show the selected items (1,2), even though the items appear in the list, etc. The simpler ListBox is working as expected.
I'm obviously missing something here. I can use the second approach, but this is really bugging me and I'd like to figure it out.
For reference, my model is:
public class ProjectEditModel
{
public Project Project { get; set; }
public IEnumerable<Project> Projects { get; set; }
public IEnumerable<Client> Clients { get; set; }
public IEnumerable<Category> Categories { get; set; }
public IEnumerable<Tag> Tags { get; set; }
public ProjectSlide SelectedSlide { get; set; }
}
Update
I just changed the ListBox name to Project.Categories (matching my model) and it now FAILS to select the item.
<%= Html.ListBox("Project.Categories",
new MultiSelectList(Model.Categories, "Id", "Name", new List<int> { 1, 2 }))%>
I'm obviously not understanding the magic that is happening here.
Update 2
Ok, this is purely naming, for example, this works...
<%= Html.ListBox("Project_Tags",
new MultiSelectList(Model.Tags, "Id", "Name", Model.Project.Tags.Select(t => t.Id)))%>
...because the field name is Project_Tags, not Project.Tags, in fact, anything other than Tags or Project.Tags will work. I don't get why this would cause a problem (other than that it matches the entity name), and I'm not good enough at this to be able to dig in and find out.
I've stumbled across this problem myself, finally I realized that the problem was a naming convention.
You cannot name the ViewBag or ViewData poperty containing the SelectList or MultiSelectList to the same name your property model containing the selected items. At least not if you're using the ListBoxFor or DropDownListFor helper.
Here's an example:
public class Person
{
public List<int> Cars { get; set; }
}
[HttpGet]
public ActionResult Create()
{
//wont work
ViewBag.Cars = new SelectList(carsList, "CarId", "Name");
//will work due to different name than the property.
ViewBag.CarsList = new SelectList(carsList, "CarId", "Name");
return View();
}
//View
#Html.ListBoxFor(model => model.Cars, ViewBag.CarsList as SelectList)
I'm sure theres plenty of other ways doing this, but it solved my problem, hope it will help someone!
I have also been stuck with this exact same issue and encountered the same problem with ListBox and ListBoxFor.
No matter what I do, I cannot get selections to occur on the ListBoxFor. If I change to the ListBox and name it something OTHER than the property name of the data I am binding to, selections occur.
But then because I'm not using ListBoxFor and the data is sitting inside a model class (Model.Departments) for example, I don't get model binding on the way back to my controller and hence the property is null.
EDIT I found a solution posted by someone else here;
Challenges with selecting values in ListBoxFor
Also, you can try to clear ModelState for c.Project.Categories in the controller:
[HttpPost]
public ActionResult Index(ModelType model)
{
ModelState.Remove("Project.Categories");
return View("Index", model);
}
And use the next construction:
<%= Html.ListBoxFor(c => c.Project.Categories,
new MultiSelectList(Model.Categories, "Id", "Name"))%>
Where c.Project.Categories is IEnumerable<int>.
Sorry for my english. Good luck!
The correct answer is that it doesn't work very well. As such I read the MVC code. What you need to do is implement IConvertible and also create a TypeConverter.
So, in my instance I had a Country class, such that people could choose from a list of countries. No joy in selecting it. I was expecting an object equals comparison on the selectedItems against the listitems but no, that's not how it works. Despite the fact that MultiListItem works and correctly gets the selected items, the moment it is bound to your model it's all based on checking that the string represnetation of your object instance matches the string "value" (or name if that is missing) in the list of items in the SelectItemList.
So, implement IConvertible, return the string value from ToString which would match the value in the SelectItemList. e.g in my case CountryCode was serialized into the SelectItem Value property , so in ToString IConvertible I returned CountryCode. Now it all selects correctly.
I will point out the TypeConverter is used on the way in. This time its the inverse. That Countrycode comes back in and "EN" needs converting into Country class instance. That's where the TypeConverter came in. It's also about the time I realised how difficult this approach is to use.
p.s so on your Category class you need to implement IConvertible. If its from the entity framework as my company is then you'll need to use the partial class to implement IConvertible and implement ToString and decorate it with a TypeConverter you wrote too.
Although this isn't an answer to your main question, it is worth noting that when MVC generates names it will turn something like Project.Tags into Project_Tags, replacing periods with underscores.
The reason that it does this is because a period in an element ID would look like an element named Project with a class of Tags to CSS. Clearly a bad thing, hence the translation to underscores to keep behaviour predictable.
In your first example,
<%= Html.ListBoxFor(c => c.Project.Categories,
new MultiSelectList(Model.Categories, "Id", "Name", new List<int> { 1, 2 }))%>
the listbox is attempting to bind to Model.Project.Categories for your strongly typed Model which has been provided to the page (using the lambda notation). I'm not sure what the second parameter in the ListBoxFor is doing though.
What is the Model that is being passed to the page?
Try this
<%= Html.ListBoxFor(c => c.Project.Categories,
new MultiSelectList(
Model.Categories
,"Id"
,"Name"
,Model.Project.Tags.Select(
x => new SelectListItem()
{
Selected = true,
Text = x.TEXT,
Value = x.ID.ToString()
}).ToList())
)
)%>
Html.ListboxFor and Html.Listbox work great when you're NOT binding the list box to its data source. I assume the intended use is this:
// Model
public class ListBoxEditModel
{
public IEnumerable<Category> Categories { get; set; }
public IEnumerable<Category> SelectedCategories { get; set; }
}
In the view:
#Html.ListBoxFor(m => m.SelectedCategories,
new MultiSelectList(Model.Categories, "Id", "Name"))
// or, equivalently
#Html.ListBox("SelectedCategories" ,
new MultiSelectList(Model.Categories, "Id", "Name"))
Note that in this case you don't have to explicitly say which values are selected in the MultiSelectList - the model you're binding to takes precedence, even if you do!