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

Persistence modelu pomocí ActiveRecord

15.16 - 21. ledna 2009 | ASP.NET 2.0

V architektuře naší MVC aplikace jsme se dostali již poměrně daleko. Vybrali jsme si šablonovací systém, máme vyřešenou integraci s IoC kontejnerem, vymazlené routování, připravené model bindery a teď je na čase nějakým způsobem model persistovat. Existuje mnoho způsobů, jak toho dosáhnout.

  1. Persistenci si můžete řešit sami na úrovni ADO.NET. Jenže proč řešit něco, co už někdo stokrát vyřešil?
  2. Použít nějaký ORM nástroj.

ORM nástrojů na výběr máme dnes tolik, že je stěží na prstech spočítáme, jaký si tedy vybrat? Pro mnohé bude první volbou LINQ to SQL. Pro jednoduché mapování (1:1) a tam, kde je možné být závislý pouze na MS SQL, je toto řešení dostačující. Osobně preferuju malinko složitější cestu, ale ve výsledku myslím, že mnohem mocnější. :)

Castle ActiveRecord a Repository pattern

Já ve svých posledních proktech používám Castle ActiveRecord, což je implemenatce návrhového vzoru ActiveRecord nad ORM frameworkem NHibernate. V mnoha ohledech práci s NHibernatem značně zjednodušuje, ať už je to mapování, automatickou správu session a podobně. Způsoby užití jsme už popsal ve spotu ActiveRecord vs. Repository pattern. Další výhodou je, že vše, co funguje v NHibernate (LINQ, Spatial, vlastní mapovací typy), můžete použí i s Castle ActiveRecord.

Datové třídy však nedědím z bázové třídy ActiveRecordBase, ale používám Repository, která se stará o CRUD operace s entitou. Pokud chcete mít mapování a závislost na ActiveRecord snadno odstranitelnou, můžete mapovací atributy vyčlenit třeba do samostatného partial souboru. Ale nejspíš nikdy v životním cyklu vaší aplikace nebudete ORM framework měnit, takže se trápit metadaty o databázovém schema je celkem zbytečné…

Základem pro repository je generické rozhraní IRepository<>, pro snadnou automatickou registraci repositoří v kontejneru ještě nadefinujeme ještě i negenerické rozhraní:

using Castle.ActiveRecord;

public interface IRepository {}

public interface IRepository<Tentity> : IRepository where TEntity : class {
  TEntity FindById(int id);
  void Save(TEntity entity);
  void Delete(TEntity entity);
}

public abstract class ActiveRecordRepositoryBase<TEntity>
  : IRepository<TEntity> where TEntity : class {
  public virtual TEntity FindById(int id) {
    return ActiveRecordMediator<TEntity>.FindByPrimaryKey(id);
  }
  public virtual void Save(TEntity entity) {
    ActiveRecordMediator<TEntity>.SaveAndFlush(entity);
  }
  public virtual void Delete(TEntity entity) {
    ActiveRecordMediator<TEntity>.DeleteAndFlush(entity);
  }
}

Pojďme si podporu pro ActiveRecord integrovat do naší MVC aplikace.

Integrace přes Binsor

Výhodou projektů z Castle, je, že se mezi sebou dokážou integrovat a využívat navzájem výhod své synergie. Nejprve si do projektu přidáme referenci na knihovnu Castle.ActiveRecord a ještě Castle.Facilities.ActiveRecordIntegration. Následně zaregistrujeme v souboru Windsor.boo facility pro integraci ActiveRecordu do IoC kontejneru:

import System.Reflection

// podpora ActiveRecord v kontejneru
facility ActiveRecordFacility:
  configuration:
    @isWeb = true, isDebug = false
    assemblies = [ Assembly.Load("MyApplication.Entities") ]
    config(keyvalues, item: add):
      show_sql = true
      command_timeout = 500
      dialect = 'NHibernate.Dialect.MsSql2005Dialect'
      connection.provider = 'NHibernate.Connection.DriverConnectionProvider'
      connection.driver_class = 'NHibernate.Driver.SqlClientDriver'
      connection.connection_string_name = 'CONN_DEMO'

Teď ještě potřebujeme ve web.configu přidat ConnectionString pod klíčem CONN_DEMO a volitelně přidat životnost session na jeden request. To uděláte tak, že do sekce system.web\httpModules přidáte následující modul:

<add
  name="PerRequestSessionScope"
  type="Castle.ActiveRecord.Framework.SessionScopeWebModule, Castle.ActiveRecord"/>

To má za výhodu to, že všechny vaše dotazy za jeden request se chovají jako jedna transakce. Takže integrace frameworku, je hotová. Teď ještě přidáme automatickou registraci samotných repositoří.

for repository in AllTypesBased of IRepository("MyApplication.Entities"):
  component repository.Name.ToLower(), repository.GetInterfaces()[0], repository:
    lifestyle PerWebRequest

Tímto se zaregistrují všechny repository, které dědí z ActiveRecordRepositoryBase a jsou pak ve vašich komponentách dostupné jako IRepository<User> (příklad pro entitu User). Pokud budete mít v repository více metod, je dobré vytvořit pro ně kontrakt, který obsahuje kontrakt generické repository. Například takto:

public interface IUsersRepository : IRepository<User> {
  User FindUserByEmail(string email);
}

Registrace takovýchto komponent už je o něco složitější, ale dá se to také zvládnout. Nejprve si zavedeme konvenci, že repository, které takovéto kontrakty implementují, se budou jmenovat až na I na začátku stejně. :) Takže pro kontrakt IUsersRepository budeme mít konkrétní implementaci UsersRepository atd. Pro jejich registraci využijeme metodu System.Type.FindInterfaces, která bere jako první argument delegát na vyhledávací funkci a druhý argument jsou hledací kritéria. Takže si vytvoříme funkci, která splňuje kontrakt delegáta, a následně ji hned využijeme pro registraci:

def contains_name(t as Type, o as Object):
  return t.Name.Contains(o.ToString())

for repository in AllTypesBased of IRepository("MyApplication.Entities"):
  repository_interface = repository.FindInterfaces(contains_name, repository.Name)
  component repository.Name.ToLower(), repository_interface, repository:
    lifestyle PerWebRequest

Cool ne? Pár řádků konfigurace a nejspíš máme vystaráno, tedy když dodržíme stanovenou konvenci, tak už se o registraci komponent datové vrstvy nemusíme starat. Stejně tak už máme zaregistrované model bindery a controllery. Trošku nám to konfigurace povyrostla, ale díky možnostem, které nám přináší Binsor, je to jen zlomek toho, co by bylo potřeba napsat v XML.

Projektík se nám pomalu rozrůstá, sice stále nic nedělá, ale je to dobrý základ pro pořádný web. Pokusím se ho hodit někam do SVN, aby bylo lehčí se v projektu orientovat a byly vidět jednotlivé změny.

Oprava předchozích článků

11.10 - 4. ledna 2009 | Webdesign

Nikdo není neomylný, i já dělam chyby. A poměrně často. Bohužel i ve svých článcích. Proto jsem se jal revidovat sérii předchozích článků o ASP.NET MVC a znovu si prošel jejich kód a ověřil, že funguje.

Prošel jsem tedy následující články:

Doplnění REST

V článku REST aplikace pomocí ASP.NET MVC jsem nastínil, jak vytvořit RESTful přístup k entitám pomocí filtrů akcí. Výslednou ukázku jsem doplnil o výstupy akce. V komentářích padlo několik dotazů, jak vlastně REST služby volat, protože zatím žádný prohlížeč neumí formulář odeslat jinou metodou než POST nebo GET.

Jediným řešením, jak ze stránek REST používat je AJAX. Proto akce vrací JSON. V pohledech pak postačí následující kód:

<% using (Ajax.BeginForm("REST", "Customers", new AjaxOptions { HttpMethod = "POST" })) {%>
  <input type="submit" value="Create New" />
<% } %>

<% using (Ajax.BeginForm("REST", "Customers", new AjaxOptions { HttpMethod = "PUT" })) {%>
  <input type="submit" value="Update" />
  <input type="hidden" name="id" value="10" />
<% } %>

<% using (Ajax.BeginForm("REST", "Customers", new AjaxOptions { HttpMethod = "DELETE" })) {%>
  <input type="submit" value="Delete" />
  <input type="hidden" name="id" value="10" />
<% } %>

<script src="/Scripts/MicrosoftAjax.js" type="text/javascript"></script>
<script src="/Scripts/MicrosoftMvcAjax.js" type="text/javascript"></script>

Vylepšená verze model binderu

V článku Model binders v ASP.NET MVC jsem ukazoval jak si napsat vlastní model binder. Taky jsem psal že uvedený postup není moc dobrý. Tady je tedy lepší verze:

public class AddressBinder : BinderBase<Address> {
  public override ModelBinderResult BindModel(ModelBindingContext bindingContext) {

    IValueProvider valueProvider = bindingContext.ValueProvider;
    Address address = bindingContext.Model as Address ?? new Address();

    address.Street = GetSafeStringValue(valueProvider, "address_street");
    address.StreetNumber = GetSafeStringValue(valueProvider, "address_street_number");
    address.PostalCode = GetSafeStringValue(valueProvider, "address_postal_code");
    address.Town = GetSafeStringValue(valueProvider, "address_town");

    return new ModelBinderResult(address);
  }
}

K tomu je ale ještě potřeba rozšířit bázovou třídu binderu:

using System;
using System.Web.Mvc;

public abstract class BinderBase<T> : IModelBinder {

  protected static string GetSafeStringValue(IValueProvider valueProvider, string key) {
    ValueProviderResult value = valueProvider.GetValue(key);

    if (value != null) {
      return value.AttemptedValue.Trim();
    }

    return String.Empty;
  }

  protected static bool GetSafeBooleanValue(IValueProvider valueProvider, string key) {
    ValueProviderResult value = valueProvider.GetValue(key);

    return value != null;
  }

  public Type ModelType {
    get {
      return typeof(T);
    }
  }

  public abstract ModelBinderResult BindModel(ModelBindingContext bindingContext);
}

Tím jsme se zbavili závislosti na HttpContextu, binder je lépe testovatelný a hlavně se dá použít i v metodě UpdateModel.

Oprava integrace IoC kontejneru do ASP.NET MVC

V ukázkových kódech článku Inversion of Control v ASP.NET MVC bylo několik chybek, chyběly usingy a některé věci jsem trošku refaktoroval.

Nová verze RoutingMacros.boo

V článku DSL pro konfiguraci URL Routingu v ASP.NET MVC jsem měl jedno makro nedopsané. V nové verzi jsem přidal sekci constraints k makru ignore_route a provedl lehký refaktoring. Plně funkční kód:

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

import System.Web
import System.Web.Routing

class IgnoreRouteInternal(Route):
  def constructor(url as string):
    super(url, StopRoutingHandler())

  override def GetVirtualPath(requestContext as RequestContext, values as RouteValueDictionary):
    return null as VirtualPathData

def apply_constraints(block as Block, constraints as MacroStatement):
  assert block
  return if not constraints

  block.Add([| route.Constraints = RouteValueDictionary() |])

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

def apply_defaults(block as Block, defaults as MacroStatement):
  assert block
  return if not defaults

  block.Add([| route.Defaults = RouteValueDictionary() |])

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

macro ignore_route:
  path, = ignore_route.Arguments
  constraints = ignore_route["constraints"] as MacroStatement

  block = Block()
  block.Add([| route = IgnoreRouteInternal($path) |])
  apply_constraints(block, constraints)
  block.Add([| RouteTable.Routes.Add(route) |])

  return block

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

  block = Block()
  block.Add([| route = Route($path, MvcRouteHandler()) |])
  apply_defaults(block, defaults)
  apply_constraints(block, constraints)
  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

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: , ,