Пользовательский атрибут C# требуется, если

Я только начинаю изучать пользовательские атрибуты, и они мне очень нравятся. Мне интересно, можно ли создать атрибут, который применяется к свойству и обозначает имя другого свойства в том же объекте. Если бы проверить, имеет ли указанное свойство значение, и если да, то потребуется украшенный атрибут. Что-то вроде этого:

[RequiredIfNotNull("ApprovedDate")]
[DisplayName("Approved By")]
[StringLength(50, ErrorMessage = "{0} must not exceed {1} characters")]
public string ApprovedBy { get; set; }

[DisplayName("Approved Date")]
[DisplayFormat(DataFormatString = "{0:d}")]
[PropertyMetadata(ColumnName = "ApprovedDate")]
public DateTime? ApprovedDate { get; set; }

Таким образом, свойство «утверждено» дополнено атрибутом RequiredIfNotNull, который ссылается на свойство для проверки на значение null. В данном случае Дата утверждения. Я бы хотел, чтобы свойство ApprovedBy требовалось, если ApprovedDate имел значение. Можно ли сделать что-то подобное? Если да, можете ли вы реализовать его на стороне сервера и на стороне клиента?


person Jim Shaffer    schedule 12.11.2014    source источник
comment
Что-то вроде этого: kevww.wordpress.com/2013/02/28/ ?   -  person b2zw2a    schedule 13.11.2014
comment
возможный дубликат RequiredIf атрибута условной проверки   -  person artm    schedule 13.11.2014
comment
Я не думаю, что это перебор. Если вы реализуете пользовательский атрибут, вы можете написать код на стороне сервера и на стороне клиента, чтобы он функционировал так же, как и все другие атрибуты кода, в противном случае вам пришлось бы писать собственное решение в коде без реализации на стороне клиента.   -  person Jim Shaffer    schedule 13.11.2014
comment
Спасибо за ссылку на возможный дубликат. Это похоже именно на то, что я ищу.   -  person Jim Shaffer    schedule 13.11.2014
comment
Когда у вас есть молоток, все выглядит как свойство, которое вы хотите приписать.   -  person crthompson    schedule 13.11.2014
comment
Я не совсем понимаю, что вы имеете ввиду. Добавление настраиваемых атрибутов позволяет реализовать дополнительные настраиваемые требования точно так же, как работает остальная часть кода MVC. Он обеспечивает беспрепятственную реализацию как на стороне сервера, так и на стороне клиента, и после создания атрибута вам никогда не придется писать ни одной строки дополнительной логики проверки. Может быть, я что-то упускаю. У тебя, видимо, есть идея получше?   -  person Jim Shaffer    schedule 13.11.2014


Ответы (1)


Вот что у меня получилось: Серверная часть:

using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Reflection;
using System.Web.Mvc;   
namespace Atlas.Core.Attributes
{
    /// <summary>
    /// Add the following decoration: [ConditionalRequired("Model", "Field")]
    /// Model = client model being used to bind object
    /// Field = the field that if not null makes this field required.
    /// </summary>
    public class ConditionalRequiredAttribute : ValidationAttribute, IClientValidatable
    {
        private const string DefaultErrorMessageFormatString = "The {0} field is required.";
        private readonly string _dependentPropertyPrefix;
        private readonly string _dependentPropertyName;

        public ConditionalRequiredAttribute(string dependentPropertyPrefix, string dependentPropertyName)
        {
            _dependentPropertyPrefix = dependentPropertyPrefix;
            _dependentPropertyName = dependentPropertyName;
            ErrorMessage = DefaultErrorMessageFormatString;
        }

        protected override ValidationResult IsValid(object item, ValidationContext validationContext)
        {
            PropertyInfo property = validationContext.ObjectInstance.GetType().GetProperty(_dependentPropertyName);
            object dependentPropertyValue = property.GetValue(validationContext.ObjectInstance, null);

            if (dependentPropertyValue != null && item == null)
                return new ValidationResult(string.Format(ErrorMessageString, validationContext.DisplayName));

            return ValidationResult.Success;
        }

        public IEnumerable<ModelClientValidationRule> GetClientValidationRules(ModelMetadata metadata, ControllerContext context)
        {
            var rule = new ModelClientValidationRule
            {
                ErrorMessage = string.Format("{0} is required", metadata.GetDisplayName()),
                ValidationType = "conditionalrequired",
            };

            rule.ValidationParameters.Add("requiredpropertyprefix", _dependentPropertyPrefix);
            rule.ValidationParameters.Add("requiredproperty", _dependentPropertyName);
            yield return rule;
        }
    }
}

Сторона клиента:

$.validator.unobtrusive.adapters.add('conditionalrequired', ['requiredpropertyprefix', 'requiredproperty'], function (options) {
        options.rules['conditionalrequired'] = {
            requiredpropertyprefix: options.params['requiredpropertyprefix'],
            requiredproperty: options.params['requiredproperty']
        };
        options.messages['conditionalrequired'] = options.message;
});

$.validator.addMethod('conditionalrequired', function (value, element, params) {
        var requiredpropertyprefix = params['requiredpropertyprefix'];
        var requiredproperty = params['requiredproperty'];
        var field = $('#' + requiredproperty).length == 0 ? '#' + requiredpropertyprefix + '_' + requiredproperty : '#' + requiredproperty;
        return !($(field).val().length > 0 && value.length == 0);
    }
);

Я настроил это, чтобы принять модель или значение префикса, а затем имя фактического поля. Причина этого в том, что во многих случаях я буду добавлять объект как часть модели, и это приведет к тому, что идентификатор формы для этого элемента будет отображаться как ModelName_FieldName. Но мне также пришло в голову, что вы можете или не можете использовать модель со встроенным объектом. В этом случае идентификатор будет просто FieldName, поэтому код на стороне клиента проверяет, существует ли элемент по FieldName, и если нет, он возвращает ModelName_FieldName, в противном случае он просто возвращает FieldName. Я еще этого не делал, но, вероятно, мне следует проверить, чтобы полученное имя поля не было нулевым.

а затем, чтобы украсить свою собственность, вы должны сделать что-то вроде этого:

[DataMember]
[DisplayName("Approved By")]
[ConditionalRequired("HOA", "ApprovedDate")]
[StringLength(50, ErrorMessage = "{0} must not exceed {1} characters")]
public string ApprovedBy { get; set; }

моя модель выглядит так:

    public class HOAModel
    {
        public HOA HOA { get; set; }
   }

моя реализация представления выглядит так:

Html.Kendo().DatePickerFor(m => m.HOA.ApprovedDate)

Итак, мой элемент на стороне клиента имеет следующий идентификатор:

<input name="HOA.ApprovedDate" class="k-input" id="HOA_ApprovedDate" role="textbox">
person Jim Shaffer    schedule 13.11.2014