MVC isn't generating the client-side validation rules for my viewmodel. The HTML just contains this:
<script type="text/javascript">
//<![CDATA[
if (!window.mvcClientValidationMetadata) { window.mvcClientValidationMetadata = []; }
window.mvcClientValidationMetadata.push({"Fields":[],"FormId":"form0","ReplaceValidationSummary":false});
//]]>
</script>
Note that Fields[] is empty!
My view is strongly-typed and uses the new strongly-typed HTML helpers (TextBoxFor(), etc).
View Model / Domain Model
public class ItemFormViewModel
{
public Item Item { get; set; }
[Required] [StringLength(100)] public string Whatever { get; set; } // for demo
}
[MetadataType(typeof(ItemMetadata))]
public class Item
{
public string Name { get; set; }
public string SKU { get; set; }
public int QuantityRequired { get; set; }
// etc.
}
public class ItemMetadata
{
[Required] [StringLength(100)] public string Name { get; set; }
[Required] [StringLength(50)] public string SKU { get; set; }
[Range(0, Int32.MaxValue)] public int QuantityRequired { get; set; }
// etc.
}
(I know I'm using a domain model as my / as part of my view model, which isn't a good practice, but disregard that for now.)
View
<%# Page Language="C#" MasterPageFile="~/Views/Shared/Site.Master"
Inherits="System.Web.Mvc.ViewPage<ItemFormViewModel>" %>
<asp:Content ID="Content2" ContentPlaceHolderID="MainContent" runat="server">
<h2>Editing item: <%= Html.Encode(Model.Item.Name) %></h2>
<% Html.EnableClientValidation(); %>
<%= Html.ValidationSummary("Could not save the item.") %>
<% using (Html.BeginForm()) { %>
<%= Html.TextBoxFor(model => model.Item.Name) %>
<%= Html.TextBoxFor(model => model.Item.SKU) %>
<%= Html.TextBoxFor(model => model.Item.QuantityRequired) %>
<%= Html.HiddenFor(model => model.Item.ItemID) %>
<%= Html.TextBox("Whatever", Model.Whatever) %>
<input type="submit" value="Save" />
<% } %>
</asp:Content>
I included the Whatever property on the view model because I suspected that MVC wasn't recursively inspecting the sub-properties of ItemFormViewModel.Item, but even that isn't being validated? I've even tried delving into the MVC framework source code but have come up empty. What could be going on?
About five seconds after I posted the question, I realized something: My view didn't have ValidationMessage placeholders anywhere. I added <%= Html.ValidationMessageFor(model => model.Item.Name) %> and lo and behold, MVC added validation rules for Item.Name to the JS block at the bottom of the page.
It turns out that MVC does not emit client-side validation rules for a field unless you actually do one of the following:
Call Html.ValidationMessage() for the property.
Call Html.Validate() for the property. (This one won't output error messages)
Render the controls using Html.EditorForModel(). (source)
Doing any of these tells MVC, "This property of my viewmodel is editable by the user, so you should be validating it." Just using the HTML helper to stick a textbox on the page -- even if you're using the new strongly-typed helpers -- isn't enough.
I have had no luck getting this to work in MVC 2 RC. According to other questions here on SO, you have to get the MicrosoftMvcJQueryValidation.js file from the MVC Futures release, hold your left foot behind your head, and whistle Dixie for half an hour. I did this and more and have not been able to make it work.
Hopefully it will be fixed in RTM.
Related
I'm really stuck at this: I have two listboxes populated from a database. I want to copy items from one list to the other. Then the changes must be saved in the database.
This is what I've got:
Custom ViewModel:
public class StudentModel
{
public IEnumerable<SelectListItem> NormalStudentsList { get; set; }
public IEnumerable<SelectListItem> StudentsNoClassList { get; set; }
public string[] NormalSelected { get; set; }
public string[] NoClassSelected { get; set; }
public string Save { get; set; }
}
Controller:
public ActionResult IndexStudents(Docent docent, int id, int klasgroepid)
{
var studentModel = new StudentModel
{
NormalStudentsList = docent.GeefStudentenNormaalList(id, klasgroepid),
StudentsNoClassList = docent.GeefStudentenNoClassList(id, klasgroepid)
};
return View(studentModel);
}
[HttpPost, Authorize]
public ActionResult IndexStudentsResult(StudentModel model, string add, string remove)
{
ModelState.Clear();
(if! string.IsNullOrEmpty(add))
//update database
SaveState(model);
return View(model);
}
But how can I update the database?? Using UpdateModel()?
or should I work with FormCollection? But I need a formCollection to work with UpdateModel()...
The Students table has a field named "ClassID", and when copying the rows from 1 list to the other, the ID has to change from the current ClassID to "0".
How can I do that? I'm really stuck at this... hope you can help.
This is my View
<%# Page Title="" Language="C#" MasterPageFile="~/Views/Shared/Site.Master" Inherits="System.Web.Mvc.ViewPage<ProjectenII.Models.Domain.StudentModel>"%>
<asp:Content ID="Content1" ContentPlaceHolderID="TitleContent" runat="server">
IndexStudents
</asp:Content>
<asp:Content ID="Content2" ContentPlaceHolderID="MainContent" runat="server">
<h2>IndexStudents</h2>
<%using (Html.BeginForm()) { %>
<%=Html.ListBoxFor(model => model.NormalSelected, new MultiSelectList(Model.NormalStudentsList, "StudentNummer", "Naam", Model.NormalSelected), new { size = "6" }); %>
<input type="submit" name="add"
id="add" value=">>" /><br />
<%=Html.ListBoxFor(model => model.NoClassSelected, new MultiSelectList(Model.StudentsNoClassList, "StudentNummer", "Naam", Model.NoClassSelected)); %>
<% } %>
<%=Html.HiddenFor(model => model.Save) %>
<input type="submit" name="apply" id="apply" value="Save!" />
</asp:Content>
Your problem is related to returning a List from the view... check this post by Phil Haack:
Model Binding To A List
Here you can see I ran into a similar problem. In my case a used checkboxes to select items in a list. The solution proposed guided me in the right direction but it wasn't the one I used, I used Phil's post.
My Post
Hope this helps.
We may also achieve using Editor helper, but making all of the multiselectlist elements selected before submit will work:
$("#NormalSelected option").prop("selected", true);
This will pass multiselectlist items to controller.
Description
I have a payment page that includes a form for entering bank account information. I have encapsulated the bank account information into a Model / Editor Template. The page itself has its own View Model, which happens to contain a BankAccount property to be passed in to the Editor.
[[View Models]]
public class PaymentPageModel {
public SomeProperty1 { get; set; }
public SomeProperty2 { get; set; }
public BankAccount BankAccount { get; set; }
...
}
public class BankAccount {
public int BankAccountTypeID { get; set; }
public string BankName { get; set; }
public string ABACode { get; set; }
public string AccountNumber { get; set;}
public IEnumerable<SelectListItem> BankAccountTypes {
get { ... // Constructs the list }
}
}
[[Payment Page HTML]]
<% using (Html.BeginForm()) { %>
<%: Html.EditorFor(m => m.BankAccount) %>
... // Other miscellaneous stuff not related to the actual BankAccount
<% } %>
[[Editor Template]
...
<%: Html.DropDownListFor(m => m.BankAccountTypeID, Model.BankAccountTypes) %>
...
Problem
Initially, this worked perfectly when I was strongly-typing the Payment page directly to the BankAccount model. The dropdown list was being populated properly, and the correct value from the model was being selected.
I recently modified the page, strongly-typing it to the PaymentPageModel, which contains the BankAccount model as a property. The HTML has not been modified. The result now, is that all the HTML values in the Editor Template are being populated properly, except for the DropDownList. It is binding the list of values properly from the BankAccountTypes select list, but the selected value is NOT being bound. I have checked to make sure that the value it is supposed to be binding to IS set properly by outputting it right next to the DropDownList.
This is driving me nuts, and is making me really question the reliability of Model binding and HTML Helpers in general, especially if I am unable to combine complex view models with Editor Templates to encapsulate presentation/functionality.
Any suggestions are greatly appreciated.
If you have strongly typed the editor template to PaymentPageModel in your main view instead of:
<%: Html.EditorFor(m => m.BankAccount) %>
you could try:
<%: Html.EditorForModel() %>
and in your editor template:
<%: Html.DropDownListFor(m => m.BankAccount.BankAccountTypeID,
Model.BankAccount.BankAccountTypes) %>
I'm not even quite sure where to start explaining this problem. I've been working on this for about the past 10 hours without a clue as to what the root cause is. If any additional details are needed, I'd be happy to provide. I'm just guessing at what is relevant at this point.
I have an MVC2 site with routes set up by by Steve Hodgkiss' wonderful RestfulRouting package, using the default route setup with nested controllers (e.g. /customer/{custid}/location/{locid} and such).
In this, I have one particular model that is giving me issues. For some reason, when the create page post's the data back to my server, the ModelName property in the ModelBindingContext object passed to the DefaultModelBinder (well, my custom class inherited from DefaultModelBinder, to handle grabbing objects from a repository). This happens only for this one model. And I can't spot the differences at all.
The broken model
public class RemedialItem : Entity
{
public virtual int Id { get; set; }
....
A working model:
public class Customer : Entity
{
public virtual int Id { get; set; }
....
Entity is just an empty class used as a marker for Reflection use.
The broken controller method in RemedialItemController.cs
[HttpGet]
public ActionResult New(int? locationId, int? applianceId)
{
var model = ViewModelFactory.Create<CreateRemedialItemViewModel>();
model.Categories = (from c in repository.Query<RemedialItemCategory>()
orderby c.Name
select c).ToList();
model.RemedialItem = new RemedialItem();
return View(model);
}
A working controller method in CustomerController.cs
[HttpGet]
public ActionResult New()
{
var viewModel = ViewModelFactory.Create<SingleCustomerViewModel>();
viewModel.Customer = new Customer();
return View(viewModel);
}
ViewModelFactory is an injected class that handles setting up some basic properties common to all view models (mainly is the user logged in and user details right now)
A broken viewmodel:
public class CreateRemedialItemViewModel : ViewModelBase
{
public RemedialItem RemedialItem { get; set; }
public IList<Location> Locations { get; set; }
public IList<Appliance> Appliances { get; set; }
public IList<RemedialItemCategory> Categories { get; set; }
}
A working ViewModel:
public class SingleCustomerViewModel : ViewModelBase
{
public Customer Customer { get; set; }
}
ViewModelBase contains a handful of properties populated by the ViewModelFactory.
The broken form in thew New view for RemedialItem:
<% using(Html.BeginForm("Create","RemedialItem",FormMethod.Post))
{%>
<%: Html.AntiForgeryToken() %>
<fieldset>
<legend>General</legend>
<div>
<%: Html.LabelFor(m=>m.RemedialItem.Category) %>
<%:Html.DropDownListFor(m=>m.RemedialItem.Category.Id, new SelectList(Model.Categories,"Id","Name")) %>
</div>
<div>
<%: Html.LabelFor(m=>m.RemedialItem.Item) %>
<%: Html.TextAreaFor(m=>m.RemedialItem.Item) %>
</div>
<div>
<%: Html.LabelFor(m=>m.RemedialItem.Note) %>
<%: Html.TextAreaFor(m=>m.RemedialItem.Note) %>
</div>
<input type="submit" value="Create Item" />
</fieldset>
<%}%>
A working New view:
<% using (Html.BeginForm("Create","Customer",FormMethod.Post)) {%>
<%: Html.ValidationSummary(true) %>
<%:Html.AntiForgeryToken() %>
<fieldset>
<legend>Fields</legend>
<p>
<%: Html.LabelFor(m=>m.Customer.Name) %>
<%: Html.TextBoxFor(m=>m.Customer.Name) %>
</p>
<p>
<%: Html.LabelFor(m=>m.Customer.Street) %>
<%: Html.TextBoxFor(m=>m.Customer.Street) %>
</p>
[...tl;dr...]
<p>
<input type="submit" value="Create" />
</p>
</fieldset>
<% } %>
Both produce similar field names:
Broken:
<label for="RemedialItem_Item">Item</label>
<textarea cols="20" id="RemedialItem_Item" name="RemedialItem.Item" rows="2">
</textarea>
Working:
<label for="Customer_Name">Name</label>
<input id="Customer_Name" name="Customer.Name" type="text" value="" />
I apologize for the overly long code dump, in short:
The working set of stuff, when posted back on the create form, has the ModelName set to Customer. The broken stuff is an empty string
Is there something I'm missing? Has anyone encountered something like this before?
I found the issue. In the ViewModel the property that held the instance of RemedialItem to display was called RemedialItem. In the action it posted to, the parameter that took the RemedialItem instance was called item, and that broke everything.
In short, when using ViewModels, make sure the parameter name that takes an object from the ViewModel is the same as the property name in the viewmodel.
There went my day.
I have a view model with a collection of other objects in it.
public ParentViewModel
{
public int Id { get; set; }
public string Name { get; set; }
public List<ChildViewModel> Child { get; set; }
}
public ChildViewModel
{
public int Id { get; set; }
public string FirstName { get; set; }
}
In one of my views I pass in a ParentViewModel as the model, and then use
<%: Html.EditorFor(x => x) %>
Which display a form for the Id and Name properties.
When the user clicks a button I call an action via Ajax to load in a partial view which takes a collection of Child:
<%# Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl<IEnumerable<Child>>" %>
<%: Html.EditorFor(x => x) %>
which then uses the custom template Child to display a form for each Child passed in.
The problem I'm having is that the form created by the Child custom template does not use the naming conventions used by the DefaultModelBinder.
ie the field name is (when loaded by Ajax):
[0].FirstName
instead of:
Child[0].FirstName
So the Edit action in my controller:
[HttpPost]
public virtual ActionResult Edit(int id, FormCollection formValues)
{
ParentViewModel parent = new ParentViewModel();
UpdateModel(parent);
return View(parent);
}
to recreate a ParentViewModel from the submitted form does not work.
I'm wondering what the best way to accomplish loading in Custom Templates via Ajax and then being able to use UpdateModel is.
Couple of things to start with is that you need to remember the default ModelBinder is recursive and it will try and work out what it needs to do ... so quite clever. The other thing to remember is you don't need to use the html helpers, actual html works fine as well :-)
So, first with the Model, nothing different here ..
public class ParentViewModel
{
public int Id { get; set; }
public string Name { get; set; }
public List<ChildViewModel> Child { get; set; }
}
public class ChildViewModel
{
public int Id { get; set; }
public string FirstName { get; set; }
}
Parent partial view - this takes an instance of the ParentViewModel
<%# Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl<ParentViewModel>" %>
<h2>Parent</h2>
<%: Html.TextBox("parent.Name", Model.Name) %>
<%: Html.Hidden("parent.Id", Model.Id) %>
<% foreach (ChildViewModel childViewModel in Model.Child)
{
Html.RenderPartial("Child", childViewModel);
}
%>
Child partial view - this takes a single instance of the ChildViewModel
<%# Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl<ChildViewModel>" %>
<h3>Child</h3>
<%: Html.Hidden("parent.Child.index", Model.Id) %>
<%: Html.Hidden(string.Format("parent.Child[{0}].Id", Model.Id), Model.Id)%>
<%: Html.TextBox(string.Format("parent.Child[{0}].FirstName", Model.Id), Model.FirstName) %>
Something to note at this point is that the index value is what is used for working out the unique record in the list. This does not need to be incremental value.
So, how do you call this? Well in the Index action which is going to display the data it needs to be passed in. I have setup some demo data and returned it in the ViewData dictionary to the index view.
So controller action ...
public ActionResult Index()
{
ViewData["Message"] = "Welcome to ASP.NET MVC!";
ViewData["Parent"] = GetData();
return View();
}
private ParentViewModel GetData()
{
var result = new ParentViewModel
{
Id = 1,
Name = "Parent name",
Child = new List<ChildViewModel>
{
new ChildViewModel {Id = 2, FirstName = "first child"},
new ChildViewModel {Id = 3, FirstName = "second child"}
}
};
return result;
}
In the real world you would call a data service etc.
And finally the contents of the Index view:
<form action="<%: Url.Action("Edit") %>" method="post">
<% if (ViewData["Parent"] != null) { %>
<%
Html.RenderPartial("Parent", ViewData["Parent"]); %>
<% } %>
<input type="submit" />
</form>
Saving
So now we have the data displayed how do we get it back into an action? Well this is something which the default model binder will do for you on simple data types in relatively complex formations. So you can setup the basic format of the action which you want to post to as:
[HttpPost]
public ActionResult Edit(ParentViewModel parent)
{
}
This will give you the updated details with the original ids (from the hidden fields) so you can update/edit as required.
New children through Ajax
You mentioned in your question loading in custom templates via ajax, do you mean how to give the user an option of adding in another child without postback?
If so, you do something like this ...
Add action - Need an action which will return a new ChildViewModel
[HttpPost]
public ActionResult Add()
{
var result = new ChildViewModel();
result.Id = 4;
result.FirstName = "** to update **";
return View("Child", result);
}
I've given it an id for easy of demo purposes.
You then need a way of calling the code, so the only view you need to update is the main Index view. This will include the javascript to get the action result, the link to call the code and a target HTML tag for the html to be appended to. Also don't forget to add your reference to jQuery in the master page or at the top of the view.
Index view - updated!
<script type="text/javascript">
function add() {
$.ajax(
{
type: "POST",
url: "<%: Url.Action("Add", "Home") %>",
success: function(result) {
$('#newchild').after(result);
},
error: function(req, status, error) {
}
});
}
</script>
<form action="<%: Url.Action("Edit") %>" method="post">
<% if (ViewData["Parent"] != null) { %>
<%
Html.RenderPartial("Parent", ViewData["Parent"]); %>
<% } %>
<div id="newchild"></div>
<br /><br />
<input type="submit" /> add child
</form>
This will call the add action, and append the response when it returns to the newChild div above the submit button.
I hope the long post is useful.
Enjoy :-)
Hmm... i personally would recommend to use a JSON result, instead of a HTML result, that you fiddle in the page...
makes the system cleaner. and your postback working ;-)
I found another way to accomplish this which works in my particular situation.
Instead of loading in a partial via via Ajax that is strongly typed to a child collection like:
<%# Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl<IEnumerable<Child>>" %>
I created a strongly typed view to the parent type and then called EditorFor on the list like so:
<%# Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl<Parent>" %>
<%: Html.EditorFor(x => x.ChildList) %>
This then calls a Custom Display Template and the result is that all the HTML elements get named correctly and the Default Model binder can put everything back together.
I have a registration form which displays a users Name (textbox), Email (textbox) and Division (SelectList). The Name and Email are pre-populated (I'm using Windows Authentication, Intranet app), and I want to send the SelectedValue from the DropDown to my controller as an Int32, I don't want to send the entire SelectList back. This list is small now, but will grow to considerable size.
I a class called RegistrationViewModel, it contains public properties for these fields. However, when I use SelectList for the DivisionList, I receive this error: No parameterless constructor defined for this object..
If i change the Type, it works no problem, but Division is null or 0. Is there a way to pass the SelectedValue from a DropDown to a Controller Action method as a Int32?
Edit 1:
I'm not really sure what I'm doing, I've been using MVC for about 48 hours, watched the PDF, TechEd, and TechDays videos.
My apologies, here is my controller code:
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Register(RegistrationViewModel rvm)
{
IApplicationContext context = ContextRegistry.GetContext();
IValidationErrors errors = new ValidationErrors();
IValidator validator = (IValidator)context.GetObject("RegistrationValidator");
bool valid = validator.Validate(rvm, errors);
if (valid)
repo.SaveRegistration();
else
ViewData["DivisionList"] = repo.GetDivisions();
return View(rvm);
}
RegistrationViewModel Class
public class RegistrationViewModel
{
public string Name { get; set; }
public string Email { get; set; }
//public SelectList DivisionList { get; private set; }
public int Division { get; set; }
}
Here's the view
<%# Page Language="C#"
MasterPageFile="~/Views/Shared/Site.Master"
Inherits="System.Web.Mvc.ViewPage<RegistrationViewModel>" %>
<%# Import Namespace="Project1.Entities"%>
<%# Import Namespace="Project1.Models"%>
<asp:Content ID="registerTitle" ContentPlaceHolderID="TitleContent" runat="server">
Register
</asp:Content>
<asp:Content ID="registerContent" ContentPlaceHolderID="MainContent" runat="server">
...
<% using (Html.BeginForm())
{ %>
<div>
<fieldset>
<legend>Account Information</legend>
<p>
<label for="name">Name:</label>
<%= Html.TextBox("Name", User.Identity.Name.GetDisplayName()) %>
<%= Html.ValidationMessage("username") %>
</p>
<p>
<label for="email">Email:</label>
<%= Html.TextBox("email", User.Identity.Name.GetEmailFromLogin()) %>
<%= Html.ValidationMessage("email") %>
</p>
<p>
<label for="division">Division:</label>
<%= Html.DropDownList("DivisionList", ViewData["DivisionList"] as SelectList)%>
<%= Html.ValidationMessage("confirmPassword") %>
</p>
<p>
<input type="submit" value="Register" />
</p>
</fieldset>
</div>
<% } %>
</asp:Content>
Edit 2:
Eilon: Here is what I changed it too:
Controller:
public ActionResult Register()
{
ViewData["DivisionList"] = repo.GetDivisions();
return View();
}
View:
<%= Html.DropDownList("DivisionValue", ViewData["DivisionList"] as SelectList)%>
I recieve this exception:
There is no ViewData item with the key 'DivisionValue' of type 'IEnumerable'.
When I updated the View to this:
<%= Html.DropDownList("DivisionList", ViewData["DivisionList"] as SelectList)%>
It works just great! It only seems to work if all the "Division" items named identically. If I change the name the View crashes or the ViewModel "Division" property is sent as 0.
Why is that?
The RegistrationViewModel type should contain a simple-typed property such as:
public string DivisionValue { get; set; }
Or change the type to int, DateTime, or whatever the appropriate type is.
In HTML and HTTP the only thing that gets posted back for a drop down list is the name of the field and the selected value.
To get everything to match up you also need to change the view to render a different input name for the drop down list:
<%= Html.DropDownList("DivisionValue", ViewData["DivisionList"] as SelectList)%>
Notice that I'm using "DivisionValue" is the value of the list, and DivisionList as the list of all available items.
I'd just be more explicit with the SelectList type. I'd suggest creating the SelectList in the controller action and forget about casting it in the view. My code works like this (CRUD Edit page):
..in the Action:
ViewData["WorkType.ID"] = new SelectList(this._vacancySvc.GetVacancyWorkTypes(),
"ID", "Name", ViewData["WorkType.ID"] ?? vacancy.WorkType.ID);
..and in the view:
<p><% =Html.Encode("Work Type:") %><br />
<% =Html.DropDownList("Worktype.ID")%><span class="smallgrey">(required)</span><br />
.. you can see that either the initial selection (from DB) is persisted or the ViewData from post backs (like if the form fails validation) thru the use of the [null coalescing operator][1] (??).
Moreover, if i refactored this code, i'd prob like to use a ViewModel object like you are.
The only thing is: (1) you'd never need to reference the ViewModel SelectList property in the view coz MVC auto binds this for us by the Html.DropDownList() overload.. and (2) i'd still need to ref the ViewData in the action anyway to get the selected value from a failed validation post back so what's the point really??