Перегружаем стандартные DataAnnotation атрибуты для использования с custom resource provider

    Представьте, что у вас есть legacy проект Asp.NET MVC версии 5, которому немало лет. В нем используется самописный ResourceProvider, который умеет доставать из базы ресурс и показывать его на UI. В зависимости от различных условий (например, от того, откуда пользователь пришел на сайт), ресурсы будут показаны разные. Теперь пришло время сделать так, чтобы все намертво захардкоженные строки в Data Annotation атрибутах, такие как:
    [Display(Name = "Username")]
    [Required(ErrorMessage = "Please enter the username")]
    [StringLength(64, ErrorMessage = "Username cannot exceed 64 characters")]
    public string Username{ get; set; }
    

    тоже могли получать свои значения, используя ResourceProvider. Как это сделать, используя немного наследования и доступной в Asp.NET MVC кастомизации, я покажу под катом.

    Вся необходимая логика по нахождению именно того ресурса, который нужно показать в текущей ситуации, инкапсулирована в классе ResourceProvider и связанных с ним классов, поэтому нам достаточно вызвать следующий его метод:
    string resource = ResourceProvider.GetResource(name);
    


    Display attribute


    DisplayAttribute, появившийся в .NET Framework 4, в отличие от своего предшественника, DisplayNameAttribute, помечен как sealed, поэтому мы не сможем использовать магию наследования в этом случае. Здесь мы воспользуемся тем, что DisplayName также доступен в классе System.Web.Mvc.ModelMetadata. Создадим класс, наследующий от DataAnnotationsModelMetadataProvider, переопределим метод CreateMetadata, чтобы подменить DisplayName на полученный от ResourceProvider. В качестве IoC контейнера используем Ninject. Чтобы задать имя ресурса, создадим свой собственный атрибут DisplayResourceAwareAttribute.
    public class DisplayResourceAwareAttribute : Attribute
    {
        public string ResourceName { get; set; }
    
        public DisplayResourceAwareAttribute() { }
    }
    
    public class ResourceAwareMetadataProvider : DataAnnotationsModelMetadataProvider
    {
        [Inject]
        public ResourceProvider ResourceProvider { get; set; }
    
        protected override ModelMetadata CreateMetadata(
            IEnumerable<Attribute> attributes,
            Type containerType,
            Func<object> modelAccessor,
            Type modelType,
            string propertyName)
        {
            var modelMetadata = base.CreateMetadata(attributes, containerType, modelAccessor, modelType, propertyName);
    
            var displayNameAttr = attributes.SingleOrDefault(a => typeof(DisplayResourceAwareAttribute) == a.GetType()) as DisplayResourceAwareAttribute;
            if (displayNameAttr != null)
            {
                modelMetadata.DisplayName = ResourceProvider.GetResource(displayNameAttr.ResourceName);
            }
    
            return modelMetadata;
        }
    }
    

    Вышеприведенный код ищет среди атрибутов атрибут типа DisplayResourceAwareAttribute, и если такой существует, то мы обновим свойство DisplayName значением, полученным с помощью ResourceProvider.GetResource. Теперь мы должны использовать новый MetadataProvider вместо стандартного. Для этого в метод Application_Start в файле Global.asax.cs, добавим следующую строку:
    protected void Application_Start()
    {
        ...
        ModelMetadataProviders.Current = DependencyResolver.Current.GetService<ResourceAwareMetadataProvider>();
        ...
    }
    

    Теперь в модели мы можем избавить от явно заданного описания поля, заменив стандартный атрибут на наш:
    [DisplayResourceAware(ResourceName = "UsernameResource")]
    [Required(ErrorMessage = "Please enter the username")]
    [StringLength(64, ErrorMessage = "Username cannot exceed 64 characters")]
    public string Username{ get; set; }
    

    Никаких дополнительных изменений в представлениях, контроллерах не требуются.

    Валидационные атрибуты


    С валидационными атрибутами, такими как Required, StringLength и т.д. проще, потому что мы можем наследовать свои атрибуты от них и в конструкторе установить свойству ErrorMessage нужное нам значение.
    Share post
    AdBlock has stolen the banner, but banners are not teeth — they will be back

    More
    Ads

    Comments 2

      0
      Станислав, а не проще сделать вот так — [Display(Name = «Name», ResourceType = typeof(Resource))], где Resource — это нужный нам ресурсный файл. Тогда он подставит нужный перевод для Name, который возьмет из ресурсника. Или Вы решали другую задачу?
        0
        Да, конечно — это предпочтительный вариант. Но если в проекте уже используется другой механизм ресурсов — приходится интегрировать уже существующее решение.

      Only users with full accounts can post comments. Log in, please.