
ASP_NET_MVC_4_Framework_s_primerami_na_C_dlya_p
.pdfusing System.Web.Mvc; using MvcModels.Models;
namespace MvcModels.Controllers
{
public class HomeController : Controller
{
private Person[] personData =
{
new Person
{
PersonId = 1, FirstName = "Adam", LastName = "Freeman", Role = Role.Admin
},
new Person
{
PersonId = 2, FirstName = "Steven",
LastName = "Sanderson", Role = Role.Admin
},
new Person
{
PersonId = 3, FirstName = "Jacqui", LastName = "Griffyth", Role = Role.User
},
new Person
{
PersonId = 4, FirstName = "John", LastName = "Smith", Role = Role.User
},
new Person
{
PersonId = 5, FirstName = "Anne", LastName = "Jones", Role = Role.Guest
}
};
public ActionResult Index(int id)
{
Person dataItem = personData.Where(p => p.PersonId == id).First(); return View(dataItem);
}
}
}
Для этого метода действия мы создали файл представления под названием /Views/Home/Index.cshtml. Содержимое этого файла показано в листинге 22-3. Мы использовали шаблонный вспомогательный метод DisplayFor, чтобы отображать значения некоторых свойств модели представления Person.
Листинг 22-3: Файл /Views/Home/Index.cshtml
@model MvcModels.Models.Person @{
ViewBag.Title = "Index";
561

}
<h2>Person</h2>
<div><label>ID:</label>@Html.DisplayFor(m => m.PersonId)</div> <div><label>First Name:</label>@Html.DisplayFor(m => m.FirstName)</div> <div><label>Last Name:</label>@Html.DisplayFor(m => m.LastName)</div> <div><label>Role:</label>@Html.DisplayFor(m => m.Role)</div>
Наконец, мы добавили стили CSS в файл /Content/Site.css, как показано в листинге 22-4.
Листинг 22-4: Дополнительные стили в файле Site.css
label { display: inline-block; width: 100px; font-weight:bold; margin: 5px;} form label { float: left;}
input.text-box { float: left; margin: 5px;}
button[type=submit] { margin-top: 5px; float: left; clear: left;} form div {clear: both;}
Понимание модели связывания данных
Связывание данных представляет собой мост между запросом HTTP и методами C#, которые определяют наши методы действий контроллера. Связывание данных используется в той или иной степени в большинстве приложений MVC Framework, в том числе и в том простом примере, который мы создали в предыдущей главе. Чтобы увидеть связывание данных в действии, запустите приложение и перейдите по ссылке /Home/Index/1. Результат показан на рисунке 22-1.
Рисунок 22-1: Связывание данных
Наша ссылка содержит значение свойства PersonId объекта Person, который мы хотели отобразить:
/Home/Index/1
Когда MVC Framework вызывает метод Index контроллера Home для обслуживания запроса, он преобразовывает эту часть URL и использует ее в качестве аргумента:
public ActionResult Index(int id) {
562
Преобразование сегмента URL в аргумент метода int является примером связывания данных. В следующих разделах мы рассмотрим процесс, который был инициирован этим простым примером, а затем изучим более сложные функции связывания данных. Процесс, который запускает связывание данных, начинается после получения запроса и его обработки системой маршрутизации. Мы не изменили для приложения конфигурацию маршрутизации, и для обработки запроса был использован маршрут по умолчанию, который Visual Studio добавила в файл /App_Start/RouteConfig.cs. Он показан в листинге 22-5.
Листинг 22-5: Маршрут по умолчанию, добавленный в приложение Visual Studio
using System;
using System.Collections.Generic; using System.Linq;
using System.Web; using System.Web.Mvc;
using System.Web.Routing;
namespace MvcModels
{
public class RouteConfig
{
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapRoute( name: "Default",
url: "{controller}/{action}/{id}", defaults: new
{
controller = "Home", action = "Index",
id = UrlParameter.Optional
}
);
}
}
}
Определение и принципы работы маршрутов были описаны в главе 13, и мы не будем повторять эту информацию здесь. Для связывания данных самой важной частью маршрута является дополнительная переменная сегмента id. Когда мы переходим по ссылке /Home/Index/1, то ее последний сегмент, который указывает на интересующий нас объект Person, соотносится с переменной маршрутизации id.
Механизм вызова действий, которые мы рассмотрели в главе 15, с помощью информации о маршрутизации определяет, что для обслуживания запроса необходим метод Index, но он не может вызвать метод Index, если у него нет значений для аргумента метода.
Механизм вызова действий по умолчанию, ControllerActionInvoker (см. главу 15), использует механизмы связывания данных (model binders) для создания объектов, которые необходимы для вызова действия. Механизмы связывания данных определяются интерфейсом IModelBinder, который показан в листинге 22-6. Позже в данной главе мы вернемся к этому интерфейсу, когда будем создавать пользовательский механизм связывания данных.
Листинг 22-6: Интерфейс IModelBinder
namespace System.Web.Mvc
{
public interface IModelBinder
{
563
object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext);
}
}
В приложении MVC может быть несколько механизмов связывания данных, и каждый механизм может создавать один или несколько типов моделей. Когда механизму вызова действий нужно вызвать какой-либо метод действия, он смотрит на параметры, которые определяют данный метод, и находит механизм связывания данных для типа каждого параметра. В нашем примере механизм вызова действий обнаружит, что метод Index имеет только один параметр int. Затем он найдет механизм связывания, работающий со значениями int, и вызовет его метод BindModel.
Механизм связывания должен предоставить значение int, так чтобы его можно было использовать для вызова метода Index. Обычно для этого он трансформирует какой-либо элемент данных запроса (например, значения формы или строковые значения запроса), но в MVC Framework нет никаких ограничений на то, как будут получены данные.
Далее в этой главе мы покажем вам несколько примеров пользовательских механизмов связывания. Мы также продемонстрируем вам некоторые функции класса ModelBindingContext, который передается в метод IModelBinder.BindModel.
Использование модели связывания данных по умолчанию
Пользовательские механизмы связывания в приложении определяются редко, чаще всего используется встроенный класс механизма связывания, DefaultModelBinder. Он используется механизмом вызова действий, когда отсутствует пользовательский механизм связывания для данного типа. По умолчанию этот механизм связывания проверяет четыре места, которые показаны в таблице 22-1, чтобы найти данные, соответствующие имени параметра, для которого выполняется связывание.
Таблица 22-1: Порядок поиска данных для параметра классом DefaultModelBinder
Место
Request.Form
RouteData.Values
Request.QueryString Данные строки запроса из URL.
Файлы, загруженные как часть запроса (пример загрузки файлов показан в
Request.Files
главе 11).
Места проверяются в указанном порядке. Так, в нашем примере DefaultModelBinder будет искать значение параметра id следующим образом:
1.Request.Form["id"]
2.RouteData.Values["id"]
3.Request.QueryString["id"]
4.Request.Files["id"]
Как только значение найдено, поиск будет остановлен. В нашем примере поиск по данным формы не дал результата, но требуемое имя было найдено в переменной маршрутизации. Это означает, что поиск в строке запроса и загруженных файлах не будет проводиться вообще.
564

Подсказка
Если вы используете механизм связывания по умолчанию, важно, чтобы параметры вашего метода действия соответствовали названиям свойств. Наш пример приложения работает, потому что имя параметра метода действия соответствует имени переменной маршрутизации. Если параметр будет называться, к примеру, personId, механизм связывания по умолчанию не сможет
найти соответствующее значение данных, и запрос не будет обработан.
Связывание простых типов
Для простых типов параметров DefaultModelBinder пытается преобразовать полученное из данных запроса строковое значение в тип параметра с помощью класса System.ComponentModel.TypeDescriptor. Если значение не может быть преобразовано, например, если мы предоставили значение apple для параметра, который требует значение int, то DefaultModelBinder не сможет связать данные.
Чтобы увидеть возникающую из-за этого проблему, запустите приложение и перейдите по ссылке /Home/Index/apple. На рисунке 22-2 показан ответ от сервера.
Рисунок 22-2: Ошибка при обработке свойства модели
565

Механизм связывания по умолчанию упорно пытается привести предоставленное нами значение apple к требуемому типу int, что вызывает ошибку, показанную на рисунке.
Упростить работу механизма связывания можно с помощью типов, допускающих значение null, которые обеспечивают запасную позицию. Вместо того чтобы требовать числовое значение, параметр int? позволяет механизму связывания установить аргументу метода действия значение null. В листинге 22-7 показано, как мы применили тип, допускающий значение null, к действию
Index.
Листинг 22-7: Используем тип, допускающий значение null, для параметра метода действия
public ActionResult Index(int? id)
{
Person dataItem = personData.Where(p => p.PersonId == id).First(); return View(dataItem);
}
Если вы запустите приложение и перейдите по ссылке /Home/Index/apple, то увидите, что мы решили только часть проблемы, как показано на рисунке 22-3.
Рисунок 22-3: Запрос для значения null
Мы изменили характер проблемы – механизм связывания может использовать null в качестве значения аргумента id метода Index, но код метода действия не обрабатывает значение null. Это можно было бы исправить, явно включив в метод код для обработки значений null, но мы установили для параметра значение по умолчанию, которое будет использоваться вместо null. В листинге 22-8 показано, как мы применили значение параметра по умолчанию в методе действия
Index.
566

Листинг 22-8: Применяем значение параметра по умолчанию в методе действия Index
public ActionResult Index(int id = 1)
{
Person dataItem = personData.Where(p => p.PersonId == id).First(); return View(dataItem);
}
Всякий раз, когда механизм связывания не сможет найти значение для параметра id, будет использоваться значение по умолчанию 1, что равнозначно выбору объекта Person со значением свойства PersonId 1, как показано на рисунке 22-4.
Рисунок 22-4: Результат использования значения параметра по умолчанию в методе действий
Подсказка
Имейте в виду, что мы решили проблему нечисловых значений для механизм связывания, но мы все еще можем получить значения int, для которых в контроллере Home нет действительных объектов Person. Например, механизм
связывания может запросто преобразовать последний сегмент URL-адресов
/Home/Index/-1 и /Home/Index/500 в значения int. Это предоставит методу действия действительное значение для вызова метода Index, но приведет к
ошибке, потому что наш код не выполняет никаких дополнительных проверок. Рекомендуем вам обратить внимание на диапазон значений параметров, с которым может работать ваш метод действия, и создать соответствующие тесты.
Анализ с учетом настроек культуры
Класс DefaultModelBinder использует различные настройки культуры для преобразования типов из разных областей данных запроса. Значения, получаемые из URL (данные маршрутизации и строки запроса), преобразуются без учета настроек культуры (culture-insensitive parsing), но эти настройки принимаются во внимание для значений из данных форм.
567
Чаще всего это вызывает проблемы со значениями DateTime. Даты, которые обрабатываются без учета настроек культуры, должны быть в универсальном формате yyyy-mm-dd. Значения дат из форм должны быть в формате, указанном сервером. Это означает, что на сервере с настройками культуры UK формат даты будет dd-mm-yyyy, в то время как на сервере с настройками культуры US формат будет mm-dd-yyyy, хотя yyyy-mm-dd будет приемлемым в обоих случаях.
Если значение даты не соответствует формату, оно не будет преобразовано. Следовательно, мы должны убедиться, что все даты в URL соответствуют универсальному формату записи. Мы также должны быть осторожны при обработке значений дат, предоставленных пользователями. Механизм связывания по умолчанию предполагает, что пользователи будет записывать даты в соответствии с форматом, указанном в настройках культуры сервера, что вряд ли будет соответствовать действительности, если приложение будет использоваться клиентами из разных стран.
Связывание сложных типов
Когда параметр метода действия является сложным типом (т.е. типом, который не может быть преобразован с помощью класса TypeConverter), то DefaultModelBinder класс использует рефлексию (reflection), чтобы получить список общих свойств, а затем связывает их по очереди. Для демонстрации, как это работает, мы добавили два новых метода действий в контроллер Home, как показано в листинге 22-9.
Листинг 22-9: Добавляем новые методы действий в контроллер Home
using System;
using System.Collections.Generic; using System.Linq;
using System.Web; using System.Web.Mvc;
using MvcModels.Models;
namespace MvcModels.Controllers
{
public class HomeController : Controller
{
private Person[] personData =
{
new Person
{
PersonId = 1, FirstName = "Adam", LastName = "Freeman", Role = Role.Admin
},
new Person
{
PersonId = 2, FirstName = "Steven",
LastName = "Sanderson", Role = Role.Admin
},
new Person
{
PersonId = 3, FirstName = "Jacqui", LastName = "Griffyth", Role = Role.User
},
new Person
{
PersonId = 4,
568
FirstName = "John", LastName = "Smith", Role = Role.User
},
new Person
{
PersonId = 5, FirstName = "Anne", LastName = "Jones", Role = Role.Guest
}
};
public ActionResult Index(int id = 1)
{
Person dataItem = personData.Where(p => p.PersonId == id).First(); return View(dataItem);
}
public ActionResult CreatePerson()
{
return View(new Person());
}
[HttpPost]
public ActionResult CreatePerson(Person model)
{
return View("Index", model);
}
}
}
Перегруженная версия метода CreatePerson без параметров создает новый объект Person и передает его в метод представления, в результате чего визуализируется представление
Views/Home/CreatePerson.cshtml, которое показано в листинге 22-10.
Листинг 22-10: Представление CreatePerson.cshtml
@model MvcModels.Models.Person @{
ViewBag.Title = "CreatePerson";
}
<h2>Create Person</h2> @using (Html.BeginForm())
{
<div>@Html.LabelFor(m => m.PersonId)@Html.EditorFor(m => m.PersonId)</div> <div>@Html.LabelFor(m => m.FirstName)@Html.EditorFor(m => m.FirstName)</div> <div>@Html.LabelFor(m => m.LastName)@Html.EditorFor(m => m.LastName)</div> <div>@Html.LabelFor(m => m.Role)@Html.EditorFor(m => m.Role)</div>
<button type="submit">Submit</button>
}
Это представление визуализирует простой набор элементов label и editor для свойств объекта Person, с которыми мы работаем, а также элемент form, который отправляет данные элементов editor к методу действия CreatePerson с атрибутом HttpPost. Он использует представление Views/Home/Index.cshtml для отображения данных, переданных в форме. Чтобы увидеть, как работают новые методы действий, запустите приложение и перейдите по ссылке /Home/CreatePerson, как показано на рисунке 22-5.
569

Рисунок 22-5: Используем методы действий CreatePerson
Отправляя форму к методу CreatePerson, мы создаем различные ситуации для связывания данных. Механизм связывания по умолчанию обнаруживает, что наш метод действия требует объект Person и обрабатывает все его свойства по очереди. Для каждого свойства простого типа механизм связывания пытается найти значение запроса, как и в предыдущем примере. Так, например, обнаружив свойство PersonID, он будет искать значение данных PersonID, которые найдет в данных формы в запросе.
Если для свойства требуется сложный тип, то для нового типа процесс повторяется - будут получены общедоступные свойства и механизм связывания попытается найти значения для каждого из них. Разница в том, что имена свойств являются вложенными. Например, свойство HomeAddress класса Person принадлежит типу Address, который показан в листинге 22-11.
Листинг 22-11: Вложенный класс модели
public class Address
{
public string Line1 { get; set; } public string Line2 { get; set; } public string City { get; set; } public string PostalCode { get; set; } public string Country { get; set; }
}
Чтобы найти значение для свойства Line1, механизм связывания будет искать значение для HomeAddress.Line1, то есть, он объединит имя свойства, указанное в объекте модели, с именем свойства, указанном в типе свойства.
Создаем легко связываемый HTML
Использование строк с префиксами означает, что мы должны создавать представления, которые будут их визуализировать - хотя вспомогательные методы значительно упрощают эту задачу. В листинге 22-12 показано, как мы обновили файл представления CreatePerson.cshtml, чтобы использовать несколько свойств из типа Address.
Листинг 22-12: Обновляем представление CreatePerson.cshtml, чтобы использовать свойства из типа Address
@model MvcModels.Models.Person @{
570