Using for the first time: Asp.NET MVC, NHibernate(FNH), DI using Ninject. I was able to get everything working with one database, but now I'm trying to use two databases (DB1 and DB2 for sake of the eg). I have a dictionary of SessionFactory(s), that is keyed by a database identifier.
I can't figure out how to select the correct Session based on what is being requested from constructor injection. I have seen this How to inject different NHibernate Sessions (multi-db) to same repository with Controller controling which sesions with Ninject, but I didn't get it to work.
public ProductController(DB1.Model.IRepository<Product> prodRepo, DB2.Model.IRepository<Account> acctRepo)
{
[...]
}
NinjectWebCommon.cs snippet
private static void RegisterServices(IKernel kernel)
{
kernel.Bind<ISession>().ToMethod(ctx => NHibernateSessionModule.Provider.GetCurrentSession()).InRequestScope();
kernel.Bind(typeof(DB1.Model.IRepository<>)).To(typeof(NHibernateRepository<>));
kernel.Bind(typeof(DB2.Model.IRepository<>)).To(typeof(NHibernateRepository<>));
}
NHibernateSessionModule.cs: does UOW via Begin/End request
public class NHibernateSessionModule : IHttpModule
{
public static ISessionFactoryProvider Provider = new MultipleSessionFactoryProvider();
public void Dispose() { }
public void Init(HttpApplication context)
{
context.BeginRequest += BeginRequest;
context.EndRequest += EndRequest;
}
public void BeginRequest(object sender, EventArgs e)
{
Provider.BindNew();
}
public void EndRequest(object sender, EventArgs e)
{
Provider.Unbind();
}
}
MultipleSessionFactoryProvider.cs: Not quite sure I'm doing everything here correctly.
public class MultipleSessionFactoryProvider : ISessionFactoryProvider
{
public Dictionary<string, ISessionFactory> SessionFactories { get; private set; }
public static Func<Dictionary<string,ISessionFactory>> InitSessionFactories = GetFactories;
public MultipleSessionFactoryProvider() : this(InitSessionFactories())
{
}
public MultipleSessionFactoryProvider(Dictionary<string, ISessionFactory> factories)
{
SessionFactories = factories;
}
public static Dictionary<string, ISessionFactory> GetFactories()
{
Dictionary<string, ISessionFactory> ret = new Dictionary<string, ISessionFactory>();
Dictionary<string, string> connectionStrings = new Dictionary<string, string>();
connectionStrings.Add(ConfigurationManager.ConnectionStrings["DB1"].Name, ConfigurationManager.ConnectionStrings["DB1"].ConnectionString.ToString());
connectionStrings.Add(ConfigurationManager.ConnectionStrings["DB2"].Name, ConfigurationManager.ConnectionStrings["DB2"].ConnectionString.ToString());
foreach (KeyValuePair<string, string> pair in connectionStrings)
{
//Better way to do the mapping?
ISessionFactory factory = Fluently.Configure()
.Database(MsSqlConfiguration.MsSql2008.ConnectionString(pair.Value))
.Mappings(cfg => cfg.FluentMappings.Conventions.Setup(x => x.Add(AutoImport.Never()))
.AddFromAssemblyOf<ProductMap>())
.BuildConfiguration()
.CurrentSessionContext<WebSessionContext>().BuildSessionFactory();
ret.Add(pair.Key, factory);
}
return ret;
}
public void BindNew()
{
foreach (KeyValuePair<string, ISessionFactory> factory in SessionFactories)
{
Bind(OpenSession(factory.Key));
}
}
public void Bind(ISession session)
{
CurrentSessionContext.Bind(session);
}
public void Unbind()
{
foreach (KeyValuePair<string, ISessionFactory> factory in SessionFactories)
{
if (CurrentSessionContext.HasBind(factory.Value))
{
var sess = CurrentSessionContext.Unbind(factory.Value);
sess.Dispose();
}
}
}
public ISession OpenSession(string factoryId)
{
return SessionFactories[factoryId].OpenSession();
}
public ISession GetCurrentSession()
{
string factoryId = GetIdentifier(); //<--- How to implement this
return SessionFactories[factoryId].GetCurrentSession();
}
public String GetIdentifier()
{
return "DB1"; //Hardcoded for example
}
So, how can I implement GetIdentifier(), or alter my Ninject binding. I did take a look at Ninject Named binding, but didn't understand how to use that when I bind the Session.
public ProductController([Name("DB1")]DB1.Model.IRepository<Product> prodRepo, [Name("DB2")]DB2.Model.IRepository<Account> acctRepo)
Since this is the first go around with these technologies for me, please let me know if I'm doing anything out of practice, or that might be of concern; I'd like to stay with the HttpModule.
You need two bindings for session, one for each database
kernel.Bind<ISession>().ToMethod(ctx => GetSessionForDB1()).WhenInjectedInto(typeof(DB1.Model.IRepository<>)).InRequestScope();
kernel.Bind<ISession>().ToMethod(ctx => GetSessionForDB2()).WhenInjectedInto(typeof(DB2.Model.IRepository<>)).InRequestScope();
Related
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; }
}
I cannot figure this one out. I have a N-Tier ASP.MVC application and I am writing my first Unit Test and it seems to fail on my AutoMapper configuration. I have used AutoMapper a million times and never had any problems using it.
I'm sure I am missing something simple, but I have been staring at this for 24 hours now.
Class Library: APP.DOMAIN
public class User : IEntity<int>
{
public int Id { get; set; }
[StringLength(20), Required]
public string UserName { get; set; }
}
Class Library: APP.SERVICE
References App.Domain
public class UserViewModel
{
public int Id { get; set; }
public string UserName { get; set; }
}
I have my AutoMapper bootstrapper in the service layer.
public static class AutoMapperBootstrapper
{
public static void RegisterMappings()
{
Mapper.CreateMap<User, UserViewModel>();
}
}
UserService.cs
public class UserService : IUserService
{
private readonly IUserRepository _userRepository;
public UserService(IUserRepository userRepository)
{
_userRepository = userRepository;
}
public List<UserViewModel> GetUsers()
{
var users = _userRepository.GetAll();
if (users == null)
{
throw new Exception("No users found.");
}
return Mapper.Map<List<UserViewModel>>(users); // FAILS ON AUTOMAPPER
}
}
ASP.MVC Layer: APP.WEB
References App.Service
private void Application_Start(object sender, EventArgs e)
{
// Register AutoMapper
AutoMapperBootstrapper.RegisterMappings();
Mapper.AssertConfigurationIsValid();
// Code that runs on application startup
AreaRegistration.RegisterAllAreas();
GlobalConfiguration.Configure(WebApiConfig.Register);
RouteConfig.RegisterRoutes(RouteTable.Routes);
}
Unit Test Layer:
public class TestUserRepository :IUserRepository
{
public IEnumerable<User> GetAll()
{
var users = new List<User>()
{
new User { Id = 1, UserName = "Mary"},
new User { Id = 2, UserName = "Joe"}
};
return users;
}
}
public class UserServiceTest
{
private IUserService _userService;
private readonly IUserRepository _userRepository;
public UserServiceTest()
{
_userRepository = new TestUserRepository();
}
[Fact]
public void GetUsers_Should_Return_Correct_Number_Of_Users()
{
// Arrange
_userService = new UserService(_userRepository);
// Act
var result = _userService.GetUsers(); // FAILS ON AUTOMAPPER
// Assert
Assert.True(result.Any(u => u.UserName == "Mary"));
}
}
Failing Test Message:
*** Failures ***
Exception
AutoMapper.AutoMapperMappingException: AutoMapper.AutoMapperMappingException : Missing type map configuration or unsupported mapping.
Mapping types:
User -> UserViewModel
App.Data.Model.User -> App.Service.ViewModels.UserViewModel
Destination path:
List`1[0]
Source value:
App.Data.Model.User
at App.Service.Services.UserService.GetUsers() in D:\Repositories\App\App.Service\Services\UserService.cs:line 36
at App.Tests.Service.Tests.UserServiceTest.GetUsers_Should_Return_Correct_Number_Of_Users() in D:\Repositories\App\App.Tests\Service.Tests\UserServiceTest.cs:line 34
A little late to the party but have you tried setting the mapping before running the test?
public class UserServiceTest
{
public UserServiceTest()
{
// register the mappings before running the test
AutoMapperBootstrapper.RegisterMappings();
}
...
}
What we would need to do is Inject Custom Mapper Mock as given below. Add all those custom profiles that you have used for that particular class that you are unit testing and inject ConfigureMapper() in the Constructor of that class which is expecting IMapper Object
public IMapper ConfigureMapper()
{
var config = new MapperConfiguration(cfg =>
{
cfg.AddProfile<CustomProfile>();
cfg.AddProfile<UserCustomProfile>();
cfg.AddProfile<UserWorkProfile>();
});
return config.CreateMapper();
}
Hope this solves the issue.
I'm not sure what the problem is, it's been a while since I've last used AutoMapper, but I'm quite sure that the following will work:
return users.Select(Mapper.Map<UserViewModel>);
I have a problem with this line:
var authorDTO = mapper.Map<AuthorCreationDTO>(AuthorinsideDB);
So I change the version of Autormapper
from:
<PackageReference Include="AutoMapper.Extensions.Microsoft.DependencyInjection" Version="7.0.0" />
to
Version="6.0.0"
and it worked.
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.
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();
}
I'm using Rob's mvc startesite http://mvcstarter.codeplex.com/ with ASP.Net MVC 2, Ninject2, NoRM (http://github.com/atheken/NoRM) and MongoDB. It works so fast and the developpement is even faster but I'm facing a big problem, I at some points, get connection timeout. I can't figure out what I'm doing wrong.
I already asked a question here : I get this error that I don't understand why, using NoRM and Mongo in my MVC project and here http://groups.google.com/group/norm-mongodb/browse_thread/thread/7882be16f030eb29 but I still in the dark.
Thanks a lot for the help!
EDITED*
Here's my MongoSession object :
public class MongoSession : ISession{
private readonly Mongo _server;
public MongoSession()
{
//this looks for a connection string in your Web.config - you can override this if you want
_server = Mongo.Create("MongoDB");
}
public T Single<T>(System.Linq.Expressions.Expression<Func<T, bool>> expression) where T : class {
return _server.GetCollection<T>().AsQueryable().Where(expression).SingleOrDefault();
}
public IQueryable<T> All<T>() where T : class {
return _server.GetCollection<T>().AsQueryable();
}
public void Save<T>(IEnumerable<T> items) where T : class {
foreach (T item in items) {
Save(item);
}
}
public void Save<T>(T item) where T : class {
var errors = DataAnnotationsValidationRunner.GetErrors(item);
if (errors.Count() > 0)
{
throw new RulesException(errors);
}
_server.Database.GetCollection<T>().Save(item);
}
public void Delete<T>(System.Linq.Expressions.Expression<Func<T, bool>> expression) where T : class
{
var items = All<T>().Where(expression);
foreach (T item in items)
{
Delete(item);
}
}
public void Delete<T>(T item) where T : class
{
_server.GetCollection<T>().Delete(item);
}
public void Drop<T>() where T : class
{
_server.Database.DropCollection(typeof(T).Name);
}
public void Dispose() {
_server.Dispose();
}
}
And now my MongoRepositoryBase
public abstract class MongoRepositoryBase<T> : ISession<T> where T : MongoObject
{
protected ISession _session;
protected MongoRepositoryBase(ISession session)
{
_session = session;
}
public T Single(ObjectId id)
{
return _session.All<T>().Where(x => x.Id == id).FirstOrDefault();
}
public T Single(Expression<Func<T, bool>> expression)
{
return _session.Single(expression);
}
public IQueryable<T> All()
{
return _session.All<T>();
}
public void Save(IEnumerable<T> items)
{
foreach (T item in items)
{
Save(item);
}
}
public void Save(T item)
{
_session.Save(item);
}
public void Delete(System.Linq.Expressions.Expression<Func<T, bool>> expression)
{
var items = _session.All<T>().Where(expression);
foreach (T item in items)
{
Delete(item);
}
}
public void DeleteAll()
{
var items = _session.All<T>();
foreach (T item in items)
{
Delete(item);
}
}
public void Delete(T item)
{
_session.Delete(item);
}
public void Drop()
{
_session.Drop<T>();
}
public void Dispose()
{
_session.Dispose();
}
}
And an exemple of an other Repository implemantation :
public class PlaceRepository : MongoRepositoryBase<Place>, IPlaceRepository
{
public PlaceRepository(ISession session) : base(session)
{
}
public List<Place> GetByCategory(PlaceCategory category, bool publishedOnly)
{
var query = _session.All<Place>()
.OrderBy(x => x.Name)
.Where(x => x.Category == category);
if (publishedOnly) query = query.Where(x => x.Published);
if (publishedOnly) query = query.Where(x => x.ShowOnMap);
return query.ToList();
}
public Place FindByName(string name)
{
var query = _session.All<Place>()
.Where(x => x.Name.ToLower().Contains(name.ToLower()))
.Where(x => x.Published);
return query.FirstOrDefault();
}
public string[] FindSuggestionsByName(string name)
{
var query = _session.All<Place>()
.OrderBy(x => x.Name)
.Where(x => x.Name.ToLower().StartsWith(name.ToLower()))
.Where(x => x.Published);
var places = query.ToList();
var names = new string[places.Count];
var i = 0;
foreach (var place in places)
{
names[i++] = place.Name;
}
return names;
}
}
Vinny,
I've never used Ninject, so I could be way off with this suggestion. But it seems possible that having a static MongoSession instance might be holding connections open. Have you tried TransientBehavior instead of SingletonBehavior? Or maybe change your code to call Dispose (or use using) after you convert your ShortcutLinks to a List? All
var shortcutLionks = _session.All<ShortcutLinks>().ToList();
_session.Dispose();
A better approach might be to use some sort of repository or DAO where the session details are hidden from the controller. I have a RepositoryBase sample at http://www.codevoyeur.com/Articles/20/A-NoRM-MongoDB-Repository-Base-Class.aspx.
Stuart Harris has a similar, arguably more complete implementation at http://red-badger.com/Blog/post/A-simple-IRepository3cT3e-implementation-for-MongoDB-and-NoRM.aspx
Pooled MongoDB connections are relatively cheap to create, so it's probably best to make sure the data access methods are disposing after your done getting/saving data.
If I add throw new NotImplementedException(); in the Dispose() method of my MongoRepositoryBase class it does not get call so I guess Ninject does not handle this for me, If I had
protected override void OnActionExecuted(ActionExecutedContext filterContext)
{
_recipeRepo.Dispose();
base.OnActionExecuted(filterContext);
}
In my controller it does get call. It seems to be fine, thx!