Главная страница   /   22.5. Настройка системы модели связывания данных (ASP.NET MVC 4

ASP.NET MVC 4

ASP.NET MVC 4

Адам Фриман

22.5. Настройка системы модели связывания данных

Мы рассмотрели процесс связывания данных по умолчанию. Как и следовало ожидать, в следующих разделах мы продемонстрируем вам несколько примеров настройки системы связывания.

Создаем пользовательский провайдер значений

Определяя пользовательский провайдер значений, мы можем добавить в процесс связывания собственный источник данных. Провайдеры значений реализуют интерфейс 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 к классу модели.