Привязка модели (model binding) в ASP.NET Core MVC — это механизм сопоставления параметров HTTP-запроса с параметрами методами контроллера. Мы уже сталкивались с работой этого механизма (просто не знали, что это он), когда разбирались с обработкой форм в контроллерах. Здесь мы вернемся к изучению работы механизма привязки модели, но уже изучим его более подробно.
Интерфейс IModelBinder
Чтобы найти и сопоставить данные из запроса с параметрами действия в контроллере используется привязчик модели (model binder), который представляет объект, реализующий интерфейс IModelBinder
. Например, рассмотрим следующее действие контроллера:
public IActionResult Post(int id, bool draft) { return Ok(new { Id=id, Draft=draft}); }
и соответствующий этому действию запрос, который может выглядеть следующим образом:
https://localhost:7092/Home/Post/1?draft=true
или так:
https://localhost:7092/Home/Post/?draft=true&id=1
в любом случае мы получим вот такой результат в браузере:
Поиск значений привязчиком модели осуществляется в следующих источниках в порядке их приоритета:
- Данные форм. Хранятся в объекте
Request.Form
- Данные маршрута, то есть те данные, которые формируются в процессе сопоставления строки запроса маршруту. Хранятся в объекте
RouteData.Values
- Данные строки запроса. Хранятся в объекте
Request.Query
Все эти источники данных представляют словари, в которых значения хранятся в виде пар «ключ-значение». То есть привязчик модели в приведенных выше вариантах запросов действовал по разному, а именно:
при запросе https://localhost:7092/Home/Post/1?draft=true
выполнялись следующие действия:
- проверка значения
Request.Form["id"]
. - так как значения id в данных формы не было, то произошла проверка
RouteData.Values["id"]
. Такой параметр маршрута был найден в маршруте по умолчанию (последний необязательный параметр). Параметру метода id было присвоено значение 1. - проверка значения
Request.Form["draft"]
. Значение не было обнаружено - проверка значения
RouteData.Values["draft"]
. Значение не было обнаружено - проверка значения
Request.Query["draft"]
. Значение обнаружено — параметру метода draft было присвоено значениеtrue
.
Проверить, что привязчик работает именно в такой последовательности можно выполнив вот такой запрос:
https://localhost:7092/Home/Post/5?draft=true&id=1
здесь используется И параметр id
маршрута (значение 5
) И параметр запроса (id=1
). Так как приоритет проверки значений маршрута выше, чем параметров запроса, то в браузере мы увидим:
В случае, если параметры метода представляют сложные данные, например, класс, то привязчик модели будет действовать аналогичным образом, используя рефлексию и рекурсивно перебирая все свойства параметра. При этом, привязчик будет искать вначале значения с ключами типа [имя_параметра].[имя_свойства]
. Если подобных значений не будет найдено, то привязчик ищет значения просто по имени свойства.
Например, создадим такое действие контроллера:
public IActionResult Post(Post post) { return Ok(post); }
где Post
— это модель следующего вида:
public class Post { public int Id { get; set; } public string Title { get; set; } public string Author { get; set; } public string Body { get; set; } }
здесь уже параметром метода выступает класс. Попробуем выполнить следующий запрос к контроллеру:
localhost:7163/home/post/10?Title=Привет,мир
в браузере мы увидим следующий результат:
в данном случае, привязчик обнаружил в параметрах маршрута значение для свойства Post.Id
, а в параметрах запроса значение для Post.Title
. Так как для двух других свойств значений не нашлось, то им было присвоено значение по умолчанию — null
.
Свойство контроллера ModelState
Свойство ModelState хранит информацию о работе привязчика модели. Вполне возможна ситуация, при которой привязчик модели не найдет требуемое значение (см. пример выше) или же найденное значение не сможет быть сконвертировано в нужный тип. Например, если свойство имеет тип int, а в параметрах запроса будет содержаться строка.
Если параметр представляет ссылочный тип, свойство ModelState.IsValid
возвратит false
если привязчик не сможет найти для него значение. Это означает, что привязка завершилась с ошибкой и, возможно, что параметры метода мы не сможем использовать. Вернемся к нашему примеру. Когда мы выполнили запрос:
localhost:7163/home/post/10?Title=Привет,мир
то привязчик не нашел значения для двух параметров и присвоил им значение null
. Добавим в действие контроллера проверку свойства ModelState.IsValid
public IActionResult Post(Post post) { if (ModelState.IsValid) return Ok(post); else return BadRequest(ModelState); }
то есть, если работа привязчика завершается с ошибкой, то нам возвращается код 400 и информация о состоянии модели. Вот как будет выглядеть вывод в этом случае:
Другой вариант, когда работа привязчика завершается ошибкой — невозможность сконвертировать значение из запроса в значение параметра:
localhost:7163/home/post/?Title=Привет,мир&id=три&body=text&author=admin
при таком запросе мы, вроде бы определили значения всех свойств модели, но значение id вместо числа содержит строку. В этом случае мы увидим следующую ошибку:
а значение свойства Id объекта типа Post будет установлено в значение по умолчанию, то есть
0
. Мы можем не присваивать значения для значимых типов и в этом случае свойство ModelState.IsValid
будет возвращать true. Например, уберем из запрос параметр id:
localhost:7163/home/post/?Title=Привет,мир&body=text&author=admin
в этом случае мы увидим такой вывод в браузере
то есть, привязчик не обнаружил никаких ошибок.
Итого
В этой части мы более подробно познакомились с механизмом привязки модели. За привязку модели отвечает объект, реализующий интерфейс IModelBinder
. Узнать состояние привязки модели мы можем из свойства ModelState.IsValid
контроллера.