gig*_*igi 5 jquery unobtrusive-validation asp.net-mvc-3
我有一个模型来验证,问题是出生日期.它必须由3个下拉列表组成(日,月,年).
<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);
        }
问题:
LE:我为这样的日期创建了一个自定义模型绑定器:
    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());
我用它是这样的:
public ActionResult Register([ModelBinder(typeof(DobModelBinder))]RegistrationModel regInfo)
DateOfBirth绑定良好.
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"
        };
    }
}
我应用了这样的属性
        [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;
            });
        });
客户端验证似乎不起作用.我该如何解决?我对这些适配器的工作方式感到困惑.
Dar*_*rov 14
您可以使用自定义编辑器模板.
在进入实现细节之前,让我们先看一下最终解决方案的外观.
所以我们可以有一个视图模型(一如既往)用一些数据注释属性装饰,指示我们想要附加到它的元数据:
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; }
}
然后我们可以有一个控制器:
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
            )
        );
    }
}
一个视图(~/Views/Home/Index.cshtml):
@model MyViewModel
@using (Html.BeginForm())
{
    @Html.EditorFor(x => x.Dob)
    <button type="submit">OK</button>
}
以及一个相应的编辑器模板,它允许我们显示3个下拉列表,用于编辑DateTime字段而不是简单的文本框(~/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]属性:
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]属性修饰的属性相关联,以便执行解析:
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;
    }
}
请注意,如果字段未使用自定义属性修饰,则绑定器如何仅使用默认绑定器.这样它就不会干扰我们不想要tripple ddl行为的其他DateTime字段.模型绑定器将简单地与以下DateTime?类型相关联Application_Start:
ModelBinders.Binders.Add(typeof(DateTime?), new TrippleDDLDateTimeModelBinder());
好的,到目前为止,我们有一个执行服务器端验证的解决方案.这始终是你应该开始的.因为那里你也可以停下来,仍然有一个安全和工作的网站.
当然,如果您有时间,您现在可以通过实施客户端验证来改善用户体验.客户端验证不是强制性的,但它可以节省带宽并避免服务器往返.
因此,我们首先使我们的2个自定义属性实现IClientValidatable接口,这是启用不显眼的客户端验证的第一步.
[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两个属性.剩下的就是编写相应的不显眼的适配器.
这当然应该在一个单独的javascript文件中完成.例如,它可能是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个必要的脚本,以启用不显眼的客户端验证:
<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>
| 归档时间: | 
 | 
| 查看次数: | 9885 次 | 
| 最近记录: |