středa 24. prosince 2014

ASP.NET MVC Binding - kolekce

K ASP.NET MVC přistupují někteří vývojáři jako k zázračnému blackboxu, ze kterého vypadávají výsledky tak nějak sami, pokud ovšem nechceme dělat něco neobvyklého a který se někdy chová nevyzpytatelně. Přitom tato technologie není nijak složitá, je dobře promyšlená a umožňuje dělat téměř vše, co chceme. Jednou z prvních oblastí je binding - tedy naplnění vstupních objektů Action metody controlleru a to ze zdrojů hodnot. Zdrojů hodnot je několik (a je možné si napsat další), ale nejdůležitějším a nejpoužívanějším je FormValueProvider - tedy využíti hodnot zaslaného formuláře

Naplnění kolekcí

Nejlépe se jakékoliv plnění popisuje tak, že se ukáže, co je na vstupu a co z toho výchozí nastavení MVC udělá. Prvním předpokladem, aby se název vstupní hodnoty shodoval s názvem objektu či vlastnosti, kterou chceme plnit.

Například zde máme view, které zobrazuje seznam jmen, z nichž se může žádné a nebo všechna vybrat:

@{
    IEnumerable<string> names = new[] { "Martin", "Jakub", "Jan" };
}

@using (Html.BeginForm())
{
    foreach (var name in names)
    {
        <input type="checkbox" value="@name" name="names" />@name<br/>
    }
    <input type="submit" />
}

Tato action metoda však bude obsahovat prázdnou kolekci, neboť v požadavku byly z prohlížeče poslány obsahy prvku names a tedy výchozí MVC  binder neví, že mají naplnit vstupní parametr authors:


[HttpPost]
public ActionResult Index(List<string> authors)
{
    return View();
}

Stačí však přejmenovat authors na names a vstupní kolekce je naplněna korektně:

[HttpPost]
public ActionResult Index(List<string> names)
{
    return View();
}

Přitom nezáleží na velikosti písmen, může se napsat nAmES a i tak se kolekce naplní - zkrátka to není case sensitive.
A jaký má být vstupní typ objektu? Takový, jaký potřebujeme, tedy v případě kolekcí a výše uvedeného případu může vypadat například takto:

  • List<string> names
  • IEnumerable<string> names
  • string[] names

a nebo  pokud to mám podat obecně, jakýkoliv typ IEnumerable<T>, ICollection<T>, IList<T>, T[], Collection<T> List<T>.

Kolekce objektů

jak je vidět, binding do kolekcí s jednoduchým typem funguje bez problému. Co ale v případě, že máme objekty následující třídy (tedy bez nějaké jednoznačné hodnoty):

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

Checkbox má jen jednu hodnotu a jak tedy přimět binder, aby tuto hodnotu nějak použil na vlastnosti? Můžeme si sice napsat vlastní binder a nějak to udělat, ale takové řešení není elegantní. Jde to ale takto:

@{
    IEnumerable<Person> persons = new[] {
        new Person{FirstName ="Martin", LastName="Strimpfl"},
        new Person{FirstName = "Jakub", LastName="Strimpfl"},
        new Person{FirstName = "Jan", LastName = "Strimpfl"}
    };
}

@using (Html.BeginForm())
{
    foreach (var person in persons)
    {
        var index = Guid.NewGuid().ToString();

        <input type="checkbox" value="@index" name="persons.index" />@person.FirstName@: @person.LastName<br />

        <input type="hidden" value="@person.FirstName" name="persons[@index].FirstName" />
        <input type="hidden" value="@person.LastName" name="persons[@index].LastName" />
    }
    <input type="submit" />
}

Prvky, které obsahují vlastnosti objektů, budou skryté (hidden) a jejich jméno bude určené podle vzorce:

název kolekce [index prvku].jmeno vlastnosti

Prvek typu checkbox pak bude obsahovat hodnotu indexu - nepředané indexy (=checkbox není aktivní), nebudou zpracovány.

Poznámka: v příkladu používám guid, ale je možné použít konstrukci for a vzestupnou hodnotu - fantazii se meze nekladou.

Tento přístup je i řešením případů, kdy objekt, který používáme, nemá jednoznačný jediný identifikátor, tedy kdy je jedinečnost objektu dána více jak jednou vlastností.

 Nejčastěji pro kolekci v modelech pro views používám IEnumerable<T>  a nebo obyčejné pole - ale používání polí pro bindování je záludné a tedy jednoznačně upřednostňuji generický typ IEnumerable<T>.




Žádné komentáře:

Okomentovat