Книга: ASP.NET MVC Framework

Model Binding

Model Binding

После того как механизм MVC Framework получил запрос от клиента с набором некоторых параметров, произвел определение необходимого контроллера и действия, возникает задача сопоставления параметров запроса параметрам выбранного действия контроллера. Задача решается просто, когда параметров немного. В этом случае сопоставление параметров происходит по их наименованию: параметрам метода действия с определенным именем присваиваются значения параметров запроса с теми же именами. В качестве примера рассмотрим действие Update контроллера AdminController. Во фрагменте приведено определение метода с параметрами:

public ActionResult Update(Guid? userid, string email,
      string comment, bool isApproved, bool isLockedOut)

Для этого действия подразумевается, что при его вызове будут переданы параметры с именами: userid, email, comment, isApproved, isLockedOut. Такие параметры передаются с запросом при отправлении формы с нашего представления Select. В следующем фрагменте рассмотрим основной HTML-код формы этого представления, который отображается в браузере пользователя:

<form action="/Admin/Update" method="post">
...
  <input name="userId" type="hidden"
      value="a4530eee-8634-4258-ac00-0ea63f7cc783" />
...
  <input name="email" type="text" value="[email protected]" />
...
  <textarea cols="20" name="comment" rows="2">
    &lt;b&gt,Добрый день!&lt;/b&gt;
  </textarea>
...
  <input checked="checked"
      name="isApproved" type="checkbox" value="true" />
...
  <input name="isLockedOut"
      type="checkbox" value="true" />
...
  <input type="submit" value="Coxpaнить" />
</form>

После того как пользователь нажмет кнопку Сохранить, данные с формы отправляются на сервер в виде параметров с именами, определенными в разметке атрибутами name. После этого задача сопоставления параметров становится тривиальной.

Но что делать, когда форма содержит десятки вводимых полей? Неужели создавать десятки параметров у метода действия контроллера? Нет, MVC Framework содержит механизм, который позволяет избежать такого некрасивого шага, как многочисленные параметры метода. Такой механизм называется Model Binding (привязка модели). Чтобы продемонстрировать работу этого механизма, выполним ряд изменений в коде. Для начала определим комплексный тип, который будет содержать все необходимые данные, передаваемые в действие Update:

public class UserData
{
  public Guid? UserId { get; set; }
  public string Email { get; set; }
  public string Comment { get; set; }
  public bool IsApproved { get; set; }
  public bool IsLockedOut { get; set; }
}

Обратите внимание, что для определения параметров мы используем свойства. Механизм Model Binding требует, чтобы использовались свойства, но не простые поля. Соответственно данному типу изменим определение метода Update:

public ActionResult Update(UserData userData)
{
  if (!userData.UserId.HasValue)
    throw new HttpException(404, "Пользователь не найден");
  MembershipProvider mp = Membership.Provider;
  MembershipUser user = mp.GetUser(userData.UserId, false);
  user.Email = userData.Email;
  user.Comment = userData.Comment;
  user.IsApproved = userData.IsApproved;
  if (user.IsLockedOut && !userData.IsLockedOut)
    user.UnlockUser();
  mp.UpdateUser(user);
  return RedirectToAction("Index");
}

Теперь, чтобы механизм MVC Framework смог произвести сопоставление параметров с помощью встроенного механизма Model Binding, нам необходимо модифицировать код представления Select так, как показано в следующем фрагменте:

<% using (Html.BeginForm("Update", "Admin")) { %>
<%= Html.Hidden("userData.UserId", (Guid)user.ProviderUserKey)%>
<%= Html.AntiForgeryToken() %>
<fieldset>
  <legend>Данные</legend>
  <p>
    <label for="email">Email</label>
    <%=Html.TextBox("userData.Email", user.Email)%>
  </p>
  <p>
    <label for="comment">Комментарий</label>
    <%=Html.TextArea("userData.Comment", user.Comment)%>
  </p>
  <p>
    <label for="isApproved">
      <%=Html.CheckBox("userData.IsApproved", user.IsApproved)%>
        подтвержден
    </label>
  </p>
  <p>
    <label for="isLockedOut">
      <%=Html.CheckBox("userData.IsLockedOut", user.IsLockedOut)%>
        заблокирован
    </label>
  </p>
</fieldset>
<input type="submit" value="Coxpaнить" />
<% } %>

Обратите внимание на то, что для всех полей формы мы использовали наименование вида userData.Свойство. Например, поле email стало полем с именем userData.Email. Такое именование позволяет классу DefaultModelBinder, механизму Model Binding по умолчанию, сопоставить множественные параметры формы комплексному типу UserData.

Примечание

Строго говоря, в данном случае вам необязательно указывать для элементов формы префикс userData. Так как в сопоставлении участвует только один параметр комплексного типа, то механизм DefaultModelBinder автоматически определит значения его свойств, предположив, что все элементы формы относятся к единственному параметру userData. Однако мы рекомендуем указывать подобный префикс в любом случае для повышения читаемости кода и возможности более простого расширения кода в будущем.

*********************************************

Важной частью MVC Framework является возможность определять собственные механизмы Model Binding. Эта возможность предоставляет разработчику определять то, как параметры запроса или значения формы поступают к действию контроллера для обработки. Для демонстрации работы этого механизма добавим к нашей модели UserData еще одно свойство CurrentMembershipUser, которое будет автоматически инициализироваться при сопоставлении параметров:

public class UserData {
public MembershipUser CurrentMembershipUser { get; set; }
}

Теперь реализуем наш собственный механизм Model Binding, создав класс UserDataBinder, реализующий интерфейс IModelBinder. Этот интерфейс содержит всего один метод BindModel, с помощью которого и выполняется вся работа по сопоставлению параметров:

public class UserDataBinder : IModelBinder
{
  public object BindModel(ControllerContext controllerContext,
         ModelBindingContext bindingContext)
  {
    UserData userData = new UserData();
    userData.UserId = new
       Guid(controllerContext.HttpContext.Request["UserId"]);
    userData.Email = controllerContext.HttpContext.Request["Email"];
    userData.Comment =
       controllerContext.HttpContext.Request["Comment"];
    userData.IsApproved =
       controllerContext.HttpContext.Request["IsApproved"] != "false";
    userData.IsLockedOut =
       controllerContext.HttpContext.Request["IsLockedOut"] != "false";
    MembershipProvider mp = Membership.Provider;
    userData.CurrentMembershipUser =
       mp.GetUser(userData.UserId, false);
    return userData;
  }
}

Обратите внимание на то, что при реализации своего механизма Model Binding мы сами указываем, какие параметры запроса и каким образом соответствуют ожидаемому комплексному типу вызываемого действия. Для того чтобы использовать эту реализацию интерфейса IModelBinder, мы должны зарегистрировать ее в Global.asax с помощью следующей конструкции:

protected void Application_Start()
{
  ...
  ModelBinders.Binders.Add(typeof(UserData), new UserDataBinder());
}

Здесь мы добавляем в коллекцию еще один вариант Model Binder, который призван выполнять сопоставление типа UserData для всех действий любого контроллера в приложении.

Другим вариантом подключения нашего класса UserDataBinder может стать использование атрибута ModelBinderAttribute, в этом случае мы сможем явно указать, для какого конкретного параметра нужно использовать свой вариант Model Binder. ModelBinderAttribute позволяет более гибко управлять тем, когда и как применяются пользовательские элементы Model Binder, что не редко может быть полезным. При этом регистрировать в Global.asax UserDataBinder не потребуется. Используется атрибут ModelBinderAttribute следующим способом:

public ActionResult Update(
  [ModelBinder(typeof(UserDataBinder))]  UserData userData)

Как вы видите, в данном случае атрибут использован для конкретного параметра одного-единственного действия.

В общем случае использование стандартного варианта Model Binding в виде класса DefaultModelBinder достаточно для осуществления сопоставления параметров запроса и параметров метода действия. Однако существует еще одна полезная функция механизма Model Binding в MVC Framework. Эта функция реализуется атрибутом BindAttribute и позволяет еще более гибко настраивать процесс сопоставления параметров по умолчанию. Атрибут BindAttribute имеет следующие параметры:

? Prefix — позволяет переопределить префикс при сопоставлении по умолчанию;

? Include — позволяет определить список допустимых параметров, которые будут участвовать в сопоставлении, остальные параметры, не входящие в этот список, рассматриваться не будут;

? Exclude — позволяет определить "черный" список параметров, которые не должны участвовать в процессе сопоставления. Такие параметры будут игнорироваться.

Использование параметра Prefix позволяет применять в представлении префикс для элементов формы, отличный от имени параметра метода действия. Например, вместо префикса userData в рассмотренном ранее примере, мы могли бы использовать сокращенный префикс ud, определив все элементы управления формы в подобном виде:

<%= Html.Hidden("ud.UserId", (Guid)user.ProviderUserKey)%>

Чтобы механизм Model Binder по умолчанию узнал про наш новый префикс, необходимо задать атрибут BindAttribute в требуемом месте при определении параметров метода действия:

public ActionResult Update([Bind(Prefix = "ud")] UserData userData)

Параметры Include и Exclude атрибута BindAttribute могут быть полезны в тех случаях, когда необходимо избежать автоматического сопоставления в комплексном типе для каких-то определенных свойств. Это может потребоваться для обеспечения безопасности или по каким-то другим соображениям. Например, чтобы запретить сопоставление свойства IsLockedOut, мы можем указать атрибут BindAttribute следующим образом:

public ActionResult Update(
  [Bind(Exclude = "IsLockedOut")] UserData userData)

Иногда требуется задать определенный список разрешенных для сопоставления параметров. Для этого используется параметр Include, которому можно задать список разрешенных для сопоставления свойств. В следующем примере мы разрешаем для сопоставления только два свойства: Userid и Email:

public ActionResult Update(
  [Bind(Include = "UserId, Email")] UserData userData)

Механизм сопоставления комплексных параметров форм с параметрами методов действий в MVC Framework значительно упрощается с помощью встроенного средства DefaultModelBinder. Этот механизм может гибко настраиваться с помощью атрибута BindAttribute, который позволяет задавать списки допустимых и недопустимых для сопоставления свойств и, вдобавок к этому, переопределять префикс, используемый в представлении. Если же разработчику недостаточно функционала механизма Model Binding по умолчанию, он волен переопределить этот механизм своей реализацией и использовать его как глобально во всем приложении, так и определяя его для конкретного параметра определенного действия.

Оглавление книги


Генерация: 0.088. Запросов К БД/Cache: 0 / 0
поделиться
Вверх Вниз