Asp.NET MVC: checking age> 18 having 3 combo boxes

I have 3 combo fields showing the day, month and year of birth in MVC. I would like to calculate the age and prohibit registration for guys under 18. While JS.

Something similar to what is shown in this image: enter image description here

This is done using DataAnnotations and EditorFor. The actual source code is similar to the following. How do I change to test the three controls together?

[Required(ErrorMessageResourceType = typeof (Resources), 
          ErrorMessageResourceName = "RequiredField")]
[Range(1, 31)]
[LocalizedDisplayName(typeof (RA.Resources), "RegistrationDayOfBirth")]
public int BirthDay { get; set; }

[Required(ErrorMessageResourceType = typeof (Resources), 
          ErrorMessageResourceName = "RequiredField")]
[Range(1, 12)]
[LocalizedDisplayName(typeof (RA.Resources), "RegistrationMonthOfBirth")]
public int BirthMonth { get; set; }

[Required(ErrorMessageResourceType = typeof (Resources), 
          ErrorMessageResourceName = "RequiredField")]
[Range(1800, int.MaxValue, ErrorMessageResourceType = typeof (Resources),
       ErrorMessageResourceName = "MoreThanFieldRequired")]
[LocalizedDisplayName(typeof (RA.Resources), "RegistrationYearOfBirth")]
public int BirthYear { get; set; }

[LocalizedDisplayName(typeof (RA.Resources), "RegistrationDateOfBirth")]
public DateTime DateOfBirth { get; set; }

      

+3


source to share


2 answers


If you want to stick with the 3-field approach and also have dynamic validation (i.e. deny me access today if it's my 18th birthday tomorrow, but let me be tomorrow), you'll need to get creative.

Then you will need to create a custom validator and some custom attributes to go with it.

How you decide this depends on the amount of work you want to do and where you want to apply the validation logic.

Server side validation only

The easiest option is to define this in the class itself, but this will only limit the server side validation.

Create a custom attribute applied at the class level that expects to have three fields (I added an interface to make this easier and doesn't require reflection) and validate this as needed:

// Interface to define individual fields:
public interface IHasIndividualDateOfBirth
{
  int BirthDay { get; set; }
  int BirthMonth { get; set; }
  int BirthYear { get; set; }
}

// Note new class level attribute, and interface declaration:
[MinAge(AgeInYears = 18)]
public class Birthday: IHasIndividualDateOfBirth
{
  [Required]
  [Range(1, 31)]
  public int BirthDay { get; set; }
  [Required]
  [Range(1, 12)]
  public int BirthMonth { get; set; }
  [Required]
  [Range(1800, 2200)]
  public int BirthYear { get; set; }

  public DateTime BirthDate { get; set; }
}

// Declare a new ValidationAttribute that can be used at the class level:
[AttributeUsage(AttributeTargets.Class)]
public class MinAgeAttribute : ValidationAttribute
{
  public int AgeInYears { get; set; }

  // Implement IsValid method:
  protected override ValidationResult IsValid(object value, 
                                              ValidationContext validationContext)
  {
    // Retrieve the object that was passed in as our DateOfBirth type
    var objectWithDob = validationContext.ObjectInstance 
                          as IHasIndividualDateOfBirth;

    if (null != objectWithDob)
    {
      // TODO: Handle invalid dates from the front-end (30 Feb for example)
      DateTime dateOfBirth = new DateTime(objectWithDob.BirthYear, 
                                          objectWithDob.BirthMonth, 
                                          objectWithDob.BirthDay);

      // Check that the age is more than the minimum requested
      if (DateTime.Now >= dateOfBirth.AddYears(AgeInYears))
      {
        return ValidationResult.Success;
      }

      return new ValidationResult("You are not yet 18 years old");
    }

    return new ValidationResult("Class doesn't implement IHasIndividualBirthday");
  }
}

      

While the implementation IValidatableObject

may seem simpler, it is not as flexible as using an attribute, and also (like the class-based validation above) does not provide a client-side validation method.



Other options would be to create a validator that depends on a number of other fields (in which case you will probably need to use reflection to look for other fields and work out which ones go) and you will need to make sure you only run the validator once (not per field) or write your own validator and editor for the DateTime property so that instead of displaying a single field that you could remove the calendar control it creates three separate fields: after.

Client-side and server-side validation

To test client side work, you will need to do it at the property level, which will require additional work for you - you can, for example, use the DateTime field that you have on the model as a hidden field that is filled through JS when the user fills in the individual fields and then confirms it.

Then your attribute would have to implement IClientValidatable

, which would allow you to hook into client-side validation options, and also display some metadata of the elements to reveal the age requirement:

[AttributeUsage(AttributeTargets.Property)]
public class MinAgeAttribute : ValidationAttribute, IClientValidatable
{
  public int AgeInYears { get; set; }

  protected override ValidationResult IsValid(object value,
                                              ValidationContext validationContext)
  {
    // [Similar to before]
  }

  public IEnumerable<ModelClientValidationRule> GetClientValidationRules(
                                                        ModelMetadata metadata,
                                                        ControllerContext context)
  {
    return new[]
    {
      new ModelClientValidationMinAgeRule(ErrorMessage, AgeInYears)
    };
  }
}

public class ModelClientValidationMinAgeRule : ModelClientValidationRule
{
  public ModelClientValidationMinAgeRule(string errorMessage, int ageInYears)
  {
      ErrorMessage = errorMessage;
      // Validation Type and Parameters must be lowercase
      ValidationType = "minage";
      ValidationParameters.Add("ageinyears", ageInYears);
  }
}

      

Then for the client side, you need to register some custom validators in jQuery.Validate or similar (I recommend your own JS file included in the kit jqueryval

):

$(function ($) {
  $.validator.addMethod("minage", function(value, element, params) {
    if ($(element).val() != '') {

      var ageInYears = params;

      // take date from BirthDate element and compare with ageInYears.
      return false;
    }
  });

  $.validator.unobtrusive.adapters.addSingleVal("minage", "ageinyears");
}(jQuery));

      

+3


source


Zhaph's answer is good, but it doesn't mention how to approach this from a client perspective once you've implemented his solution. In fact, it switches validation with three separate fields for one new BirthDate

. Therefore, you will need to include the validation result from this property on the page, rather than three separate fields. You usually do something like:

<label for="@Html.IdFor(m => m.BirthMonth)">
    @Html.DisplayNameFor(m => m.BirthDate)
</label>
<div>
    @Html.EditorFor(m => m.BirthMonth)
    @Html.EditorFor(m => m.BirthDay)
    @Html.EditorFor(m => m.BirthYear)
</div>
@Html.ValidationMessageFor(m => m.BirthDate)

      

Key points:



  • The label is for the first field in the group, so clicking on the label will take the user to the field as it should. However, the actual label value is in the field BirthDate

    , so it represents a whole group of fields instead of the first one in this case BirthMonth

    .

  • The validation message also comes from a field BirthDate

    that will print something like "You are under 18", based on the Zhaph implementation, only once.

to implement client side controls you can use jQuery plugins like jQuery validation plugin and have a look at this article: ASP Client Side Validation MVC

+1


source







All Articles