i'm working in a new project and i'm trying to implement nhibernate with ninject in asp.net mvc. I'm having the error:
NHibernate.HibernateException: No session bound to the current context
Below is the code i'm using:
using FluentNHibernate.Automapping;
using FluentNHibernate.Cfg;
using FluentNHibernate.Cfg.Db;
using FluentNHibernate.Conventions.Helpers;
using NHibernate;
using NHibernate.Context;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
namespace TestNhibernateSessions
{
public class SessionFactory : IHttpModule
{
private static readonly ISessionFactory _SessionFactory;
static SessionFactory()
{
_SessionFactory = CreateSessionFactory();
}
public void Init(HttpApplication context)
{
context.BeginRequest += BeginRequest;
context.EndRequest += EndRequest;
}
public void Dispose()
{
}
public static ISession GetCurrentSession()
{
return _SessionFactory.GetCurrentSession();
}
private static void BeginRequest(object sender, EventArgs e)
{
ISession session = _SessionFactory.OpenSession();
session.BeginTransaction();
CurrentSessionContext.Bind(session);
}
private static void EndRequest(object sender, EventArgs e)
{
ISession session = CurrentSessionContext.Unbind(_SessionFactory);
if (session == null) return;
try
{
session.Transaction.Commit();
}
catch (Exception)
{
session.Transaction.Rollback();
}
finally
{
session.Close();
session.Dispose();
}
}
private static ISessionFactory CreateSessionFactory()
{
return Fluently.Configure()
.Database(MsSqlConfiguration.MsSql2008.ConnectionString(c => c.FromConnectionStringWithKey("DefaultConnection")))
.Mappings(m => m.AutoMappings.Add(CreateMappings()))
.CurrentSessionContext<WebSessionContext>()
.BuildSessionFactory();
}
private static AutoPersistenceModel CreateMappings()
{
return AutoMap
.Assembly(System.Reflection.Assembly.GetCallingAssembly())
.Where(t => t.Namespace != null && t.Namespace.EndsWith("Domain"))
.Conventions.Setup(c => c.Add(DefaultCascade.SaveUpdate()));
}
}
}
[assembly: WebActivatorEx.PreApplicationStartMethod(typeof(TestNhibernateSessions.App_Start.NinjectWebCommon), "Start")]
[assembly: WebActivatorEx.ApplicationShutdownMethodAttribute(typeof(TestNhibernateSessions.App_Start.NinjectWebCommon), "Stop")]
namespace TestNhibernateSessions.App_Start
{
using System;
using System.Web;
using Microsoft.Web.Infrastructure.DynamicModuleHelper;
using Ninject;
using Ninject.Web.Common;
using TestNhibernateSessions.Repository;
using TestNhibernateSessions.Service;
using NHibernate;
public static class NinjectWebCommon
{
private static readonly Bootstrapper bootstrapper = new Bootstrapper();
/// <summary>
/// Starts the application
/// </summary>
public static void Start()
{
DynamicModuleUtility.RegisterModule(typeof(OnePerRequestHttpModule));
DynamicModuleUtility.RegisterModule(typeof(NinjectHttpModule));
bootstrapper.Initialize(CreateKernel);
}
/// <summary>
/// Stops the application.
/// </summary>
public static void Stop()
{
bootstrapper.ShutDown();
}
/// <summary>
/// Creates the kernel that will manage your application.
/// </summary>
/// <returns>The created kernel.</returns>
private static IKernel CreateKernel()
{
var kernel = new StandardKernel();
try
{
kernel.Bind<Func<IKernel>>().ToMethod(ctx => () => new Bootstrapper().Kernel);
kernel.Bind<IHttpModule>().To<HttpApplicationInitializationHttpModule>();
RegisterServices(kernel);
return kernel;
}
catch
{
kernel.Dispose();
throw;
}
}
/// <summary>
/// Load your modules or register your services here!
/// </summary>
/// <param name="kernel">The kernel.</param>
private static void RegisterServices(IKernel kernel)
{
kernel.Bind<IProductRepository>().To<ProductRepository>();
kernel.Bind<IProductService>().To<ProductService>();
}
}
}
Since you're using Ninject, my recommendation is to use it to inject the session factory rather than an IHttpModule. To do so, create Ninject Provider classes as shown below. Note that this requires transaction management in code, I dislike the idea of blindly holding a transaction open during a request.
public class SessionFactoryProvider : Provider<ISessionFactory>
{
protected override ISessionFactory CreateInstance(IContext context)
{
return Fluently.Configure()
.Database(MsSqlConfiguration.MsSql2008.ConnectionString(c => c.FromConnectionStringWithKey("DefaultConnection")))
.Mappings(m => m.AutoMappings.Add(CreateMappings()))
.CurrentSessionContext<WebSessionContext>()
.BuildSessionFactory();
}
}
public class SessionProvider : Provider<ISession>
{
protected override ISession CreateInstance(IContext context)
{
var sessionFactory = context.Kernel.Get<ISessionFactory>();
var session = sessionFactory.OpenSession();
session.FlushMode = FlushMode.Commit;
return session;
}
}
Then in NinjectWebCommon:
private static void RegisterServices(IKernel kernel)
{
// session factory instances are singletons
kernel.Bind<ISessionFactory>().ToProvider<SessionFactoryProvider>().InSingletonScope();
// session-per-request
kernel.Bind<ISession>().ToProvider<SessionProvider>().InRequestScope();
}
Related
I have a working WCF application hosted in IIS (.svc), using Ninject (Ninject.Web.Common) for dependency injection. All is well and good, works great. However I am trying to implment interception on the service endpoints and am getting a ServiceActiviationException. I've included the ninject extensions for wcf, interception, and interception.dynamic proxy as well as castle.core, so I'm not really sure what I'm missing here. Any ideas?
UPDATE: I was able to get past the service validation exception but I am now getting the following error:
Can not instantiate proxy of class: MyNameSpace.CustomerService.
Could not find a parameterless constructor.
My NinjectWebCommon contents:
public class NinjectWebCommon
{
private static readonly Bootstrapper Bootstrapper = new Bootstrapper();
public static void Start()
{
DynamicModuleUtility.RegisterModule(typeof(OnePerRequestHttpModule));
DynamicModuleUtility.RegisterModule(typeof(NinjectHttpModule));
Bootstrapper.Initialize(CreateKernel);
}
public static void PostStart()
{
// Inject the WITS Application Security service into the ApplicationSecurityRoleProvider
//_kernel.Inject(Roles.Provider);
}
public static void Stop()
{
Bootstrapper.ShutDown();
}
private static IKernel CreateKernel()
{
var kernel = new StandardKernel(
new AutoMapperModule(),
new CustomerServiceModule(),
new AccountServiceModule());
try
{
kernel.Bind<Func<IKernel>>().ToMethod(ctx => () => new Bootstrapper().Kernel);
kernel.Bind<IHttpModule>().To<HttpApplicationInitializationHttpModule>();
RegisterServices(kernel);
return kernel;
}
catch
{
kernel.Dispose();
throw;
}
}
private static void RegisterServices(IKernel kernel)
{
kernel.Bind<LoggingInterceptor>().ToSelf().InRequestScope();
kernel.Bind<CustomerService>().ToSelf().Intercept().With<LoggingInterceptor>();
}
And a simple interceptor (Ive tried this also just implementing IInterceptor to no avail):
public class LoggingInterceptor : SimpleInterceptor
{
/// <summary>
/// Intercepts the specified invocation.
/// </summary>
/// <param name="invocation">The invocation to intercept.</param>
protected override void BeforeInvoke(IInvocation invocation)
{
Debug.WriteLine("Running " + invocation.ReturnValue);
base.BeforeInvoke(invocation);
}
public new void Intercept(IInvocation invocation)
{
try
{
base.Intercept(invocation);
}
catch (Exception e)
{
Debug.WriteLine("Exception: " + e.Message);
}
}
protected override void AfterInvoke(IInvocation invocation)
{
Debug.WriteLine("After Method");
base.AfterInvoke(invocation);
}
}
I'm new to Castle, NHibernate and WCF.
I implemented the session management for my MVC application based on the following article because it seemd to be the most advanced implementation of all posts I've read so far :
http://nhibernate.info/blog/2011/03/02/effective-nhibernate-session-management-for-web-apps.html
The only problem I got was that this uses some Asp.net specific functionality that isn't available in my WCF service like (HttpContext.Current.Items).
I started to use WcfFacility
public void Install(IWindsorContainer container, IConfigurationStore store)
{
container.AddFacility<WcfFacility>().Register
(
Component.For<IRepository>().ImplementedBy(typeof(RepositoryBase<,>)),
Component.For<ITimeService>()
.ImplementedBy<myTimeMvc.Webservice.TimeService>()
.Named("myTimeMvc.Webservice.TimeService"));
container.Register(
Component.For<IServiceBehavior>()
.ImplementedBy<WcfSessionPerRequestBehavior>()
);
}
My Persistance configuration:
public class PersistenceInstaller : IWindsorInstaller
{
public void Install(IWindsorContainer container, IConfigurationStore store)
{
container.Kernel.AddFacility<TypedFactoryFacility>();
container.Register(Component.For<ISessionFactory>().UsingFactoryMethod(k => CreateNhSessionFactory()));
container.Register(Component.For<ISessionFactoryProvider>().AsFactory());
container.Register(Component.For<IEnumerable<ISessionFactory>>().UsingFactoryMethod(k => k.ResolveAll<ISessionFactory>()));
container.Register(Classes.FromAssembly(Assembly.GetAssembly(typeof(HdtRepository))).InSameNamespaceAs<HdtRepository>().WithService.DefaultInterfaces().LifestyleTransient());
}
/// <summary>
/// Creates NHibernate Session Factory.
/// </summary>
/// <returns>NHibernate Session Factory</returns>
private static ISessionFactory CreateNhSessionFactory()
{
var connStr = ConfigurationManager.ConnectionStrings["DefaultConnection"].ConnectionString;
return Fluently.Configure()
.Database(
MsSqlConfiguration.MsSql2008
.UseOuterJoin()
.ConnectionString(x => x.FromConnectionStringWithKey("DefaultConnection"))
.ShowSql()
)
.Mappings(m => m.FluentMappings.AddFromAssemblyOf<TimeRecord>())
.ExposeConfiguration(cfg =>
cfg.Properties[Environment.CurrentSessionContextClass] = typeof(LazySessionContext).AssemblyQualifiedName
)
.BuildSessionFactory();
}
}
Then i tried to solve the problem with "HttpContext.Current.Items" by adding a custom extension:
namespace MyTimeService.WcfExtension
{
///<summary>
/// This class incapsulates context information for a service instance
///</summary>
public class WcfInstanceContext : IExtension<InstanceContext>
{
private readonly IDictionary items;
private WcfInstanceContext()
{
items = new Hashtable();
}
///<summary>
/// <see cref="IDictionary"/> stored in current instance context.
///</summary>
public IDictionary Items
{
get { return items; }
}
///<summary>
/// Gets the current instance of <see cref="WcfInstanceContext"/>
///</summary>
public static WcfInstanceContext Current
{
get
{
WcfInstanceContext context = OperationContext.Current.InstanceContext.Extensions.Find<WcfInstanceContext>();
if (context == null)
{
context = new WcfInstanceContext();
OperationContext.Current.InstanceContext.Extensions.Add(context);
}
return context;
}
}
/// <summary>
/// <see cref="IExtension{T}"/> Attach() method
/// </summary>
public void Attach(InstanceContext owner) { }
/// <summary>
/// <see cref="IExtension{T}"/> Detach() method
/// </summary>
public void Detach(InstanceContext owner) { }
}
}
registered the following way:
<extensions>
<behaviorExtensions>
<add name="WcfInstanceContext" type="MyTimeService.WcfExtension, MyTimeService.WcfExtension.WcfInstanceContext" />
</behaviorExtensions>
</extensions>
Then I created a custom ServiceBehavior
public class WcfSessionPerRequestBehavior : IServiceBehavior
{
private ISessionFactoryProvider _sfp;
public WcfSessionPerRequestBehavior(ISessionFactoryProvider sfp)
{
_sfp = sfp;
}
public void Validate(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase)
{
}
public void AddBindingParameters(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase, Collection<ServiceEndpoint> endpoints, BindingParameterCollection bindingParameters)
{
}
public void ApplyDispatchBehavior(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase)
{
foreach (var cdb in serviceHostBase.ChannelDispatchers)
{
var channelDispatcher = cdb as ChannelDispatcher;
if (null != channelDispatcher)
{
foreach (var endpointDispatcher in channelDispatcher.Endpoints)
{
foreach (var dispatchOperation in endpointDispatcher.DispatchRuntime.Operations)
{
dispatchOperation.CallContextInitializers.Add(new WcfSessionPerRequestCallContextInitializer(_sfp));
}
}
}
}
}
followed by a custom ICallContextInitializer:
public class WcfSessionPerRequestCallContextInitializer : ICallContextInitializer
{
private ILogger logger = NullLogger.Instance;
public ILogger Logger
{
get { return logger; }
set { logger = value; }
}
private ISessionFactoryProvider sfp;
public WcfSessionPerRequestCallContextInitializer(ISessionFactoryProvider s)
{
this.sfp = s;
}
public object BeforeInvoke(InstanceContext instanceContext, IClientChannel channel, Message message)
{
foreach (var sf in sfp.GetSessionFactories())
{
var localFactory = sf;
LazySessionContext.Bind(new Lazy<NHibernate.ISession>(() => BeginSession(localFactory)), sf);
}
return null;
}
public void AfterInvoke(object correlationState)
{
foreach (var sf in sfp.GetSessionFactories())
{
var session = LazySessionContext.UnBind(sf);
if (session == null) continue;
EndSession(session);
}
}
private static NHibernate.ISession BeginSession(ISessionFactory sf)
{
var session = sf.OpenSession();
session.BeginTransaction();
return session;
}
private void ContextEndRequest(object sender, EventArgs e)
{
foreach (var sf in sfp.GetSessionFactories())
{
var session = LazySessionContext.UnBind(sf);
if (session == null) continue;
EndSession(session);
}
}
private static void EndSession(NHibernate.ISession session)
{
if (session.Transaction != null && session.Transaction.IsActive)
{
session.Transaction.Commit();
}
session.Dispose();
}
}
and at last I adjusted the ICurrentSessionContext:
public class LazySessionContext : ICurrentSessionContext
{
private readonly ISessionFactoryImplementor factory;
private const string CurrentSessionContextKey = "NHibernateCurrentSession";
public LazySessionContext(ISessionFactoryImplementor factory)
{
this.factory = factory;
}
/// <summary>
/// Retrieve the current session for the session factory.
/// </summary>
/// <returns></returns>
public NHibernate.ISession CurrentSession()
{
Lazy<NHibernate.ISession> initializer;
var currentSessionFactoryMap = GetCurrentFactoryMap();
if (currentSessionFactoryMap == null || !currentSessionFactoryMap.TryGetValue(factory, out initializer))
{
return null;
}
return initializer.Value;
}
/// <summary>
/// Bind a new sessionInitializer to the context of the sessionFactory.
/// </summary>
/// <param name="sessionInitializer"></param>
/// <param name="sessionFactory"></param>
public static void Bind(Lazy<NHibernate.ISession> sessionInitializer, ISessionFactory sessionFactory)
{
var map = GetCurrentFactoryMap();
map[sessionFactory] = sessionInitializer;
}
/// <summary>
/// Unbind the current session of the session factory.
/// </summary>
/// <param name="sessionFactory"></param>
/// <returns></returns>
public static NHibernate.ISession UnBind(ISessionFactory sessionFactory)
{
var map = GetCurrentFactoryMap();
var sessionInitializer = map[sessionFactory];
map[sessionFactory] = null;
if (sessionInitializer == null || !sessionInitializer.IsValueCreated) return null;
return sessionInitializer.Value;
}
/// <summary>
/// Provides the CurrentMap of SessionFactories.
/// If there is no map create/store and return a new one.
/// </summary>
/// <returns></returns>
private static IDictionary<ISessionFactory, Lazy<NHibernate.ISession>> GetCurrentFactoryMap()
{
//var currentFactoryMap = (IDictionary<ISessionFactory, Lazy<NHibernate.ISession>>)HttpContext.Current.Items[CurrentSessionContextKey];
var currentFactoryMap = (IDictionary<ISessionFactory, Lazy<NHibernate.ISession>>)WcfInstanceContext.Current.Items[CurrentSessionContextKey];
if (currentFactoryMap == null)
{
currentFactoryMap = new Dictionary<ISessionFactory, Lazy<NHibernate.ISession>>();
WcfInstanceContext.Current.Items[CurrentSessionContextKey] = currentFactoryMap;
}
return currentFactoryMap;
}
}
This seems to work but since I'm new to all that stuff I can't say if I did this corretly.
Can anyone have a look at it and give me feedback?
Cheers,
Stefan
Your are using OperationContext.Current which is the correct way to do per request context implementations for WCF services, so it looks good to me...
Question is, why are you not simply using the default implementation which comes out of the box with nhibernate? The implementation is in NHibernate.Context.WcfOperationSessionContext and you would just have to use this one within your session factory setup
For example:
Fluently.Configure()
...
.ExposeConfiguration(cfg => cfg.SetProperty(
Environment.CurrentSessionContextClass,
"wcf")
or Fluently.Configure()...CurrentSessionContext<WcfOperationSessionContext>()
You could also simply setaspNetCompatibilityEnabled=true and you will get HttpContext available to both MVC and WCF.
I´m developing an ASP.NET MVC Application, in which I use NHibernate and Ninject.
The Problem is caused by the following Controller:
public class ShoppingCartController : Controller
{
private readonly Data.Infrastructure.IShoppingCartRepository _shoppingCartRepository;
private readonly Data.Infrastructure.IShopItemRepository _shopItemRepository;
public ShoppingCartController(Data.Infrastructure.IShoppingCartRepository shoppingCartController,
Data.Infrastructure.IShopItemRepository shopItemRepository)
{
_shoppingCartRepository = shoppingCartController;
_shopItemRepository = shopItemRepository;
}
public ActionResult AddToShoppingCart(FormCollection formCollection)
{
var cartItem = new Data.Models.ShoppingCartItem();
cartItem.ChangeDate = DateTime.Now;
cartItem.ShopItem = _shopItemRepository.GetShopItem(SessionData.Data.Info, Convert.ToInt32(formCollection["shopItemId"]));
//IF I DONT´T CALL THE METHOD ABOVE, AddToCart works
_shoppingCartRepository.AddToCart(SessionData.Data.Info, cartItem);
//BUT IF I CALL THE GetShopItem METHOD I GET THE EXCEPTION HERE!
return RedirectToAction("Index", "Shop");
}
}
I know most of the Time this Exception is caused by wrong Mapping, but I´m pretty sure that my Mapping is right because the AddToCart-Method works if I don´t call GetShopItem...
So here is the Code of the ShopItemRepository:
public class ShopItemRepository : ReadOnlyRepository<ShopItem>, IShopItemRepository
{
public ShopItemRepository(IUnitOfWork uow) : base(uow)
{
}
public ShopItem GetShopItem(SessionParams param, int id)
{
return CurrentSession.QueryOver<ShopItem>()
.Where(x => x.ProcessId == param.ProcessId &&
x.CatalogueId == param.CatalogueId &&
x.Id == id)
.SingleOrDefault();
}
public IList<ShopItem> GetShopItems(SessionParams param)
{
return CurrentSession.GetNamedQuery("GetShopItems")
.SetParameter("requestor_id", param.RequestorId)
.SetParameter("recipient_id", param.RecipientId)
.SetParameter("process_id", param.ProcessId)
.SetParameter("catalogue_id", param.CatalogueId)
.List<ShopItem>();
}
}
And finally the Code of my UnitOfWork (basically it is just a Wrapper for the Session because I don´t want to reference NHibernate in my MVC Project)
public class UnitOfWork : IUnitOfWork, IDisposable
{
private NHibernate.ISession _currentSession;
public NHibernate.ISession CurrentSession
{
get
{
if(_currentSession == null)
{
_currentSession = SessionFactoryWrapper.SessionFactory.OpenSession();
}
return _currentSession;
}
}
public void Dispose()
{
if(_currentSession != null)
{
_currentSession.Close();
_currentSession.Dispose();
_currentSession = null;
}
GC.SuppressFinalize(this);
}
}
Addendum:
My NinjectWebCommon Class
public static class NinjectWebCommon
{
private static readonly Bootstrapper bootstrapper = new Bootstrapper();
/// <summary>
/// Starts the application
/// </summary>
public static void Start()
{
DynamicModuleUtility.RegisterModule(typeof(OnePerRequestHttpModule));
DynamicModuleUtility.RegisterModule(typeof(NinjectHttpModule));
bootstrapper.Initialize(CreateKernel);
}
/// <summary>
/// Stops the application.
/// </summary>
public static void Stop()
{
bootstrapper.ShutDown();
}
/// <summary>
/// Creates the kernel that will manage your application.
/// </summary>
/// <returns>The created kernel.</returns>
private static IKernel CreateKernel()
{
var kernel = new StandardKernel();
kernel.Bind<Func<IKernel>>().ToMethod(ctx => () => new Bootstrapper().Kernel);
kernel.Bind<IHttpModule>().To<HttpApplicationInitializationHttpModule>();
RegisterServices(kernel);
return kernel;
}
/// <summary>
/// Load your modules or register your services here!
/// </summary>
/// <param name="kernel">The kernel.</param>
private static void RegisterServices(IKernel kernel)
{
kernel.Bind<IUnitOfWork>().To<UnitOfWork>().InRequestScope();
kernel.Bind<Data.Infrastructure.ICatalogueRepository>().To<Data.Repositories.CatalogueRepository>();
kernel.Bind<Data.Infrastructure.ICategoryRepository>().To<Data.Repositories.CategoryRepository>();
kernel.Bind<Data.Infrastructure.IContactRepository>().To<Data.Repositories.ContactRepository>();
kernel.Bind<Data.Infrastructure.IProcessRepository>().To<Data.Repositories.ProcessRepository>();
kernel.Bind<Data.Infrastructure.IShopItemRepository>().To<Data.Repositories.ShopItemRepository>();
kernel.Bind<Data.Infrastructure.IShoppingCartRepository>().To<Data.Repositories.ShoppingCartRepository>();
}
}
IUnitOfWork is set to RequestScope so in the Case of ShoppingCartController, the two Repositories share the same UOW right?
Maybe this could cause the Problem?
Are you sure that this isn´t caused by wrong mapping? I had the same Issue and could resolve it by checking my mappings again!
I followed the instructions in this article to use Ninject for MVC 4 Web API Controller Constructor injection:
http://www.peterprovost.org/blog/2012/06/19/adding-ninject-to-web-api/
the problem that i am facing is when i call the api method i get error saying "Type 'CarController' does not have a default constructor".
i have even set break points at NinjectWebCommon.CreateKernel to see if that is being called. And that does get called when application runs.
Am i missing any thing?
by the way, i installed Ninject.Web.Common and Ninject from nuget for doing this. Here is my code:
MVC WEB API Controller:
public class CarController : ApiController
{
private ICarService carService;
public CarController(ICarService carService)
{
this.carService = carService;
}
[AcceptVerbs("GET")]
public CarsResponse GetCars([FromUri] CarsRequest request)
{
return this.carService.GetCars(request);
}
}
in App_Start:
public class NinjectDependencyScope : IDependencyScope
{
IResolutionRoot resolver;
public NinjectDependencyScope(IResolutionRoot resolver)
{
this.resolver = resolver;
}
public object GetService(Type serviceType)
{
if (resolver == null)
throw new ObjectDisposedException("this", "This scope has been disposed");
return resolver.TryGet(serviceType);
}
public System.Collections.Generic.IEnumerable<object> GetServices(Type serviceType)
{
if (resolver == null)
throw new ObjectDisposedException("this", "This scope has been disposed");
return resolver.GetAll(serviceType);
}
public void Dispose()
{
IDisposable disposable = resolver as IDisposable;
if (disposable != null)
disposable.Dispose();
resolver = null;
}
}
public class NinjectDependencyResolver : NinjectDependencyScope, IDependencyResolver
{
IKernel kernel;
public NinjectDependencyResolver(IKernel kernel)
: base(kernel)
{
this.kernel = kernel;
}
public IDependencyScope BeginScope()
{
return new NinjectDependencyScope(kernel.BeginBlock());
}
}
my NinjectWebCommon looks like this:
public static class NinjectWebCommon
{
private static readonly Bootstrapper bootstrapper = new Bootstrapper();
public static void Start()
{
DynamicModuleUtility.RegisterModule(typeof(OnePerRequestHttpModule));
DynamicModuleUtility.RegisterModule(typeof(NinjectHttpModule));
bootstrapper.Initialize(CreateKernel);
}
public static void Stop()
{
bootstrapper.ShutDown();
}
private static IKernel CreateKernel()
{
var kernel = new StandardKernel();
kernel.Bind<Func<IKernel>>().ToMethod(ctx => () => new Bootstrapper().Kernel);
kernel.Bind<IHttpModule>().To<HttpApplicationInitializationHttpModule>();
RegisterServices(kernel);
GlobalConfiguration.Configuration.DependencyResolver = new NinjectDependencyResolver(kernel);
return kernel;
}
private static void RegisterServices(IKernel kernel)
{
kernel.Bind<ICarService>().To<CarService>();
}
}
I was having the same issue. I found the resolution by doing the following. I lost track of the webpage where I found the class NinjectMVCDependencyResolver.
/// <summary>
/// Creates the kernel that will manage your application.
/// </summary>
/// <returns>The created kernel.</returns>
private static IKernel CreateKernel()
{
var kernel = new StandardKernel();
kernel.Bind<Func<IKernel>>().ToMethod(ctx => () => new Bootstrapper().Kernel);
kernel.Bind<IHttpModule>().To<HttpApplicationInitializationHttpModule>();
RegisterServices(kernel);
// Install our Ninject-based IDependencyResolver into the Web API config
GlobalConfiguration.Configuration.DependencyResolver
= new NinjectDependencyResolver(kernel);
// Install into the MVC dependency resolver
System.Web.Mvc.DependencyResolver.SetResolver(
new NinjectMVCDependencyResolver(kernel));
return kernel;
}
public class NinjectMVCDependencyResolver : NinjectDependencyScope
, System.Web.Mvc.IDependencyResolver
{
private IKernel kernel;
public NinjectMVCDependencyResolver(IKernel kernel)
: base(kernel)
{
this.kernel = kernel;
}
public IDependencyScope BeginScope()
{
return new NinjectDependencyScope(kernel.BeginBlock());
}
}
I am pretty new with DI and IoC pattern.
public class LazySessionContext
{
private readonly ISessionFactoryImplementor factory;
private const string CurrentSessionContextKey = "NHibernateCurrentSession";
public LazySessionContext(ISessionFactoryImplementor factory)
{
this.factory = factory;
}
/// <summary>
/// Retrieve the current session for the session factory.
/// </summary>
/// <returns></returns>
public ISession CurrentSession()
{
Lazy<ISession> initializer;
var currentSessionFactoryMap = GetCurrentFactoryMap();
if (currentSessionFactoryMap == null ||
!currentSessionFactoryMap.TryGetValue(factory, out initializer))
{
return null;
}
return initializer.Value;
}
/// <summary>
/// Bind a new sessionInitializer to the context of the sessionFactory.
/// </summary>
/// <param name="sessionInitializer"></param>
/// <param name="sessionFactory"></param>
public static void Bind(Lazy<ISession> sessionInitializer, ISessionFactory sessionFactory)
{
var map = GetCurrentFactoryMap();
map[sessionFactory] = sessionInitializer;
}
/// <summary>
/// Unbind the current session of the session factory.
/// </summary>
/// <param name="sessionFactory"></param>
/// <returns></returns>
public static ISession UnBind(ISessionFactory sessionFactory)
{
var map = GetCurrentFactoryMap();
var sessionInitializer = map[sessionFactory];
map[sessionFactory] = null;
if (sessionInitializer == null || !sessionInitializer.IsValueCreated) return null;
return sessionInitializer.Value;
}
/// <summary>
/// Provides the CurrentMap of SessionFactories.
/// If there is no map create/store and return a new one.
/// </summary>
/// <returns></returns>
private static IDictionary<ISessionFactory, Lazy<ISession>> GetCurrentFactoryMap()
{
var currentFactoryMap = (IDictionary<ISessionFactory, Lazy<ISession>>)
HttpContext.Current.Items[CurrentSessionContextKey];
if (currentFactoryMap == null)
{
currentFactoryMap = new Dictionary<ISessionFactory, Lazy<ISession>>();
HttpContext.Current.Items[CurrentSessionContextKey] = currentFactoryMap;
}
return currentFactoryMap;
}
}
public interface ISessionFactoryProvider
{
IEnumerable<ISessionFactory> GetSessionFactories();
}
public class SessionFactoryProvider
{
public const string Key = "NHibernateSessionFactoryProvider";
}
public class NHibernateSessionModule : IHttpModule
{
private HttpApplication app;
public void Init(HttpApplication context)
{
app = context;
context.BeginRequest += ContextBeginRequest;
context.EndRequest += ContextEndRequest;
context.Error += ContextError;
}
private void ContextBeginRequest(object sender, EventArgs e)
{
var sfp = (ISessionFactoryProvider)app.Context.Application[SessionFactoryProvider.Key];
foreach (var sf in sfp.GetSessionFactories())
{
var localFactory = sf;
LazySessionContext.Bind(
new Lazy<ISession>(() => BeginSession(localFactory)),
sf);
}
}
private static ISession BeginSession(ISessionFactory sf)
{
var session = sf.OpenSession();
session.BeginTransaction();
return session;
}
private void ContextEndRequest(object sender, EventArgs e)
{
var sfp = (ISessionFactoryProvider)app.Context.Application[SessionFactoryProvider.Key];
var sessionsToEnd = sfp.GetSessionFactories()
.Select(LazySessionContext.UnBind)
.Where(session => session != null);
foreach (var session in sessionsToEnd)
{
EndSession(session);
}
}
private void ContextError(object sender, EventArgs e)
{
var sfp = (ISessionFactoryProvider)app.Context.Application[SessionFactoryProvider.Key];
var sessionstoAbort = sfp.GetSessionFactories()
.Select(LazySessionContext.UnBind)
.Where(session => session != null);
foreach (var session in sessionstoAbort)
{
EndSession(session, true);
}
}
private static void EndSession(ISession session, bool abort = false)
{
if (session.Transaction != null && session.Transaction.IsActive)
{
if (abort)
{
session.Transaction.Rollback();
}
else
{
session.Transaction.Commit();
}
}
session.Dispose();
}
public void Dispose()
{
app.BeginRequest -= ContextBeginRequest;
app.EndRequest -= ContextEndRequest;
app.Error -= ContextError;
}
}
I got this sample by chinooknugets and jfmarillo from GitHub. From the code above I am injecting the session into repository and controlling the transaction via IHttpmodule. Now there are two concerns for me:
If I implement the transaction management via the code above it would be called whenever there is a request and it would open a session for that request. That's the sole purpose of implementing "Session Per Request" approach. But what if among all my controller methods I have only one method which actually uses the repository then I don't want to open the session every time I make a request. Only during the actions which are marked by Transaction attribute in controller would handle the transaction.
I would make a request and session would be opened only when the repository requests for it, so can it be implemented via any IoC container.
I still want the httpcontext events to handle the transaction so that context+=BegingRequest and context+=EndRequest. and the transaction would be handled inside that httpmodule with the httpcontext on request. But I don't want to implement IhttpModule and put into web.config. Is there any other alternative for this approach ?
The session opening and closing would be done solely inside those httpcontext only however I want to manage it via an IoC container (preferably ninject) but only when the repository is being requesting for that session. Mind it the repository may be initialised when the controller is invoked but that shouldn't open the session inside that repository. The session should open when actually repository is performing any transient actions.
Would someone clarify what practise should I follow for this scenario? I am using Mvc 3 with ninject and nhibernate.
1, 2)
Ninject allows Lazy object creation using Ninject.Extensions.Factory 3.0.0
class MyController
{
public MyController(Lazy<SomeRepository> repository) { ... }
}
This way the repository (and the context) is created when used.
3)
Why do you want to use a HttpModule for this? There are much easier ways e.g.:
kernel.Bind<ISession>()
.ToMethod(ctx => ctx.Kernel.Get<ISessionFactory().OpenSession())
.InRequestScope()
.OnActivation(session => OpenTransaction(session))
.OnDeactivation(session => EndTransaction(session));
Starting from Ninject 3.0.0 you can add a binding for HttpModules instead of registering them in the web.config and use construcotr injection for them. But since the HttpModule has no knowledge if the context is used you have to open the transaction for all requests.