Пишем код

Заметки о .net разработке

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

without comments

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

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

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


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

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

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

<br />
 public class DateTimeModelBinder : IModelBinder<br />
    {<br />
        public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)<br />
        {<br />
            var valueResult = bindingContext.ValueProvider.GetValue(bindingContext.ModelName);<br />
            var modelState = new ModelState { Value = valueResult };<br />
            object actualValue = null;<br />
            try<br />
            {<br />
                actualValue = DateTime.Parse(valueResult.AttemptedValue, CultureInfo.CurrentCulture);<br />
            }<br />
            catch (FormatException e)<br />
            {<br />
                modelState.Errors.Add(e);<br />
            }</p>
<p>            bindingContext.ModelState.Add(bindingContext.ModelName, modelState);<br />
            return actualValue;<br />
        }<br />
    }</p>
<p>        //изменять в файле Global.asax:<br />
        protected void Application_Start()<br />
        {<br />
            //...</p>
<p>            //регистрация байндера<br />
            ModelBinders.Binders.Add(typeof(DateTime), new DateTimeModelBinder());<br />
        }<br />

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

<br />
        protected void Application_Start()<br />
        {<br />
            //...</p>
<p>            LocalizationIntegration.LocalizeDataAnnotationErrors();<br />
        }<br />

Однако, DataAnnotation-атрибуты — это не единственный источник сообщений об ошибках. Если model-binder не может преобразовать строку «blablabla» введенную в поле для int-значения, он выдаст сообщение: «The value ‘blablabla’ is not valid for Int.». Локализовать это сообщение можно следующим образом:
<br />
        protected void Application_Start()<br />
        {<br />
            //...</p>
<p>            DefaultModelBinder.ResourceClassKey = "Messages_Ru";<br />
        }<br />

При этом нужно создать ресурс с именем Messages_Ru и положить его в папку App_GlobalResources вашего MVC-проекта. В этом ресурсе необходимы, в общем-то, только два ключа-значения:
<br />
PropertyValueInvalid	Неверное значение '{0}' для поля {1}<br />
PropertyValueRequired	Не заполнено обязательное поле {0}<br />

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

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

<br />
protected void Application_Start()<br />
        {<br />
            //регистрация валидатора в проектах без Ninject.MVC3<br />
            var localizedStringProvider = new ResourceStringProvider(Resources.ResourceManager);<br />
            ModelValidatorProviders.Providers.Clear();<br />
            ModelValidatorProviders.Providers.Add(new LocalizedModelValidatorProvider(localizedStringProvider));</p>
<p>            //регистрация валидатора в проектах с Ninject.MVC3<br />
            bootstrapper.Kernel.Rebind<ModelValidatorProvider>().ToConstant(new LocalizedModelValidatorProvider(localizedStringProvider));<br />
                                  //в процессе инициализации Ninject.MVC3 зарегистрирует свою реализацию ModelValidatorProvider, поэтому необходимо произвести Rebind<br />
                                  //в тестовом проекте класс NinjectLocalizedDataAnnotationsModelValidatorProvider совмещает в себе преимцщества локализации и инъекции зависимостей<br />
        }<br />

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

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

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

Written by Shaddix

Октябрь 10th, 2011 at 12:45 пп

Posted in .net,MVC,web

Leave a Reply