Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

ASP_NET_MVC_4_Framework_s_primerami_na_C_dlya_p

.pdf
Скачиваний:
25
Добавлен:
19.03.2016
Размер:
17.66 Mб
Скачать

Вызов модели связывания данных вручную

Если метод действия определяет параметры, процесс связывания данных будет запущен автоматически, но мы можем также взять контроль над ним в свои руки. Таким образом, мы получим более явный контроль над тем, как создаются экземпляры объектов моделей, откуда поступают значения данных, и как обрабатываются ошибки анализа данных. В листинге 22-28 показано, как мы изменили наш метод действия Address в контроллере Home, чтобы вручную запустить процесс связывания.

Листинг 22-28: Запускаем процесс связывания данных вручную в методе действия Address

public ActionResult Address()

{

IList<AddressSummary> addresses = new List<AddressSummary>();

UpdateModel(addresses); return View(addresses);

}

Метод UpdateModel принимает объект модели, который мы ранее определили в качестве его параметра, и пытается извлечь значения из его открытых свойств, запуская стандартный процесс связывания.

Когда мы вызываем процесс связывания вручную, мы можем ограничить его одним источником данных. По умолчанию механизм связывания проверяет четыре адреса – данные формы, данные маршрута, строку запроса и любые загруженные файлы. В листинге 22-29 показано, как мы можем ограничить поиск механизма связывания одним адресом, в этом случае, данными формы.

Листинг 22-29: Ограничиваем поиск механизма связывания данными формы

public ActionResult Address()

{

IList<AddressSummary> addresses = new List<AddressSummary>(); UpdateModel(addresses, new FormValueProvider(ControllerContext)); return View(addresses);

}

Эта версия метода UpdateModel принимает реализацию интерфейса IValueProvider, который становится единственным источником значений данных для процесса связывания. Для каждого из четырех адресов данных по умолчанию есть реализация IValueProvider, как показано в таблице 22- 2.

Таблица 22-2: Встроенные реализации IValueProvider

Адрес

Реализация IValueProvider

Request.Form

FormValueProvider

RouteData.Values

RouteDataValueProvider

Request.QueryString QueryStringValueProvider

Request.Files

HttpFileCollectionValueProvider

Каждый из классов, перечисленных в таблице 22-2, принимает параметр конструктора ControllerContext, который мы получаем через свойство ControllerContext, определенное в классе Controller, как показано в листинге.

Наиболее распространенный способ ограничить источники данных – это проверять только значения формы. Существует удобная техника связывания, которая не требует создавать экземпляр FormValueProvider, как показано в листинге 22-30.

581

Листинг 22-30: Ограничиваем источники данных для механизма связывания

public ActionResult Address(FormCollection formData)

{

IList<AddressSummary> addresses = new List<AddressSummary>(); UpdateModel(addresses, formData);

return View(addresses);

}

Класс FormCollection реализует интерфейс IValueProvider, и если мы определяем метод действия, который принимает параметр этого типа, механизм связывания создаст объект, который мы сможем передать непосредственно в метод UpdateModel.

Подсказка

Есть и другие перегруженные версии метода UpdateModel, которые позволяют

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

Работаем с ошибками связывания

Пользователи неизбежно будут отправлять значения, которые не могут быть связаны с соответствующими свойствами модели – например, недействительные даты или текст для числовых значений. При явном вызове процесса связывания мы берем на себя ответственность за обработку таких ошибок. Механизм связывания генерирует для ошибок связывания исключение InvalidOperationException. Подробности ошибки можно узнать с помощью функции ModelState, которую мы опишем в главе 23. Но при использовании метода UpdateModel, мы должны быть готовы поймать исключение и создать сообщение об ошибке для пользователя с помощью ModelState, как показано в листинге 22-31.

Листинг 22-31: Работаем с ошибками связывания данных

public ActionResult Address(FormCollection formData)

{

IList<AddressSummary> addresses = new List<AddressSummary>(); try

{

UpdateModel(addresses, formData);

}

catch (InvalidOperationException ex)

{

// provide feedback to user

}

return View(addresses);

}

В качестве альтернативного подхода, можно использовать метод TryUpdateModel, который возвращает true, если процесс связывания прошел успешно, и false, если возникли ошибки, как показано в листинге 22-32.

Листинг 22-32: Используем метод TryUpdateModel

public ActionResult Address(FormCollection formData)

{

IList<AddressSummary> addresses = new List<AddressSummary>(); if (TryUpdateModel(addresses, formData))

{

// proceed as normal

}

582

else

{

// provide feedback to user

}

return View(addresses);

}

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

Подсказка

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

ModelState.IsValid. Мы рассмотрим ModelState в главе 23.

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

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

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

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

583

{

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, чтобы реагировать на разные запросы и создавать для них разные провайдеры значений.

584

Зарегистрировать класс фабрики в приложении можно в методе 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);

}

585

Чтобы увидеть работу нашего пользовательского провайдера, запустите приложение и перейдите по ссылке /Home/Address. Введите данные в поля Сity и Сountry и нажмите кнопку Submit. Вы увидите, что наш пользовательский провайдер значений, который имеет приоритет над встроенными провайдерами, сгенерировал значения для свойства Country каждого объекта AddressSummary, который был создан механизмом связывания, как показано на рисунке 22-10.

Рисунок 22-10: Эффект пользовательского провайдера значений

Создаем пользовательский механизм связывания

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

Пользовательский механизм связывания реализует интерфейс IModelBinder, который мы показали вам ранее в этой главе. Чтобы его продемонстрировать, мы добавили в папку Infrastructure файл AddressSummaryBinder.cs, содержимое которого вы можете увидеть в листинге 22-38.

Листинг 22-38: Содержимое класса AddressSummaryBinder.cs

using MvcModels.Models;

586

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

 

Возвращает имя модели, которая участвует в процессе связывания.

 

587

Свойство

 

Описание

 

 

 

ModelType

 

Возвращает тип создаваемой модели.

Возвращает реализацию IValueProvider, с помощью которой можно получить

ValueProvider

значения данных из запроса.

Наш пользовательский механизм связывания очень прост. Получив вызов метода 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);

588

BundleConfig.RegisterBundles(BundleTable.Bundles);

}

}

}

Мы регистрируем наш механизм связывания с помощью метода ModelBinders.Binders.Add, передавая в него тип, который поддерживает наш механизм связывания, и экземпляр класса механизма связывания. Обратите внимание, что мы удалили оператор, который регистрирует пользовательский провайдер значений. Чтобы проверить работу пользовательского механизма связывания, запустите приложение, перейдите по ссылке /Home/Address и заполните несколько элементов формы. Когда вы отправите форму, наш механизм связывания будет использовать <Not Specifed> для всех свойств, для которых вы не ввели значений, как показано на рисунке 22-11.

Рисунок 22-11: Эффект использования пользовательского механизма связывания

Подсказка

Как вариант, вы можете задать пользовательские механизмы связывания, применив атрибут ModelBinder к классу модели.

589

Резюме

В этой главе мы продемонстрировали вам процесс связывания данных, показали работу механизма связывания по умолчанию и различные способы настройки процесса. Для многих приложений MVC Framework потребуется только механизм связывания по умолчанию; он хорошо работает с HTML, который генерируют вспомогательные методы. Но для более сложных приложений может пригодиться пользовательский механизм связывания, который будет создавать объекты моделей более эффективным для определенных ситуаций способом. В следующей главе мы покажем вам, как проводить валидацию объектов моделей и как отображать пользователю ошибки, возникающие при вводе недействительных данных.

590

Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]