Полукомплексная проверка свойств модели представления в ASP.NET MVC 3

Я изо всех сил пытаюсь завершить решение для проверки сервер-клиент для полусложного сценария. У меня есть основной тип под названием DateRange:

public class DateRange {
    public DateRange (DateTime? start, DateTime? end) { ... }
    public DateTime? Start { get; private set; }
    public DateTime? End { get; private set; }
}

У меня есть модель просмотра, например:

public class MyViewModel {
    public DateRange Period { get; set; }
}

У меня есть %mvcproject%\Views\Shared\EditorTemplates\DateRange.cshtml, например:

@model MyCore.DateRange

@Html.Editor("Start", "Date")
@Html.Editor("End", "Date")

У меня также есть DateRangeModelBinder для привязки двух входных данных формы к свойству DateRange. У меня проблема с DateRangeRequiredAttribute:

public class DateRangeRequired : ValidationAttribute, IClientValidatable,
    IMetadataAware
{
    private const string DefaultErrorMessage =
           "{0} is required.";

    public DateRangeRequired(bool endIsRequired = true)
        : base(() => DefaultErrorMessage)
    {
        EndIsRequired = endIsRequired;
    }

    public bool EndIsRequired { get; set; }

    public override bool IsValid(object value)
    {
        if (value == null)
        {
            return false;
        }
        if (!value.GetType().IsAssignableFrom(typeof(DateRange)))
        {
            throw new ArgumentException("Value is not a DateRange.");
        }
        var dateRange = value as DateRange;
        return (dateRange.Start.HasValue && !EndIsRequired) ||
               (dateRange.Start.HasValue && dateRange.End.HasValue && EndIsRequired);
    }

    public override string FormatErrorMessage(string name)
    {
        return string.Format(CultureInfo.CurrentCulture, ErrorMessageString, name);
    }

    public IEnumerable<ModelClientValidationRule> GetClientValidationRules(ModelMetadata metadata, ControllerContext context)
    {
        var rule = new ModelClientValidationRule()
        {
            ErrorMessage = FormatErrorMessage(metadata.GetDisplayName()),
            ValidationType = "daterangerequired"
        };
        rule.ValidationParameters.Add("endisrequired", EndIsRequired.ToString().ToLower());
        yield return rule;
    }

    public void OnMetadataCreated(ModelMetadata metadata)
    {
        metadata.DataTypeName = "DateRange";
    }
}

Я не могу подключить его к двум входам. Это похоже на то, что должен быть ValidatorTemplate, который сочетается с EditorTemplate из-за разделенных входных данных. Любые идеи? Дайте мне знать, если необходимы дополнительные разъяснения.




Ответы (1)


Вы не показали, как именно выглядит ваша пользовательская реализация DateRangeRequiredAttribute, поэтому позвольте мне предложить пример:

public class DateRangeRequiredAttribute : ValidationAttribute, IClientValidatable
{
    private readonly string _otherProperty;
    public DateRangeRequiredAttribute(string otherProperty)
    {
        _otherProperty = otherProperty;
    }

    protected override ValidationResult IsValid(object value, ValidationContext validationContext)
    {
        var property = validationContext.ObjectType.GetProperty(_otherProperty);
        if (property == null)
        {
            return new ValidationResult(string.Format(CultureInfo.CurrentCulture, "Unknown property {0}", _otherProperty));
        }
        var otherValue = property.GetValue(validationContext.ObjectInstance, null);
        if (!(value is DateTime) || !(otherValue is DateTime))
        {
            return new ValidationResult(string.Format(CultureInfo.CurrentCulture, "The two properties to compare must be of type DateTime"));
        }

        if ((DateTime)value >= (DateTime)otherValue)
        {
            return new ValidationResult(FormatErrorMessage(validationContext.DisplayName));
        }
        return null;
    }

    public IEnumerable<ModelClientValidationRule> GetClientValidationRules(ModelMetadata metadata, ControllerContext context)
    {
        var rule = new ModelClientValidationRule
        {
            ErrorMessage = FormatErrorMessage(metadata.GetDisplayName()),
            ValidationType = "daterange"
        };
        rule.ValidationParameters.Add("other", "*." + _otherProperty);
        yield return rule;
    }
}

то вы можете украсить ею свою модель представления:

public class DateRange
{
    [DisplayFormat(ApplyFormatInEditMode = true, DataFormatString = "{0:d}")]
    [DateRangeRequired("End", ErrorMessage = "Please select a start date before the end date")]
    public DateTime? Start { get; set; }

    [DisplayFormat(ApplyFormatInEditMode = true, DataFormatString = "{0:d}")]
    [Required]
    public DateTime? End { get; set; }
}

и, наконец, в представлении зарегистрируйте адаптер:

jQuery.validator.unobtrusive.adapters.add(
    'daterange', ['other'], function (options) {
        var getModelPrefix = function (fieldName) {
            return fieldName.substr(0, fieldName.lastIndexOf(".") + 1);
        };
        var appendModelPrefix = function (value, prefix) {
            if (value.indexOf('*.') === 0) {
                value = value.replace('*.', prefix);
            }
            return value;
        };
        var prefix = getModelPrefix(options.element.name),
        other = options.params.other,
        fullOtherName = appendModelPrefix(other, prefix),
        element = $(options.form).find(':input[name="' + fullOtherName + '"]')[0];

        options.rules['daterange'] = element;
        if (options.message) {
            options.messages['daterange'] = options.message;
        }
    }
);

jQuery.validator.addMethod('daterange', function (value, element, params) {
    // TODO: some more advanced date checking could be applied here
    // currently it uses the current browser culture setting to perform
    // the parsing. If you needed to use the server side culture, this code
    // could be adapted respectively

    var date = new Date(value);
    var otherDate = new Date($(params).val());
    return date < otherDate;
}, '');

После прочтения этой порнографии вы можете подумать об использовании FluentValidation.NET, который отображает этот чрезвычайно простой сценарий проверки на пару строк в реализовать (именно так должны выполняться такие простые сценарии проверки). Я настоятельно рекомендую вам эту библиотеку. Я использую его во всех своих проектах, потому что мне надоели DataAnnotations для проверки. Они так довольно ограничены.

person Darin Dimitrov    schedule 20.12.2011
comment
это выглядит хорошо и завершено. однако это не то, что я пытался сделать со своим атрибутом. DateRange не является моделью представления, и не для каждой модели представления со свойством DateRange требуется дата окончания. Я опубликую свой код атрибута для уточнения. - person gabe; 22.12.2011
comment
@Дарин, например, FluentValidation.NET. Однако проверка на стороне клиента отсутствует. Как вы его продлеваете? Проверка на основе DataAnnotation может быть расширена, даже если код иногда сумасшедший. - person amit_g; 17.02.2012