NHibernate 3 and Autofac, can go together? (part 2)
In part 1, I introduced about NHibernate and domain model for News Management System. And today, I will continue to show you about Autofac and how to make Autofac can understand all core instances of NHIbernate. If you do not read part 1, please read it at here. The time went very quickly, so we will start jumping into Autofac now.
All source code are placed in my github at here
-
Introduce about Autofac
Autofac is an IoC container for Microsoft .NET. It manages the dependencies between classes so that applications stay easy to change as they grow in size and complexity. This is achieved by treating regular .NET classes as components. I don’t want to talk more. Just go to here for more information
-
Inversion of Control (IoC)
Don’t ambiguity between Dependency Injection and Inversion of Control? Please make clearly about it before you can continue. If you have the IoC container you will don’t care about creation object task, object lifetime management, auto-wiring up… Please google for more information about tasks that IoC container can do for you.
-
AOP (Interceptor)
Cross-cutting concerns, Advice, Pointcut, Aspect are four features that you should care about. In this example, I only focus on implementation the Exception aspect on some Repository class using AutoFac (in Autofac they are interceptor).
-
Module (separated of concern: SoC): Advantages of Structuring with Modules:
-
Decreased Configuration Complexity
-
Configuration Parameters are Explicit
-
Abstraction from the Internal Application Architecture
-
Better Type Safety
-
Dynamic Configuration
In my example, I used 2 modules for object configuration: One for registering all components for NHibernate, and one for registering all core service, interceptor for my example.
-
Intergrated NHibernate into Autofac
-
Register essential components of NHibernate (Configuration, SessionFactory, Session, Unit of work)
To NHibernate can run well, it need us config something for it. Such as: Dialect, cache, auto generated schema, show sql...; and in Fluent NHibernate, we need adding some mapping class for configuration, adding some conventions for customize generated object. So we need Configuration object for store all things above.
After we have the configuration, we will base on it for creating the SessionFactory. Because SessionFactory is very expensive, so we usually only create only one SessionFactory on one AppDomain of your application (web form or window form). And then we can suggest SessionFactory for get to you one Session. Because Session is only run in short time, we call it is Session per Request (Session-per-request is an ORM pattern that means a new session is created for each request and terminated at the end of the request). So we can have many instances of it in AppDomain. Up to you! I usually set the Unit of work above ISession of NHibernate. It make all persistence actions is become clearly. I think my explain about Configuration, SessionFactory and Session is enough to you. Below is a code the implemented for it.
public class NHibernateComponentModule : Module
{
public string ConnectionString { get; set; }
public Assembly AssemblyMapper { get; set; }
protected override void Load(ContainerBuilder builder)
{
Contract.Assert(builder != null, "Builder container is null");
Contract.Assert(!string.IsNullOrEmpty(ConnectionString), "Cannot find connection string in config file");
Contract.Assert(AssemblyMapper != null, "AssemblyMapper is null");
var cfg = BuildConfiguration();
var persistenceModel = BuildPersistenceModel();
persistenceModel.Configure(cfg);
var sessionFactory = BuildSessionFactory(cfg);
RegisterConponents(builder, cfg, sessionFactory);
}
public Configuration BuildConfiguration()
{
var config = Fluently.Configure()
.Database(MsSqlConfiguration.MsSql2005.ConnectionString(ConnectionString))
.ExposeConfiguration(c => c.SetProperty(Environment.ReleaseConnections, "on_close"))
.ExposeConfiguration(c => c.SetProperty(Environment.ProxyFactoryFactoryClass, typeof(NHibernate.ByteCode.Castle.ProxyFactoryFactory).AssemblyQualifiedName))
.ExposeConfiguration(c => c.SetProperty(Environment.Hbm2ddlAuto, "create"))
.ExposeConfiguration(c => c.SetProperty(Environment.ShowSql, "true"))
.ExposeConfiguration(BuildSchema)
.BuildConfiguration();
if (config == null)
throw new Exception("Cannot build NHibernate configuration");
return config;
}
public AutoPersistenceModel BuildPersistenceModel()
{
var persistenceModel = new AutoPersistenceModel();
persistenceModel.Conventions.Setup(c =>
{
c.Add(typeof(ForeignKeyNameConvention));
c.Add(typeof(ReferenceConvention));
c.Add(typeof(PrimaryKeyNameConvention));
c.Add(typeof(TableNameConvention));
});
persistenceModel.AddMappingsFromAssembly(AssemblyMapper);
persistenceModel.WriteMappingsTo(@"./");
return persistenceModel;
}
public ISessionFactory BuildSessionFactory(Configuration config)
{
var sessionFactory = config.BuildSessionFactory();
if (sessionFactory == null)
throw new Exception("Cannot build NHibernate Session Factory");
return sessionFactory;
}
public void RegisterConponents(ContainerBuilder builder, Configuration config, ISessionFactory sessionFactory)
{
builder.RegisterInstance(config).As<Configuration>().SingleInstance();
builder.RegisterInstance(sessionFactory).As<ISessionFactory>().SingleInstance();
builder.Register(x => x.Resolve<ISessionFactory>().OpenSession()).As<ISession>().InstancePerLifetimeScope();
builder.Register(x => new UnitOfWork(x.Resolve<ISessionFactory>())).As<IUnitOfWork>();
builder.Register(x => new UnitOfWorkFactory()).As<IUnitOfWorkFactory>().SingleInstance();
}
private static void BuildSchema(Configuration config)
{
new SchemaExport(config).SetOutputFile(@"./Schema.sql").Create(true, true);
}
}
As you see we registered SessionFactory is a InstancePerLifeTimeScope.
So we only have one SessionFactory in AppDomain.
For more information about object life cycle, please go to Autofac website.
-
Register services (Repository, Interceptor)
I think I will not explain deeply about Repository, because it has already been rather popular. You can go back some my post in the past for have knowledge about it. I just show some code example for registering Repository and Interceptor to Autofac as here:
public class ServiceComponentModule : Module
{
protected override void Load(ContainerBuilder builder)
{
Contract.Assert(builder != null, "Builder container is null");
builder.RegisterType<ExceptionInterceptor>()
.As<ExceptionInterceptor>();
builder.RegisterType<CategoryRepository>()
.As<ICategoryRepository>()
.EnableInterfaceInterceptors()
.InterceptedBy(typeof(ExceptionInterceptor));
builder.RegisterType<NewsRepository>()
.As<INewsRepository>()
.EnableInterfaceInterceptors()
.InterceptedBy(typeof(ExceptionInterceptor));
builder.RegisterType<PollRepository>()
.As<IPollRepository>()
.EnableInterfaceInterceptors()
.InterceptedBy(typeof(ExceptionInterceptor));
builder.RegisterType<UserRepository>()
.As<IUserRepository>()
.EnableInterfaceInterceptors()
.InterceptedBy(typeof(ExceptionInterceptor));
}
}
and the ExceptionInterceptor is:
public class ExceptionInterceptor : IInterceptor
{
private readonly ILog _logger;
public ExceptionInterceptor()
: this(Log4NetWrapper.GetLogger(MethodBase.GetCurrentMethod().DeclaringType))
{
}
public ExceptionInterceptor(ILog logger)
{
_logger = logger;
}
public void Intercept(IInvocation invocation)
{
try
{
_logger.Info("Before call " + invocation.Method.Name + " method");
invocation.Proceed();
_logger.Info("After call " + invocation.Method.Name + " method");
}
catch (Exception ex)
{
_logger.Error(ex.Message);
throw;
}
}
}
It is cool or not, man???
-
Intergrated CommonServiceLocator into Autofac
Why do you need know many things about register object, get objects, resolve objects? I am very lazy, so I usually delegate all tasks above for CommonServiceLocator doing. And here is the implementing of it:
public class BootStrapper
{
private static IContainer _container;
public BootStrapper()
{
_container = GetContainer();
ServiceLocator.SetLocatorProvider(() => new AutofacServiceLocator(_container));
}
public static IContainer GetContainer()
{
var builder = new ContainerBuilder();
builder.RegisterModule(new NHibernateComponentModule()
{
// TODO: should wrap the ConfigurationManager for unit testing
ConnectionString = ConfigurationManager.ConnectionStrings["NHibernate3Sample"].ConnectionString,
AssemblyMapper = typeof(CategoryMapping).Assembly
});
builder.RegisterModule(new ServiceComponentModule());
return builder.Build();
}
}
As you see, I use a BootStrapper for boot my application. This way make the application more elegant, clearly.
After you had read 2 links above about Law of Demeter (LoD), I thought you understood why did I wrap CommonServiceLocator :D.
public static class IoC
{
static IoC()
{
new BootStrapper();
}
public static TService GetInstance<TService>()
{
return ServiceLocator.Current.GetInstance<TService>();
}
public static object GetInstance(Type serviceType)
{
return ServiceLocator.Current.GetInstance(serviceType);
}
...
}
Boot your application
Just call new BootStrapper(); at the place that you want to boot your application.
In this example is in IoC class, but you can also call in Global.asax class in web form or App.xaml in WPF and Silverlight :)
That is all things I want to talking in this post. Thanks for your read and happy coding. Just dance!!!