如何验证使用3下拉菜单(日,月,年)使用jQuery不显眼的审定一个日期?

问题描述:

我有一个模型来验证和问题是生育字段的日期。
它必须由3下拉菜单(日,月,年)。

I have a model to validate and the problem is the date of birth field. It must be composed of 3 dropdowns (day, month, year).

<div id="dob-editor-field" class="model-field-editor">
      @Html.LabelFor(m => m.DateOfBirth, new { @class = "label-div" })
      @Html.Telerik().DropDownList().Name("DobDay").BindTo((SelectList)ViewData["Days"]).HtmlAttributes(new {id = "DobDaySel"})
      @Html.Telerik().DropDownList().Name("DobMonth").BindTo((SelectList)ViewData["Months"]).HtmlAttributes(new { id = "DobMonthSel"})
      @Html.Telerik().DropDownList().Name("DobYear").BindTo((SelectList)ViewData["Years"]).HtmlAttributes(new { id = "DobYearSel" })
      @Html.ValidationMessageFor(m => m.DateOfBirth)
</div>

在服务器端我这样做

        [HttpPost]
        public ActionResult Register(RegistrationModel regInfo, int DobDay, int DobMonth, int DobYear)
        {
            SetRegisterViewData(DobDay, DobMonth, DobYear);
            if (DobDay == 0 || DobMonth == 0 && DobYear == 0)
            {
                ModelState.AddModelError("DateOfBirth", "Date of birth is required");
            }
            else
            {
                DateTime dt = new DateTime(DobYear, DobMonth, DobDay);
                long ticks = DateTime.Now.Ticks - dt.Ticks;
                int years = new DateTime(ticks).Year;
                if (years < 18)
                {
                    ModelState.AddModelError("DateOfBirth", "You must be at least 18");
                }
            }            
            if (ModelState.IsValid)
            {
                //register user
                return RedirectToAction("Index", "Home");
            }
            return View(regInfo);
        }

问题:


  1. 服务器端:如何使它更好? (我想加入DOB的,
    每月,每年的属性RegistrationModel并添加属性上
    DATEOFBIRTH检查这些属性)

  2. 客户端:我在看Perform自定义客户端验证属性,但它让我困惑。是什么让它的方式?

  1. Server side : how to make it better? (i am thinking of adding dob, month, and year properties RegistrationModel and add attribute on DateOfBirth to check those properties)
  2. Client side : i was looking at Perform client side validation for custom attribute but it got me confused. What is the way to make it?

LE:
我创建了一个自定义模型粘合剂这样的日期:

LE: I created a custom model binder for the date like this:

    public class DobModelBinder : DefaultModelBinder
    {
        protected override void BindProperty(ControllerContext controllerContext, ModelBindingContext bindingContext, System.ComponentModel.PropertyDescriptor propertyDescriptor)
        {
            if (propertyDescriptor.Name == "DateOfBirth")
            {
                DateTime dob = DateTime.MinValue;
                var form = controllerContext.HttpContext.Request.Form;
                int day = Convert.ToInt32(form["DobDay"]);
                int month = Convert.ToInt32(form["DobMonth"]);
                int year = Convert.ToInt32(form["DobYear"]);
                if (day == 0 || month == 0 || year == 0)
                {
                    SetProperty(controllerContext, bindingContext, propertyDescriptor, DateTime.MinValue);
                }
                else
                {
                    SetProperty(controllerContext, bindingContext, propertyDescriptor, new DateTime(year, month, day));
                }
            }
            else
            {
                base.BindProperty(controllerContext, bindingContext, propertyDescriptor);
            }
        }
    }

我注册这样的:

ModelBinders.Binders.Add(typeof(DateTime), new DobModelBinder());

我用它是这样的:

I used it like this:

public ActionResult Register([ModelBinder(typeof(DobModelBinder))]RegistrationModel regInfo)

DATEOFBIRTH结合好。

DateOfBirth binds well.

LE2:

我创建的验证属性:

 public override bool IsValid(object value)
    {
        DateTime date = Convert.ToDateTime(value);
        return date != DateTime.MinValue;
    }

    public IEnumerable<ModelClientValidationRule> GetClientValidationRules(ModelMetadata metadata, ControllerContext context)
    {
        yield return new ModelClientValidationRule
        {
            ErrorMessage = this.ErrorMessage,
            ValidationType = "dateRequired"
        };
    }
}

public class DateGraterThanEighteen : ValidationAttribute, IClientValidatable
{
    public override bool IsValid(object value)
    {
        DateTime date = Convert.ToDateTime(value);
        long ticks = DateTime.Now.Ticks - date.Ticks;
        int years = new DateTime(ticks).Year;
        return years >= 18;
    }

    public IEnumerable<ModelClientValidationRule> GetClientValidationRules(ModelMetadata metadata, ControllerContext context)
    {
        yield return new ModelClientValidationRule
        {
            ErrorMessage = this.ErrorMessage,
            ValidationType = "dateGraterThanEighteen"
        };
    }
}

我申请这样的属性。

I applied attributes like this

        [DateGraterThanEighteen(ErrorMessage="You must be at least 18")]
        [DateRequired(ErrorMessage = "Date of birth is required")]
        public DateTime DateOfBirth { get; set; }

LE3:

在客户端我这样做:

      $(function () {
            jQuery.validator.addMethod('dobRequired', function (value, element, params) {
                if (!/Invalid|NaN/.test(new Date(value))) {
                    return true;
                }
                else {
                    return false;
                }
            }, '');
            jQuery.validator.unobtrusive.adapters.add('dateRequired', {}, function (options) {
                options.rules['dobRequired'] = true;
                options.messages['dobRequired'] = options.message;
            });
        });

客户端验证不似乎工作。
我该如何解决?我与这些适配器的工作方式有点糊涂了。

Client validation doesn't seems to work. How can I fix it? I am kinda confused with the way these adapters work.

您可以使用自定义编辑模板。

You could use a custom editor template.

让我们先来看看最终的解决方案可能看起来怎么样先进入实施细节之前。

Let's first look at how the final solution might look like first before getting into implementation details.

因此​​,我们可以有一个视图模型(一如既往)饰有一些数据注解属性指示的元数据,我们想附加到它:

So we could have a view model (as always) decorated with some data annotation attributes indicating the metadata we would like to attach to it:

public class MyViewModel
{
    [DisplayName("Date of birth:")]
    [TrippleDDLDateTime(ErrorMessage = "Please select a valid DOB")]
    [Required(ErrorMessage = "Please select your DOB")]
    [MinAge(18, ErrorMessage = "You must be at least 18 years old")]
    public DateTime? Dob { get; set; }
}

那么我们就可以有一个控制器:

then we could have a controller:

public class HomeController : Controller
{
    public ActionResult Index()
    {
        var model = new MyViewModel();
        return View(model);
    }

    [HttpPost]
    public ActionResult Index(MyViewModel model)
    {
        if (!ModelState.IsValid)
        {
            return View(model);
        }

        return Content(
            string.Format(
                "Thank you for selecting your DOB: {0:yyyy-MM-dd}", 
                model.Dob
            )
        );
    }
}

视图(〜/查看/主页/ Index.cshtml

@model MyViewModel
@using (Html.BeginForm())
{
    @Html.EditorFor(x => x.Dob)
    <button type="submit">OK</button>
}

和相应的编辑模板,这将使我们能够为编辑日期时间字段,而不是一个简单的文本框(〜/查看/共享/ EditorTemplates / TrippleDDLDateTime.cshtml $ C $ 3显示下拉列表C>)

and a corresponding editor template which will allow us to display 3 dropdown lists for editing the DateTime field instead of a simple textbox (~/Views/Shared/EditorTemplates/TrippleDDLDateTime.cshtml):

@{
    var now = DateTime.Now;
    var years = Enumerable.Range(0, 150).Select(x => new SelectListItem { Value = (now.Year - x).ToString(), Text = (now.Year - x).ToString() });
    var months = Enumerable.Range(1, 12).Select(x => new SelectListItem { Value = x.ToString("00"), Text = x.ToString() });
    var days = Enumerable.Range(1, 31).Select(x => new SelectListItem { Value = x.ToString("00"), Text = x.ToString() });

    var result = ViewData.ModelState[ViewData.TemplateInfo.HtmlFieldPrefix];
    if (result != null)
    { 
        var values = result.Value.RawValue as string[];
        years = new SelectList(years, "Value", "Text", values[0]);
        months = new SelectList(months, "Value", "Text", values[1]);
        days = new SelectList(days, "Value", "Text", values[2]);
        result.Value = null;
    }
}

<div class="trippleddldatetime">
    @Html.Label("")

    @Html.DropDownList("", years, "-- year --")
    @Html.DropDownList("", months, "-- month --")
    @Html.DropDownList("", days, "-- day --")

    @Html.ValidationMessage("")
</div>

现在让我们来看看 [TrippleDDLDateTime] 属性中如何实现:

Now let's see how the [TrippleDDLDateTime] attribute could be implemented:

public class TrippleDDLDateTimeAttribute : ValidationAttribute, IMetadataAware
{
    public void OnMetadataCreated(ModelMetadata metadata)
    {
        metadata.TemplateHint = "TrippleDDLDateTime";
    }

    public override bool IsValid(object value)
    {
        // It's the custom model binder that is responsible for validating 
        return true;
    }
}

注意属性如何实现 IMetadataAware 接口,可以让我们的视图模型财产我们写的自定义编辑器模板相关联的( TrippleDDLDateTime.cshtml )。

和接下来是 [MINAGE] 属性:

public class MinAgeAttribute : ValidationAttribute
{
    private readonly int _minAge;
    public MinAgeAttribute(int minAge)
    {
        _minAge = minAge;
    }

    public override bool IsValid(object value)
    {
        if (value == null)
        {
            return true;
        }

        DateTime date = Convert.ToDateTime(value);
        long ticks = DateTime.Now.Ticks - date.Ticks;
        int years = new DateTime(ticks).Year;
        return years >= _minAge;
    }
}

拼图的最后一块是编写一个将关联到的属性与 [TrippleDDLDateTime] 属性来修饰,以完成解析的自定义模型绑定:

The last piece of the puzzle is to write a custom model binder that will be associated to properties decorated with the [TrippleDDLDateTime] attribute in order to perform the parsing:

public class TrippleDDLDateTimeModelBinder : DefaultModelBinder
{
    public override object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
    {
        var metadata = bindingContext.ModelMetadata;
        var trippleDdl = metadata.ContainerType.GetProperty(metadata.PropertyName).GetCustomAttributes(typeof(TrippleDDLDateTimeAttribute), true).FirstOrDefault() as TrippleDDLDateTimeAttribute;
        if (trippleDdl == null)
        {
            return base.BindModel(controllerContext, bindingContext);
        }

        var prefix = bindingContext.ModelName;
        var value = bindingContext.ValueProvider.GetValue(prefix);
        var parts = value.RawValue as string[];
        if (parts.All(string.IsNullOrEmpty))
        {
            return null;
        }

        bindingContext.ModelState.SetModelValue(prefix, value);

        var dateStr = string.Format("{0}-{1}-{2}", parts[0], parts[1], parts[2]);
        DateTime date;
        if (DateTime.TryParseExact(dateStr, "yyyy-MM-dd", CultureInfo.InvariantCulture, DateTimeStyles.None, out date))
        {
            return date;
        }

        bindingContext.ModelState.AddModelError(prefix, trippleDdl.ErrorMessage);

        return null;
    }
}

注意如何粘结剂简单地使用默认的粘结剂如果该字段不与自定义属性装饰。这样,它不与其他日期时间字段的干扰,我们不希望特里普尔DDL行为。该模型将粘结剂简单地与相关的的DateTime 键入的Application_Start

ModelBinders.Binders.Add(typeof(DateTime?), new TrippleDDLDateTimeModelBinder());

OK,到目前为止,我们已经执行服务器端验证的解决方案。这总是你应该开始什么。因为这就是你也可以停止,仍然有一个安全和工作现场。

OK, so far we have a solution that performs server side validation. That's always what you should start with. Because that's where you can also stop and still have a safe and working site.

当然,如果你有时间,你现在就可以提高通过实施客户端验证的用户体验。客户端验证是不是强制性的,但它节省了带宽,避免服务器往返。

Of course if you have time you could now improve the user experience by implementing client side validation. Client side validation is not compulsory, but it saves bandwidth and avoids server round-trips.

因此​​,我们通过我们的2自定义属性实施的 IClientValidatable 接口,是实现不显眼的客户端验证的第一步。

So we start by making our 2 custom attributes implement the IClientValidatable interface which is the first step in enabling unobtrusive client side validation.

[TrippleDDLDateTime]

public class TrippleDDLDateTimeAttribute : ValidationAttribute, IMetadataAware, IClientValidatable
{
    public void OnMetadataCreated(ModelMetadata metadata)
    {
        metadata.TemplateHint = "TrippleDDLDateTime";
    }

    public override bool IsValid(object value)
    {
        // It's the custom model binder that is responsible for validating 
        return true;
    }

    public IEnumerable<ModelClientValidationRule> GetClientValidationRules(ModelMetadata metadata, ControllerContext context)
    {
        var rule = new ModelClientValidationRule();
        rule.ErrorMessage = ErrorMessage;
        rule.ValidationType = "trippleddldate";
        yield return rule;
    }
}

[MINAGE]

public class MinAgeAttribute : ValidationAttribute, IClientValidatable
{
    private readonly int _minAge;
    public MinAgeAttribute(int minAge)
    {
        _minAge = minAge;
    }

    public override bool IsValid(object value)
    {
        if (value == null)
        {
            return true;
        }

        DateTime date = Convert.ToDateTime(value);
        long ticks = DateTime.Now.Ticks - date.Ticks;
        int years = new DateTime(ticks).Year;
        return years >= _minAge;
    }

    public IEnumerable<ModelClientValidationRule> GetClientValidationRules(ModelMetadata metadata, ControllerContext context)
    {
        var rule = new ModelClientValidationRule();
        rule.ErrorMessage = ErrorMessage;
        rule.ValidationType = "minage";
        rule.ValidationParameters["min"] = _minAge;
        yield return rule;
    }
}

好了,我们已经实现了 GetClientValidationRules 两个属性。所有剩下的就是编写相应的不显眼的适配器。

OK, so we have implemented the GetClientValidationRules on both attributes. All that's left is to write the corresponding unobtrusive adapters.

这应该理所当然的单独的JavaScript文件来完成。例如,它可能是 trippleddlAdapters.js

This should be done in a separate javascript file of course. For example it could be trippleddlAdapters.js:

(function ($) {
    $.fn.getDateFromTrippleDdls = function () {
        var year = this.find('select:nth(0)').val();
        var month = this.find('select:nth(1)').val();
        var day = this.find('select:nth(2)').val();
        if (year == '' || month == '' || day == '') {
            return NaN;
        }

        var y = parseInt(year, 10);
        var m = parseInt(month, 10);
        var d = parseInt(day, 10);

        var date = new Date(y, m - 1, d);
        var isValidDate = date.getFullYear() == y && date.getMonth() + 1 == m && date.getDate() == d;
        if (isValidDate) {
            return date;
        }

        return NaN;
    };

    $.validator.unobtrusive.adapters.add('trippleddldate', [], function (options) {
        options.rules['trippleddldate'] = options.params;
        if (options.message) {
            options.messages['trippleddldate'] = options.message;
        }
    });

    $.validator.addMethod('trippleddldate', function (value, element, params) {
        var parent = $(element).closest('.trippleddldatetime');
        var date = parent.getDateFromTrippleDdls();
        console.log(date);
        return !isNaN(date);
    }, '');

    $.validator.unobtrusive.adapters.add('minage', ['min'], function (options) {
        options.rules['minage'] = options.params;
        if (options.message) {
            options.messages['minage'] = options.message;
        }
    });

    $.validator.addMethod('minage', function (value, element, params) {
        var parent = $(element).closest('.trippleddldatetime');
        var birthDate = parent.getDateFromTrippleDdls();
        if (isNaN(birthDate)) {
            return false;
        }

        var today = new Date();
        var age = today.getFullYear() - birthDate.getFullYear();
        var m = today.getMonth() - birthDate.getMonth();
        if (m < 0 || (m === 0 && today.getDate() < birthDate.getDate())) {
            age--;
        }
        return age >= parseInt(params.min, 10);
    }, '');
})(jQuery);

最后,我们包括3必要的脚本的网页,使不显眼的客户端验证:

Finally we include the 3 necessary scripts to the page to enable the unobtrusive client side validation:

<script src="@Url.Content("~/Scripts/jquery.validate.js")" type="text/javascript"></script>
<script src="@Url.Content("~/Scripts/jquery.validate.unobtrusive.js")" type="text/javascript"></script>
<script src="@Url.Content("~/Scripts/trippleddlAdapters.js")" type="text/javascript"></script>