Konfigurácia knižnice Automapper s Unity IOC

S mapovaním nejakého objektu na iný objekt sa stretol hádam každý programátor. Ide o zvlášť "populárnu" činnosť v projektoch využívajúcich architektúry ako MVC alebo MVVM. Fragmenty kódu, kde sú mapované vlastnosti modelu na vlastnosti príslušného viewmodelu a naopak vedia poriadne pokaziť estetický dojem z kódu. Ich písanie je navyše nudné, otravné a čo je najhoršie - opakujúce sa.

AutoMapper

Našťastie existujú nástroje, ktoré toto mapovanie robia za nás. Jedným z takých je knižnica AutoMapper, ktorej autorom je Jimmy Bogard (spoluautor kníh ASP.NET MVC in Action a ASP.NET MVC 2 in Action). Jej aktuálnu verziu je možné do projektu nainštalovať prostredníctvom PM konzoly príkazom:

PM> Install-Package AutoMapper
Väčšina návodov na použitie AutoMapper-a používa statickú triedu Mapper, obvykle v dvoch základných krokoch:
  1. definícia mapovania:
    Mapper.CreateMap<SourceType, TargetType>();
    
  2. samotné mapovanie:
    var target = Mapper.Map<SourceType, TargetType>(source);
    

Problém spojený so statickými triedami a metódami však nastane v prípade potreby jednotkového testovania zdrojového kódu a jeho izolácie. Statické triedy a statické metódy sú elementami procedurálného kódu a ich tzv. mockovanie v prípade potreby izolácie nejakej testovanej komponenty je nemožné. Trieda Mapper z knižnice AutoMapper je však len akýmsi wrapper-om pre dve triedy, ktoré poskytujú skutočnú funkcionalitu - Configuration a MappingEngine.

AutoMapper a Unity

Cieľom je teda integrovať AutoMapper do projektu, v ktorom je použitý niektorý z DI (Dependency Injection) kontajnerov. V tomto konkrétnom prípade pôjde o DI kontajner Unity od spoločnosti MIcrosoft. Princíp by mal ale bude fungovať pre ľubovoľný iný DI kontajner.

Základný resolving pre kľúčové typy knižnice AutoMapper-a by mal vyzerať nasledovne:
  • žiadosť pre rozhranie IConfiguration vráti typ Configuration,
  • žiadosť pre rozhranie IConfigurationProvider vráti typ Configuration a
  • žiadosť pre rozhranie IMappingEngine vráti typ MappingEngine.

Konkrétna konfigurácia kontajnera Unity by mala byť jasná, za zmienku možno stojí spôsob registrovania samotného typu Configuration. Inštancia triedy Configuration je vytváraná pomocou factory metódy, ktorá volá priamo konštruktor triedy Configuration. Parameter ContainerControlledLifetimeManager určuje životnosť inštancie triedy Configuration - v tomto prípade to znamená Singleton, nakoľko chceme mať v celej aplikácii jedinú inštanciu tejto triedy, ktorá bude obsahovať konfiguráciu pre všetky mapovania.


    var container = new UnityContainer();
    container.RegisterType<Configuration>(new ContainerControlledLifetimeManager(),
        new InjectionFactory((c) => new Configuration(new TypeMapFactory(), MapperRegistry.AllMappers())));
 
    container.RegisterType<IConfigurationConfiguration>();
    container.RegisterType<IConfigurationProviderConfiguration>();
    container.RegisterType<IMappingEngineMappingEngine>();

Použitie objektu typu IMappingEngine pre realizáciu samotného mapovania:

    var mappingEngine = container.Resolve<IMappingEngine>();
    var target = mappingEngine.Map<SourceTypeTargetType>(source);

Registrácia mapovania

Pravdepodobne nie je žiadúce, aby sme mali všetky konkrétne mapovania definované niekde na jednom mieste a radšej uprednostníme trochu modulárnejšie riešenie. Keďže máme k dispozícii DI kontajner, pôjde to celkom ľahko. Najprv vytvoríme rozhranie, ktoré bude definovať konkrétne mapovanie typov. Pre ilustráciu uvediem i príklad implementácie tohto rozhrania:

    public interface IMappingRegistration
    {
        void RegisterMapping(IConfiguration configuration);
    }
    public class SampleMappingRegistration : IMappingRegistration
    {
        public void RegisterMapping(IConfiguration configuration)
        {
            configuration.CreateMap<SourceTypeTargetType>();
        }
    }

Takto vytvorené mapovanie je následne potrebné registrovať v konfigurácii pre Unity kontajner:

    container.RegisterType<IMappingRegistrationSampleMappingRegistration>();
    container.RegisterType<IMappingRegistrationAnotherMappingRegistration>();

No a na záver už len ostáva niekde pri štarte aplikácie (Bootstrap) všetky tieto mapovania registrovať:

    var config = container.Resolve<IConfiguration>();
    var mappings = container.ResolveAll<IMappingRegistration>();
    foreach (var mappingRegistration in mappings)
        mappingRegistration.RegisterMapping(config);

Výsledkom je vlastne distribúcia definícií konkrétnych mapovaní do viacerých tried (implementujúcich rozhranie IMappingRegistration). Navyše celý tento spôsob integrovania knižnice AutoMapper do projektu nevytvára žiadne väzby na statickú triedu Mapper, náš kód ostáva testovateľný, čistý a loosely coupled.

Publikované Saturday, September 10, 2011 5:06 PM michal.kohut

Komentáre

Bez komentárov