trying to implement session per web request, No current session context configure - asp.net-mvc

As I said in the title I want to implement session per web request.
My session provider is configured like this (I'm not interested in changing this conf.)
public class SessionProvider
{
public static SessionProvider Instance { get; private set; }
private static ISessionFactory _SessionFactory;
static SessionProvider()
{
var provider = new SessionProvider();
provider.Initialize();
Instance = provider;
}
private SessionProvider()
{
}
private void Initialize()
{
string csStringName = "ConnectionString";
var cfg = Fluently.Configure()
....ommiting mappings and db conf.
.ExposeConfiguration(c => c.SetProperty("current_session_context_class", "web"))
.BuildConfiguration();
_SessionFactory = cfg.BuildSessionFactory();
}
public ISession OpenSession()
{
return _SessionFactory.OpenSession();
}
public ISession GetCurrentSession()
{
return _SessionFactory.GetCurrentSession();
}
}
Inside Global.asax.cs I have following code related to session per web req.
private static ISessionFactory SessionFactory { get; set; }
protected void Application_Start()
{
SessionFactory = MyDomain.SessionProvider.Instance.OpenSession().SessionFactory;
AreaRegistration.RegisterAllAreas();
RegisterGlobalFilters(GlobalFilters.Filters);
RegisterRoutes(RouteTable.Routes);
}
protected void Application_BeginRequest(object sender, EventArgs e)
{
var session = SessionFactory.OpenSession();
CurrentSessionContext.Bind(session);
}
protected void Application_EndRequest(object sender, EventArgs e)
{
var session = CurrentSessionContext.Unbind(SessionFactory);
session.Dispose();
}
On debuggin my webapp I get error: No current session context configured.
ErrorMessage referenced to global.asax line CurrentSessionContext.Bind(session);
Updated
Added .ExposeConfiguration(c => c.SetProperty("current_session_context_class", "web"))
and now I have error message on retrieving data from my controller like this
Error message: Session is closed! Object name: 'ISession'.
Controller code:
using (ISession session = SessionProvider.Instance.GetCurrentSession())
{
using (ITransaction transaction = session.BeginTransaction())
{
data = session.QueryOver<Object>()
...ommiting
transaction.Commit();
return PartialView("_Partial", data);
}
}

1st Question
You need to configure it in your nhibernate configuration section:
<property name="current_session_context_class">web</property>
Also I currently do something like this:
if (!CurrentSessionContext.HasBind(SessionFactory))
{
CurrentSessionContext.Bind(SessionFactory.OpenSession());
}
2nd Question
Please look at the following article to modify configuration fluently: currentsessioncontext fluent nhibernate how to do it?
3rd Question
You are closing your session twice.
using (ISession session = SessionProvider.Instance.GetCurrentSession())
closes your session. Then you did it again in Application_EndRequest. If you have another question please post a new question.

Related

Calling service/repository methods in ASP.Net Core middleware

ASP.Net Core noob here...I am using an ASP.Net Core WebAPI core project using DNX451 with EF 6.
I have a requirement to implement API Key auth in our service. To do this I have created middleware that gets information from the request and proceeds with authentication. It is SUPPOSED to go to the database, get the key to match, and then return and do the validation.
Here is the middleware implemented to look at the context and get the APIKey
AuthenticationHandler
public class AuthorizationHandler
{
private readonly RequestDelegate _next;
private IAuthenticationService _authenticationService;
public AuthorizationHandler(RequestDelegate next, IAuthenticationService authService)
{
_authenticationService = authService;
_next = next;
}
public async Task Invoke(HttpContext context)
{
try
{
var apiKey = context.Request.Headers["Key"];
var location = context.Request.Headers["Host"];
var locationKey = _authenticationService.GetApiKey(location);
if (apiKey == locationKey)
await _next(context);
context.Response.StatusCode = 403;
context.Response.Headers.Add("WWW-Authenticate",
new[] { "Basic" });
}
catch (Exception ex)
{
context.Response.StatusCode = 500;
context.Response.Headers.Add("WWW-Authenticate",
new[] { "Basic" });
}
}
}
Here is the startup class with context and middleware registration
public class Startup
{
public Startup(IHostingEnvironment env)
{
var builder = new ConfigurationBuilder()
.AddJsonFile("appsettings.json")
.AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true);
builder.AddEnvironmentVariables();
Configuration = builder.Build();
}
public IConfiguration Configuration { get; set; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.AddScoped(k => new DbContext(Configuration["Data:Context:ConnectionString"]));
// Add framework services.
services.AddMvc();
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
loggerFactory.AddConsole(Configuration.GetSection("Logging"));
loggerFactory.AddDebug();
app.UseIISPlatformHandler();
app.UseStaticFiles();
app.RegisterAuthorizationHeader();
app.RegisterAuthorization();
app.UseMvc();
}
// Entry point for the application.
public static void Main(string[] args) => WebApplication.Run<Startup>(args);
}
Here is Auth service
public interface IAuthenticationService
{
string GetApiKey(string location);
}
public class AuthenticationService: IAuthenticationService
{
private IApiKeyRepository _apiKeyRepository;
public AuthenticationService(IApiKeyRepository repo)
{
_apiKeyRepository= repo;
}
public string GetApiKey(string location)
{
return _apiKeyRepository.GetApiKeyByLocation(location);
}
}
The repo
public interface IApiRepository
{
string GetApiKeyByLocation(string location);
}
public class ApiRepository: IApiRepository
{
private DbContext _context;
public ApiRepository(DbContext context)
{
_context = context;
}
public string GetApiKeyByLocation(string location)
{
var apiRow = _context.ApiKeyStore.FirstOrDefault(a => a.Location == location);
return apiRow == null ? string.Empty : apiRow.APIKey;
}
}
When attempting this I get the following error:
The context cannot be used while the model is being created. This
exception may be thrown if the context is used inside the
OnModelCreating method or if the same context instance is accessed by
multiple threads concurrently. Note that instance members of DbContext
and related classes are not guaranteed to be thread safe.
Now, when I debug this every break point is hit twice. I believe I understand WHY this issue is occurring but have no idea how to fix it.
Can someone give me an idea, please? Any better solution ideas?
To use scoped dependencies in a middleware (which is necessarily a singleton by definition), the best approach is to flow it as a parameter of InvokeAsync instead of flowing it via the constructor:
public async Task Invoke(HttpContext context, IAuthenticationService authenticationService)
{
try
{
var apiKey = context.Request.Headers["Key"];
var location = context.Request.Headers["Host"];
var locationKey = authenticationService.GetApiKey(location);
if (apiKey == locationKey)
await _next(context);
context.Response.StatusCode = 403;
context.Response.Headers.Add("WWW-Authenticate",
new[] { "Basic" });
}
catch (Exception ex)
{
context.Response.StatusCode = 500;
context.Response.Headers.Add("WWW-Authenticate",
new[] { "Basic" });
}
}

MVC Get/Impersonate Windows User In Repository

I have an intranet application that uses the Windows username and passes that to a procedure to return data.
I'm using dependency injection, but I don't believe I have the method to get the username separated properly.
I'm trying to keep this secure by not passing in the username as a parameter, but I also want to be able to impersonate (or bypass my GetWindowsUser() method) and send in another username so I can test results for other users.
One idea I had for this was to set a session variable in another page with another (impersonated) username, then check if that session variable exists first before grabbing the actual user name, but I couldn't figure out how to access the session variable in the repository.
WEB API CONTROLLER
public class DropDownDataController : ApiController
{
private IDropDownDataRepository _dropDownDataRepository;
//Dependency Injection using Unity.WebAPI NuGet Package
public DropDownDataController(IDropDownDataRepository dropDownDataRepository)
{
_dropDownDataRepository = dropDownDataRepository;
}
[HttpGet]
public HttpResponseMessage MyList()
{
try
{
return _dropDownDataRepository.MyList();
}
catch
{
throw new HttpResponseException(new HttpResponseMessage(HttpStatusCode.NotFound));
}
}
}
REPOSITORY
public class DropDownDataRepository : IDropDownDataRepository, IDisposable
{
private DatabaseEntities db = new DatabaseEntities();
public HttpResponseMessage MyList()
{
//(This should be separated somehow, right?)
//Create a new instance of the Utility class
Utility utility = new Utility();
//Grab the windowsUser from the method
var windowsUser = utility.GetWindowsUser();
//Pass windowsUser parameter to the procedure
var sourceQuery = (from p in db.myProcedure(windowsUser)
select p).ToList();
string result = JsonConvert.SerializeObject(sourceQuery);
var response = new HttpResponseMessage();
response.Content = new StringContent(result, System.Text.Encoding.Unicode, "application/json");
return response;
}
}
INTERFACE
public interface IDropDownDataRepository : IDisposable
{
HttpResponseMessage MyList();
}
UTILITY CLASS
public class Utility
{
public string GetWindowsUser()
{
//Get the current windows user
string windowsUser = HttpContext.Current.User.Identity.Name;
return windowsUser;
}
}
UPDATE 1
In addition to what Nikolai and Brendt posted below, the following is also needed to allow Web Api controllers work with the session state.
Accessing Session Using ASP.NET Web API
Abstract the Utility class and inject it into the repository.
Then you can stub or mock for testing.
public interface IUtility
{
string GetWindowsUser();
}
public class TestUtility : IUtility
{
public string GetWindowsUser()
{
return "TestUser";
}
}
public class DropDownDataRepository : IDropDownDataRepository, IDisposable
{
private IUtility _utility;
public DropDownDataRepository(IUtility utility)
{
_utility = utility;
}
}
EDIT
Also the repository should not return an HTTPResponseMessage type it should just return a List<T> of the domain model you're accessing.
i.e.
public List<Model> MyList()
{
//Grab the windowsUser from the method
var windowsUser = _utility.GetWindowsUser();
//Pass windowsUser parameter to the procedure
var sourceQuery = (from p in db.myProcedure(windowsUser)
select p).ToList();
return sourceQuery
}
Then move the JSON portion to the controller.
One idea I had for this was to set a session variable in another page
with another (impersonated) username, then check if that session
variable exists first before grabbing the actual user name, but I
couldn't figure out how to access the session variable in the
repository.
Potentially, if you add in a dependency to session, you need to isolate it, e.g.
public class DropDownDataRepository : IDropDownDataRepository, IDisposable
{
// ... other fields
private ISession session;
public DropDownDataRepository(ISession session)
{
this.session = session;
}
public HttpResponseMessage MyList()
{
var myUserName = this.session.UserName;
// ... etc
With ISession being something like:
public interface ISession
{
string UserName { get; }
}
Implemented as:
public class MySession : ISession
{
public string UserName
{
get
{
// potentially do some validation and return a sensible default if not present in session
return HttpContext.Current.Session["UserName"].ToString();
}
}
}
Of course there is the potential to decouple this MySession class from HttpContext if desired.
With regards to this:
//(This should be separated somehow, right?)
//Create a new instance of the Utility class
Utility utility = new Utility();
Yes, anytime you create a new object you are tightly coupling them together, which will give you issues, for example, if you try to unit test it in isolation.
In this instance you could extract an IUtility interface from Utility:
public class Utility : IUtility
{
string GetWindowsUser();
}
Then:
public class DropDownDataRepository : IDropDownDataRepository, IDisposable
{
// ... other fields
private IUtility utility;
public DropDownDataRepository(IUtility utility)
{
this.utility = utility;
// .... etc
Then you have removed the depenedency between Utility and DropDownDataRepository, and can substitute in another type or mock with ease.
I got a lot of help from Nikolai and Brent and got most of the way there with their posted answers, but ended up figuring out the complete answer on my own. The problems I was having were related to not being able to access session variables in a WebAPI. So, I'm sure there are cleaner solutions to this, but I definitely improved what I had and came up with the following code, which works.
This answer was needed to allow access to the session variable in Web Api - Accessing Session Using ASP.NET Web API
GLOBAL.asax.cs
public class MvcApplication : System.Web.HttpApplication
{
protected void Application_Start()
{
GlobalConfiguration.Configure(WebApiConfig.Register);
UnityConfig.RegisterComponents();
AreaRegistration.RegisterAllAreas();
FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
RouteConfig.RegisterRoutes(RouteTable.Routes);
BundleConfig.RegisterBundles(BundleTable.Bundles);
}
//Added to allow use of session state in Web API
protected void Application_PostAuthorizeRequest()
{
if (IsWebApiRequest())
{
HttpContext.Current.SetSessionStateBehavior(SessionStateBehavior.Required);
}
}
//Added to allow use of session state in Web API
private bool IsWebApiRequest()
{
return HttpContext.Current.Request.AppRelativeCurrentExecutionFilePath.StartsWith(WebApiConfig.UrlPrefixRelative);
}
protected void Session_Start(Object sender, EventArgs e)
{
//Default set the session variable to none
Session["_impersonatedUser"] = "none";
}
protected void Session_End(Object sender, EventArgs e)
{
//Reset the session variable to blank
Session["_impersonatedUser"] = "";
}
}
UNITY.config
public static class UnityConfig
{
public static void RegisterComponents()
{
var container = new UnityContainer();
// register all your components with the container here
// it is NOT necessary to register your controllers
// e.g. container.RegisterType<ITestService, TestService>();
container.RegisterType<IDropDownDataRepository, DropDownDataRepository>();
container.RegisterType<IUtilityRepository, UtilityRepository>();
container.RegisterType<ISessionRepository, SessionRepository>();
//MVC5
//Unity.MVC5 NuGet Package
DependencyResolver.SetResolver(new Unity.Mvc5.UnityDependencyResolver(container));
//WEB API
//Unity.WebApi NuGet Package
GlobalConfiguration.Configuration.DependencyResolver = new Unity.WebApi.UnityDependencyResolver(container);
}
}
WEB API CONTROLLER
public class DropDownDataController : ApiController
{
private IDropDownDataRepository _dropDownDataRepository;
//Dependency Injection using Unity.WebAPI NuGet Package
public DropDownDataController(IDropDownDataRepository dropDownDataRepository)
{
_dropDownDataRepository = dropDownDataRepository;
}
[HttpGet]
public HttpResponseMessage MyList()
{
try
{
var sourceQuery = _dropDownDataRepository.MyList();
//JSON stuff moved to controller
string result = JsonConvert.SerializeObject(sourceQuery);
var response = new HttpResponseMessage();
response.Content = new StringContent(result, System.Text.Encoding.Unicode, "application/json");
return response;
}
catch
{
throw new HttpResponseException(new HttpResponseMessage(HttpStatusCode.NotFound));
}
}
protected override void Dispose(bool disposing)
{
_dropDownDataRepository.Dispose();
base.Dispose(disposing);
}
}
DROPDOWNDATA REPOSITORY
public class DropDownDataRepository : IDropDownDataRepository, IDisposable
{
private DatabaseEntities db = new DatabaseEntities();
private IUtilityRepository _utilityRepository;
private ISessionRepository _sessionRepository;
//Dependency Injection of Utility and Session
public DropDownDataRepository(IUtilityRepository utilityRepository, ISessionRepository sessionRepository)
{
_utilityRepository = utilityRepository;
_sessionRepository = sessionRepository;
}
//Changed to a list here
public List<MyProcedure> MyList()
{
string windowsUser;
//Check the session variable to see if a user is being impersonated
string impersonatedUser = _sessionRepository.ImpersonatedUser;
//Grab the windowsUser from the Utility Repository
windowsUser = _utilityRepository.GetWindowsUser();
if (impersonatedUser != "none")
{
windowsUser = impersonatedUser;
}
//Pass windowsUser parameter to the procedure
var sourceQuery = (from p in db.MyProcedure(windowsUser)
select p).ToList();
return sourceQuery;
}
private bool disposed = false;
protected virtual void Dispose(bool disposing)
{
if (!this.disposed)
{
if (disposing)
{
db.Dispose();
}
}
this.disposed = true;
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
}
DROPDOWNDATA INTERFACE
public interface IDropDownDataRepository : IDisposable
{
//Changed to list here
List<MyProcedure> MyList();
}
UTILITY REPOSITORY
public class UtilityRepository : IUtilityRepository
{
public string GetWindowsUser()
{
//Get the current windows user
string windowsUser = HttpContext.Current.User.Identity.Name;
return windowsUser;
}
}
UTILITY INTERFACE
public interface IUtilityRepository
{
string GetWindowsUser();
}
SESSION REPOSITORY
public class SessionRepository : ISessionRepository
{
public string ImpersonatedUser
{
get
{
return HttpContext.Current.Session["_impersonatedUser"].ToString();
}
}
}
SESSION INTERFACE
public interface ISessionRepository
{
string ImpersonatedUser { get; }
}

Use Unity to resolve current NHibernate session for injection into Repositories

I have a multi-project MVC 5 solution, where NHibernate repositories are declared in a Core.Data class library, but my session management is in the Wen API Core.Api project. It creates and destroys a session per request:
public override void OnActionExecuting(HttpActionContext actionContext)
{
// start a session
var session = SessionFactory.OpenSession();
CurrentSessionContext.Bind(session);
session.BeginTransaction();
}
public override void OnActionExecuted(HttpActionExecutedContext actionExecutedContext)
{
// commit the current session
var session = SessionFactory.GetCurrentSession();
var transaction = session.Transaction;
if (transaction != null && transaction.IsActive)
{
transaction.Commit();
}
session = CurrentSessionContext.Unbind(SessionFactory);
session.Close();
}
Now when I instantiate a repository in a controller action,I would like this particular session to be injected into the repository. How can I achieve this? I can do a BaseRepository<T>: IRepository<T>, with a constructor that finds the session, but I would really much rather like it injected. How can I achieve this?
This is a snippet from how we do it.
public class UnityConfig
{
private static readonly Lazy<IUnityContainer> _container = new Lazy<IUnityContainer>(() =>
{
var container = new UnityContainer();
RegisterTypes(container);
return container;
});
//easy access to the container from anywhere in the application
public static T Resolve<T>()
{
//uses the Resolve<T> extension method
return GetConfiguredContainer().Resolve<T>();
}
private static void Register(IUnityContainer container)
{
container.RegisterType<IRepository, Repository>(
new InjectionConstructor(new ResolvedParameter<ISession>()));
container.RegisterType<ISession>(new PerRequestLifetimeManager(),
new InjectionFactory(c =>
c.Resolve<ISessionFactory>().OpenSession()
));
container
.RegisterType<ISessionFactory>(
new ContainerControlledLifetimeManager(),
new InjectionFactory(c =>
{
var v =
Fluently.Configure()
.Database(MsSqlConfiguration.MsSql2008
.ConnectionString(d => d.FromConnectionStringWithKey("web.config.connection.string.key"))
.ShowSql()
.Dialect<CustomOcMsSqlDialect>())
.Mappings(m => m.FluentMappings.AddFromAssemblyOf<IRepository>()
.Conventions.AddFromAssemblyOf<IRepository>())
.BuildSessionFactory();
return v;
})
);
}
}
public interface IRepository
{
}
public class Repository : IRepository
{
private readonly ISession _session;
public Repository(ISession session)
{
_session = session;
}
}
public class SomeController : Controller
{
public ActionResult SomeAction()
{
var repo = UnityConfig.Resolve<IRepository>();
var dbEntity = repo.Load(123);
return View("SomeView");
}
}
We wire up the repository, the session and the session-factory through unity. The factory is set to ContainerControlled(unity singleton). The session is set to PerRequest, so we get a new session for each request. And the repository uses the regular lifetime-manager so we get a new one for each resolve.
This way you can ask unity for a repository and get the same session throughout the entire request. It will also dispose the session automatically at the end of the request. But I'm sure you could hook into the ApplicationEndRequest event and do some housecleaning as well if you wanted.
Hope this helps!

recommendation pattern to use for handling sessions per web requests in mvc using NHibernate

For example.
My session factory is located in MyDomain.SessionProvider class.
Session can be open using ISession session = SessionProvider.Instance.OpenSession()
Step: SessionProvider.cs
public static SessionProvider Instance { get; private set; }
private static ISessionFactory _SessionFactory;
static SessionProvider()
{
var provider = new SessionProvider();
provider.Initialize();
Instance = provider;
}
private SessionProvider()
{
}
private void Initialize()
{
string csStringName = "ConnectionString";
var cfg = Fluently.Configure()
//ommiting mapping and db conf.
.ExposeConfiguration(c => c.SetProperty("current_session_context_class", "web"))
.BuildConfiguration();
_SessionFactory = cfg.BuildSessionFactory();
}
public ISession OpenSession()
{
return _SessionFactory.OpenSession();
}
public ISession GetCurrentSession()
{
return _SessionFactory.GetCurrentSession();
}
Step: Global.asax.cs
public static ISessionFactory SessionFactory { get; private set; }
Application Start
SessionFactory = SessionProvider.Instance.OpenSession().SessionFactory;
App_BeginRequest
var session = SessionFactory.OpenSession();
CurrentSessionContext.Bind(session);
EndRequest
dispose session
var session = CurrentSessionContext.Unbind(SessionFactory);
session.Dispose();
Step3.HomeController
I should be using current session like
var session = SessionProvider.Instance.GetCurrentSession();
using (var tran = session.BeginTransaction())
{
//retrieve data from session
}
Now, with trying to retrieve data on my controller like desc. in Step3. I got error message that my session is closed. I tried to remove Application_EndRequest block inside global.asax cause my transaction is wrapped with session but with no success. Still same error.
Second/side question: is this pattern accepted widely, or it is better to wrapped inside custom attributes on mvc controllers. Thanks.
Updated:
On my controller when try to instantiate current session in line
var session = SessionProvider.Instance.GetCurrentSession();
I'm getting following error:
**Connection = 'session.Connection' threw an exception of type 'NHibernate.HibernateException'**
**base {System.ApplicationException} = {"Session is closed"}**
Thanks #LeftyX
I solved this problem using TekPub video Mastering NHibernate with some customizations.
Global.asax
//Whenever the request from page comes in (single request for a page)
//open session and on request end close the session.
public static ISessionFactory SessionFactory =
MyDomain.SessionProvider.CreateSessionFactory();
public MvcApplication()
{
this.BeginRequest += new EventHandler(MvcApplication_BeginRequest);
this.EndRequest +=new EventHandler(MvcApplication_EndRequest);
}
private void MvcApplication_EndRequest(object sender, EventArgs e)
{
CurrentSessionContext.Unbind(SessionFactory).Dispose();
}
private void MvcApplication_BeginRequest(object sender, EventArgs e)
{
CurrentSessionContext.Bind(SessionFactory.OpenSession());
}
protected void Application_Start()
{
SessionFactory.OpenSession();
}
and inside my controller
var session = MvcApplication.SessionFactory.GetCurrentSession();
{
using (ITransaction tx = session.BeginTransaction())
{... omitting retrieving data}
}
You can find a couple of simple and easy implementations here and here and find some code here.
I like Ayende's approach to keep everything simple and clean:
public class Global: System.Web.HttpApplication
{
public static ISessionFactory SessionFactory = CreateSessionFactory();
protected static ISessionFactory CreateSessionFactory()
{
return new Configuration()
.Configure(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "hibernate.cfg.xml"))
.BuildSessionFactory();
}
public static ISession CurrentSession
{
get{ return (ISession)HttpContext.Current.Items["current.session"]; }
set { HttpContext.Current.Items["current.session"] = value; }
}
protected void Global()
{
BeginRequest += delegate
{
CurrentSession = SessionFactory.OpenSession();
};
EndRequest += delegate
{
if(CurrentSession != null)
CurrentSession.Dispose();
};
}
}
In my projects I've decided to use a IoC container (StructureMap).
In case you're interested you can have a look here.

Autofac, (Fluent) nHibernate, ISession "Session is closed!" intermittently

I'm having random "Session is closed!" errors with the following configuration of Autofac and Fluent nHibernate:
Global.asax.cs:
builder.Register(x => new NHibernateConfigurator().GetSessionFactory()).SingleInstance();
builder.Register(x => x.Resolve<ISessionFactory>().OpenSession()).InstancePerHttpRequest();
NHibernateConfigurator.cs
public class NHibernateConfigurator
{
public Configuration Configure()
{
var configuration = new Configuration();
configuration.SessionFactory()
.Proxy.Through<ProxyFactoryFactory>()
.Integrate.Using<Oracle10gDialect>();
FluentConfiguration fluentConfiguration = Fluently.Configure(configuration);
fluentConfiguration.Mappings(m => m.FluentMappings.AddFromAssemblyOf<UserMap>());
return fluentConfiguration.BuildConfiguration();
}
public ISessionFactory GetSessionFactory()
{
var configuration = Configure();
return configuration.BuildSessionFactory();
}
}
SomeController.cs:
private readonly IRepository repository;
public SomeController(IRepository repository)
{
this.repository = repository
}
[Transaction]
public ActionResult Index()
{
var result = repository.GetUsers();
return View(result);
}
TransactionAttribute.cs
public class TransactionAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
DependencyResolver.Current.GetService<ISession>().BeginTransaction(IsolationLevel.ReadCommitted);
}
public override void OnActionExecuted(ActionExecutedContext filterContext)
{
ITransaction currentTransaction = DependencyResolver.Current.GetService<ISession>().Transaction;
if (currentTransaction.IsActive)
{
if (filterContext.Exception != null && filterContext.ExceptionHandled)
{
currentTransaction.Rollback();
}
}
}
public override void OnResultExecuted(ResultExecutedContext filterContext)
{
ITransaction currentTransaction = DependencyResolver.Current.GetService<ISession>().Transaction;
base.OnResultExecuted(filterContext);
try
{
if (currentTransaction.IsActive)
{
if (filterContext.Exception != null && !filterContext.ExceptionHandled)
{
currentTransaction.Rollback();
}
else
{
currentTransaction.Commit();
}
}
}
finally
{
currentTransaction.Dispose();
}
}
}
IRepository.cs:
public interface IRepository
{
IList<User> GetUsers();
}
Repository.cs:
public class Repository : IRepository
{
private readonly ISession session;
public Repository(ISession session)
{
this.session = session;
}
public IList<User> GetUsers()
{
return session.QueryOver<User>().List();
}
}
This current set-up works, but seems to fail intermittently (after a few page reloads or restarts of Cassini) with GetUsers throwing "Session is closed!" errors. I thought registering the ISessionFactory on an InstancePerHttpRequest would avoid these issues. No luck. Any ideas? New to nHibernate and Autofac, so if I failed to post enough relevant information, let me know.
It appears my configuration is working perfectly for any nHibernate calls outside of my custom membership providers and custom role providers - they are the issue as far as I can tell, which means this question isn't really addressing the issue.
How is your Repository registered? Is it InstancePerHttpRequest (which is should be), or Singleton (which it shouldn't).
I have had a similar problem using MVC 3 action filters and seems they are cached quite aggressively in MVC 3 and found that sessions were not always being opened as OnActionExecuted not always fire.
Move your session into the controller constructor like so:
public SomeController(
ISession session,
ILogger logger,
IRepository<Something> someRepository )
{
_session = session;
_logger = logger;
_someRepository = someRepository;
}
In your action where you want to wrap a transaction:
using (var transaction = _session.BeginTransaction())
{
// do something with your repository
_someRepository.Add(new Something());
transaction.commit();
}

Resources