måndag 12 mars 2012

Castle Windsor med mera.


Notera: Läs även nästa inlägg, som innehåller en rättning/korrigering av detta inlägg.

När IoC-kontainern Castle Windsor ska sättas upp kan man välja att registrera komponenter (klasser) via XML-konfiguration eller genom kod.

Tänkte här snabbt visa hur man konfigurerar Castle Windsor genom att registrera klasser via kod. Innan jag gör detta ska jag visa ett gäng klasser som med Linq2Sql hämtar personer från databasen (det är alltså dessa klasser som ska registreras och hanteras från Castle Windsor).

Först har vi ett wrapper interface för System.Linq.Data.DataContext (för att förenkla enhetstesterna). Innehåller endast en metod för detta demosyfte, men kan självfallet utökas.
    public interface IDataContext
    {
        IEnumerable<TResult> GetTable<TResult>(params object[] parameters);
    }

Implementation av IDataContext:
    /// <summary>
    /// Wrapper for System.Linq.Data.DataContext
    /// </summary>
    public class DataContextWrap : IDataContext
    {
        private readonly DataContext _db;
 
        public DataContextWrap(DataContext db)
        {
            if (db == null)
            {
                throw new ArgumentNullException("db");
            }
            _db = db;         }         public IEnumerable<TResult> GetTable<TResult>(params object[] parameters)         {             return _db.GetTable<TResult>(parameters);         }     }
Interface för klasser som hämtar någon typ av entiteter från någon typ av källa:
    public interface IRepository<TEntity>
    {
        /// <summary>
        /// Gets a collection of <see cref="TEntity"/>
        /// </summary>
        IEnumerable<TEntity> GetAll();
 
        /// <summary>
        /// Gets an <see cref="TEntity"/> by id
        /// </summary>        
        TEntity GetById(Guid id);
    }
Implementation av IRepository<Person> (i detta demo ska vi hämta personer):
    public class PersonRepository : IRepository<Person>
    { 
        private readonly IDataContext _db;
 
        public PersonRepository(IDataContext db)
        {
            if (db == null)
            {
                throw new ArgumentNullException("db");
            }
 
            _db = db;
        }
 
        public IEnumerable<Person> GetAll()
        {
            return _db.GetTable<Person>().ToList();
        }
 
        public Person GetById(Guid id)
        {
            return _db.GetTable<Person>().FirstOrDefault(person => person.ID == id);
        }
    }
Slutligen en person service, som vi ifrån code-behind / controller användar för att hämta och presentera personer.
    public interface IPersonService
    {
        /// <summary>
        /// Gets all persons
        /// </summary>
        IEnumerable<Person> GetAll();

        /// <summary>
        /// Gets a person by their userid
        /// </summary>
        Person GetById(Guid userid);
    }
Implementation av IPersonService:
    public class PersonService : IPersonService
    {
        private readonly IRepository<Person> _repository;
 
        public PersonService(IRepository<Person> repository)
        {
            if (repository == null)
            {
                throw new ArgumentNullException("repository");
            }
 
            _repository = repository;
        }
 
        public IEnumerable<Person> GetAll()
        {
            return _repository.GetAll().OrderBy(person => person.FirstName);
        }
 
        public Person GetById(Guid id)
        {
            return _repository.GetById(id) ?? Person.Empty;            
        }
    }
När vi nu ska använda PersonService, vill vi inte skriva följande rad för att få tag på en instans:
    IPersonService personService = new PersonService(
        new PersonRepository(new DataContextWrap(new DataContext(Connection.String)))
    );
Förutom att ovanstående rad skapar upp nya instanser av klasserna, skapas även en anslutning mot databasen upp vilket är kostsamt och onödigt att göra gång på gång. Istället för att göra ovanstående anrop kan en IoC-kontainer användas för att hålla rätt på beroenden mellan servicar, repositorys och databasklasser. Efter att ha registrerat klasserna med Windsor Castle räcker följande räcker för att få ut en instans av PersonService:
    var personService = IoC.Resolve<IPersonService>();
Registrering av ovanstående klasser görs första gången hjälp-klassen IoC refereras:
    public class IoC
    {
        private static readonly IWindsorContainer Container = new WindsorContainer();
 
        private IoC()
        {
        }
 
        static IoC()
        {
            RegisterComponents();
        }
 
        public static T Resolve<T>()
        {
            return Container.Resolve<T>();
        }
 
        private static void RegisterComponents()
        {
            Container.Register(
                Component.For<IDataContext>()                        
                            .UsingFactoryMethod(() => new DataContextWrap(new DataContext(Connection.String))),
 
                Component.For<IRepository<Person>, PersonRepository>(),
                Component.For<IPersonServicePersonService>()
            );           
        }
    }
Notera även IDataContext och anropet till "UseFactoryMethod" som säger åt Windsor att när vi begär IDataContext ska följande funktion anropas för att få tag på en instans. Dock vill vi inte att Windsor ska skapa upp nya instanser varje gång vi begär en instans. Därför ska ovanstående metod ersättas med nedanstående för att säga åt Windsor att behandla instanserna som singeltons dvs. endast skapa upp instanser en gång.
        private static void RegisterComponents()
        {
            Container.Register(
                Component.For<IDataContext>()                        
                            .LifeStyle
                            .Singleton
                            .UsingFactoryMethod(() => new DataContextWrap(new DataContext(Connection.String))),
 
                Component.For<IRepository<Person>, PersonRepository>()
                            .LifeStyle
                            .Singleton,
 
                Component.For<IPersonServicePersonService>()
                            .LifeStyle
                            .Singleton
            );           
        }


Om jag i ovanstående kod, väljer att klassen PersonRepository även ska använda en PersonCache, behöver jag bara lägga till denna som ett konstruktor-argument i PersonRepository och sedan registrera PersonCache med Windsor. Windsor ser då till att rätt klass initieras med rätt argument, vilket är riktigt fint!

Sammafattning:

Detta inlägg svävade ut lite och nämnde bland annat wrappers för DataContext i Linq2Sql, lätt hänt att det blir så. IoC är bra och smidigt när man väl har kommit igång med det. Det finns några olika IoC-bibliotek för .NET och de fungerar syntaxmässigt nästan likadant.

/Nils

Inga kommentarer:

Skicka en kommentar