MVC Validation Fundamentals (with Entity Framework) - asp.net-mvc

MVC Validation Fundamentals (with Entity Framework)
Scenario:
I have a model class as below (autogenerated via Entity Framework EF.x DbContext Generator).
(There is no view model at the moment).
public partial class Activity
{
public int Id { get; set; }
public byte Progress { get; set; }
public decimal ValueInContractCurrency { get; set; }
public System.DateTime ForecastStart { get; set; }
public System.DateTime ForecastEnd { get; set; }
public int DepartmentId { get; set; }
public int OwnerId { get; set; }
public int StageId { get; set; }
public int StatusId { get; set; }
public virtual Department Department { get; set; }
public virtual Owner Owner { get; set; }
public virtual Stage Stage { get; set; }
public virtual Status Status { get; set; }
}
When I submit a blank form on the strongly-typed view, I get these validation messages:
The Progress field is required.
The ValueInContractCurrency field is required.
The ForecastStart field is required.
The ForecastEnd field is required.
i.e. all the fields in the db table.
If I fill these in, and submit again, then the controller gets called. The controller then returns back to the view page due to IsValid being false.
The screen is then redisplayed with these validation messages:
The StageId field is required.
The DepartmentId field is required.
The StatusId field is required.
The OwnerId field is required.
i.e. all the foreign key fields in the db table (these are also all select boxes).
If I fill these in, the form then submits succesfully and is saved to the db.
Questions:
Where is the validation coming from, given that I have not used any [Required] attributes? Is this something to do with entity framework?
Why is the form not validating everything right away client-side, what's different about foreign keys (or select boxes) that they are only checked by IsValid() even though they are empty and therefore clearly invalid?
How do you make everything get validated in one step (for empty fields), so the user does not have to submit the form twice and all validation messages are shown at once? Do you have to turn off client side validation?
(I tried adding [Required] attributes to the foreign key fields, but that didn't seem to make any difference (presumably they only affect IsValid). I also tried calling Html.EnableClientValidation() but that didn't make any difference either).
4..Lastly, I've seen people using [MetadataType[MetadataType(typeof(...)]] for validation. Why would you do that if you have a viewmodel, or is it only if you don't?
Obviously I'm missing some fundamentals here, so in addition if anyone knows of a detailed tutorial on how exactly the MVC validation process works step-by-step including javascript/controller calls, rather than just another essay on attributes, then I could do with a link to that too :c)
More info for Mystere Man:
Solution setup as follows:
.NET4
MVC3
EF5
EF5.x Db Context Generator
"Add Code Generation Item" used on edmx design surface to associate EF.x Db Context Generator files (.tt files)
Controller looks like this:
// GET: /Activities/Create
public ActionResult Create()
{
ViewBag.DepartmentId = new SelectList(db.Departments, "Id", "Name");
ViewBag.OwnerId = new SelectList(db.Owners, "Id", "ShortName");
ViewBag.ContractId = new SelectList(db.Contracts, "Id", "Number");
ViewBag.StageId = new SelectList(new List<string>());
ViewBag.StatusId = new SelectList(db.Status.Where(s => s.IsDefaultForNewActivity == true), "Id", "Name");
return View();
}
// POST: /Activities/Create
[HttpPost]
public ActionResult Create(Activity activity)
{
if (ModelState.IsValid)
{
db.Activities.Add(activity);
db.SaveChanges();
return RedirectToAction("Index");
}
ViewBag.DepartmentId = new SelectList(db.Departments, "Id", "Name");
ViewBag.OwnerId = new SelectList(db.Owners, "Id", "ShortName");
ViewBag.ContractId = new SelectList(db.Contracts, "Id", "Number");
ViewBag.StageId = new SelectList(db.Stages, "Id", "Number");
ViewBag.StatusId = new SelectList(db.Status, "Id", "Name");
return View(activity);
}
View is like this:
<!-- this refers to the EF.x DB Context class shown at the top of this post -->
#model RDMS.Activity
<script src="#Url.Content("~/Scripts/jquery.validate.min.js")" type="text/javascript"></script>
<script src="#Url.Content("~/Scripts/jquery.validate.unobtrusive.min.js")" type="text/javascript"></script>
#using (Html.BeginForm()) {
#Html.ValidationSummary(true)
<fieldset>
<legend>Activity</legend>
<div class="editor-label">
#Html.LabelFor(model => model.StageId, "Stage")
</div>
<div class="editor-field">
#Html.DropDownList("StageId", String.Empty)
#Html.ValidationMessageFor(model => model.StageId)
</div>
<div class="editor-label">
#Html.LabelFor(model => model.Progress)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.Progress)
#Html.ValidationMessageFor(model => model.Progress)
</div>
<!-- ETC...-->
<p>
<input type="submit" value="Create" />
</p>
</fieldset>
}

The reason why you get required validation is because the properties are value types (ie they can't be null). Since they can't be null, the framework requires you fill in values for them (otherwise it would have to throw some weird exception).
This problem manifests itself in several ways. I've seen this over and over and over here on Slashdot. I am not sure why so many people fall into this problem, but it's pretty common. Usually this results in a strange exception referring to no default constructor being thrown, but for some reason that did not happen here.
The problem stems from your use of ViewBag and naming the items in ViewBag the same as your model properties. When the page is submitted, the model binder gets confused by similarly named items.
Change these to add List at the end:
ViewBag.DepartmentList = new SelectList(db.Departments, "Id", "Name");
ViewBag.OwnerList = new SelectList(db.Owners, "Id", "ShortName");
ViewBag.ContractList = new SelectList(db.Contracts, "Id", "Number");
ViewBag.StageList = new SelectList(new List<string>());
ViewBag.StatusList = new SelectList(db.Status
.Where(s => s.IsDefaultForNewActivity == true), "Id", "Name");
And change your view to use the strongly typed versions of DropDownListFor:
#Html.DropDownList(x => x.StageId, ViewBag.StageList, string.Empty)
... and so on
One other item of note. In the example above, I hope you're not using some kind of global data context or worse, a singleton. That would be disastrous and could cause data corruption.
If db is just a member of your controller that you new up in the constructor, that's ok, though not ideal. A better approach is to either create a new context in each action method, wrapped by a using statement (then the connection gets closed and destroyed right away) or implement IDisposable on the controller and call Dispose explicitly.
An even better approach is not doing any of this in your controller, but rather in a business layer, but that can wait until you're further along.

Where is the validation coming from, given that I have not used any [Required] attributes? Is this something to do with entity framework?
There is a default validation provider in MVC (not EF), checking two things :
the type of the provided value (a string in an int property) => (not sure, but something like) yyy is not valid for field xxx
a "check null" attribute for Value types (it will complain if you let an empty field corresponding to an int property, and would accept an empty field for an int? property). => The xxx field is required
This second behaviour can be desactivated in global.asax (the property name is rather clear) :
DataAnnotationsModelValidatorProvider.AddImplicitRequiredAttributeForValueTypes = false;
With client side validation enabled, these validations, and the one related to DataAnnotations (Required, StringLength...) will raise Validation errors on client side, before going to the Controller. It avoids a roundtrip on the server, so it's not useless. But of course, you can't rely on client validation only.
Why is the form not validating everything right away client-side, what's different about foreign keys (or select boxes) that they are only
checked by IsValid() even though they are empty and therefore clearly
invalid?
Hmmm, I must admit I ain't got a satisfying answer... So I let this one for a more competent one. They are taken as error in the ModelState.IsValid, because when the ClientSide Validation passed, you then go to ModelBinding (Model binding looks at your POSTed values, looks at the arguments of the corresponding HttpPost method (ActionResult Create for you), and try to bind the POSTed values with these arguments. In your case, the binding sees a Activity activity argument. And he doesn't get anything for StageId (for example) in your POSTed fields. As StageId is not nullable, he puts that as an error in the ModelState dictionary => ModelState is not valid anymore.
But I don't know why it's not catched by Client side validation, even with a Required attribute.
How do you make everything get validated in one step (for empty
fields), so the user does not have to submit the form twice and all
validation messages are shown at once? Do you have to turn off client
side validation?
Well, you'd have to turn off client validation, as you can't trust client validation only. But client validation, as said, is fine to avoid a useless roundtrip to the server.
Lastly, I've seen people using
[MetadataType(typeof(...)]] for validation. Why would you
do that if you have a viewmodel, or is it only if you don't?
It's only if you ain't got a ViewModel, but work on your Model class. It's only usefull when you work with Model First or Database First, as your entity classes are generated (with T4) each time your edmx changes. Then, if you put custom data annotations on your class, you would have to put it back manually after each class (file) generation, which would be stupid. So the [MetadataType(typeof()]] is a way to add annotations on a class, even if the "base class files" are re-generated.
Hope this helps a bit.
By the way, if you're interested in validation, take a look on FluentValidation.
This is a very nice... fluent validation (would you have guessed ?) library.

If the field is not nullable, EF think that it is required.
Cause foreign keys is not nullable, so navigation properties is required too.
To get all validation at once you need to use ViewModel that is transformed in entity mode in controller after validation.
More about attribute validation in mvc, you can read here.

Related

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

MVC 4 Client Side Field validation for a list of fields (instead of fields as members of a class)

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.

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 !

How to control error keys using ModelStateWrapper=

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.
}

Why is ListBoxFor not selecting items, but ListBox is?

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!

Resources