ASP.NET MVC: Atrybut BIND

W ASP.NET MVC do dyspozycji jest dość mało popularny atrybut Bind, który pozwala określić zachowanie bindingu pomiędzy modelem a widokiem. Załóżmy, że mamy następujący model:

public class Person
{
   public string FirstName { get; set; }
   public string LastName { get; set; }
   public string Email { get; set; }
}

Następnie napiszemy prostą akcję, zwracającą model do widoku:

public ActionResult Create()
{
  var person=new Person();       
  return View(person);
}

Widok stanowić będzie prosty formularz:

@using (Html.BeginForm()) {
   @Html.AntiForgeryToken()
   @Html.ValidationSummary(true)

   <fieldset>
       <legend>Person</legend>

       <div class="editor-label">
           @Html.LabelFor(model => model.FirstName)
       </div>
       <div class="editor-field">
           @Html.EditorFor(model => model.FirstName)
           @Html.ValidationMessageFor(model => model.FirstName)
       </div>

       <div class="editor-label">
           @Html.LabelFor(model => model.LastName)
       </div>
       <div class="editor-field">
           @Html.EditorFor(model => model.LastName)
           @Html.ValidationMessageFor(model => model.LastName)
       </div>

       <div class="editor-label">
           @Html.LabelFor(model => model.Email)
       </div>
       <div class="editor-field">
           @Html.EditorFor(model => model.Email)
           @Html.ValidationMessageFor(model => model.Email)
       </div>

       <p>
           <input type="submit" value="Create" />
       </p>
   </fieldset>
}

Następnie przyjrzyjmy się szablonowi akcji POST:

[HttpPost]
public ActionResult Create(Person person)
{
  try
  {
      // TODO: Add insert logic here

      return RedirectToAction("Index");
  }
  catch
  {
      return View();
  }
}

Widzimy obiekt person jako parametr wejściowy. ASP.NET MVC sam zadba, aby połączyć dane z formularza HTML w klasę C#. Framework zrobi to za pomocą bindingu. Domyślnie wszystkie właściwości klasy, występujące w formularzu są wiązane na podstawie nazw. Zaglądając do html, zobaczymy:

<div class="editor-label">
 <label for="FirstName">FirstName</label>
</div>
<div class="editor-field">
 <input class="text-box single-line" id="FirstName" name="FirstName" type="text" value="" />
 <span class="field-validation-valid" data-valmsg-for="FirstName" data-valmsg-replace="true"></span>
</div>

<div class="editor-label">
 <label for="LastName">LastName</label>
</div>
<div class="editor-field">
 <input class="text-box single-line" id="LastName" name="LastName" type="text" value="" />
 <span class="field-validation-valid" data-valmsg-for="LastName" data-valmsg-replace="true"></span>
</div>

<div class="editor-label">
 <label for="Email">Email</label>
</div>
<div class="editor-field">
 <input class="text-box single-line" id="Email" name="Email" type="text" value="" />
 <span class="field-validation-valid" data-valmsg-for="Email" data-valmsg-replace="true"></span>
</div>

Nazwy właściwości klasy oraz kontrolek input pokrywają się. Atrybut BIND daje nam większą kontrolę nad tym jak dane będą wiązane. Można np. wiązać wyłącznie wskazane właściwości tzn.:

[HttpPost]
public ActionResult Create([Bind(Include = "FirstName,LastName")]Person person)
{
}

Po przecinku podaje się właściwości, które chcemy wiązać. W powyższym przykładzie tylko imię i nazwisko zostaną przekazane, pomijając adres email. Analogicznie można wyłączyć jakieś właściwości za pomocą Exclude:

[HttpPost]
public ActionResult Create([Bind(Exclude = "FirstName,LastName")]Person person)
{
}

Atrybutu nie trzeba wcale doczepiać do konkretnego parametru wejściowego. Możliwe jest również jego użycie bezpośrednio, globalnie na modelu:

[Bind(Exclude = "FirstName,LastName")]
public class Person
{
   public string FirstName { get; set; }
   public string LastName { get; set; }
   public string Email { get; set; }
}

Bardzo użyteczną właściwością Bind jest Prefix. W praktyce używa się go, gdy mamy do czynienia z częściowymi widokami oraz zagnieżdżonymi typami. Spróbujmy zmodyfikować powyższy przykład, aby pokazać zastosowanie prefiksu. Zacznijmy od encji:

public class Person
{
   public string FirstName { get; set; }
   public string LastName { get; set; }
   public string Email { get; set; }
}

public class EmployeeInfo
{
   public Person PersonInfo { get; set; }
   
}

Zmodyfikowana akcja:

public ActionResult Create()
{
  var employee = new EmployeeInfo();
  employee.PersonInfo=new Person();

  return View(employee);
}

Wygenerowany HTML:

<div class="editor-label">
 <label for="PersonInfo_FirstName">FirstName</label>
</div>
<div class="editor-field">
 <input class="text-box single-line" id="PersonInfo_FirstName" name="PersonInfo.FirstName" type="text" value="" />
 <span class="field-validation-valid" data-valmsg-for="PersonInfo.FirstName" data-valmsg-replace="true"></span>
</div>

<div class="editor-label">
 <label for="PersonInfo_LastName">LastName</label>
</div>
<div class="editor-field">
 <input class="text-box single-line" id="PersonInfo_LastName" name="PersonInfo.LastName" type="text" value="" />
 <span class="field-validation-valid" data-valmsg-for="PersonInfo.LastName" data-valmsg-replace="true"></span>
</div>

<div class="editor-label">
 <label for="PersonInfo_Email">Email</label>
</div>
<div class="editor-field">
 <input class="text-box single-line" id="PersonInfo_Email" name="PersonInfo.Email" type="text" value="" />
 <span class="field-validation-valid" data-valmsg-for="PersonInfo.Email" data-valmsg-replace="true"></span>
</div>

Jak widać identyfikatory zawierają prefiks np. PersonInfo.FirstName zamiast FirstName . Oczywiście wynika to z tego, że wiążemy teraz model Employee, który zawiera zagnieżdżony obiekt o nazwie PersonInfo. Chcemy jednak być w stanie skorzystać z następującej akcji:

[HttpPost]
public ActionResult Create(Person person)
{
  try
  {
      // TODO: Add insert logic here

      return RedirectToAction("Index");
  }
  catch
  {
      return View();
  }
}

Innymi słowy, przekazujemy Employee, ale chcemy otrzymać wyłącznie Person ponieważ taki model akurat podana akcja tylko obsługuje. Można stworzyć Person samemu na podstawie identyfikatorów oraz FormsCollection:

[HttpPost]
public ActionResult Create(FormCollection formCollection)
{
  try
  {
      Person person=new Person();
      person.FirstName = formCollection["PersonInfo.FirstName"];
      person.LastName = formCollection["PersonInfo.LastName"];
      person.Email = formCollection["PersonInfo.Email"];

      return RedirectToAction("Index");
  }
  catch
  {
      return View();
  }
}

Rozwiązanie oczywiście bardzo mało eleganckie. Lepiej skorzystać ze wspomnianego atrybutu Bind:

[HttpPost]
public ActionResult Create([Bind(Prefix = "PersonInfo")]Person person)
{
}

3 thoughts on “ASP.NET MVC: Atrybut BIND”

  1. Literowka, zamiast:
    employee.Person=new Person();
    Powinno byc:
    employee.PersonInfo = new Person();

Leave a Reply

Your email address will not be published.