ASP.NET MVC3 Validation of nested view model object fields - asp.net-mvc-3

I have a view model that looks like this:
public class VenueIndexViewModel : BaseViewModel
{
public VenueAddViewModel Venue;
...
}
public class VenueAddViewModel
{
...
[Required(ErrorMessage = "This field is required")]
public string State { get; set; }
...
}
In my view, I'm rendering a form with with a drop down list for this property like so:
using (var form = Html.BeginForm())
{
...
#Html.DropDownListFor(x => x.Venue.State, Model.GetStates())
#Html.ValidationMessageFor(x => x.Venue.State)
...
}
This works, but the problem is that the the Required attribute on the view model appears to be ignored. If I look at the HTML, the data-val-* attributes are missing as well.
<select id="Venue_State" name="Venue.State">...</select>
However, if I change the rendering to a textbox...
using (var form = Html.BeginForm())
{
...
#Html.TextBoxFor(x => x.Venue.State)
#Html.ValidationMessageFor(x => x.Venue.State)
...
}
I see the expected data-val-* attributes and the validation works:
<input data-val="true"
data-val-required="This field is required"
id="Venue_State" name="Venue.State" type="text" value="">
I should note that I have other view models elsewhere that use DropDownListFor with a flat view model (no nested objects) and the validation works fine there, so I'm thinking I've hit a bug in the MVC validation handling for drop down lists when using a nested view model. Can anyone confirm / advise?

As far as I know you can't have client side validation on nested objects. And a quick google search seems to confirm that.
http://forums.asp.net/t/1737269.aspx/1

Related

Save and retrieve checkbox values asp.net mvc3

I am a newbie to MVC3 technology and trying to workout my way get through a small problem.
I simply need to get checked checkbox values to be saved in database and on Edit view check them back.
<input type="checkbox" value="Photo" name="DocSub" /> Photograph<br />
<input type="checkbox" value="BirthCertificate" name="DocSub" /> Copy Of Birth Certificate<br />
<input type="checkbox" value="School Leaving Certificate" name="DocSub" /> School Leaving Certificate<br />
When the Submit button is clicked, the [HTTPPOST] Action method of the desired controller is called. There I receive the selected values in this form :
var selectedCheckBoxValues = Request.Form["DocSub"];
I am getting the all the checked checkbox values in comma separated form and able to store them to the database, but wondering if this is the right approach to go by.
Also I need to know to retrieve checkbox values from database on Edit view in already checked form.
the typical apporoach to these problems is to use a view with a model
ie, suppose this is view Documents.cshtml
#model DocumentViewModel
#Html.LabelFor(m => m.Photo)
#Html.CheckBoxFor( m => m.Photo )
#Html.LabelFor(m => m.BirthCertificate)
#Html.CheckBoxFor( m => m.BirthCertificate )
#Html.LabelFor(m => m.SchoolLeavingCertificate)
#Html.CheckBoxFor( m => m.SchoolLeavingCertificate )
and use a viewmodel to pass data to the view
the viewmodel is a class where you have the data your going to send to the view, ie.
public class DocumentViewModel{
public bool Photo {get;set;}
public bool BirthCertificate { get; set; }
public bool SchoolLeavingCertificate {get;set;}
}
and you'd have a controller that populates the viewmodel and calls the view
public ActionResult Documents()
{
var modelData = new DocumentViewModel();
//or retrieve from database at this point
// ie. modelData.Photo = some database value
return View(modelData);
}
[HttpPost]
public ActionResult Documents(DocumentViewModel documentsVM)
{
if (ModelState.IsValid)
{
//update the database record, save to database... (do stuff with documentsVM and the database)
return RedirectToAction("NextAction");
}
//else, if model is not valid redirect back to the view
return View(documentsVM);
}
look for tutorials out there on mvc basics. read code.

MVC3 Modelbinder EF4 ICollection property [duplicate]

I'm working on my first ASP.NET MVC 3 application and I've got a View that looks like this:
#model IceCream.ViewModels.Note.NotesViewModel
#using (Html.BeginForm())
{
#Html.ValidationSummary(true)
#Html.TextBoxFor(m => m.Name)
foreach (var item in Model.Notes)
{
#Html.EditorFor(m => item);
}
<input type="submit" value="Submit"/>
}
And I have an EditorTemplate that looks like this:
#model IceCream.ViewModels.Note.NoteViewModel
<div>
#Html.HiddenFor(m => m.NoteID)
#Html.TextBoxFor(m => m.NoteText)
#Html.CheckBoxFor(m => m.IsChecked)
</div>
NotesViewModel looks like so:
public class NotesViewModel
{
public string Name { get; set; }
public IEnumerable<NoteViewModel> Notes { get; set; }
}
NoteViewModel looks like this:
public class NoteViewModel
{
public int NoteID { get; set; }
public System.DateTime Timestamp { get; set; }
public string NoteText { get; set; }
public bool IsChecked { get; set; }
}
The NotesViewModel is populated just fine when it is passed to the view. However when the submit button is clicked, the controller action handling the post has only the value for the Name property of the viewmodel. The Notes property - the list of notes that have been checked/unchecked by the user - is null. I've got a disconnect between the populating of those TextBoxFor and CheckBoxFor elements when the view is displayed and the ViewModel being sent back. Guidance on this?
SOLUTION
Thanks go to Mystere Man for setting me straight on this. As I understand it, essentially by changing my loop to
#Html.EditorFor(m => m.Notes)
changes the underlying HTML, which I understand provides for the proper model binding on the post. Looking at the resulting HTML, I see that I get the following generated for one of the Notes:
<div>
<input id="Notes_0__NoteId" type="hidden" value="1" name="Notes[0].NoteId">
<input id="Notes_0__NoteText" type="text" value="Texture of dessert was good." name="Notes[0].NoteText">
<input id="Notes_0__IsChecked" type="checkbox" value="true" name="Notes[0].IsChecked>
</div>
Which is different than this HTML generated by my original code:
<div>
<input id="item_NoteId" type="hidden" value="1" name="item.NoteId>
<input id="item_NoteText" type="text" value="Texture of dessert was good." name="item.NoteText" >
<input id="item_IsChecked" type="checkbox" value="true" name="item.IsChecked">
</div>
By looping through the Notes, the generated HTML essentially loses any references to the viewmodel's Notes property and while the HTML gets populated correctly, the setting of the checkbox values has no way to communicate their values back to the viewmodel, which I guess is the point of the model binding.
So I learned something, which is good.
You're a smart guy, so look at your view. Then, consider how the HTML gets generated. Then, consider how on postback the Model Binder is supposed to know to re-populate Notes based on the generated HTML.
I think you'll find that your HTML doesn't have enough information in it for the Model Binder to figure it out.
Consider this:
#EditorFor(m => Model.Notes)
Rather than the for loop where you are basically hiding the context from the EditorFor function.
And for those that just want the answer as a for loop:
#for (int x = 0; x < Model.Notes.Count(); x++) {
#Html.HiddenFor(m => m.Notes[x].NoteId)
#Html.EditorFor(m => m.Notes[x].NoteText)
#Html.EditorFor(m => m.Notes[x].IsChecked)
}

Using ASP.NET MVC 3 with Razor, what's the most effective way to add an ICollection to a Create view?

I'm using Entity Framework Code First to generated my database, so I have an object defined like the following:
public class Band
{
public int Id { get; set; }
[Required(ErrorMessage = "You must enter a name of this band.")]
public string Name { get; set; }
// ...
public virtual ICollection<Genre> Genres { get; set; }
}
Now I'm looking at a create view for this and the default scaffolding isn't adding Genres to my form, which from past experience is about what I expect.
Looking online I've found Using ASP.NET MVC v2 EditorFor and DisplayFor with IEnumerable<T> Generic types which seems to come closest to what I want, but doesn't seem to make sense with Razor and possibly MVC 3, per ASP.NET MVC 3 Custom Display Template With UIHint - For Loop Required?.
At present I've added the listing of genres to the ViewBag and then loop through that listing in my create view:
#{
List<Genre> genreList = ViewBag.Genres as List<Genre>;
}
// ...
<ul>
#for (int i = 0; i < genreList.Count; i++)
{
<li><input type="checkbox" name="Genres" id="Genre#(i.ToString())" value="#genreList[i].Name" /> #Html.Label("Genre" + i.ToString(), genreList[i].Name)</li>
}
</ul>
Outside of not yet handling cases where the user has JavaScript disabled and the checkboxes need to be re-checked, and actually updating the database with this information, it does output the genres as I'd like.
But this doesn't feel right, based on how good MVC 3 has become.
So what's the most effective way to handle this in MVC 3?
I don't send lists into my View via the ViewBag, instead I use my viewmodel to do this. For instance, I did something like this:
I have an EditorTemplate like this:
#model IceCream.ViewModels.Toppings.ToppingsViewModel
<div>
#Html.HiddenFor(x => x.Id)
#Html.TextBoxFor(x =x> x.Name, new { #readonly="readonly"})
#Html.CheckBoxFor(x => x.IsChecked)
</div>
which I put in my Views\IceCream\EditorTemplates folder. I use this to display some html for allowing the user to "check" any particular topping.
Then in my View I've got something like this:
#HtmlEditorFor(model => model.Toppings)
and that will use that result in my EditorTemplate being used for each of the toppings in the Toppings property of my viewmodel.
And then I've got a viewmodel which, among other things, includes the Toppings collection:
public IEnumerable<ToppingsViewModel> Toppings { get; set; }
Over in my controller, among other things, I retrieve the toppings (however I do that in my case) and set my viewmodel's property to that collection of toppings. In the case of an Edit, where toppings may have been selected previously, I set the IsChecked member of the TopingsViewModel and it'll set the corresponding checkboxes to checked.
Doing it this way provided the correct model binding so that when the user checked a few toppings, the underlying items in the collection reflected those selections. Worked well for me, hope it's helpful for you.

Custom attribute with dash in name using EditorFor/TextBoxFor/TextBox helpers

I am using Knockout-JS to bind properties in my view to my view model. Knockout-JS uses a custom attribute called 'data-bind' that you have to append to controls in which you want to be bound to view model objects.
Example:
<input type='text' name='first-name' data-bind='value: firstName'/>
Notice the 'data-bind' attribute.
In my view rendering, I am having trouble rendering a textbox that has this attribute. I am aware the Html.EditorFor, Html.TextBoxFor, and Html.TextBox helpers all take an anonymous object that you can use to specify custom attributes. The only problem with this implementation is C# doesn't allow dashes as variable names, so this won't compile:
#Html.EditorFor(m => m.FirstName, new { data-bind = "value: firstName" });
The only thing I can think of is this (in view-model):
public class DataBindingInput
{
public string Value { get; set; }
public string DataBindingAttributes { get; set }
}
public class MyViewModel
{
...
public DataBindingValue firstName { get; set; }
....
}
And a view template called "DataBindingInput.cshtml":
#model DataBindingInput
<input type='text' data-binding='#Model.DataBindingAttributes' value='#Model.Value'>
The only trouble with this is I lose the automatic generation of the input name so it won't work on a post-back because the model binder has no idea how to bind it.
How can I make this work?
Thanks to Crescent Fish above, looks like you can just use underscores and MVC 3 will convert them to dashes since underscores aren't allowed in HTML attribute names.

Adding profile fields to registration form asp.net mvc3

I am new to mvc and would like to add an additional field to my registration page that is simply a dropdownlist bound to a table in my model (a table of organization names and IDs). However, in my default application I see that the AccountController is using the RegisterModel model to create the register form view. This is fine, I don't want to disturb this. But I want to add a new select box on the page bound to a different model (my model with the organizations). How do I accomplish this?
I've found other posts that suggest I create a wrapper model for both my model and the RegisterModel, but this isn't working. My wrapper model looks like this:
public class RegisterPeopleModel
{
public RegisterModel reg { get; set; }
public fwfEntities fwf { get; set; }
}
And now the field validator for password is no longer working. The code in the view:
<div class="editor-field">
#Html.PasswordFor(m => m.reg.ConfirmPassword)
#Html.ValidationMessageFor(m => m.reg.ConfirmPassword)
</div>
Now renders this:
<div class="editor-field">
<input data-val="true" data-val-equalto="The password and confirmation password do not match." data-val-equalto-other="*.Password" id="reg_ConfirmPassword" name="reg.ConfirmPassword" type="password" />
<span class="field-validation-valid" data-valmsg-for="reg.ConfirmPassword" data-valmsg-replace="true"></span>
</div>
Notice that the IDs of the span and input no longer match. The form no longer works at all. This is leading me to believe I'm taking the wrong approach. Is there a better way of getting my select list on the page bound to a different model?
Thanks in advance.
Is there a better way of getting my select list on the page bound to a
different model?
If the model doesn't meet the requirements of the view you should modify this model. In ASP.NET MVC it is a good practice to design view models and pass only view models to views and not domain models (like for example EF autogenerated classes which is what this fwfEntities type seem to be). So design a view model which contains only the properties needed by your view and have your controller action query the database in order to fetch the model then map the model to a view model and finally pass this view model to the view.
Without seeing RegisterModel my guess is that ConfirmPassword has not got a required attribute for example;
[Required(ErrorMessage = "A password is required")]
public string ConfirmPassword { get; set; }
oh I believe the period in the id is replaced with an underscore so as not to cause issues with Jquery.

Resources