Управление привязкой

Последнее обновление: 28.03.2022

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

BindRequired и BindNever

Атрибут 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 string AddUser(User user)
{
    return $"Id: {user.Id}  Name: {user.Name}  Age: {user.Age}  HasRight: {user.HasRight}";
}

В данном случае не столь важно, отправляются данные через строку запроса или форму. Здесь важен механизм привязки. Так, мы можем обратиться к этому методу со следующим запросом:

https://localhost:7288/Home/AddUser?HasRight=true

И мы получим следующий вывод:

Атрибут BindRequired в ASP.NET Core MVC и C#

То есть это вполне валидный запрос, при обработке которого создается объект 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 string AddUser(User user)
{
    if (ModelState.IsValid)
    {
        return $"Id: {user.Id}  Name: {user.Name}  Age: {user.Age}  HasRight: {user.HasRight}";
    }
    string errors = $"Количество ошибок: {ModelState.ErrorCount}. Ошибки в свойствах: ";
    foreach(var prop in ModelState.Keys)
    {
        errors = $"{errors}{prop}; ";
    }
    return errors;
}

Если для свойства с атрибутом BindRequired не будет передано значение, то в объект ModelState, который представляет словарь, будет помещена информация об ошибках, а свойство ModelState.IsValid возвратит false. И в данном случае, проверяя значение ModelState.IsValid, мы можем проверить корректность создания объекта User. При этом все ключи в словаре ModelState будут представлять свойства, в которых произошли ошибки.

Теперь нам обязательно надо будет указать значение для свойства Name, а свойство HasRight будет исключено из привязки:

BindNever в ASP.NET Core MVC и C#

Если же для свойства Name не передать значение, то словарь ModelState будет содержать информацию об ошибке для этого свойства:

BindRequired и привязка модели в ASP.NET Core MVC и C#

Кроме того, мы можем применять атрибут 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

Атрибут 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 string AddUser([Bind("Name", "Age", "HasRight")] User user)
{
    return $"Name: {user.Name}  Age: {user.Age}  HasRight: {user.HasRight}";
}

В качестве параметра в атрибут Bind передается набор свойств объекта User, которые будут участвовать в процессе привязки. Здесь перечислены все свойства кроме Id. Но, допустим, уберем пару свойств:

public string AddUser([Bind("Name")] User user)
{
    return $"Name: {user.Name}  Age: {user.Age}  HasRight: {user.HasRight}";
}

Теперь в привязке участвует только свойство Name, поэтому даже если в запросе мы передадим значения для всех остальных свойств, эти значения учитываться не будут, а для соответствующих свойств, не участвующих в привязке, будут применяться значения по умолчанию:

Атрибут Bind в ASP.NET Core MVC и C#

Если мы используем атрибут 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; }
}

В этом случае в методе контроллера можно не применять данный атрибут к параметру:

public string AddUser(User user)
{
    return $"Name: {user.Name}  Age: {user.Age}  HasRight: {user.HasRight}";
}

Источники привязки

В одной из прошлых тем говорилось про порядок обхода привязчиком модели различных источников для получения значений. То есть привязчик обходит следующие источники в порядке приоритета для поиска значений:

  • Данные отправленных форм в 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]. Для этого возьмем следующий контроллер с двумя методами:

public class HomeController : Controller
{
    public IActionResult Index()
    {
        return View();
    }

    [HttpPost]
    public string Index([FromQuery] Person person)
    {
        return $"Name: {person.Name}  Age: {person.Age}";
    }
}
public record class Person(string Name, int Age);

Здесь метод Index, который обрабатывает POST-запросы, принимает данные в виде объекта Person. Причем к параметру применяется атрибут [FromQuery]

Другой метод Index возвращает объект ViewResult, то есть использует представление. Как правило для post-запросов используются формы. Поэтому для метода Index создадим представление Index.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-версии метода Index используем адрес:

https://localhost:7288/Home/Index?Name=Alice&Age=21
Источники привязки модели в ASP.NET Core MVC и C#

В коде формы не установлен адрес, поэтому в качестве адреса отправки будет использоваться текущий адрес, то есть Home/Index?Name=Alice&Age=21, который содержит строку запроса с двумя параметрами. В итоге данные формы будут игнорироваться:

Настройка model binding в ASP.NET Core MVC и C#

Подобным образом применяются и все остальные атрибуты.

Помощь сайту
Юмани:
410011174743222
Перевод на карту
Номер карты:
4048415020898850