Русификация ASP.Net MVC3-приложений

Мой первый «коммерческий» проект на ASP.Net MVC — небольшой сервис, ориентированный на русскоговорящую аудиторию. В этой заметке я хотел бы собрать проблемы, с которыми я столкнулся в процессе «русификации» MVC3 — то есть адаптации к российской локали и русификация интерфейса и сообщений об ошибках.

Проблемы, описываемые в этом посте:

  • Указание культуры, использующейся по-умолчанию в байндингах
  • Создание кастомного байндера
  • Русификация сообщений от DataAnnotations-атрибутов
  • Русификация сообщений от дефолтного байндера
  • Проблемы интеграции локализации и Ninject


Начнем с простого: как известно, даты в разных странах принято писать по разному. И если в России 1.10.2011 — это 1 октября, то, например, в США — это 10 января. Похожая проблема и с дробными числами: 1,025 — в России это чуть больше единицы, а в США — 1025 (десятичный разделитель в США — точка).
Чтобы MVC-шный байндер при обработке пользовательского ввода правильно обрабатывал даты/числа, нужно в web.config указать нужную вам культуру:

<system.web>
    <globalization culture="ru-ru" />
</system.web>

Однако, байндер «подхватит» эту культуру только в случае POST-запросов. В случае GET-запросов всегда используется InvariantCulture. То есть если у вас на сайте форма, в которой есть дата и которая отправляется методом GET — ждите проблем. При вводе «15.10.2010» вы получите ошибку (байндер будет считать, что 15 — номер месяца).
К счастью, в MVC3 есть множество точек расширения, и ничего не мешает нам написать наш собственный байндер, который во всех случаях будет использовать CurrentCulture:

 public class DateTimeModelBinder : IModelBinder
    {
        public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
        {
            var valueResult = bindingContext.ValueProvider.GetValue(bindingContext.ModelName);
            var modelState = new ModelState { Value = valueResult };
            object actualValue = null;
            try
            {
                actualValue = DateTime.Parse(valueResult.AttemptedValue, CultureInfo.CurrentCulture);
            }
            catch (FormatException e)
            {
                modelState.Errors.Add(e);
            }

            bindingContext.ModelState.Add(bindingContext.ModelName, modelState);
            return actualValue;
        }
    }
       
        //изменять в файле Global.asax:
        protected void Application_Start()
        {
            //...

            //регистрация байндера
            ModelBinders.Binders.Add(typeof(DateTime), new DateTimeModelBinder());
        }

Следующая проблема, которая у меня возникла — это локализация сообщений об ошибках.
Наверное, все знают о достаточно удобном механизме валидации вью-моделей с помощью DataAnnotation-атрибутов типа [Required], [RegularExpression] и других.
У них у всех можно, конечно, задавать сообщение об ошибке типа: [Required(ErrorMessage = «Поле необходимо заполнить!»)]. Но задавать это сообщение для каждого атрибута — это какое же дублирование получится! В решении этой проблемы мне помогла статья jgauffin, у которого я и утащил с небольшими правками реализацию локализации.
Интересующиеся могут почитать исходную статью, а менее любопытные просто утянуть готовый проект, в котором локализация выделена в отдельную небольшую библиотеку (с уже локализованными атрибутами Required, RegularExpression, StringLength и др.).
Интеграция локализаций занимает ровно одну строчку:

        protected void Application_Start()
        {
            //...

            LocalizationIntegration.LocalizeDataAnnotationErrors();
        }

Однако, DataAnnotation-атрибуты — это не единственный источник сообщений об ошибках. Если model-binder не может преобразовать строку «blablabla» введенную в поле для int-значения, он выдаст сообщение: «The value ‘blablabla’ is not valid for Int.». Локализовать это сообщение можно следующим образом:

        protected void Application_Start()
        {
            //...

            DefaultModelBinder.ResourceClassKey = "Messages_Ru";
        }

При этом нужно создать ресурс с именем Messages_Ru и положить его в папку App_GlobalResources вашего MVC-проекта. В этом ресурсе необходимы, в общем-то, только два ключа-значения:

PropertyValueInvalid	Неверное значение '{0}' для поля {1}	
PropertyValueRequired	Не заполнено обязательное поле {0}	

В тестовом проекте это тоже присутствует.

При интеграции всего вышеупомянутого с IoC-контейнером Ninject (а точнее, с Ninject.MVC3 — nuget-пакетом, предоставляющим интеграцию) возникли небольшие проблемы, из-за того, что Ninject переопределяет DataAnnotationsModelValidatorProvider, чтобы проводить инъекцию зависимостей в DataAnnotation-атрибуты.
Эта проблема решена классами LocalizationIntegration и NinjectLocalizedDataAnnotationsModelValidatorProvider в тестовом проекте, но суть изменений сводится к следующему:

protected void Application_Start()
        {
            //регистрация валидатора в проектах без Ninject.MVC3
            var localizedStringProvider = new ResourceStringProvider(Resources.ResourceManager);
            ModelValidatorProviders.Providers.Clear();
            ModelValidatorProviders.Providers.Add(new LocalizedModelValidatorProvider(localizedStringProvider));


            //регистрация валидатора в проектах с Ninject.MVC3
            bootstrapper.Kernel.Rebind<ModelValidatorProvider>().ToConstant(new LocalizedModelValidatorProvider(localizedStringProvider));
                                  //в процессе инициализации Ninject.MVC3 зарегистрирует свою реализацию ModelValidatorProvider, поэтому необходимо произвести Rebind
                                  //в тестовом проекте класс NinjectLocalizedDataAnnotationsModelValidatorProvider совмещает в себе преимцщества локализации и инъекции зависимостей
        }

Данаая реализация отлично подходит для быстрого решения повседневных задач с минимумом интервенции в существующую систему. Интересующимся рекомендую также ознакомиться с проектом MvcExtensions, который решает эту (и другие) проблемы более комплексно. Я также надеюсь в ближайшее время ознакомиться с ним более детально и, возможно, написать о процессе знакомства :)

P.S. Тестовый проект с реализацией всего вышеупомянутого: DataAnnotation.zip

Опубликовать в Facebook
Опубликовать в Google Plus

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *