I can't seem to figure out how to validate the pieces of a partial view for an ViewModel that has the partial ViewModel as a child object. Here's my lowest level piece, which will ALWAYS be consumed as a partial view inside other form tags:
namespace MVC3App.ViewModels
{
public class Payment : IValidatableObject
{
public decimal Amount { get; set; }
public int CreditCardNumber { get; set; }
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
if (Amount < 20)
yield return new ValidationResult("Please pay more than $20", new string[] { "Amount" });
}
}
}
And here's the 'main' ViewModel that includes it:
namespace MVC3App.ViewModels
{
public class NewCustomerWithPayment :IValidatableObject
{
public string Name { get; set; }
public int Age { get; set; }
public ViewModels.Payment PaymentInfo { get; set; }
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
if (Age < 18)
yield return new ValidationResult("Too young.", new string[] { "Age" });
}
}
}
For the View of the NewCustomerWithPayment, I have this:
#model MVC3App.ViewModels.NewCustomerWithPayment
#using (Html.BeginForm()) {
#Html.ValidationSummary(true)
<fieldset>
<legend>NewCustomerWithPayment</legend>
<div class="editor-label">
#Html.LabelFor(model => model.Name)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.Name)
#Html.ValidationMessageFor(model => model.Name)
</div>
<div class="editor-label">
#Html.LabelFor(model => model.Age)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.Age)
#Html.ValidationMessageFor(model => model.Age)
</div>
</fieldset>
#Html.Partial("Payment")
<p><input type="submit" value="Create" /></p>
}
And the Partial View "Payment" is ALWAYS rendered inside another Html.Beginform tag, it just has this:
#model MVC3App.ViewModels.Payment
<h2>Payment</h2>
<fieldset>
<legend>Payment</legend>
<div class="editor-label">
#Html.LabelFor(model => model.Amount)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.Amount)
#Html.ValidationMessageFor(model => model.Amount)
</div>
<div class="editor-label">
#Html.LabelFor(model => model.CreditCardNumber)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.CreditCardNumber)
#Html.ValidationMessageFor(model => model.CreditCardNumber)
</div>
</fieldset>
My problem is that I cannot get the Validation on the 'Payment' viewmodel to work. Can anyone with experience using IValidatableObject on ViewModels which are rendered as Partial Views chime in and give me a validation pattern that works? I can live without JavaScript validation if I have to.
These answers all have some great info, but my immediate issue was resolved by using this:
#Html.EditorFor(model => model.PaymentInfo)
Instead of this:
Html.Partial("Payment", Model.PaymentInfo)
I was surprised that this worked, but it does. The EditorFor helper renders out the partial view just like Html.Partial, and wires in the validation automatically. For some reason, it does call the validation twice on the child model (Payment in my example), which seems to be a reported issue for some other people (http://mvcextensions.codeplex.com/workitem/10), so I have to include a boolean for 'HasBeenValidated' on each model and check for it at the beginning of the Validate call.
Update: you must move your view to the EditorTemplates folder under /Views/Shared/ in order for the view to be used by the EditorFor helper. Otherwise, the EditorFor will give you the default editing fields for the types.
Here is a lame example of a custom validator for a checkbox :) I would write a custom validator or use a regex maybe. This may get you on the right path and be easier.
[AttributeUsage(AttributeTargets.Property, AllowMultiple = false, Inherited = false)]
public class CheckBoxMustBeTrueAttribute : ValidationAttribute, IClientValidatable
{
#region IClientValidatable Members
public IEnumerable<ModelClientValidationRule> GetClientValidationRules(ModelMetadata metadata,
ControllerContext context)
{
yield return new ModelClientValidationRule
{
ErrorMessage = FormatErrorMessage(metadata.GetDisplayName()),
ValidationType = "requiredcheckbox"
};
}
#endregion
public override bool IsValid(object value)
{
if (value is bool)
{
return (bool) value;
}
return true;
}
}
Most probably IValidatableObject is recognized only on the root model. You can call the inner model Validate method from the root model:
public class NewCustomerWithPayment :IValidatableObject {
...
public ViewModels.Payment PaymentInfo { get; set; }
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
if (Age < 18)
yield return new ValidationResult("Too young.", new string[] { "Age" });
if (this.PaymentInfo != null)
yield return this.PaymentInfo.Validate(validationContext);
}
}
Note: Not sure if the above compiles.
Related
simple custom validation,
my model and custom validation:
public class Registration
{
[Required(ErrorMessage = "Date of Birth is required")]
[AgeV(18,ErrorMessage="You are not old enough to register")]
public DateTime DateOfBirth { set; get; }
}
public class AgeVAttribute : ValidationAttribute
{
private int _maxAge;
public AgeVAttribute(int maxAge)
{
_maxAge = maxAge;
}
public override bool IsValid(object value)
{
return false; <--- **this never gets executed.... what am I missing?**
}
}
(Please see the inline comment above)
view:
#using (Html.BeginForm()) {
#Html.ValidationSummary("Errors")
<fieldset>
<legend>Registration</legend>
<div class="editor-label">
#Html.LabelFor(model => model.DateOfBirth)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.DateOfBirth)
</div>
<p>
<input type="submit" value="Create" />
</p>
</fieldset>
}
Can't repro.
Model:
public class Registration
{
[Required(ErrorMessage = "Date of Birth is required")]
[AgeV(18, ErrorMessage = "You are not old enough to register")]
public DateTime DateOfBirth { set; get; }
}
public class AgeVAttribute : ValidationAttribute
{
private int _maxAge;
public AgeVAttribute(int maxAge)
{
_maxAge = maxAge;
}
public override bool IsValid(object value)
{
return false;
}
}
Controller:
public class HomeController : Controller
{
public ActionResult Index()
{
return View(new Registration
{
DateOfBirth = DateTime.Now.AddYears(-10)
});
}
[HttpPost]
public ActionResult Index(Registration model)
{
return View(model);
}
}
View:
#model Registration
#using (Html.BeginForm())
{
#Html.ValidationSummary("Errors")
<fieldset>
<legend>Registration</legend>
<div class="editor-label">
#Html.LabelFor(model => model.DateOfBirth)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.DateOfBirth)
</div>
<p>
<input type="submit" value="Create" />
</p>
</fieldset>
}
The IsValid method is always hit when the form is submitted. Also notice that I haven't enabled client side validation because I didn't include the jquery.validate.js and the jquery.validate.unobtrusive.js scripts. If you have included them and there's an error chances are that client side validation will prevent your form from even being submitted to the server in which case it would be normal for the IsValid method not being invoked.
I am using nhibernate and mvc3 in asp.net
I'm trying to add data into table where my table schema is like this:
public class HobbyMasters
{
[Key]
public virtual int HobbyId { get; set; }
[Required(ErrorMessageResourceType = typeof(Resources), ErrorMessageResourceName = "NameRequired")]
public virtual string HobbyName { get; set; }
public virtual HobbyTypes HobbyType { get; set; }
[Required]
public virtual string HobbyDetails { get; set; }
[Required]
public virtual ICollection<HobbyMasters> HobbyNames { get; set; }
}
public class HobbyTypes
{
[Key]
public virtual int HobbyTypeId { get; set; }
[Required]
public virtual string HobbyType { get; set; }
public virtual ICollection<HobbyTypes> Hobby { get; set; }
}
in my Controller
public ActionResult Create()
{
ViewBag.c1 = (ICollection<HobbyTypes>)(new Hobby_MasterService().GetAllHobbyTypes());
return View();
}
//
// POST: /Hobbies/Create
[HttpPost]
public ActionResult Create(HobbyMasters hobby)
{
ViewBag.c1 = (ICollection<HobbyTypes>)new Hobby_MasterService().GetAllHobbyTypes();
try
{
if (ModelState.IsValid)
{
new Hobby_MasterService().SaveOrUpdateHobby(hobby);
return RedirectToAction("Index");
}
}
}
in the view:
#using (Html.BeginForm("Create", "Hobbies", FormMethod.Post))
{
#Html.ValidationSummary(true)
<fieldset>
<legend>Hobby Master</legend>
<div class="editor-label">
#Html.LabelFor(model => model.HobbyName)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.HobbyName)
#Html.ValidationMessageFor(model => model.HobbyName)
</div>
<div class="editor-label">
#Html.LabelFor(model => model.HobbyType)
</div>
<div class="Editor-field">
#Html.DropDownListFor(model =>model.HobbyType.HobbyTypeId, new SelectList(ViewBag.c1, "HobbyTypeId", "HobbyType"), "-- Select --")
#Html.ValidationMessageFor(model => model.HobbyType)
</div>
<div class="editor-label">
#Html.LabelFor(model => model.HobbyDetails)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.HobbyDetails)
#Html.ValidationMessageFor(model => model.HobbyDetails)
</div>
</fieldset>
<p><input type="Submit" value="Create" /> </p>
}
Apparently i found that My Modelstate.IsValid is always false.....
since it stores only the HobbyId and getting Hobby Type as null the HobbyMasters hobbytype object.....
dnt knw where i'm going wrong may be in dropdownlist or something else.....
Plaese help me asap:(
There are a couple of issues with your code:
First is the fact that you decorated HobbyNames collection property with the [Required] attribute. You should use this attribute only on simple properties. In fact you could leave the property but it will have absolutely no effect
The second issue with your code is that you have decorated the HobbyType string property of the HobbyTypes model with a [Required] attribute but you never use this property in your view. So no value is sent when you submit the form and your model is invalid.
Another issue with your code is that you bound the dropdown list to the model => model.HobbyType.HobbyTypeId property. But the HobbyTypeId is not a nullable type. And yet you made your dropdown contain a default value: "-- Select --". This is not possible. If you want to have a dropdown list with an optional value you must bind it to a nullable property on your model.
I have tried to clean up your code a little.
Model:
public class HobbyMasters
{
public virtual int HobbyId { get; set; }
[Required]
public virtual string HobbyName { get; set; }
public virtual HobbyTypes HobbyType { get; set; }
[Required]
public virtual string HobbyDetails { get; set; }
public virtual ICollection<HobbyMasters> HobbyNames { get; set; }
}
public class HobbyTypes
{
[Required]
public virtual int? HobbyTypeId { get; set; }
public virtual string HobbyType { get; set; }
public virtual ICollection<HobbyTypes> Hobby { get; set; }
}
Controller:
public class HomeController : Controller
{
public ActionResult Create()
{
ViewBag.c1 = (ICollection<HobbyTypes>)(new Hobby_MasterService().GetAllHobbyTypes());
var model = new HobbyMasters();
return View(model);
}
//
// POST: /Hobbies/Create
[HttpPost]
public ActionResult Create(HobbyMasters hobby)
{
if (ModelState.IsValid)
{
try
{
new Hobby_MasterService().SaveOrUpdateHobby(hobby);
return RedirectToAction("Index");
}
catch (Exception ex)
{
ModelState.AddModelError("", ex.Message);
}
}
ViewBag.c1 = (ICollection<HobbyTypes>)new Hobby_MasterService().GetAllHobbyTypes();
return View(hobby);
}
}
View:
#model HobbyMasters
#using (Html.BeginForm())
{
#Html.ValidationSummary(true)
<fieldset>
<legend>Hobby Master</legend>
<div class="editor-label">
#Html.LabelFor(model => model.HobbyName)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.HobbyName)
#Html.ValidationMessageFor(model => model.HobbyName)
</div>
<div class="editor-label">
#Html.LabelFor(model => model.HobbyType)
</div>
<div class="Editor-field">
#Html.DropDownListFor(model => model.HobbyType.HobbyTypeId, new SelectList(ViewBag.c1, "HobbyTypeId", "HobbyType"), "-- Select --")
#Html.ValidationMessageFor(model => model.HobbyType.HobbyTypeId)
</div>
<div class="editor-label">
#Html.LabelFor(model => model.HobbyDetails)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.HobbyDetails)
#Html.ValidationMessageFor(model => model.HobbyDetails)
</div>
</fieldset>
<p><input type="Submit" value="Create" /></p>
}
Also I would very strongly recommend you to use view models. Don't pass your domain entities to your views. Define view models.
I have a case where a complex partial view needs different validation of fields depending on where the partial view is used.
I thought I could get around this by making the partial view take an interface as the model type and implementing two different ViewModels based on the interface. The data annotations in the two ViewModels would be different. I would then supply an instance of the correct ViewModel to the partial view.
But what I'm finding is that the only annotations that are recognized are those on the interface itself. DAs on the interface-implementing ViewModel classes are ignored, even though those are the objects that are being passed as models. So my plan isn't working.
Is there a way around this? A better approach? I'd prefer not to split the partial view into separate views if I can avoid it.
ETA: This is an abbreviated version of the partial view, as requested:
#model IPerson
#Html.ValidationSummary(false)
<fieldset>
<table class="editForm">
<tr>
<td class="editor-label">
#Html.LabelFor(model => model.FirstName)
</td>
<td class="editor-field">
#Html.EditorFor(model => model.FirstName)
#Html.ValidationMessageFor(model => model.FirstName)
</td>
<td class="editor-label">
#Html.LabelFor(model => model.LastName)
</td>
<td class="editor-field">
#Html.EditorFor(model => model.LastName)
#Html.ValidationMessageFor(model => model.LastName)
</td>
</tr>
</table>
<fieldset>
The real partial view is quite long and has a lot of #if statements managing the rendering (or not) of optional sections, but it doesn't do anything tricky.
My idea isn't going to work: this thread reminded me that classes don't inherit attributes from their interfaces. (As the answer points out, what would happen if two interfaces specified the same property with different attributes, and both were implemented by one class?)
It might work with a common base class. I will try that tomorrow.
Thanks, everybody.
Ann, you're right. I've deleted my comment. You can't post an interface back through your view. However, I don't know what exactly your trying to do since I can't see your code. Maybe something like this? I'm passing an interface to the view, but passing it back as the class I'm expecting. Again, I'm not sure the application is here.
Let's say you have classes like this:
[MetadataType(typeof(PersonMetaData))]
public class Customer : IPerson {
public int ID { get; set; }
public string Name { get; set; }
[Display(Name = "Customer Name")]
public string CustomerName { get; set; }
}
public class Agent : IPerson {
public int ID { get; set; }
public string Name { get; set; }
}
public partial class PersonMetaData : IPerson {
[Required]
public int ID { get; set; }
[Required]
[Display(Name="Full Name")]
public string Name { get; set; }
}
public interface IPerson {
int ID { get; set; }
string Name { get; set; }
}
public interface IAgent {
int AgentType { get; set; }
}
public interface ICustomer {
int CustomerType { get; set; }
}
Your Controller looks like:
public ActionResult InterfaceView() {
IPerson person = new Customer {
ID = 1
};
return View(person);
}
[HttpPost]
public ActionResult InterfaceView(Customer person) {
if (ModelState.IsValid) {
TempData["message"] = string.Format("You posted back Customer Name {0} with an ID of {1} for the name: {2}", person.CustomerName, person.ID, person.Name);
}
return View();
}
And your View Looks like this:
#model DataTablesExample.Controllers.Customer
<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>
#if (#TempData["message"] != null) {
<p>#TempData["message"]</p>
}
#using (Html.BeginForm()) {
#Html.ValidationSummary(true)
<fieldset>
<legend>IPerson</legend>
#Html.HiddenFor(model => model.ID)
<div class="editor-label">
#Html.LabelFor(model => model.Name)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.Name)
#Html.ValidationMessageFor(model => model.Name)
</div>
<div class="editor-label">
#Html.LabelFor(model => model.CustomerName)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.CustomerName)
#Html.ValidationMessageFor(model => model.CustomerName)
</div>
<p>
<input type="submit" value="Save" />
</p>
</fieldset>
}
Well, actually you have a very reasonable idea! and can be archived is you use the non generic version of the HtmlHelper methods (ex. "#Html.Editor" instead of "#Html.EditorFor"), because the generic versions recreate the ModelMetadata (i don't know why!) based on the generic parameter type and don't use the ModelMetadata of the view. Freaking awful, isn't it?
Hope this help.
How do i update a domain object with ViewModel with AutoMapper using Entity framework?
I have a View to edit a Question entity.
This is my Edit action:
public ActionResult Edit(int id)
{
var question = db.Question.Single(q => q.question_id == id);
Mapper.CreateMap<Question, EditQuestionViewModel>();
EditQuestionViewModel eqvm = Mapper.Map<Question, EditQuestionViewModel>(question);
eqvm.QuestionTypes = new SelectList(db.Question_Type, "type_code", "type_description", question.type_code);
eqvm.Categories = new SelectList(db.Category, "category_id", "category_name", question.category_id);
eqvm.Visibility = new SelectList(new Dictionary<int, string> {
{ 1, "Ja"},
{ 0, "Nej"}
}, "Key", "Value");
return View(eqvm);
}
And my ViewModel looks like this:
public class EditQuestionViewModel
{
public int question_id { get; set; }
public string question_wording { get; set; }
public bool visible { get; set; }
public int question_number { get; set; }
public string help_text { get; set; }
public Category Category { get; set; }
public Question_Type Question_Type { get; set; }
public string SelectedCategory { get; set; }
public string SelectedQuestionType { get; set; }
public SelectList Categories { get; set; }
public SelectList QuestionTypes { get; set; }
public SelectList Visibility { get; set; }
public string RefUrl { get; set; }
}
This is the View:
#using (Html.BeginForm("Edit", "AdminQuestion", FormMethod.Post))
{
#Html.ValidationSummary(true)
<fieldset>
<legend>Spørgsmål</legend>
<div class="editor-label">
#Html.LabelFor(model => model.question_wording, "Spørgsmål")
</div>
<div class="editor-field">
#Html.TextAreaFor(model => model.question_wording, new { #class = "required", rows = 3, cols = 50 })
#Html.ValidationMessageFor(model => model.question_wording)
</div>
<div class="editor-label">
#Html.LabelFor(model => model.SelectedCategory, "Hvilken kategori tilhører dette spørgsmål?")
</div>
<div class="editor-field">
#Html.DropDownListFor(model => model.SelectedCategory, Model.Categories)
#Html.ValidationMessageFor(model => model.SelectedCategory)
</div>
<div class="editor-label">
#Html.LabelFor(x => x.SelectedQuestionType, "Spørgsmålstype")
</div>
<div class="editor-field">
#Html.DropDownListFor(model => model.SelectedQuestionType, Model.QuestionTypes)
#Html.ValidationMessageFor(model => model.SelectedQuestionType)
</div>
<div class="editor-label">
#Html.LabelFor(model => model.visible, "Skal dette spørgsmål være synligt?")
</div>
<div class="editor-field">
#Html.DropDownListFor(model => model.visible, Model.Visibility)
#Html.ValidationMessageFor(model => model.visible)
</div>
<div class="editor-label">
#Html.LabelFor(model => model.question_number, "Hvilket nummer har spørgsmålet inden for sin kategori?")
</div>
<div class="editor-field">
#Html.TextBoxFor(model => model.question_number, new { #class = "required digits" })
#Html.ValidationMessageFor(model => model.question_number)
</div>
<div class="editor-label">
#Html.LabelFor(model => model.help_text, "Hjælpetekst som hjælper brugeren med at forstå spørgsmålet:")
</div>
<div class="editor-field">
#Html.TextAreaFor(model => model.help_text, new { rows = 20, cols = 50 })
#Html.ValidationMessageFor(model => model.help_text)
</div>
<br />
<input type="submit" value="Gem" />
</fieldset>
How do i update the entity when i submit the form ?
How should the mapping between the ViewModel and EF Model look like, using AutoMapper?
The properties
public string SelectedCategory { get; set; }
public string SelectedQuestionType { get; set; }
In the ViewModel are supposed to be linked with category_id and type_code in the EF model
Also notice the property
public bool visible { get; set; }
I use BIT in my database. Will this work with the values 0 and 1, which is use in the SelectList?
Thanks!
you would need to get the object from entity framework, and then use automapper like this:
var item = repository.getbyid(model.Id);
_mappingEngine.Map(viewModel, item);
repository.save(item);
When you submit your form, you need to have an action on your controller that will handle the post to the server.
So in addition to the Edit action you currently have, you will need to have another action defined like so:
[HttpPost]
public ActionResult Edit(EditQuestionViewModel model)
{
//Do the mapping to from your ViewModel to the EF model here
return View();
}
What this does is sets up a handler so your form can post the data back to the controller and it will bind your fields on your form to the model parameter.
Once you have done this, you can simply map the model back to EF and persist it to the database.
Also, using a bool is perfectly valid and EF will translate and save it as a 0 or 1 in the database for you.
I have created a custom validator in my asp.net mvc3 application like this:
{
if (customerToValidate.FirstName == customerToValidate.LastName)
return new ValidationResult("First Name and Last Name can not be same.");
return ValidationResult.Success;
}
public static ValidationResult ValidateFirstName(string firstName, ValidationContext context)
{
if (firstName == "Nadeem")
{
return new ValidationResult("First Name can not be Nadeem", new List<string> { "FirstName" });
}
return ValidationResult.Success;
}
and I have decorated my model like this:
[CustomValidation(typeof(CustomerValidator), "ValidateCustomer")]
public class Customer
{
public int Id { get; set; }
[CustomValidation(typeof(CustomerValidator), "ValidateFirstName")]
public string FirstName { get; set; }
public string LastName { get; set; }
}
my view is like this:
#model CustomvalidatorSample.Models.Customer
#{
ViewBag.Title = "Index";
}
<h2>
Index</h2>
#using (#Html.BeginForm())
{
#Html.ValidationSummary(false)
<div class="editor-label">
#Html.LabelFor(model => model.FirstName, "First Name")
</div>
<div class="editor-field">
#Html.EditorFor(model => model.FirstName)
</div>
<div class="editor-label">
#Html.LabelFor(model => model.LastName, "Last Name")
</div>
<div class="editor-field">
#Html.EditorFor(model => model.LastName)
</div>
<div>
<input type="submit" value="Validate" />
</div>
}
But validation doesn't fire. Please suggest solution.
Thanks
How do you know the validation doesn't fire? Are you setting a break point in your controller?
You are not displaying any validation errors in your view. You need to add the following lines to the view.
#Html.ValidationMessageFor(model => model.FirstName)
#Html.ValidationMessageFor(model => model.LastName)
You will want to remove the custom validation from the class. Leave it on the properties though.