Ручная привязка моделей

55

Процесс привязки моделей выполняется автоматически, когда метод действия определяет параметры, однако при желании этим процессом можно управлять напрямую. Это обеспечит более явный контроль над тем, каким образом создаются объекты модели; откуда получаются значения данных и как обрабатываются ошибки разбора данных. В этой статье продолжим использовать разработанный ранее проект MvcModels. В примере ниже приведен измененный метод действия Address() из контроллера Home, в котором процесс привязки вызывается явным образом:

using System;
using System.Linq;
using System.Collections.Generic;
using System.Web.Mvc;
using MvcModels.Models;

namespace MvcModels.Controllers
{
    public class HomeController : Controller
    {
        // ...

        public ActionResult Address()
        {
            List<AdressSummary> addresses = new List<AdressSummary>();
            UpdateModel(addresses);
            return View(addresses);
        }
	}
}

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

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

// ...
public ActionResult Address()
{
    List<AdressSummary> addresses = new List<AdressSummary>();
    UpdateModel(addresses, new FormValueProvider(ControllerContext));
    return View(addresses);
}
// ...

Эта версия метода UpdateModel() получает реализацию интерфейса IValueProvider, который становится единственным источником значений данных для процесса привязки. Как показано в следующей таблице, каждое из четырех стандартных местоположений представлено реализацией интерфейса IValueProvider:

Встроенные реализации интерфейса IValueProvider
Источник Реализация IValueProvider
Request.Form FormValueProvider
RouteData.Values RouteDataValueProvider
Request.QueryString QueryStringValueProvider
Request.Files HttpFileCollectionValueProvider

Конструкторы всех классов, перечисленных в таблице, принимают параметр ControllerContext, который можно получить из свойства по имени ControllerContext, определенного в классе Controller. Чаще всего источник данных ограничивается значениями формы. Существует изящный трюк привязки, которым можно воспользоваться; он заключается в том, что создавать экземпляр FormValueProvider не обязательно, как показано в примере ниже:

// ...
public ActionResult Address(FormCollection formData)
{
    List<AdressSummary> addresses = new List<AdressSummary>();
    UpdateModel(addresses, formData);
    return View(addresses);
}
// ...

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

Существуют другие перегруженные версии метода UpdateModel(), которые позволяют указывать префикс для поиска и свойства модели, предназначенные для включения в процесс привязки.

Обработка ошибок привязки

Неизбежно возникают ситуации, когда пользователи предоставляют значения, которые не могут быть привязаны к соответствующим свойствам модели - например, недопустимые даты или текст вместо числовых значений. При явном вызове привязки моделей мы сами отвечаем за обработку ошибок подобного рода. Связыватель модели сообщает об ошибках привязки путем генерации исключения InvalidOperationException. Детали ошибки можно получить с помощью класса ModelState. Однако в случае использования метода UpdateModel() необходимо подготовиться к перехвату исключения и применению ModelState для выдачи пользователю сообщения об ошибке, как показано в примере ниже:

// ...
public ActionResult Address(FormCollection formData)
{
    List<AdressSummary> addresses = new List<AdressSummary>();
    try
    {
        UpdateModel(addresses, formData);
    }
    catch (InvalidOperationException exception)
    {
        // Отобразить ошибку пользователю
    }
    return View(addresses);
}
// ...

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

// ...
public ActionResult Address(FormCollection formData)
{
    List<AdressSummary> addresses = new List<AdressSummary>();
    if (TryUpdateModel(addresses, formData))
    {
    	// Продолжить обработку обычным образом
    }
    else {
    	// Отобразить ошибку пользователю
    }
    return View(addresses);
}
// ...

Единственной причиной предпочесть метод TryUpdateModel() перед UpdateModel() может быть нежелание перехватывать и обрабатывать исключения. Для процесса привязки моделей оба подхода функционально идентичны. Когда привязка модели вызывается автоматически, ошибки привязки не сопровождаются исключениями. Вместо этого нужно проверять результат привязки с помощью свойства ModelState.IsValid.

Пройди тесты
Лучший чат для C# программистов