Данное руководство устарело. Актуальное руководство: Руководство по ASP.NET Core 7
Фреймворк MVC предоставляет ряд атрибутов, с помощью которых мы можем изменить стандартный механизм привязки.
Атрибут BindRequired
требует обязательного наличия значения для свойства модели.
Атрибут BindNever
указывает, что свойство модели надо исключить из механизма привязки.
Пусть у нас есть модель User:
public class User { public int Id { get; set; } public string Name { get; set; } public int Age { get; set; } public bool HasRight { get; set; } }
И пусть у нас есть метод, который в качестве параметра принимает объект этой модели:
public IActionResult AddUser(User user) { string userInfo = $"Id: {user.Id} Name: {user.Name} Age: {user.Age} HasRight: {user.HasRight}"; return Content(userInfo); }
В данном случае не столь важно, отправляются данные через строку запроса или форму. Здесь важен механизм привязки. Так, мы можем обратиться к этому методу со следующим запросом:
http://localhost:54274/Home/AddUser?HasRight=true
И мы получим следующий вывод:
То есть это вполне валидный запрос, при обработке которого создается объект User. Для тех свойств, для которых не переданы значения, устанавливаются значения по умолчанию, например, для строковых свойств - пустые строки, для числовых свойств - число 0. Но вряд ли подобный объект User можно считать удовлетворительным, поскольку, у него должно быть установлено, как минимум, имя - свойство Name. То есть имя выступает в качестве обязательного критерия, и чтобы это указать, используем атрибут BindRequired.
А, к примеру, свойство HasRight не должно устанавливаться напрямую. Поэтому для него можно применить атрибут BindNever:
using Microsoft.AspNetCore.Mvc.ModelBinding; public class User { public int Id { get; set; } [BindRequired] public string Name { get; set; } public int Age { get; set; } [BindNever] public bool HasRight { get; set; } }
Далее изменим метод, который получает объект User:
public IActionResult AddUser(User user) { if(ModelState.IsValid) { string userInfo = $"Id: {user.Id} Name: {user.Name} Age: {user.Age} HasRight: {user.HasRight}"; return Content(userInfo); } return Content($"Количество ошибок: {ModelState.ErrorCount}"); }
Если для свойства с атрибутом BindRequired не будет передано значение, то в объект ModelState будет помещена информация об ошибках,
а свойство ModelState.IsValid
возвратит false. И в данном случае, проверяя значение ModelState.IsValid
, мы можем проверить корректность
создания объекта User.
Теперь нам обязательно надо будет указать значение для свойства Name, а свойство HasRight будет исключено из привязки:
Кроме того, мы можем применять атрибут BindingBehavior, который устанавливает поведение привязки с помощью одно из значений одноименного перечисления BindingBehavior:
Required
: аналогично примению атрибута BindRequired
Never
: аналогично примению атрибута BindNever
Optional
: действие по умолчанию, мы можем передавать значение, а можем и не передавать, тогда будут применяться значения по умолчанию
Например, мы могли бы изменить модель User так:
public class User { public int Id { get; set; } [BindingBehavior(BindingBehavior.Required)] public string Name { get; set; } [BindingBehavior(BindingBehavior.Optional)] public int Age { get; set; } [BindingBehavior(BindingBehavior.Never)] public bool HasRight { get; set; } }
Атрибут Bind позволяет установить выборочную привязку отдельных значений. Так, уберем из модели User атрибуты привязки:
public class User { public int Id { get; set; } public string Name { get; set; } public int Age { get; set; } public bool HasRight { get; set; } }
И применим атрибут Bind
в методе AddUser:
public IActionResult AddUser([Bind("Name", "Age", "HasRight")] User user) { string userInfo = $"Name: {user.Name} Age: {user.Age} HasRight: {user.HasRight}"; return Content(userInfo); }
В качестве параметра в атрибут Bind передается набор свойств объекта User, которые будут участвовать в процессе привязки. Здесь перечислены все свойства. Но, допустим, уберем пару свойств:
public IActionResult AddUser([Bind("Name")] User user) { string userInfo = $"Name: {user.Name} Age: {user.Age} HasRight: {user.HasRight}"; return Content(userInfo); }
Теперь в привязке участвует только свойство Name, поэтому даже если в запросе мы передадим значения для всех остальных свойств, эти значения учитываться не будут, а для соответствующих свойств, не участвующих в привязке, будут применяться значения по умолчанию:
Если мы используем атрибут Bind применительно к параметру метода, как в случае выше, то мы переопределяем привязку только для конкретного метода. Если нам надо глобально переопределить привязку для модели User во всех методах, то атрибут Bind применяется в целом к модели:
using Microsoft.AspNetCore.Mvc; [Bind("Name")] public class User { public int Id { get; set; } public string Name { get; set; } public int Age { get; set; } public bool HasRight { get; set; } }
В одной из прошлых тем говорилось про порядок обхода привязчиком модели различных источников для получения значений. То есть привязчик обходит следующие источники в порядке приоритета для поиска значений:
Данные отправленных форм в Request.Form
Данные маршрута в RouteData.Values
Данные строки запроса в Request.Query
Но группа атрибутов позволяет переопределить это поведения, указав один целевой источник для поиска значений:
[FromHeader]: данные берутся из заголовков запроса
[FromQuery]: данные берутся из строки запроса
[FromRoute]: данные берутся из значений маршрута
[FromForm]: данные берутся из полученных форм
[FromBody]: данные берутся из тела запроса. Этот атрибут может применяться, когда в качестве источника данных выступает не форма и не строка запроса, а, скажем, данные отправляются через AJAX
Атрибут FromBody может применяться, если метод имеет только один параметр, иначе будет сгенерировано исключение.
Например, получим данные о юзер-агенте из запроса:
public IActionResult GetUserAgent([FromHeader(Name="User-Agent")] string userAgent) { return Content(userAgent); }
В атрибут FromHeader передается строковый параметр, который указывает нужный заголовок.
Или используем атрибут [FromQuery]
. Для этого возьмем модель User:
public class User { public int Id { get; set; } public string Name { get; set; } public int Age { get; set; } }
В контроллере определим два метода:
public IActionResult AddUser() { return View(); } [HttpPost] public IActionResult AddUser([FromQuery] User user) { string userInfo = $"Name: {user.Name} Age: {user.Age}"; return Content(userInfo); }
Как правило для post-запросов используются формы. И мы даже можем добавить форму. Так, для метода AddUser создадим представление AddUser.cshtml:
<form method="post"> <p> <label>Имя</label><br /> <input type="text" name="Name" /> </p> <p> <label>Возраст</label><br /> <input type="number" name="Age" /> </p> <p> <input type="submit" value="Отправить" /> </p> </form>
При отправке формы по умолчанию привязчик вначале просматривает данные из полученной формы. Если каких-то данных там не окажется, то он просматривает данные маршрута и строки запроса.
Однако установленный атрибут FromQuery
переопределяет это действие: теперь привязчик будет сразу просматривать данные из строки запроса.
Даже если мы отправим вместе с формой какие-то данные, то они будут игнорироваться. Например, для обращения к get-версии метода AddUser исползуем адрес Home/AddUser?Name=Alice&Age=21
:
В коде формы не установлен адрес, поэтому в качестве адреса отправки будет использоваться текущий адрес, то есть Home/AddUser?Name=Alice&Age=21
, который содержит строку запроса с двумя параметрами. В итоге данные
формы будут игнорироваться:
Подобным образом применяются и все остальные атрибуты.