ASP.NET mvc 2 - binding a birth date with drop downs - asp.net-mvc-2

In ASP.NET MVC 2, how would you go about binding a view model property that is a DateTime where the application must have 3 drop down lists for choosing month, day, year?I've read Scott H.'s blog post about binding dates some time ago, and that seems entirely too convoluted for such a simple case. Surely there's a cleaner / better way to do it?
Whatever solution I use, I would like to retain built-in validation using the DataAnnotations stuff, and I'd also like to be able to specify a min / max date range using a validation attribute.
My first thought was a simple custom model binder like so:
protected override void BindProperty(ControllerContext controllerContext, ModelBindingContext bindingContext, PropertyDescriptor propertyDescriptor)
{
var model = bindingContext.Model as RsvpViewModel;
var form = controllerContext.HttpContext.Request.Form;
if (model == null)
throw new ArgumentException("bindingContext.Model");
if (propertyDescriptor.Name.Equals("BirthDate"))
{
if (!string.IsNullOrEmpty(form["BirthYear"]) &&
!string.IsNullOrEmpty(form["BirthMonth"]) &&
!string.IsNullOrEmpty(form["BirthDay"]))
{
try
{
var yy = int.Parse(form["BirthYear"]);
var mm = int.Parse(form["BirthMonth"]);
var dd = int.Parse(form["BirthDay"]);
model.BirthDate = new DateTime(yy, mm, dd);
return;
}
catch (Exception)
{
model.BirthDate = DateTime.MinValue;
return;
}
}
}
base.BindProperty(controllerContext, bindingContext, propertyDescriptor);
}
Then I tried creating a DateTimeAttribute to do the validation, but ran into some difficulty specifying a date range in the attribute declaration because attribute parameter types are limited, and DateTime is not one of the allowable types.
I ended up creating an IDateRangeProvider interface and an implementation specific to birth dates like so:
public interface IDateRangeProvider
{
DateTime GetMin();
DateTime GetMax();
}
public class BirthDateRangeProvider : IDateRangeProvider
{
public DateTime GetMin()
{
return DateTime.Now.Date.AddYears(-100);
}
public DateTime GetMax()
{
return DateTime.Now.Date;
}
}
This allowed me to use a DateTime property on my view model and retain all of the build in goodness...
[DisplayName("Date of Birth:")]
[Required(ErrorMessage = "Date of birth is required")]
[DateTime(ErrorMessage = "Date of birth is invalid", RangeProvider=typeof(BirthDateRangeProvider))]
public DateTime? BirthDate { get; set; }
But really, the whole solution smells of overengineering and overthinking it. Isn't there a better way?

create seprate 3 Dropdownlist and add
required validation attribute for
them.
and use BdateList,BMonthList to
populate your DropdownList
[DisplayName("Date of Birth ")]
public DateTime? Birthdate { get; set; }
[Required(ErrorMessage = "Date is required")]
public int BirthDateDate { get; set; }
[Required(ErrorMessage = "Month is required")]
public int BirthDateMonth { get; set; }
[Required(ErrorMessage = "Year is required")]
public int BirthDateYear { get; set; }
public List<System.Web.Mvc.SelectList> BDateList
{
get
{
// create List here
}
}
and in post method you could assign
user selected values values to Model BirthDate
BirthDate.Date.AddDays(BirthDateDate -1).AddMonths(BirthDateMonth)

an idea for you
have class birthDate
public class birthDate{
public int day{get;set;}
public int month{get;set;}
public int year{get;set;}
}
now in your entity:
set your birthdate element to private
and add the birth date items into the class
after you just handle the item for each part together.
and process this to one date:)

you can have custom type for birthday which will have properties like
public class BirthDateModel
{
[Required(), Range(1, 12)]
public Int32 BirthMonth { get; set; }
[Required, Range(1, 31)]
[DayShouldBeValid("BirthYear", "BirthMonth")]
public Int32 BirthDay { get; set; }
[Required,Range(1990, 2012)]
public virtual Int32 BirthYear { get; set; }
public DateTimeOffset GetBirthDate()
{
DateTimeOffset birthDate;
if (DateTimeOffset.TryParse(String.Format("{0}-{1}-{2}", BirthMonth, BirthDay, BirthYear), out birthDate))
return birthDate;
// what should be returned here?
return DateTime.MinValue;
}
}
Now create custom validator aka DayShouldBeValid to check for the month and year, the day is valid or not.
Not each part of a date have your own control.

Related

Handling Dates with OData v4, EF6 and Web API v2.2

I'm in the midst of upgrading from v1-3 to v4, but I've run into a few problems.
My understanding is that DateTime is unsupported, and I have to always use DateTimeOffset. Fine.
But before I was storing Sql date data type in the DateTime, now it seems I get this error:
Member Mapping specified is not valid. The type 'Edm.DateTimeOffset[Nullable=False,DefaultValue=,Precision=]' of member 'CreatedDate' in type 'MyEntity' is not compatible with 'SqlServer.date[Nullable=False,DefaultValue=,Precision=0]'
What is the work around for this? I need to be able to store specifically just dates in the database (time and locality is not important). Would be great if I could get the Edm.Date aswell as a returned data type, but I didn't have that before.
Thanks.
Edit: Example classes
Before:
public class Ticket
{
[Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id { get; set; }
[Required, MaxLength(50)]
public string Reference { get; set; }
[Column(TypeName = "date")]
public DateTime LoggedDate { get; set; }
}
After:
public class Ticket
{
[Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id { get; set; }
[Required, MaxLength(50)]
public string Reference { get; set; }
[Column(TypeName = "date")]
public DateTimeOffset LoggedDate { get; set; }
}
This isn't valid in EF.
One option is to define a new property in the entity. Say Title is mapped to EF:
public partial class Title
{
public int Id { get; set; }
public string Name { get; set; }
public Nullable<System.DateTime> CreatedOn { get; set; }
}
then add a new property of DateTimeOffset:
public partial class Title
{
[NotMapped]
public DateTimeOffset? EdmCreatedOn
{
// Assume the CreateOn property stores UTC time.
get
{
return CreatedOn.HasValue ? new DateTimeOffset(CreatedOn.Value, TimeSpan.FromHours(0)) : (DateTimeOffset?)null;
}
set
{
CreatedOn = value.HasValue ? value.Value.UtcDateTime : (DateTime?)null;
}
}
}
and the code for generate OData Model looks like:
public static IEdmModel GetModel()
{
ODataConventionModelBuilder builder = new ODataConventionModelBuilder();
EntityTypeConfiguration<Title> titleType= builder.EntityType<Title>();
titleType.Ignore(t => t.CreatedOn);
titleType.Property(t => t.EdmCreatedOn).Name = "CreatedOn";
builder.EntitySet<Title>("Titles");
builder.Namespace = typeof(Title).Namespace;
return builder.GetEdmModel();
}
}
The controller looks like:
public class TitlesController : ODataController
{
CustomerManagementSystemEntities entities = new CustomerManagementSystemEntities();
[EnableQuery(PageSize = 10, MaxExpansionDepth = 5)]
public IHttpActionResult Get()
{
IQueryable<Title> titles = entities.Titles;
return Ok(titles);
}
public IHttpActionResult Post(Title title)
{
entities.Titles.Add(title);
return Created(title);
}
}
For anyone coming to this in the future, the OData v4 team have fixed this issue.
[Column(TypeName = "date")]
public DateTime Birthday { get; set; }
This will now auto-resolve to Edm.Date.
If you are like me and are doing date type by convention, you have to manually declare the properties as dates lest they be auto-resolved as DateTimeOffset. OData currently does not allow you to add your own conventions.
customer.Property(c => c.Birthday).AsDate();
http://odata.github.io/WebApi/#12-01-DateAndTimeOfDayWithEF
You can refer to the link below to define your DateTimeAndDateTimeOffsetWrapper to do the translation between two types.
http://www.odata.org/blog/how-to-use-sql-spatial-data-with-wcf-odata-spatial/
Define two properties on your model, one is DateTime which only exists in the Edm model, the other is DateTimeOffset which only exists in the DB.
If the solution above doesn't meet your request, you have to change the data to DateTime before saving it to database and change it back to DateTimeOffset after retrieving it from database in the controller actions.
You can define two almost-same classes to achieve this. The only difference is that one has DateTime property and the other has DateTimeOffset property.
The former one is used for EF and mapping into DB.
The latter one is used for defining OData Edm model and presenting to the users.
As I said above, you have to do the translation between these two classes before saving the data and after retrieving the data.
You can add the AppendDatetimeOffset method to add automatically the methods
using the microsoft T4 engine (i.e. updating the template file *.tt). So that when regenerating the code, you don't have to append classes again. Hope this Helps :)
public string Property(EdmProperty edmProperty)
{
return string.Format(
CultureInfo.InvariantCulture,
(_ef.IsKey(edmProperty) ? "[Key]" : "") +
"{0} {1} {2} {{ {3}get; {4}set; }} {5}",
Accessibility.ForProperty(edmProperty),
_typeMapper.GetTypeName(edmProperty.TypeUsage),
_code.Escape(edmProperty),
_code.SpaceAfter(Accessibility.ForGetter(edmProperty)),
_code.SpaceAfter(Accessibility.ForSetter(edmProperty)),
AppendDateTimeOffset(edmProperty));
}
public string AppendDateTimeOffset(EdmProperty edmProperty){
if(!_typeMapper.GetTypeName(edmProperty.TypeUsage).Contains("DateTime")) return " ";
//proceed only if date time
String paramNull = #"public Nullable<System.DateTimeOffset> edm{0}
{{
get
{{
return {0}.HasValue ? new DateTimeOffset({0}.Value, TimeSpan.FromHours(0)) : (DateTimeOffset?)null;
}}
}}";
String paramNotNull = #"public System.DateTimeOffset edm{0}
{{
get
{{
return new DateTimeOffset({0}, TimeSpan.FromHours(0));
}}
}}";
String s= String.Empty;
if(edmProperty.Nullable){
s = string.Format(paramNull, edmProperty.Name);
}else
{
s = string.Format(paramNotNull, edmProperty.Name);
}
return s;
}

How to add data while inserting or updating entities

We're using RIA Services in our Silverlight app, and for one of our entities we want to track who creates and update them and when. For this we've added these properties:
public class Person
{
public string CreatedBy { get; set; }
public DateTime CreatedOn { get; set; }
public string LastModifiedBy { get; set; }
public DateTime LastModifiedOn { get; set; }
}
We would like to update these values in the domain service so that we don't have to do this on the client (and because entitities will also be added/updated server side(. I tried to do it by modified the domain service method like this:
public void InsertPerson(Person person)
{
person.CreatedBy = GetCurrentUser();
person.CreatedOn = DateTime.Now();
DbEntityEntry<Person> entityEntry = this.DbContext.Entry(person);
if ((entityEntry.State != EntityState.Detached))
{
entityEntry.State = EntityState.Added;
}
else
{
this.DbContext.Persons.Add(person);
}
}
public void UpdatePerson(Person person)
{
person.LastModifiedBy = GetCurrentUser();
person.LastModifiedOn = DateTime.Now();
DbContext.Persons.AttachAsModified(person, ChangeSet.GetOriginal(person), DbContext);
}
but that didn't seem to add this data at all. I then tried to do it with sql queries after inserting/updating entities with
DbContext.Database.ExecuteSqlCommand("UPDATE Persons SET LastModifiedById = {0}, LastModifiedOn = {1} where Id = {2}", GetCurrentUser(), DateTime.Now, person.Id);
which actually updates the database, but the client is not updated/notified of the changes until the entities is fetch from the database again.
Does anyone have a good idea of how to best achieve this?
yes call the
DBContext.SaveChanges()
to actually commit the changes into the database

Improve navigation property names when reverse engineering a database

I'm using Entity Framework 5 with Visual Studio with Entity Framework Power Tools Beta 2 to reverse engineer moderately sized databases (~100 tables).
Unfortunately, the navigation properties do not have meaningful names. For example, if there are two tables:
CREATE TABLE Contacts (
ContactID INT IDENTITY (1, 1) NOT NULL,
...
CONSTRAINT PK_Contacts PRIMARY KEY CLUSTERED (ContactID ASC)
}
CREATE TABLE Projects (
ProjectID INT IDENTITY (1, 1) NOT NULL,
TechnicalContactID INT NOT NULL,
SalesContactID INT NOT NULL,
...
CONSTRAINT PK_Projects PRIMARY KEY CLUSTERED (ProjectID ASC),
CONSTRAINT FK_Projects_TechnicalContact FOREIGN KEY (TechnicalContactID)
REFERENCES Contacts (ContactID),
CONSTRAINT FK_Projects_SalesContact FOREIGN KEY (SalesContactID)
REFERENCES Contacts (ContactID),
...
}
This will generate classes like this:
public class Contact
{
public Contact()
{
this.Projects = new List<Project>();
this.Projects1 = new List<Project>();
}
public int ContactID { get; set; }
// ...
public virtual ICollection<Project> Projects { get; set; }
public virtual ICollection<Project> Projects1 { get; set; }
}
public class Project
{
public Project()
{
}
public int ProjectID { get; set; }
public int TechnicalContactID { get; set; }
public int SalesContactID { get; set; }
// ...
public virtual Contact Contact { get; set; }
public virtual Contact Contact1 { get; set; }
}
I see several variants which would all be better than this:
Use the name of the foreign key: For example, everything after the last underscore (FK_Projects_TechnicalContact --> TechnicalContact). Though this probably would be the solution with the most control, this may be more difficult to integrate with the existing templates.
Use the property name corresponding to the foreign key column: Strip off the suffix ID (TechnicalContactID --> TechnicalContact)
Use the concatenation of property name and the existing solution: Example TechnicalContactIDProjects (collection) and TechnicalContactIDContact
Luckily, it is possible to modify the templates by including them in the project.
The modifications would have to be made to Entity.tt and Mapping.tt. I find it difficult due to the lack of intellisense and debug possibilities to make those changes.
Concatenating property names (third in above list) is probably the easiest solution to implement.
How to change the creation of navigational properties in Entity.tt and Mapping.tt to achieve the following result:
public class Contact
{
public Contact()
{
this.TechnicalContactIDProjects = new List<Project>();
this.SalesContactIDProjects = new List<Project>();
}
public int ContactID { get; set; }
// ...
public virtual ICollection<Project> TechnicalContactIDProjects { get; set; }
public virtual ICollection<Project> SalesContactIDProjects { get; set; }
}
public class Project
{
public Project()
{
}
public int ProjectID { get; set; }
public int TechnicalContactID { get; set; }
public int SalesContactID { get; set; }
// ...
public virtual Contact TechnicalContactIDContact { get; set; }
public virtual Contact SalesContactIDContact { get; set; }
}
There a few things you need to change inside the .tt file. I choose to use the third solution you suggested but this requires to be formatted like FK_CollectionName_RelationName. I split them up with '_' and use the last string in the array.
I use the RelationName with the ToEndMember property to create a property name. FK_Projects_TechnicalContact will result in
//Plularized because of EF.
public virtual Contacts TechnicalContactContacts { get; set; }
and your projects will be like this.
public virtual ICollection<Projects> SalesContactProjects { get; set; }
public virtual ICollection<Projects> TechnicalContactProjects { get; set; }
Now the code you may ask. Ive added 2 functions to the CodeStringGenerator class in the T4 file. One which builds the propertyName recieving a NavigationProperty. and the other one generating the code for the property recieving a NavigationProperty and the name for the property.
//CodeStringGenerator class
public string GetPropertyNameForNavigationProperty(NavigationProperty navigationProperty)
{
var ForeignKeyName = navigationProperty.RelationshipType.Name.Split('_');
var propertyName = ForeignKeyName[ForeignKeyName.Length-1] + navigationProperty.ToEndMember.Name;
return propertyName;
}
public string NavigationProperty(NavigationProperty navigationProperty, string name)
{
var endType = _typeMapper.GetTypeName(navigationProperty.ToEndMember.GetEntityType());
return string.Format(
CultureInfo.InvariantCulture,
"{0} {1} {2} {{ {3}get; {4}set; }}",
AccessibilityAndVirtual(Accessibility.ForProperty(navigationProperty)),
navigationProperty.ToEndMember.RelationshipMultiplicity == RelationshipMultiplicity.Many ? ("ICollection<" + endType + ">") : endType,
name,
_code.SpaceAfter(Accessibility.ForGetter(navigationProperty)),
_code.SpaceAfter(Accessibility.ForSetter(navigationProperty)));
}
If you place the above code in the class you still need to change 2 parts. You need to find the place where the constructor part and the navigation property part are being build up of the entity. In the constructor part (around line 60) you need to replace the existing code by calling the method GetPropertyNameForNavigationProperty and passing this into the escape method.
var propName = codeStringGenerator.GetPropertyNameForNavigationProperty(navigationProperty);
#>
this.<#=code.Escape(propName)#> = new HashSet<<#=typeMapper.GetTypeName(navigationProperty.ToEndMember.GetEntityType())#>>();
<#
And in the NavigationProperties part (around line 100) you also need to replace the code with the following.
var propName = codeStringGenerator.GetPropertyNameForNavigationProperty(navigationProperty);
#>
<#=codeStringGenerator.NavigationProperty(navigationProperty, propName)#>
<#
I hope this helps and you can always debug the GetPropertyNameForNavigationProperty function and play a little with the naming of the property.
Building on BikeMrown's answer, we can add Intellisense to the properties using the RelationshipName that is set in MSSQL:
Edit model.tt in your VS Project, and change this:
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")]
<#
}
#>
<#=codeStringGenerator.NavigationProperty(navigationProperty)#>
<#
}
}
to this:
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")]
<#
}
#>
/// <summary>
/// RelationshipName: <#=code.Escape(navigationProperty.RelationshipType.Name)#>
/// </summary>
<#=codeStringGenerator.NavigationProperty(navigationProperty)#>
<#
}
}
Now when you start typing a property name, you get a tooltip like this:
It's probably worth noting that if you change your DB model, the properties may find themselves pointing at different DB fields because the EF generates navigation property names based on their respective DB field name's alphabetic precedence!
Found this question/answer very helpful. However, I didn't want to do as much as Rikko's answer. I just needed to find the column name involved in the NavigationProperty and wasn't seeing how to get that in any of the samples (at least not without an edmx to pull from).
<#
var association = (AssociationType)navProperty.RelationshipType;
#> // <#= association.ReferentialConstraints.Single().ToProperties.Single().Name #>
The selected answer is awesome and got me going in the right direction for sure. But my big problem with it is that it took all of my already working navigation properties and appended the base type name to them, so you'd end up with with things like the following.
public virtual Need UnitNeed { get; set;}
public virtual ShiftEntered UnitShiftEntered {get; set;}`
So I dug into the proposed additions to the .tt file and modified them a bit to remove duplicate type naming and clean things up a bit. I figure there's gotta be someone else out there that would want the same thing so I figured I'd post my resolution here.
Here's the code to update within the public class CodeStringGenerator
public string GetPropertyNameForNavigationProperty(NavigationProperty navigationProperty, string entityname = "")
{
var ForeignKeyName = navigationProperty.RelationshipType.Name.Split('_');
var propertyName = "";
if (ForeignKeyName[ForeignKeyName.Length-1] != entityname){
var prepender = (ForeignKeyName[ForeignKeyName.Length-1].EndsWith(entityname)) ? ReplaceLastOccurrence(ForeignKeyName[ForeignKeyName.Length-1], entityname, "") : ForeignKeyName[ForeignKeyName.Length-1];
propertyName = prepender + navigationProperty.ToEndMember.Name;
}
else {
propertyName = navigationProperty.ToEndMember.Name;
}
return propertyName;
}
public string NavigationProperty(NavigationProperty navigationProperty, string name)
{
var endType = _typeMapper.GetTypeName(navigationProperty.ToEndMember.GetEntityType());
var truname = name;
if(navigationProperty.ToEndMember.RelationshipMultiplicity != RelationshipMultiplicity.Many){
if(name.Split(endType.ToArray<char>()).Length > 1){
truname = ReplaceLastOccurrence(name, endType, "");
}
}
return string.Format(
CultureInfo.InvariantCulture,
"{0} {1} {2} {{ {3}get; {4}set; }}",
AccessibilityAndVirtual(Accessibility.ForProperty(navigationProperty)),
navigationProperty.ToEndMember.RelationshipMultiplicity == RelationshipMultiplicity.Many ? ("ICollection<" + endType + ">") : endType,
truname,
_code.SpaceAfter(Accessibility.ForGetter(navigationProperty)),
_code.SpaceAfter(Accessibility.ForSetter(navigationProperty)));
}
public static string ReplaceLastOccurrence(string Source, string Find, string Replace)
{
int place = Source.LastIndexOf(Find);
if(place == -1)
return Source;
string result = Source.Remove(place, Find.Length).Insert(place, Replace);
return result;
}
and here's the code to update within the model generation,
update both occurrences of this:
var propName = codeStringGenerator.GetPropertyNameForNavigationProperty(navigationProperty)
to this
var propName = codeStringGenerator.GetPropertyNameForNavigationProperty(navigationProperty, entity.Name);

conditional either or validation in asp.net mvc2

In my registration page I have land line phone number and mobile number fields.
I need to ensure that the user needs to add at least one phone number either the land line or mobile.
How do I do this?
Thanks
Arnab
You could write a custom validation attribute and decorate your model with it:
[AttributeUsage(AttributeTargets.Class)]
public class AtLeastOnePhoneAttribute: ValidationAttribute
{
public override bool IsValid(object value)
{
var model = value as SomeViewModel;
if (model != null)
{
return !string.IsNullOrEmpty(model.Phone1) ||
!string.IsNullOrEmpty(model.Phone2);
}
return false;
}
}
and then:
[AtLeastOnePhone(ErrorMessage = "Please enter at least one of the two phones")]
public class SomeViewModel
{
public string Phone1 { get; set; }
public string Phone2 { get; set; }
}
For more advanced validation scenarios you may take a look at FluentValidation.NET or Foolproof.
Adding a solution that can be applied to individual properties, rather than overriding the validation method at the class level...
Create the following custom attribute. Note the "otherPropertyName" parameter in the constructor. This will allow you to pass in the other property to use in validation.
public class OneOrOtherRequiredAttribute: ValidationAttribute
{
public string OtherPropertyName { get; set; }
public OneOrOtherRequiredAttribute(string otherPropertyName)
{
OtherPropertyName = otherPropertyName;
}
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
var otherPropertyInfo = validationContext.ObjectType.GetProperty(OtherPropertyName);
var otherValue = (string)otherPropertyInfo.GetValue(validationContext.ObjectInstance, null);
if (string.IsNullOrEmpty(otherValue) && string.IsNullOrEmpty((string)value))
{
return new ValidationResult(this.ErrorMessage); //The error message passed into the Attribute's constructor
}
return null;
}
}
You can then decorate your properties like so: (be sure to pass in the name of the other property to compare with)
[OneOrOtherRequired("GroupNumber", ErrorMessage = "Either Group Number or Customer Number is required")]
public string CustomerNumber { get; set; }
[OneOrOtherRequired("CustomerNumber", ErrorMessage="Either Group Number or Customer Number is required")]
public string GroupNumber { get; set; }

ASP.net MVC Validation on mutiple controls

I have a strongly typed view which is bound to a ViewModel, one prupose of which is to capture the customers date of birth. To do this I have a number of fields within the ViewModel, defined as follows:
public DateTime DOB {get;set;}
public int? DOBDay
{
get
{
return _DOBDay;
}
set
{
_DOBDay = value;
SetDOB();
}
}
public int? DOBMonth
{
get
{
return _DOBMonth;
}
set
{
_DOBMonth = value;
SetDOB();
}
}
public int? DOBYear
{
get
{
return _DOBYear;
}
set
{
_DOBYear = value;
SetDOB();
}
}
public List<SelectListItem> DOBDayItems { get; set; }
public List<SelectListItem> DOBMonthItems { get; set; }
public List<SelectListItem> DOBYearItems { get; set; }
protected void SetDOB()
{
if (this.DOBDay.HasValue && this.DOBMonth.HasValue && this.DOBYear.HasValue)
{
DateTime dob = new DateTime(this.DOBYear.Value, this.DOBMonth.Value, this.DOBDay.Value);
//Check within smalldatetime range
if (dob < new DateTime(2079, 6, 6) && dob > new DateTime(1900, 1, 1))
{
this.DOB = dob;
}
}
}
This then facilitates 3 dropdowns on my form, bound to DOBDay, DOBMonth and DOBYear respectively (n.b. this has proven to be the easiest method of entering a date of birth in a number of user experience testing experiments we have carried out). The DOB is then set whenever any of these is changed which works fine.
I am using DataAnnotations to validate the form which works fine for validating each one of the 3 dropdowns (required / max values) however there is the additional validation needed to ensure that DOB is a valid date - 30 Feb 1985 would pass the individual dropdown validation however is not valid. I would like this to highlight all 3 controls, but potentially only be fired by the DOBYear drop down but am not sure how to go about this - is it possible?