Klávesové zkratky na tomto webu - rozšířené Na obsah stránky

Nedělní WTF: Základy programování v jazyce Boo

17.16 - 8. března 2009 | Jen tak

Při pravidelném proklikávání se službou Devlogy.cz jsem zaznamenal výskyt dalšího dílu seriálu Základy programování v jazyce Boo na serveru programujte.com. Vzpomněl jsem si na první díl seriálu, který jsem četl před časem, a opět se mi naježila srst. Šel jsem se tedy podívat, jak s tím autor vládne dál.

No děs a bída. Jestli se takhle někde programuje a ještě k tomu v Boo, tak to je mi moc líto. Boo vzniklo jako agilní jazyk inspirovaný jednoduchostí Pythonu a silou dotnetu. Sice je silně typový, ale nenutí nás typy explicitně vyjadřovat. V podstatě nás nenutí k žádným zbytečnostem. Jeho filosofii lze ukázat na následujících ukázkách výpisu Hello World na konzolu:

class Program {
  public static void Main() {
    System.Console.Write("Hello Wold");
  }
}

Začal jsem nejjednodušším programem v C#, který vypíše na konzolu Hello World. Teď to samé v Boo:

print 'Hello World'

Ano, to je vše.

Když se vrátím k článkům na programujte.com, je vidět, že autor netuší, která bije, když píše:

„Každý kód v Boo musí začínat jmenými prostory.“

A skutečně ukázku s Hello World okořenil krásně zbytečným importem jmenného prostoru System. Není nad to mást začátečníky spoustou zbytečností, protože pak mají pocit, že se toho naučili hodně. :) Bohužel praxe ukazuje, že méně je mnohdy více a tak se posuneme o kousek dál. Tedy spíš zpátky na začátek, kde autor popisuje instalaci IDE. Už tohle je pěkná blbost, protože k vyzkoušení Boo žádné IDE nepotřebujete. Boo je totiž vybaveno interaktivní konzolou booish, která je podobná těm, které můžete znát z dynamických jazyků Python, Ruby nebo třeba z mona – pro C#. Prostě konzola, kde píšete kód a rovnou se vám pod rukama vykonává. Pro začátečnické pokusy ideální.

Další skvělé expresivní zážitky najdeme v druhém a třetím díle v odstavcích věnovaných proměnným. Autor se zapomněl zmínit, že máme k dispozici celý typový systém dotnetu.

„Typů proměnných je více a my si probereme jen ty základní.“

A už začíná deklarovat jednu proměnnou za druhou.

celeCislo as int  //definice, aktuální hodnota 0
celeCislo = 200  //inicializace proměnné
celeCislo as int = 5

Fakt krásné. Všimněte si prvního komentáře. To je stavební kámen další ukázky:

//příklad cyklu while
import System
i as int
while(i < 5):
   print i
   i++

Za takový kód bych si dobrovolně zakázal oběd, abych dostal trochu krve do mozku. Ukázka je bezesporu funkční, ale obsahuje zbytečný import, spoléhá se na implicitní inicializaci hodnotové proměnné, navíc zanáší šum v podobě zbytečných závorek. Takže jak se tedy Boo píše? Ukázka s proměnnými:

integer = 5
realNumber = 5.0
text = 'some text'

Jednoduše přiřadíme hodnotu a kompilátor už ví jakého je typu – a není to object ani variant. ;)

i = 0

while i < 5:
  print i++

A tady je celá myšlenka ukázky cyklu while. Proč je lepší?

  1. Používat komentáře je krásná věc, ale většinou je lepší psát tak, aby se komentáře používat vůbec nemusely. Většinou stačí slušně pojmenovat proměnné (nejlépe celým jménem). Tam, kde chcete napsat komentář, raději zvažte, zda není lepší vytvořit novou metodu s dostatečně popisným názvem.
  2. Zbytečným importem nic nezkazíme, ale zvýšíme šum. Někdo by dokonce mohl přemýšlet nad tím, proč tam ten import vlastně je.
  3. Používat neinicializované proměnné je už na výprask. Než uvádět explicitně typ, to tam radši napište tu implicitní hodnotu, ten kód je pak mnohem srozumitelnější.
  4. No a závorka. Pokud na ní jste z Cčkových jazyků zvyklí, prosím, ale je to další zbytečné zašumění kódu.
  5. Hodit postfixovou inkrementaci na jeden řádek s printem už je jen taková třešnička na dortu.

Výborná je taky ukázka s for cyklem nebo proměnné typu char

Psát texty pro začátečníky je těžké, proto se o ně ani nepokouším, ale neměl by je psát začátečník. Protože chyby, které se v „mládí“ naučíte a zažijete si je, budete pak opakovat a opakovat, než se z nich poučíte. A to stojí čas a ten jsou peníze…

Poučení z dnešního WTF: „Čím méně kódu napíšete dnes, o to se vám bude lépe spravovat zítra.“ :)

Jaký pohled si vybrat?

18.07 - 10. ledna 2009 | Webdesign

ASP.NET MVC je velice modulární, jeho největší devízou je, že skoro vše lze nahradit něčím jiným. Nejinak je tomu i u ViewEnginu (šablonovací systém). Dokonce můžete v jedné aplikaci používat několik šablonovacích systémů vedle sebe. A to už se vyplatí!

Jaký je výběr?

Existuje spousta známých i neznámých šablonovacích systémů. Najznámější jsou asi, počkejte si na to, PHP/ASP. Ha! Ano, PHPASP nejsou nic jinýho než šablonovací systémy. To že v nich někdo dokáže psát celé aplikace – klobouk dolu.

Šablonovací systém najdete téměř v každém oblíbeném webovém frameworku. V ASP.NET MVC mu většinou říkáme ASPX nebo WebForms view engine. To je ten, který se používá v základu. Narozdíl od ASP můžete v ASPX šablonách používat i serverové značky. Toho lze využít pro tvorbu Master pages a Content pages, nebo pro snadnou lokalizaci za pomocí asp:Localize. :) V takové šabloně můžete navíc použít jakýkoli dotnetí jazyk pro logiku pohledu. Následuje ukázka pohledu s jazykem C#:

<%@ Page Language="C#" Inherits="System.Web.Mvc.ViewPage" %>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
  <title>Test view page</title>
</head>
<body>
  <h1>Test view page</h1>

  <ul>
    <% foreach (var company in Model.Companies) { %>
    <li><%= company.Name %></li>
    <% } %>
  </ul>

  <% using(Html.BeginForm("create", "company", FormMethod.Post) { %>
   <fieldset>
     <legend>Create new company</legend>
     <label for="companyName">Name:</label>
     <%= Html.TextBox("companyName") %>
     <input type="submit" value="Create company" />
   </fieldset>
  <% } %>
</body>
</html>

Stejná ukázka za použití Visual Basicu:

<%@ Page Language="VB"  Inherits="System.Web.Mvc.ViewPage" %>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
  <title>Test view page</title>
</head>
<body>
  <h1>Test view page</h1>

  <ul>
    <% For Each company In Model.Companies %>
    <li><%= company.Name %></li>
    <% Next %>
  </ul>

  <% Using Html.BeginForm("create", "company", FormMethod.Post) %>
   <fieldset>
     <legend>Create new company</legend>
     <label for="companyName">Name:</label>
     <%= Html.TextBox("companyName") %>
     <input type="submit" value="Create company" />
   </fieldset>
  <% End Using %>
</body>
</html>

Osobně v pohledech upřednostňuju Visual Basic – je to díky jeho ukecanosti. Ano, i ta se někdy může hodit. V pohledech je pak lépe vidět, kde začíná a končí for cyklus nebo podmíněný blok. Nejspíš lze stejného efektu dosáhnout i za použití IronRuby. Ale to zjistím asi velice brzy. :)

Když už jsme u toho Ruby, potažmo u Railsů, nesmím zapomenout zmínit Haml. Kód v něm může vypadat celkem obskurně, ale má přísnou logiku a zachovává čistou strukturu pohledu.

%html{:xmlns => 'http://www.w3.org/1999/xhtml'}
  %head
    %title Test view page
  %body
    %h1 Test view page
    %ul
      -model.companies.each do |company|
        %li= company.name
    %form{:action => url.action({:action => 'Create'}), :method => 'post'}
      %fieldset
        %legend Create new company
        %label{:for => 'companyName'} Name:
        =html.text_box('companyName')
        %input{:type => 'submit', :value => 'Create company'}

Do ASP.NET MVC lze přidat jeho port do dotnetu: NHaml. Pro logiku pohledu lze použít jazyky C#, IronRuby a Boo.

Obecným šablonovacím jazykem je Velocity, které si našlo cestu například do MonoRailu v podobě NVelocity. Samozřejmě je ho možné používat i v ASP.NET MVC.

<html xmlns="http://www.w3.org/1999/xhtml">
<head>
  <title>Test view page</title>
</head>
<body>
  <h1>Test view page</h1>

  <ul>
#foreach($company in $model.Companies)
    <li>$company.Name</li>
#end
  </ul>

  <form action="$url.Action('create')" method="POST">
    <fieldset>
      <legend>Create new company</legend>
      <label for="companyName">Name:</label>
      $html.TextBox('companyName')
      <input type="submit" value="Create company" />
    </fieldset>
  </form>
</body>
</html>

Na půdě MonoRailu vznikl i další šablonovací systém postavený nad Boo s poetickým názvem Brail. V šablonách se může používat i rozšířená syntaxe Boo, která není závislá na odsazování a používá end literály pro definici bloků.

<html xmlns="http://www.w3.org/1999/xhtml">
<head>
  <title>Test view page</title>
</head>
<body>
  <h1>Test view page</h1>

  <ul>
    <?brail for company in model.Companies: ?>
    <li>${company.Name}</li>
    <?brail end ?>
  </ul>

  <?brail using Html.BeginForm('create', 'company', FormMethod.Post): ?>
    <fieldset>
      <legend>Create new company</legend>
      <label for="companyName">Name:</label>
      ${Html.TextBox('companyName')}
      <input type="submit" value="Create company" />
    </fieldset>
  <?brail end ?>
</body>
</html>

Samozřejmě můžete místo <?brail ?> použít zkrácený zápis <% %>, to už je jen na vás, jestli se vám víc líbí PHP nebo ASP styl…

A pomalu se blížíme k poslednímu zajímavému šablonovacímu systému. A tím je Spark. Spark si klade za cíl, být co nejblíže kodérovi a jeho syntaxe je založená primárně na XHTML. Pro logiku pohledu využíváte rozšířených značek a atributů, které jsou překládány do vámi zvoleného jazyka. Zvolit si můžete tradičně mezi C#, IronRuby a IronPython.

<html xmlns="http://www.w3.org/1999/xhtml">
<head>
  <title>Test view page</title>
</head>
<body>
  <h1>Test view page</h1>

  <ul>
    <li each="company in model.Companies">${company.name}</li>
  </ul>

  <form action="${url.action('create')}" method="POST">
    <fieldset>
      <legend>Create new company</legend>
      <label for="companyName">Name:</label>
      ${html.text_box('companyName')}
      <input type="submit" value="Create company" />
    </fieldset>
  </form>
</body>
</html>

Závěr

Když se podíváte na výše uvedené ukázky, zjistíte, že mají mnohé společné. Na vás je si vybrat. Každý z šablonovacích systémů má svá specifika a například Sparku bych se chtěl podrobněji věnovat v některém z následujících spo­tů.

Pokud se rozhodnete některý z view enginů použít, zkuste to přes projekt MVC Contrib.

Díky možnostem otevřené architektury ASP.NET MVC a možnosti zvolit si k práci jazyk, který vám nejvíce vyhovuje, můžete docílit nejen úspěchu u žen, ale i dle vašich preferencí! No řekněte, kdo z vás to má?

V ukázkách NHamlu a Sparku jsem zvolil integraci s jazykem IronRuby.

DSL pro konfiguraci URL Routingu v ASP.NET MVC

15.11 - 27. prosince 2008 | Webdesign

Binsor je skvělou konfigurační DSL pro Windsor IoC kontejner. Proč nevyužít možností, které nám využití jazyka Boo přináší a nerozšířit si Binsor o pár dalších pravidel pro konfiguraci routingu v ASP.NET MVC?

Routing v ASP.NET MVC

System.Web.Routing je jedním ze základních kamenů ASP.NET MVC. Přijímá parametry z URL a posílá je MvcRouteHandleru, který vybere controller a jeho akci, která má požadavek zpracovat. Routing je na MVC nezávislý a využívá se třeba i v ASP.NET Dynamic Data.

Pokud chceme routing nakonfigurovat, máme několik možností, jak toho dosáhnout. Tou první je využít standardní API:

public void Application_Start() {
  RouteTable.Routes.Add(
    new Route("{resource}.axd/{*pathInfo}", new StopRoutingHandler())
  );

  RouteTable.Routes.Add("default",
    new Route("{controller}/{action}/{id}", new MvcRouteHandler()) {
      Defaults = new RouteValueDictionary(new {
        controller = "Home",
        action = "Index"
      })
    }
  );
}

Jak jistě vidíte, psát takto routovací pravidla je celkem na dlouho. Kód obsahuje zbytečně mnoho šumu a můžete snadno něco opomenout. Proto je součástí MVC frameworku několik extenzí, které mají konfiguraci usnadnit:

public void Application_Start() {
  RouteTable.Routes.IgnoreRoute("{resource}.axd/{*pathInfo}");

  RouteTable.Routes.MapRoute(
    "default",
    "{controller}/{action}/{id}",
    new { controller = "Home", action = "Index" }
  );
}

Takový kód už je o poznání kratší, většina šumu už je pryč, ale stejně tu zůstává několik zbytečných znaků a na první pohled není vidět, co vlastně je ta anonymní třída. Navíc jde o konfiguraci a ta by si zasloužila být v konfiguračním souboru. Změna routy si pak nevyžádá rekompilaci aplikace, ale pouze restart… Proto jsem se rozhodl vytvořit si vlastní konfigurační DSL a to za pomocí Boo.

Krok první: návrh DSL

Začněme tedy s tím, jak by taková DSL měla vypadat. To, co má dělat, je už nastíněno výše. První můj návrh vypadal asi takto:

ignore:
  route "{resource}.axd/{*pathInfo}"

map:
  route "Default", "{controller}/{action}/{id}":
    defaults:
      controller = "Home"
      action = "Index"

Bloky ignore a map by mohli mít vícero potomků route. Tuto variantu jsem však záhy zapověděl, protože vyžaduje zbytečné odsazení navíc a v delších konfiguracích už nemusí být jasné, jestli zrovna jsme v bloku ignore nebo map popřípadě jiném. Tak jsem nad tím přemýšlel a nakonec zvolil následující variantu:

ignore_route "{resource}.axd/{*pathInfo}"

map_route "Default", "{controller}/{action}/{id}":
  defaults:
    controller = "Home"
    action = "Index"

Takže syntaxi bych měl, teď přijde ta veselejší část: implementace.

Krok druhý: implementace DSL

Jedním z důvodů proč jsou oblíbené dynamické jazyky jako Ruby a Python je meta programování. Meta programování nám umožňuje vytvářet nové programové konstrukce v jazyku, zkrátka obohatit jeho sémantiku. Já si pro implementaci vybral Boo.

Ačkoli je Boo statický jazyk, díky otevřenému konceptu kompilátoru, můžeme jazyk obohacovat stejně efektivně, jako v jazyce dynamickém. Výhodou je, že gramatickou správnost našich rozšíření můžeme ověřit při kompilaci. Základním kamenem při tvorbě vlastních DSL jsou meta funkce, které jsem však nepoužil, a makra.

Makro je třída. Ano lze ho napsat v jakémkoli jazyce nad CLI. Stačí nám implementovat rozhraní IAstMacro a třídu pojmenovat s konvenčním přídomkem Macro, např. Map_routeMacro. V implementovaných metodách si pak můžete dle libosti hrát s AST. Takhle je v C# napsanej třeba Binsor. Já jsem však k implementaci routovacích maker zvolil přímo jazyk Boo, který má od verze 0.8.1 podporu pro snadné psaní maker přímo v jazyce.

Začneme jednodušším makrem pro ignorování routy:

import Boo.Lang.Extensions
import Boo.Lang.Compiler
import Boo.Lang.Compiler.Ast
import Boo.Lang.Compiler.Ast.Visitors

macro ignore_route:
  path, = ignore_route.Arguments
  block = Block()
  block.Add([| RouteTable.Routes.Add(Route($path, StopRoutingHandler())) |])
  return block

Pro snadnější pochopení si kód trochu popíšeme. Na prvních řádkách si naimportujeme potřebné jmenné prostory. Klíčovým slovem macro zahájíme psaní makra a v zápětí mu přiřadíme jméno, protože, co dokážeme pojmenovat, dokážeme ovládat. Pak si načteme první parametr a uložíme si ho do proměnné path. Pak si vytvoříme nový blok kódu. A teď začíná magic. Všechno co je v bloku [| … |] je normální kód v Boo, který se převede na AST. Pomocí $ lze do toho kódu zasahovat zvenčí, takže místo $path bude v kódu obsah proměnné path. Připravený blok kódu pak vrátíme kompilátoru.

Díky našemu makru se kód ignore_route "test" přeloží na RouteTable.Routes.Add(Route("test", StopRoutingHandler())) a pokud je naimportován jmenný prostor System.Web.Routing, kompilátor všemu rozumí a máme jednoduchou syntaxi pro definování ignorovaných rout.

Bylo to celkem snadné, že? Satačí pochopit základní princip. Ono totiž klíčové slovo macro není nic jiného než makro, které bere jako první parametr název makra a následující blok je de facto implementací metody Expand. :)

Postoupíme dál. Makro map_route už bude o něco větší oříšek, protože může obsahovat vnořené bloky defaults, kde jsou vypsané výchozí hodnoty parametrů routy, a constraints, kde jsou podmínky, které tyto parametry musejí splňovat.

macro map_route:
  name, path = map_route.Arguments
  defaults = map_route["defaults"] as MacroStatement or MacroStatement()
  constraints = map_route["constraints"] as MacroStatement or MacroStatement()

  block = Block()
  block.Add([| route = Route($path, MvcRouteHandler()) |])
  block.Add([| route.Defaults = RouteValueDictionary() |])
  block.Add([| route.Constraints = RouteValueDictionary() |])

  for exp as ExpressionStatement in defaults.Block.Statements:
    binary = exp.Expression as BinaryExpression
    block.Add([| route.Defaults.Add($(binary.Left.ToString()), $(binary.Right)) |])

  for exp as ExpressionStatement in constraints.Block.Statements:
    binary = exp.Expression as BinaryExpression
    block.Add([| route.Constraints.Add($(binary.Left.ToString()), $(binary.Right)) |])

  block.Add([| RouteTable.Routes.Add($name, route) |])

  return block

macro defaults:
  allowedParents as List = [ "map_route" ]
  parent as MacroStatement = defaults.GetAncestor(NodeType.MacroStatement)
  assert allowedParents.Contains(parent.Name)

  parent["defaults"] = defaults

macro constraints:
  allowedParents as List = [ "map_route", "ignore_route" ]
  parent as MacroStatement = constraints.GetAncestor(NodeType.MacroStatement)
  assert allowedParents.Contains(parent.Name)

  parent["constraints"] = constraints

Makro map_route je trošku rozšířenou obdobou ignore_route, podívejme se na makro defaults, které přináší něco jiného. Zprvu si nadefinujeme, jaká makra mohou toto makro obsahovat, a následně ověříme, že opravdu není v jiném makru. Pak vezmeme kód tohoto makra a pošleme ho ke zpracování nadřazenému makru. Žádný kód generovat nebudeme. Makro constraints je na tom téměr stejně, jen předpokládá, že bude i v ignore_route, což zatím není implementováno, ale budiž.

Vraťme se k makru map_route a konkrétně k for cyklům. Tam totiž projíždíme předané kusy kódu. Jednotlivé řádky považujeme za výrazy. Konkrétně výraz přiřazení, což je binární výraz. Binární výraz má vždy pravou a levou stranu a operátor. Na levé straně máme klíče do RouteValueDictionary, což jsou řetězce. Proto levou stranu musíme převést na řetězec metodou ToString(). Pravá strana je hodnota přidružená ke klíči a může to být cokoli (object). Takže tam i cokoli pošleme.

Vezmeme oba kusy kódu a uložíme je do souboru RoutingMacros.boo

Krok třetí: užívání DSL

Než začneme makra využívat, musíme si je nejprve naimportovat. Na začátek souboru Windsor.boo přidáme řádky:

import file from RoutingMacros.boo
import System.Web.Routing
import System.Web.Mvc

No a potom už můžeme na konci souboru přidávat routovací pravidla.

Update

Kompletní skripty pro routování naleznete v článku Oprava předchozích článků, kde jsem provedl několik oprav v makru ignore_route a lehký refactoring.

Model binders v ASP.NET MVC

20.15 - 13. prosince 2008 | ASP.NET 2.0

Jednou z pěkných vlastností ASP.NET MVC je, že data která posíláte akci, můžete v kódu získávat přes silně typové parametry metody (akce). Pokud dostatečně dodržujete dané konvence, můžete takto získat i třeba komplexní typy.

To je zařízeno něčím, co se jmenuje model binders. Základem je DefaultModelBinder, který implementuje rozhranní IModelBinder. Dále pak atributy BindAttribute a jeho bratříček k psaní vlastních atributů CustomModelBinderAttribute a v neposlední řadě metody řadiče UpdateModel a TryUpdateModel.

O tom jak používat DefaultModelBinder, si můžete přečíst třeba u ScottaGu. Já bych se spíš chtěl zaměřit na možnosti rozšíření, které nám infrastruktura model binders přináší.

Velkou slabinou konvenčního DefaultModelBinderu je, že ve view musíte dodržovat jmenné konvence vašeho modelu. Sice to může přinést spoustu benefitů, ale i problémů.

Možnosti

Možností, jak bindery používat, je mnoho. Můžete si třeba bindovat z AppSettings nebo z třeba z cookie. I to je pomocí model binders možné. Pojďme se ale podívat na to, jak bindovat váš model na data odeslaná z view s odlišnou jmennou konvencí…

Základy

Vlastní bindery můžeme vytvořit tak, že implementuje rozhraní IModelBinder. Hotovou implementaci pak musíme zaregistrovat při startu aplikace. Pro ukázku si uděláme velice jednoduchý příklad načítání adresy z FormsCollection. Mějme datovou třídu adresa:

public class Address {
    public string Street { get; set; }
    public string StreetNumber { get; set; }
    public string Town { get; set; }
    public string PostalCode { get; set; }
}

K ní si vytvoříme model binder:

public class AddressBinder : IModelBinder {
  public ModelBinderResult BindModel(ModelBindingContext bindingContext) {
    var address = new Address {
      Street = bindingContext.HttpContext.Request["address_street"].Trim(),
      StreetNumber = bindingContext.HttpContext.Request["address_street_number"].Trim(),
      PostalCode = bindingContext.HttpContext.Request["address_postal_code"].Trim(),
      Town = bindingContext.HttpContext.Request["address_town"].Trim(),
    };

    return new ModelBinderResult(address);
  }
}

Který nakonec zaregistrujeme v Global.asax:

protected void Application_Start() {
  ModelBinders.Binders[typeof(Address)] = new AddressBinder();
}

Když teď akci předáme parametr typu Address, bude automaticky vázán pomocí AddressBinderu. Stejně tak, použijeme-li metodu UpdateModel. V některých scénářích zjistíte, že výše uvedený binder, není úplně skvělý, ba co víc, že je v podstatě dost k ničemu. Navíc taky přijdete na to, že když váš model bude trochu bohatší, tak se váš Global.asax pěkně natáhne, navíc pokud budou mít vaše bindery závislosti na jiných službách, začne v tom být pěkný bordel…

Binsor na scénu

Minule jsme si ukázali, jak propojit ASP.NET MVC s IoC kontejnerem a dnes ho využijeme a trochu si ulehčíme práci… Jak jsem již psal, všechny model bindery implementují rozhranní IModelBinder a toho můžeme využít pro jejich registraci do kontejneru, přidáním následujících řádků do souboru Windsor.boo:

for binder in AllTypesBased of IModelBinder("<nazev assembly s Model Bindery>"):
  component binder.Name.ToLower(), IModelBinder, binder

Máme je zaregistrované v kontejneru, ale potřeby psát něco do Global.asax jsme se nezbavili. To je pravda, ale vzápětí to napravíme. Budeme ještě potřebovat generickou bázovou třídu. Proč gerenerickou? No, je v tom takovej fígl – ten prozradím až za chvíli. :) Teď k věci:

public abstract class ModelBinderBase<T> : IModelBinder {
  public Type ModelType {
    get { return typeof(T); }
  }
  public abstract ModelBinderResult BindModel(ModelBindingContext bindingContext);
}

V podstatě jsme rozšířili rozhraní IModelBinder o znalost typu datového objektu se kterým pracuje. Proč? Vzpomeňte si na registraci binderu, kde je klíčem ve slovníku binderů typ datového objektu. Ano, to je on!

Jěště trochu poupravíme náš AddressBinder:

public class AddressBinder : ModelBinderBase<Address> {
  public override ModelBinderResult BindModel(ModelBindingContext bindingContext) {
    var address = new Address {
      Street = bindingContext.HttpContext.Request["address_street"].Trim(),
      StreetNumber = bindingContext.HttpContext.Request["address_street_number"].Trim(),
      PostalCode = bindingContext.HttpContext.Request["address_postal_code"].Trim(),
      Town = bindingContext.HttpContext.Request["address_town"].Trim(),
    };

    return new ModelBinderResult(address);
  }
}

Pořád tu zůstává ta nepěkná závislost na HttpRequestu, je snadno řešitelná, ale našemu příkladu nevadí a vypořádáme se s ní někdy jindy… Takže vraťme se zase k Windsor.boo a na jeho konec přidejme následující řádky:

for modelBinder as duck in IoC.Container.ResolveAll of IModelBinder():
  ModelBinders.Binders[modelBinder.ModelType] = modelBinder

Upozorňuji, že tyto řádky musí být až na konci souboru. Musí se volat, až po tom, co se zaregistrují všechny komponenty, protože tady si vyzvedáváme již hotové bindery z kontejneru a registrujeme je do ASP.NET MVC.

Možná jste si povšimli formulky as duck. Boo je staticky typovaný jazyk, stejně jako C#, jen využívá implicitního typování. V tomto případě nám generická metoda ResolveAll vrací hotové instance, které implementují rozhranní IModelBinder a taky mají tento silný typ. A toto rozhraní neví nic o tom s jakým typem modelu je svázáno.

Naštěstí Boo podporuje duck typing, což nám přidává tak trochu dynamičnost – pozdní vazbu. Já vím, že všechny moje bindery dědí z bázové třídy, která má vlastnost ModelType a s použítím as duck jí můžu zavolat. Tohle je vlastnost, kterou bude C# umět až ve verzi 4.0, do té doby je v něm toto velice těžko řešitelné (osobně jsem se o to ani nepokoušel, ale nejspíš nějak přes reflexi by to jít mělo).

Tím jsme se zbavili nutnosti registrovat každý model binder zvlášť.

Inversion of Control v ASP.NET MVC

10.30 - 24. listopadu 2008 | ASP.NET 2.0

Dneska si ukážeme, jak využít modularity ASP.NET MVC k tomu, abychom mohli snadno integrovat IoC kontejner. Ten pak bude sloužit k tvorbě controllerů a injekci jejich závislostí (dependecy injection). Jako kontejner použijeme Castle Windsor a konfigurační DSL v jazyce Boo – Binsor.

ASP.NET MVC je framework, který si za cíl bere jasné rozdělení odpovědností, modularitu, snadnou rozšiřitelnost a snadnou testovatelnost aplikací nad ním postavených. Což jsou nejpalčivější neduhy „klasického ASP.NET.“

Inversion of Control a Dependency Injection

Inversion of Control (IoC) je tak trochu jiný pohled na programování. Když píšete aplikaci, nepíšete ji jako program, ale jako sadu komponent, kterým „vdechnete život“ pomocí konfiguračního skriptu. Každá komponenta má v systému svoji úlohu, a pokud nám přestane její funkcionalita vyhovovat, lze ji velice snadno nahradit zásahem na jednom místě. Aby to bylo opravdu tak snadné, musí nahrazovaná komponenta splňovat určitý kontrakt – implementovat rozhranní.

Přes tato rozhranní mezi sebou jednotlivé komponenty komunikují, využívají deklarované služby, posílají si data. Jenže, jak mají vědět, kterou komponentu mají za daným rozhranním hledat. Ony to vědět nemusejí, tedy, vlastně by vůbec neměly.

Tak potom kdo? IoC kontejner!

Kontejner je mozkem aplikace. Je to ten jediný, který ví, které komponenty splňují určité kontrakty. Jak se to doví? Při startu kontejner nacpeme komponentami a on nám je později na vyžádání vrací. Dokonce jde tak daleko, že pokud si vyžádáme komponentu, vrátí nám ji včetně všech jejích závislostí a závislosti závislostí. :) Dostanete kompletní graf objektů, které jsou pro danou chvíli potřeba k vykonání dané činnosti a jsou v kontejneru zaregistrované.

Kontejner se tedy stará o dependency injection. Ale není to jediná služba, kterou nám dokáže poskytnout. Jeho další schopností je řídit životnost komponent. Už nikdy více nemusíte psát singletony! Pokud potřebujete singleton, řeknete kontejneru a on se o to postará.

Castle Windsor

Windsor je jedním z takových kontejnerů určený pro dotnet. Jistě najdete spoustu alternativních jako Spring.Net, StructureMap, Unity nebo Ninject. Každý z nich má své výhody, jiné postupy, ale i omezení. Windsor jsem si vybral z několika důvodů:

  1. Je součástí opensource projektu Castle,
  2. tudíž má celkem velkou komunitu uživatelů i vývojářů
  3. a navíc dokáže s dalšími projekty z Castle spolupracovat.
  4. Má širokou škálu způsobů konfigurace: programově, programově přes fluent interface, XML a hlavně pomocí Binsor.

Na toto téma jsem měl menší vnitrofiremní prezentaci. Slajdy z ní si může také prohlédnout.

O Binsoru je ve slajdech také zmínka a jednou už jsem o něm psal.

Integrace s ASP.NET MVC

A konečně se dostávám k tomu, o čem tento spot vlastně je. Jak integrovat Windsor kontejner do naší webové aplikace?

Předpokládám, že máte ASP.NET MVC projekt již ve svém studiu. Pak je nutné mít binárky Windsoru. Pravděpodobně budou stačit ty z RC3. Osobně používám aktuální verzi z trunku. Přidejte si reference na knihovny Castle.Core, Castle.DynamicProxy, Castle.MicroKernel a Castle.Windsor. Pak si budeme muset vytvořit novou továrnu na výrobu controllerů. Vlastně nemusíme, již je součástí MVC Contrib, jen jsem jí trochu zjednodušil.

using System;
using System.Web.Mvc;
using System.Web.Routing;
using Castle.Windsor;

namespace BlogSpots.Infrastructure {
  public class WindsorControllerFactory : IControllerFactory {

    private IWindsorContainer _container;

    public WindsorControllerFactory(IWindsorContainer container) {
      if (container == null) {
        throw new ArgumentNullException("container");
      }
      _container = container;
    }

    public virtual IController CreateController(RequestContext context, string controllerName) {
      controllerName = controllerName.ToLower() + "controller";
      return (IController)_container.Resolve(controllerName);
    }

    public virtual void ReleaseController(IController controller) {
      var disposable = controller as IDisposable;
      if (disposable != null) {
        disposable.Dispose();
      }

      _container.Release(controller);
    }
  }
}

Tato továrna bude fungovat pro Windsor. Ještě ale chci přidat podporu pro Binsor, který je součástí Rhino.Commons. K těm se dostanete přes SVN a spolu s nimi dostanete i aktuální verzi Windsoru. Do projektu si ještě přidáme reference na Rhino.Commons a Rhino.Commons.Binsor. Pak si ještě vytvoříme statický helper pro práci s kontejnerem.

using System;
using System.Web.Mvc;
using Castle.Windsor;
using Rhino.Commons;

namespace BlogSpots.Infrastructure {
  public static class IoC {
    private static readonly IWindsorContainer _container;

    static IoC() {
      _container = new RhinoContainer("windsor.boo");
    }

    public static IWindsorContainer Container {
      get {
        return _container;
      }
    }
  }
}

Tím jsme si nainicializovali kontejner s konfigurací v souboru windsor.boo. Tak, a teď už nám zbývá jen zaregistrovat továrnu pro naši aplikaci a napsat konfigurační skript.

V Global.asax zaregistrujeme továrnu na controllery následovně:

protected void Application_Start() {
  ControllerBuilder.Current.SetControllerFactory(new WindsorControllerFactory(IoC.Container));
}

Do web.config přidáme registraci modulu, který nám umožní nastavit životnost objektu na jeden request:

<system.webServer>
  <modules>
    <add name="PerRequestLifestyle"
      type="Castle.MicroKernel.Lifestyle.PerWebRequestLifestyleModule, Castle.MicroKernel" />
  </modules>
</system.webServer>

Tato registrace je pouze pro IIS 7 integrated mode, je nutné ji ještě přidat do system.web\httpModules, aby vám fungovala i na DevServeru.

Boo na scénu

Tímto máme za sebou vše důležité, pro napsání konfiguračního skriptu a spuštění aplikace. Pokud chcete přidat do VisualStudia podporu pro Boo, ve kterém jsou Binsor konfigurační skripty psané, doporučuju stáhnout si a doinstalovat BooLang Studio.

Pojďme tedy k vytvoření konfiguračního souboru! V rootu aplikace si vytvořte soubor Windsor.boo a nastavíme mu vlastnost Build Action na Content, aby se nám pěkně kopíroval při publikování projektu. Do konfiguráku zadejte následující řádky:

import System.Web.Mvc from System.Web.Mvc

for controller in AllTypesBased of Controller("<nazev assembly MVC projektu>"):
  component controller.Name.ToLower(), controller:
    lifestyle PerWebRequest

Takto jsme do kontejneru zaregistrovali všechny controllery v našem MVC projektu. Postupně můžeme registrovat další komponenty a budovat naší aplikaci, ale o tom zas někdy jindy. Tedy snad…

Binsor – síla konfigurace

15.18 - 25. srpna 2008 | ASP.NET 2.0

Pokud se dostanete do stádia, kdy potřebujete takovou architekturu, kde je potřeba používat plug-iny třetí strany, kde je potřeba snadno vyměnit komponentu za jinou, přichází ke slovu nějaký druh konfigurace.

Velice často se můžeme setkat s konfigurací komponent v XML. Ať už je to Provider pattern známý z ASP.NET, či vlastní konfigurační sekce, nebo konfigurace Windsor kontejneru, pořád tu máme XML. Jeho ohromnou výhodou je, že se dají systémové komponenty snadno překonfigurovat bez nutnosti celý systém překompilovat. Stačí pouhý restart aplikace. Navíc nástrojů na editaci a validaci XML máme nepočítaně, takže změnu konfigurace zvládne téměř každý.

Velkou nevýhodou XML, je jeho omezené výrazivo. Špatně se v něm píše foreach nebo if. Prostě na složitější konstrukce je potřeba programovací jazyk. A nebo skriptovací!

Boo na scénu

Boo je staticky typovaný objektový jazyk nad CLI (dot net) inspirovaný syntaxí Pythonu. Jeho hlavní silou je metaprogramování. Na úrovni kompilátoru se snadno dají psát makra. Takových maker lze využít k tvorbě přehledných DSL a jednou z takových je i Binsor.

Windsor + Boo = Binsor

Windsor je IoC kontejner na platformě dot net a je součástí opensource projektu Castle. Windsor má v základu dvě možnosti jak konfigurovat komponenty.

  1. Programově na úrovni mikrokernelu.
  2. XML konfigurací.

XML konfigurace je nejčastěji užívanou možností, kvůli výše popsaným výhodám. V trunku se nedávnou objevila i konfigurace pomocí DSL na principu fluent interface přímo v C#. Taková konfigurace je velice pěkná, ale je tu pořád nutnost při každé změně znova celý projekt kompilovat. Proto je tu Binsor, který si bere sílu programovacího jazyka, efektnost XML konfigurace a navíc jednoduchost a přímočarost syntaxe.

Konfigurace komponent

Nejčastější aktivitou s Binsorem nejspíš bude registrace komponent. :)

component 'my_component', IServiceContract, ServiceImplementation:
    # nastavime parametr konstruktoru
    constructorParameter = 10
    # nastavime vlastnost sluzby
    SomeProperty = "Hello Word!"

Kód je v celku jednoduchý. Na prvním řádku začínamé klíčovým slovem component, které říká, že registrujeme komponentu. Prvním parametrem je název komponenty, přes který se na ní můžeme odkazovat. Druhý parametr je rozhranní služby a třetí je konkrétní implementace, která bude při rozpoznání kontejnerem vrácena, např. IoC.Container.Resolve<IServiceContract>(). Za dvojtečkou pokračuje výčet nastavovaných vlastností a parametrů. Můžeme takto nastavovat i speciální vlastnost ovlivňující životnost objektu.

component HttpRequest:
    lifestyle Singleton

Všimněte si, že se ani nemusí při registraci uvádět referenční název nebo abstraktní typ. Může se klidně rovnou registrovat typ konkrétní, který se bude v tomto případě chovat jako singleton. Takovéto ukázky jsou fešné, ale nic, co bychom nezvládli pomocí XML. Pojďme trochu dál.

for type in AllTypesBased of Controller("MyApplication.Web"):
    component type

Tento kód zaregistruje všechny controllery (třídy, které jsou potomky třídy Controller) z assembly MyApplication.Web. Krom generické metody AllTypesBased, je tu ještě generická metoda AllTypesWithAttribute a negenerická AllTypes. Všechny mají jako parametr název assembly, jejíž typy procházejí.

Facility

Neméně důležitou součástí konfigurace jsou facility, které dodávají kontejneru nové možnosti a zapouzdřují větší sady komponent do logických jednotek. Pro ukázku konfigurace ActiveRecord komponent pomocí Binsoru:

facility ActiveRecordFacility:
    configuration:
        @isWeb = true, isDebug = true
        assemblies = [ Assembly.Load("MyApplication.Entities") ]
        config(keyvalues, item: add):
            show_sql = true
            command_timeout = 5000
            cache.foo.use_query_cache = false
            dialect = 'NHibernate.Dialect.MsSql2005Dialect'
            connection.provider = 'NHibernate.Connection.DriverConnectionProvider'
            connection.driver_class = 'NHibernate.Driver.SqlClientDriver'
            connection.connection_string = 'connectionString1'

Shrnutí

Osobně se mi možnosti konfigurace přes Binsor velice líbí, ale je celkem možné, že ne každému to může vyhovovat. Pokud ale používáte Windsor a přijde vám XML konfigurace nepřehledná, je toto možná cesta, jak z toho ven. Pokud chcete nějaké lepší příklady, doporučuju si stáhnout SVN repository https://rhino-tools.svn.sourceforge.net/svnroot/rhino-tools/trunk/rhino-commons a projít si testy. Jako editor se mi osvědčil SharpDevelop, který má podporu pro Boo přímo v základu, nebo doinstalovat BooLang Studio do Visual Studia.

Tagy: , ,