ASP.NET MVC 4
Адам Фриман
Настройка системы модели связывания данных
Мы рассмотрели процесс связывания данных по умолчанию. Как и следовало ожидать, в следующих разделах мы продемонстрируем вам несколько примеров настройки системы связывания.
Создаем пользовательский провайдер значений
Определяя пользовательский провайдер значений, мы можем добавить в процесс связывания собственный источник данных. Провайдеры значений реализуют интерфейс IValueProvider
, который показан в листинге 22-33.
Листинг 22-33: Интерфейс IValueProvider
namespace System.Web.Mvc
{
public interface IValueProvider
{
bool ContainsPrefix(string prefix);
ValueProviderResult GetValue(string key);
}
}
Механизм связывания вызывает метод ContainsPrefix
, чтобы определить, может ли провайдер значений предоставить данные для определенного префикса. Метод GetValue
возвращает значение для данного ключа данных или null
, если провайдер не имеет соответствующих данных.
Мы добавили в пример проекта папку Infrastructure
и создали новый файл под названием CountryValueProvider.cs
, с помощью которого будем предоставлять значения для свойства Country
. Содержимое этого файла показано в листинге 22-34.
Листинг 22-34: Содержимое файла CountryValueProvider.cs
using System.Globalization;
using System.Web.Mvc;
namespace MvcModels.Infrastructure
{
public class CountryValueProvider : IValueProvider
{
public bool ContainsPrefix(string prefix)
{
return prefix.ToLower().IndexOf("country") > -1;
}
public ValueProviderResult GetValue(string key)
{
if (ContainsPrefix(key))
{
return new ValueProviderResult("USA", "USA",
CultureInfo.InvariantCulture);
}
else
{
return null;
}
}
}
}
Этот провайдер значений отвечает только на запросы значений для свойства Country
, и он всегда возвращает значение USA
. Для всех других запросов мы возвращаем null
, указывая, что не можем предоставить данные.
Мы должны возвращать значения данных как класс ValueProviderResult
. Этот класс имеет три параметра конструктора. Первый - это данные, которые мы хотим связать с запрошенным ключом. Второй параметр представляет собой версию значения данных, которая является безопасной для отображения. Последний параметр - это информация о культуре, которая относится к значению; мы указали InvariantCulture
.
Чтобы зарегистрировать провайдер значений в приложении, нам нужно создать фабрику, которая будет создавать экземпляры нашего провайдера. Этот класс можно наследовать от абстрактного класса ValueProviderFactory
. В листинге 22-35 показано содержимое файла CustomValueProviderFactory.cs
, который мы создали в папке Infrastructure
.
Листинг 22-35: Содержимое файла CustomValueProviderFactory.cs
using System.Web.Mvc;
namespace MvcModels.Infrastructure
{
public class CustomValueProviderFactory : ValueProviderFactory
{
public override IValueProvider GetValueProvider(ControllerContext controllerContext)
{
return new CountryValueProvider();
}
}
}
Когда механизму связывания необходимо получить значения для процесса связывания, он вызывает метод GetValueProvider
. Наша реализация просто создает и возвращает экземпляр класса CountryValueProvider
, но можно использовать данные, предоставленные через параметр ControllerContext
, чтобы реагировать на разные запросы и создавать для них разные провайдеры значений.
Зарегистрировать класс фабрики в приложении можно в методе Application_Start
файла Global.asax
, как показано в листинге 22-36.
Листинг 22-36: Регистрируем фабрику провайдеров значений
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Http;
using System.Web.Mvc;
using System.Web.Optimization;
using System.Web.Routing;
using MvcModels.Infrastructure;
namespace MvcModels
{
public class MvcApplication : System.Web.HttpApplication
{
protected void Application_Start()
{
AreaRegistration.RegisterAllAreas();
ValueProviderFactories.Factories.Insert(0, new CustomValueProviderFactory());
WebApiConfig.Register(GlobalConfiguration.Configuration);
FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
RouteConfig.RegisterRoutes(RouteTable.Routes);
BundleConfig.RegisterBundles(BundleTable.Bundles);
}
}
}
Чтобы зарегистрировать класс фабрики, мы добавляем экземпляр в статическую коллекцию ValueProviderFactories.Factories
. Механизм связывания просматривает провайдеры значений по очереди, следовательно, если мы хотим иметь приоритет над встроенными провайдерами, то можем поставить наш пользовательский провайдер на первое место в коллекции с помощью метода Insert
.
Если мы хотим сделать наш провайдер резервным и использовать его только тогда, когда другие провайдеры не могут предоставить значение данных, то добавим его в конец коллекции с помощью метода Add
:
ValueProviderFactories.Factories.Add(new CustomValueProviderFactory());
В данном примере мы хотим, чтобы наш провайдер значений использовался прежде любого другого провайдера, и поэтому мы добавили его с помощью метода Insert
. Прежде чем мы сможем протестировать наш провайдер значений, необходимо изменить метод действия Address
, чтобы механизм связывания проверял не только данные формы для поиска значений для свойств модели. В листинге 22-37 показано, как мы сняли ограничение на источник значений в методе UpdateModel
.
Листинг 22-37: Снимаем ограничения на источники значений для свойств модели
public ActionResult Address()
{
IList<AddressSummary> addresses = new List<AddressSummary>();
UpdateModel(addresses);
return View(addresses);
}
Чтобы увидеть работу нашего пользовательского провайдера, запустите приложение и перейдите по ссылке /Home/Address
. Введите данные в поля Сity
и Сountry
и нажмите кнопку Submit
. Вы увидите, что наш пользовательский провайдер значений, который имеет приоритет над встроенными провайдерами, сгенерировал значения для свойства Country
каждого объекта AddressSummary
, который был создан механизмом связывания, как показано на рисунке 22-10.
Рисунок 22-10: Эффект пользовательского провайдера значений
Создаем пользовательский механизм связывания
Чтобы изменить поведение механизма связывания по умолчанию, можно создать пользовательской механизм связывания для определенного типа.
Пользовательский механизм связывания реализует интерфейс IModelBinder
, который мы показали вам ранее в этой главе. Чтобы его продемонстрировать, мы добавили в папку Infrastructure
файл AddressSummaryBinder.cs
, содержимое которого вы можете увидеть в листинге 22-38.
Листинг 22-38: Содержимое класса AddressSummaryBinder.cs
using MvcModels.Models;
using System.Web.Mvc;
namespace MvcModels.Infrastructure
{
public class AddressSummaryBinder : IModelBinder
{
public object BindModel(ControllerContext controllerContext,
ModelBindingContext bindingContext)
{
AddressSummary model = (AddressSummary) bindingContext.Model
?? new AddressSummary();
model.City = GetValue(bindingContext, "City");
model.Country = GetValue(bindingContext, "Country");
return model;
}
private string GetValue(ModelBindingContext context, string name)
{
name = (context.ModelName == "" ? "" : context.ModelName + ".") + name;
ValueProviderResult result = context.ValueProvider.GetValue(name);
if (result == null || result.AttemptedValue == "")
{
return "<Not Specified>";
}
else
{
return (string) result.AttemptedValue;
}
}
}
}
Чтобы создать экземпляр типа модели, который поддерживается механизмом связывания, MVC Framework вызовет метод BindModel
. Мы зарегистрируем механизм связывания немного позже. Наш класс AddressSummaryBinder
будет создавать только экземпляры класса AddressSummary
, что намного упростит код (можно создавать пользовательские механизм связывания, которые поддерживают несколько типов, но мы предпочитаем один механизм для одного типа).
Подсказка
В этом механизме связывания мы не выполняем валидацию входных данных, беспечно предполагая, что пользователь предоставит допустимые значения для всех свойств объекта
Person
. Мы рассмотрим валидацию в главе 23, а здесь сосредоточимся на базовом процессе связывания данных.
Параметрами метода BindModel
являются объект ControllerContext
, с помощью которого можно получить информацию о текущем запросе, и объект ModelBindingContext
, который предоставляет подробную информацию об искомом объекте модели, а также доступ к остальным возможностям связывания данных в приложении MVC. В таблице 22-3 описаны наиболее полезные свойства, определенные классом ModelBindingContext
.
Таблица 22-3: Наиболее полезные свойства класса ModelBindingContext
Свойство | Описание |
Model |
Возвращает объект модели, переданный в метод UpdateModel , если связывание было вызвано вручную. |
ModelName |
Возвращает имя модели, которая участвует в процессе связывания. |
ModelType |
Возвращает тип создаваемой модели. |
ValueProvider |
Возвращает реализацию IValueProvider , с помощью которой можно получить значения данных из запроса. |
Наш пользовательский механизм связывания очень прост. Получив вызов метода BindModel
, мы проверяем, было ли установлено свойство Model
объекта ModelBindingContext
. Если это так, то мы будем генерировать значение данных для этого объекта, а если нет, то мы создадим новый экземпляр класса AddressSummary
. Чтобы получить значения для свойств City
и Country
, мы вызываем метод GetValue
, а потом возвращаем заполненный объект AddressSummary
.
В методе GetValue
мы используем реализацию IValueProvider
, полученную из свойства ModelBindingContext.ValueProvider
, чтобы получить значения для свойств объекта модели.
Свойство ModelName
сообщает нам, нужно ли добавить к имени искомого свойства какой-либо префикс. Если помните, наш метод действия пытается создать коллекцию объектов AddressSummary
, следовательно, отдельные элементы input
будет иметь значения атрибутов name
с префиксами [0]
и [1]
. В запросе мы будем искать значения [0].City
, [0].Country
и так далее. Наконец, мы поставляем значение по умолчанию <Not Specified>
, если не можем найти значение для свойства или свойство является пустой строкой (то есть пользователь не ввел значение в элемент input
и отправил форму на сервер).
Регистрируем пользовательский механизм связывания
Мы должны зарегистрировать наш механизм связывания, чтобы сообщить приложению, какие типы он поддерживает. Это можно сделать в методе Application_Start
файла Global.asax
, как показано в листинге 22-39.
Листинг 22-39: Регистрируем пользовательский механизм связывания
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Http;
using System.Web.Mvc;
using System.Web.Optimization;
using System.Web.Routing;
using MvcModels.Infrastructure;
using MvcModels.Models;
namespace MvcModels
{
public class MvcApplication : System.Web.HttpApplication
{
protected void Application_Start()
{
AreaRegistration.RegisterAllAreas();
// This statement has been commented out
//ValueProviderFactories.Factories.Insert(0,
// new CustomValueProviderFactory());
ModelBinders.Binders.Add(typeof (AddressSummary), new AddressSummaryBinder());
WebApiConfig.Register(GlobalConfiguration.Configuration);
FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
RouteConfig.RegisterRoutes(RouteTable.Routes);
BundleConfig.RegisterBundles(BundleTable.Bundles);
}
}
}
Мы регистрируем наш механизм связывания с помощью метода ModelBinders.Binders.Add
, передавая в него тип, который поддерживает наш механизм связывания, и экземпляр класса механизма связывания. Обратите внимание, что мы удалили оператор, который регистрирует пользовательский провайдер значений. Чтобы проверить работу пользовательского механизма связывания, запустите приложение, перейдите по ссылке /Home/Address
и заполните несколько элементов формы. Когда вы отправите форму, наш механизм связывания будет использовать <Not Specifed>
для всех свойств, для которых вы не ввели значений, как показано на рисунке 22-11.
Рисунок 22-11: Эффект использования пользовательского механизма связывания
Подсказка
Как вариант, вы можете задать пользовательские механизмы связывания, применив атрибут
ModelBinder
к классу модели.