ASP.Net MVC 3 - Client side unobtrusive validation

2019-02-05 03:25发布

I am new to ASP.Net MVC 3, facing some issues while trying to implementing client side unobtrusive validation for a editor template I have created for showing date in a custom way.

UI
I need to show date in a three texbox UI format as

enter image description here


I have put up a EditorTemplate for displaying the date in three parts as

@model DateTime?

<table class="datetime">
<tr>
    <td>@Html.TextBox("Day", (Model.HasValue ? Model.Value.ToString("dd") : string.Empty)) </td>
    <td class="separator">/</td>
    <td>@Html.TextBox("Month", (Model.HasValue ? Model.Value.ToString("MM") : string.Empty))</td>
    <td class="separator">/</td>
    <td>@Html.TextBox("Year", (Model.HasValue ? Model.Value.ToString("yyyy") : string.Empty))</td>
</tr>
<tr>
    <td class="label">dd</td>
    <td/>
    <td class="label">mm</td>
    <td/>
    <td class="label">yyyy</td>
</tr>
</table>

Model
I have to bind a Date of Birth field which is a property in a subobject of my model to this property, in this structure

MyModel
    --> MySubModel
          --> DateOfBirth

public class MySubModel
{
    ...

    [DataType(DataType.Date)]
    [Display(Name = "Date of birth")]
    [DateTimeClientValidation()]
    public DateTime DateofBirth { get; set; }

    ...
}

Clientside Validation

I have put up a custom validation attribute which implements IClientValidatable as

public class DateTimeClientValidationAttribute : ValidationAttribute, IClientValidatable
{
    ...

    public IEnumerable<ModelClientValidationRule> GetClientValidationRules(ModelMetadata metadata, ControllerContext context)
    {
        List<ModelClientValidationRule> clientRules = new List<ModelClientValidationRule>();

        //Combined date should be valid
        ModelClientValidationRule validDateRule = new ModelClientValidationRule
        {
            ErrorMessage = "Please enter a valid date.",
            ValidationType = "validdate"
        };
        validDateRule.ValidationParameters.Add("dayelement", metadata.PropertyName + ".Day");
        validDateRule.ValidationParameters.Add("monthelement", metadata.PropertyName + ".Month");
        validDateRule.ValidationParameters.Add("yearelement", metadata.PropertyName + ".Year");
        clientRules.Add(validDateRule);

        return clientRules;
    }
    ...
}

I am trying to emit the element names of Day, Month & Year textboxes here to client side validation elements, so that I will write a client side jquery validation method and adapter later which would consume those elements and do the validation at the client side.

View
Now, to use this editor template, I put in View the following lines

@model MyModel
...
<tr>
    <td class="editor-label">
        @Html.LabelFor(m => m.MySubModel.DateofBirth)
    </td>
    <td class="editor-field">
        @Html.EditorFor(m => m.MySubModel.DateofBirth)
        @Html.ValidationMessageFor(m => m.MySubModel.DateofBirth)
    </td>
</tr>
...

Added all relevant jquery validation files in the view as references

Questions

  1. This is not emitting out the unobtrusive javascript validation attributes in the html, though I have implemented IClientValidatable. For testing purpose when I put the same attribute (DateTimeClientValidation) on another property in the model which was not using this editor template, then it emitted out those validation attributes, it is not emitting it out only for this editor template. Where could have I gone wrong ?
  2. Regarding Validation Message span for the editor template, is it right that I put it in View only or should I put it directly in the editor template (@Html.ValidationMessageFor(m => m.MySubModel.DateofBirth))
  3. In this example, am I right in the design, I have put in DateTimeClientValidationAttribute, which actually is an attribute I put on model, but this component knows a bit about UI (since it is trying to emit out the Day, Month & Year elements name to the client), this makes Model know a bit about View, am I breaking any design principles here ?
  4. In DateTimeClientValidationAttribute, I am trying to emit out the day, month & year elements names to the client, so that the client script can do validations on it. But since the model property DateofBirth is in a subobject, the actual element name in the script is MySubObject.DateOfBirth, which makes the Day textbox name to be MySubObject.DateofBirth.Day, how can I find that fully qualified model name in the GetClientValidationRules method, so that I can emit out the name to the client ?

Thanks for being patient to read out all this, and for the answers

2条回答
叛逆
2楼-- · 2019-02-05 04:01

Ok so a few things. First - if at all possible, don't split the date up into three fields. You'll save yourself a world of headache by just setting it up as a single field with some validation and maybe jQueryUI's DatePicker.

Next, in your custom attribute code inside DateTimeClientValidationAttribute, you never actually check to see if the date is valid by implementing IsValid:

protected override ValidationResult IsValid(object value, 
    ValidationContext validationContext) 
{
   ...
   // Put date parts together and check is valid...
   if (DateTime.TryParse(year+"/"+month+"/"+day, out dateResult))
      return ValidationResult.Success;

   // Not valid
   return new ValidationResult(String.Format(ErrorMessageString, 
      validationContext.DisplayName));
}

Next you'll have to create an unobtrusive validation adapter and a jQuery validator method which puts the params together and tries to parse out the date:

jQuery.validator.unobtrusive.adapters.add(
    'validdate', // notice this is coming from how you named your validation rule
    ['dayelement'],
    ['monthelement'],
    ['yearelement']
    function (options) {
        options.rules['datepartcheck'] = options.params;
        options.messages['datepartcheck'] = options.message;
    }
);
jQuery.validator.addMethod('datepartcheck', function (value, element, params) {
    var year = params[2];
    var month = params[1];
    var day = params[0];

    var birthDate = year + '/' + month-1 + '/' + day;
    var isValid = true;

    try {
        // datepicker is a part of jqueryUI.
        // include it and you can take advantage of .parseDate:
        $.datepicker.parseDate('yy/mm/dd', birthDate);
    }
    catch (error) {
        isValid = false;
    }
    return isValid;
}, '');
查看更多
贪生不怕死
3楼-- · 2019-02-05 04:05

After some effort, I made the control to work, putting in if it is helpful to anybody out there.

The first point is to have the control defined as

@model DateTime?

<table class="datetime">
<tr>
    <td>@Html.TextBox("", (Model.HasValue ? Model.Value.ToString("dd") : string.Empty)) </td>
    <td class="separator">/</td>
    <td>@Html.TextBox("", (Model.HasValue ? Model.Value.ToString("MM") : string.Empty))</td>
    <td class="separator">/</td>
    <td>@Html.TextBox("", (Model.HasValue ? Model.Value.ToString("yyyy") : string.Empty))</td>
</tr>
<tr>
    <td class="label">dd</td>
    <td/>
    <td class="label">mm</td>
    <td/>
    <td class="label">yyyy</td>
</tr>
</table>

Make sure, you give empty strings as name to all the three textboxes, this forces the MVC framework to generate same name corresponding to Model DateofBirth for all the three textboxes in the client side. Also, unobtrusive javascript validation parameters would be generated for the first textbox in the list, since it is the first occurence of a edit control with the name of the model.

On the client side, on any event on any of these textboxes fires all the relevant validation events, since all these 3 textboxes have the same name.

On top of the regular validations like required,.... we have to write a custom client validation routine which would combine all these three values and checks whether the combined value forms a valid date. This can be achieved as in the answer given by Jeff.

查看更多
登录 后发表回答