I'm trying to get my viewmodel set up for a form that will collect information about people joining a team. The form must contain some leadup information, then a list of 5 "Team Members" (each containing a name, email, and phone), the first two of which will be required. For my validation I would like it to be on the individual fields, like this:
Person 1:
Name: (validation messaage)
Email: (validation message)
Phone: (validation message)
Person 2:
Name: (validation messaage)
Email: (validation message)
Phone: (validation message)
Person 3:
Name:
Email:
Phone:
Person 4:
Name:
Email:
Phone:
Person 5:
Name:
Email:
Phone:
The relevant portion of my viewmodel is currently:
[Required]
public TeamMember TeamMember1 { get; set; }
[Required]
public TeamMember TeamMember2 { get; set; }
public TeamMember TeamMember3 { get; set; }
public TeamMember TeamMember4 { get; set; }
public TeamMember TeamMember5 { get; set; }
so in my view, I just write:
#Html.EditorFor(model=>model.TeamMember1)
#Html.EditorFor(model=>model.TeamMember2)
#Html.EditorFor(model=>model.TeamMember3)
#Html.EditorFor(model=>model.TeamMember4)
#Html.EditorFor(model=>model.TeamMember5)
the editor template looks like this:
#model MyProject.Models.TeamMember
<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.Email)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.Email)
#Html.ValidationMessageFor(model => model.Email)
</div>
<div class="editor-label">
#Html.LabelFor(model => model.Phone)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.Phone)
#Html.ValidationMessageFor(model => model.Phone)
</div>
Sorry for throwing so much into one thread, but does anyone have a suggestion as to how best to set this up? I've thought about inheriting from RequiredAttribute and replacing [Required] on the TeamMember properties, but I'm not sure how to set the validation messages on the child fields. Right now, even if it's empty it passes the required check, I'm assuming because the objects are bound (and so not null) even though all of the properties are blank.
Any feedback is appreciated.
You can write a custom validator. Below is an example of how you can access the values of other properties. You can then either decorate TeamMember or the property with this custom
validation attribute depending on the validation logic. I would recommend it at the class level
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
//Retreive value of Name property
var nameProperty = validationContext.ObjectType.GetProperty("Name");
var namePropertyValue = (string)nameProperty.GetValue(validationContext.ObjectInstance, null);
var propertyBeingValidatedValue = (string)value;
//Validation logic
if ((!string.IsNullOrEmpty(propertyBeingValidatedValue)) && (!string.IsNullOrEmpty(namePropertyValue)))
{
//returning null means everything is good.
return null;
}
//return a message in any other case.
return new ValidationResult("Validation Message");
}
Related
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.
I am building an ASP.Net MVC 3 Web application using Entity Framework 4.1. To perform validation within one of my Views which accepts a ViewModel. I am using Data Annotations which I have placed on the properties I wish to validate.
ViewModel
public class ViewModelShiftDate
{
public int shiftDateID { get; set; }
public int shiftID { get; set; }
[DisplayName("Start Date")]
[Required(ErrorMessage = "Please select a Shift Start Date/ Time")]
public DateTime? shiftStartDate { get; set; }
[DisplayName("Assigned Locum")]
public int assignedLocumID { get; set; }
public SelectList AssignedLocum { get; set; }
}
View
#using (Html.BeginForm())
{
#Html.ValidationSummary(true)
<br />
<div class="editor-label">
#Html.LabelFor(model => model.shiftStartDate)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.shiftStartDate, new { #readonly = "readonly" })
#Html.ValidationMessageFor(model => model.shiftStartDate)
</div>
<br />
<div class="editor-label">
#Html.LabelFor(model => model.assignedLocumID)
</div>
<div class="editor-field">
#Html.DropDownListFor(model => model.assignedLocumID, Model.AssignedLocum)
#Html.ValidationMessageFor(model => model.assignedLocumID)
</div>
<br />
<p>
<input type="submit" value="Save" />
</p>
<br />
}
The SelectList 'AssignedLocum' is passed into my View for a DropDownList, and the item selected is assigned to the property 'assignedLocumID'.
As you can see from my ViewModel, the only required field is 'shiftStartDate', however, when I hit the Submit button in my View, the drop down list 'AssignedLocum' also acts a required field and will not allow the user to submit until a value is selected.
Does anyone know why this property is acting as a required field even though I have not tagged it to be so?
Thanks.
Try to use default value for dropdown (for example "Please select")
#Html.DropDownListFor(model => model.assignedLocumID, Model.AssignedLocum, "Please select")
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.
Here's my model's property:
[Required(ErrorMessage = "Debe escribir su fecha de nacimiento")]
[Display(Name = "Fecha de Nacimiento")]
[DataType(DataType.Date)]
public DateTime DateOfBirth { get; set; }
In my AccountController, I create a new object of this model and pass it to the View:
<div class="editor-label">
#Html.LabelFor(model => model.DateOfBirth)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.DateOfBirth)
#Html.ValidationMessageFor(model => model.DateOfBirth)
</div>
Remember, at this point this is a Create form. The model's properties have no values.
However, this is rendered in my HTML.
Is there some way to tell the view engine to not render anything in this field?
If you change your definition to a Nullable, then it'll start off as null and no value will be displayed if one isn't set (which by default it isn't).
public DateTime? DateOfBirth { get; set; }
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.