NHibernate - "Session is closed! Object name: 'ISession' - c#

I'm using NHibernate on a custom membership provider in a MVC3 application. Whenever I try to login though, I get the exception:
Session is closed!
Object name: 'ISession'
The code in the membership provider looks like this:
ContractRepository repository;
public string UserDescription { get; set; }
public CustomSqlMembershipProvider() {
this.repository = new ContractRepository(ProviderPortal.Persistance.NHibernateSessionStorage.RetrieveSession());
}
public override bool ValidateUser(string username, string password) {
var user = repository.GetContractForUser(username);
if (user == null)
return false;
else {
UserDescription = user.Description;
return true; //TODO: come back and add user validation.
}
}
And here is the retrieve session methods:
public static ISession RetrieveSession() {
HttpContext context = HttpContext.Current;
if (!context.Items.Contains(CURRENT_SESSION_KEY)) OpenCurrent();
var session = context.Items[CURRENT_SESSION_KEY] as ISession;
return session;
}
private static void OpenCurrent() {
ISession session = NHibernateConfiguration.CreateAndOpenSession();
HttpContext context = HttpContext.Current;
context.Items[CURRENT_SESSION_KEY] = session;
}
This is where the exception is happening:
public Contract GetContractForUser(string UserName) {
return (Contract)session.CreateCriteria(typeof(Contract))
.Add(Restrictions.Eq("Login", int.Parse(UserName))).UniqueResult();
}
Somewhere between the CustomSqlMembershipProvider constructor being called, and the ValidateUser method being called, the session is being closed. Any ideas? My other Controllers are injected with an open session via DI, but this one is giving me the hardest time.

Do you get this one consistently or on and off?
We were getting this using Spring.net for our DI and using OpenSessionInView
We had to add the following http module to change the storage options for the current thread.
public class SpringThreadStorageModule : IHttpModule
{
static SpringThreadStorageModule()
{
LogicalThreadContext.SetStorage(new HybridContextStorage());
}
#region IHttpModule Members
public void Dispose()
{
// do nothing
}
public void Init(HttpApplication context)
{
// we just need the staic init block.
}
#endregion
}

Related

Catching errors from services in Exception Filter

I've created a service that's used throughout my aspnet project that retrieves and validates a header among other things. Issue is that the Exception Filter is not able to catch the errors that are thrown by the service as it's not in the scope of the Exception Filter thus giving the user an ugly internal server error. Is there any way to gracefully return an argument error with description to the user with the use of the services?
The Startup:
services.AddScoped<UserService>();
services
.AddMvc(x =>
{
x.Filters.Add(typeof(Filters.MyExceptionFilter));
})
The Exception Filter:
public class MyExceptionFilter : IExceptionFilter
{
public void OnException(ExceptionContext context)
{
if (context.Exception is ArgumentException argumentException)
{
var response = context.HttpContext.Response;
context.ExceptionHandled = true;
response.StatusCode = 400;
context.Result = new ObjectResult(argumentException.Message);
return;
}
}
}
The Service:
public class UserService
{
public readonly string UserId;
public UserService(IHttpContextAccessor context)
{
if (!context.HttpContext.Request.Headers.TryGetValue("x-test", out var user))
{
throw new ArgumentException($"x-test header is required.");
}
UserId = user;
//do other stuff
}
}
Controller Action Method:
public async Task<IActionResult> Delete(string id,
[FromServices] UserService userService)
{
//do stuff
}
A rule-of-thumb in C# is do as little work in the constructor as possible. There's a few good reasons for this (e.g. you can't dispose a class that threw an exception in it's constructor). Another good reason is, as you have found out, construction might happen in a different place (e.g. a DI container) than the place you actually use the class.
The fix should be quite straightforward - just move the logic out of the constructor. You could use a Lazy<T> to do this for example:
public class UserService
{
public readonly Lazy<string> _userId ;
public UserService(IHttpContextAccessor context)
{
_userId = new Lazy<string>(() =>
{
if (!context.HttpContext.Request.Headers.TryGetValue("x-test", out var user))
{
throw new ArgumentException($"x-test header is required.");
}
return user;
});
//do other stuff
}
public string UserId => _userId.Value;
}
Or you could just get the value when you needed it:
public class UserService
{
public readonly IHttpContextAccessor _context;
public UserService(IHttpContextAccessor context)
{
_context = context;
//do other stuff
}
public string UserId
{
get
{
if (_context.HttpContext.Request.Headers.TryGetValue("x-test", out var user))
{
return user;
}
else
{
throw new ArgumentException($"x-test header is required.");
}
}
}
}

.NET Core Singleton Session Wrapper

I am trying to create a Session Wrapper class for ASP.NET Core Razor Pages.
In traditional ASP.NET web forms my singleton session wrapper class used to work normally, but i am not sure if in .net core it will work the same or the same class will be shared across all requests.
I want to store specific information for each request (session) and not to be shared across all requests.
i created the following wrapper :
public class MySession
{
//the ISession interface mandatory
public ISession Session { get; set; }
private static MySession instance;
private MySession() {
}
public static MySession Instance
{
get
{
if (instance == null)
{
instance = new MySession();
}
return instance;
}
}
//properties
public User User
{
get => SessionHelper.SessionExtensions.Get<User>(this.Session, "User");
set => SessionHelper.SessionExtensions.Set<User>(this.Session, "User", value);
}
public bool LoggedIn
{
get => SessionHelper.SessionExtensions.Get<bool>(this.Session, "LoggedIn");
set => SessionHelper.SessionExtensions.Set<bool>(this.Session, "LoggedIn", value);
}
}
and from my page model (Login.cshtml.cs) i am doing the following :
public void OnGet()
{
MySession.Instance.Session = HttpContext.Session;
}
and i am accessing the session and storing and retrieving information perfectly as expected for example :
public ActionResult OnPostAuthenticate()
{
string username = Request.Form["username"];
string password = Request.Form["password"];
User user = this.userDataManager.GetUserAuthentication(username, password);
if (user != null)
{
//authentication success
//save session variables
MySession.Instance.User = user;
MySession.Instance.LoggedIn = true;
return new JsonResult(new { success = true });
}
else
{
return new JsonResult(new { success = false });
}
}
And as i said everything work perfect, but i want to know if it is SAFE to use the session this way or my requests will be messed up and the same session information will be shared across all requests ?

Using DI to add Interceptor to NHibernate Sessions in legacy code

So, there's a bug in some legacy code I'm maintaining. It causes some mild data corruption, so it's rather serious. I've found the root cause, and have made a sample application that reliable reproduces the bug. I would like to fix it with as little impact on existing applications as possible, but I'm struggling.
The bug lies in the data access layer. More specifically, in how an interceptor is injected into a new Nhibernate Session. The interceptor is used to set a specific entity property when saving or flushing. The property, LoggedInPersonID, is found on nearly all our entities. All entities are generated from CodeSmith templates using the database schema, so the LoggedInPersonID property corresponds to a column that is found on nearly all tables in the database. Together with a couple of other columns and triggers, it is used to keep track of which user created and modified a record in the database. Any transaction that inserts or updates data need to supply a LoggedInPersonID value, or else the transaction will fail.
Whenever a client requires a new session, a call is made to OpenSession in the SessionFactory (not Nhibernate's SessionFactory, but a wrapper). The code below shows the relevant parts of the SessionFactory wrapper class:
public class SessionFactory
{
private ISessionFactory sessionFactory;
private SessionFactory()
{
Init();
}
public static SessionFactory Instance
{
get
{
return Nested.SessionFactory;
}
}
private static readonly object _lock = new object();
public ISession OpenSession()
{
lock (_lock)
{
var beforeInitEventArgs = new SessionFactoryOpenSessionEventArgs(null);
if (BeforeInit != null)
{
BeforeInit(this, beforeInitEventArgs);
}
ISession session;
if (beforeInitEventArgs.Interceptor != null
&& beforeInitEventArgs.Interceptor is IInterceptor)
{
session = sessionFactory.OpenSession(beforeInitEventArgs.Interceptor);
}
else
{
session = sessionFactory.OpenSession();
}
return session;
}
}
private void Init()
{
try
{
var configuration = new Configuration().Configure();
OnSessionFactoryConfiguring(configuration);
sessionFactory = configuration.BuildSessionFactory();
}
catch (Exception ex)
{
Console.Error.WriteLine(ex.Message);
while (ex.InnerException != null)
{
Console.Error.WriteLine(ex.Message);
ex = ex.InnerException;
}
throw;
}
}
private void OnSessionFactoryConfiguring(Configuration configuration)
{
if(SessionFactoryConfiguring != null)
{
SessionFactoryConfiguring(this, new SessionFactoryConfiguringEventArgs(configuration));
}
}
public static event EventHandler<SessionFactoryOpenSessionEventArgs> BeforeInit;
public static event EventHandler<SessionFactoryOpenSessionEventArgs> AfterInit;
public static event EventHandler<SessionFactoryConfiguringEventArgs> SessionFactoryConfiguring;
public class SessionFactoryConfiguringEventArgs : EventArgs
{
public Configuration Configuration { get; private set; }
public SessionFactoryConfiguringEventArgs(Configuration configuration)
{
Configuration = configuration;
}
}
public class SessionFactoryOpenSessionEventArgs : EventArgs
{
private NHibernate.ISession session;
public SessionFactoryOpenSessionEventArgs(NHibernate.ISession session)
{
this.session = session;
}
public NHibernate.ISession Session
{
get
{
return this.session;
}
}
public NHibernate.IInterceptor Interceptor
{
get;
set;
}
}
/// <summary>
/// Assists with ensuring thread-safe, lazy singleton
/// </summary>
private class Nested
{
internal static readonly SessionFactory SessionFactory;
static Nested()
{
try
{
SessionFactory = new SessionFactory();
}
catch (Exception ex)
{
Console.Error.WriteLine(ex);
throw;
}
}
}
}
The interceptor is injected through the BeforeInit event. Below is the interceptor implementation:
public class LoggedInPersonIDInterceptor : NHibernate.EmptyInterceptor
{
private int? loggedInPersonID
{
get
{
return this.loggedInPersonIDProvider();
}
}
private Func<int?> loggedInPersonIDProvider;
public LoggedInPersonIDInterceptor(Func<int?> loggedInPersonIDProvider)
{
SetProvider(loggedInPersonIDProvider);
}
public void SetProvider(Func<int?> provider)
{
loggedInPersonIDProvider = provider;
}
public override bool OnFlushDirty(object entity, object id, object[] currentState, object[] previousState,
string[] propertyNames, NHibernate.Type.IType[] types)
{
return SetLoggedInPersonID(currentState, propertyNames);
}
public override bool OnSave(object entity, object id, object[] currentState,
string[] propertyNames, NHibernate.Type.IType[] types)
{
return SetLoggedInPersonID(currentState, propertyNames);
}
protected bool SetLoggedInPersonID(object[] currentState, string[] propertyNames)
{
int max = propertyNames.Length;
var lipid = loggedInPersonID;
for (int i = 0; i < max; i++)
{
if (propertyNames[i].ToLower() == "loggedinpersonid" && currentState[i] == null && lipid.HasValue)
{
currentState[i] = lipid;
return true;
}
}
return false;
}
}
Below is a helper class used by applications to register a BeforeInit event handler:
public static class LoggedInPersonIDInterceptorUtil
{
public static LoggedInPersonIDInterceptor Setup(Func<int?> loggedInPersonIDProvider)
{
var loggedInPersonIdInterceptor = new LoggedInPersonIDInterceptor(loggedInPersonIDProvider);
ShipRepDAL.ShipRepDAO.SessionFactory.BeforeInit += (s, args) =>
{
args.Interceptor = loggedInPersonIdInterceptor;
};
return loggedInPersonIdInterceptor;
}
}
}
The bug is especially prominent in our web services (WCF SOAP). The web services endpoint bindings are all basicHttpBinding. A new Nhibernate session is created for each client request. The LoggedInPersonIDInterceptorUtil.Setup method is called after a client is authenticated, with the authenticated client's ID captured in the closure. Then there's a race to reach code that triggers a call to SessionFactory.OpenSession before another client request registers an event handler to the BeforeInit event with a different closure - because, it's the last handler in the BeforeInit event's invocation list that "wins", potentially returning the wrong interceptor. The bug usually happens when two clients are making requests nearly simultaneously, but also when two clients are calling different web service methods with different execution times (one taking longer from authentication to OpenSession than another).
In addition to the data corruption, there's also a memory leak as the event handlers aren't de-registered? It might be the reason why our web service process is recycled at least once a day?
It really looks like the BeforeInit (and AfterInit) events need to go. I could alter the signature of the OpenSession method, and add an IInterceptor parameter. But this would break a lot of code, and I don't want to pass in an interceptor whenever a session is retrieved - I would like this to be transparent. Since the interceptor is a cross cutting concern in all applications using the DAL, would dependency injection be a viable solution? Unity is used in some other areas of our applications.
Any nudge in the right direction would be greatly appreciated :)
Instead of supplying the interceptor at each ISessionFactory.OpenSession call, I would use a single interceptor instance globally configured (Configuration.SetInterceptor()).
This instance would retrieve the data to use from an adequate context allowing to isolate this data per request/user/whatever suits the application.
(System.ServiceModel.OperationContext, System.Web.HttpContext, ..., depending on the application kind.)
The context data in your case would be set where LoggedInPersonIDInterceptorUtil.Setup is currently called.
If you need to use the same interceptor implementation for applications requiring different contextes, then you will need to choose the context to use according to some configuration parameter you would add (or inject it as a dependency in your interceptor).
Dependency Injection example:
DependencyInjectionInterceptor.cs:
using NHibernate;
using System;
using Microsoft.Extensions.DependencyInjection;
namespace MyAmazingApplication
{
public class DependencyInjectionInterceptor : EmptyInterceptor
{
private readonly IServiceProvider _serviceProvider;
public DependencyInjectionInterceptor(IServiceProvider serviceProvider)
{
_serviceProvider = serviceProvider;
}
public T GetService<T>() => _serviceProvider.GetService<T>();
public T GetRequiredService<T>() => _serviceProvider.GetRequiredService<T>();
}
}
Startup.cs:
public void ConfigureServices(IServiceCollection services)
{
...
var cfg = new Configuration();
... // your config setup
cfg.SetListeners(NHibernate.Event.ListenerType.PreInsert, new[] { new AuditEventListener() });
cfg.SetListeners(NHibernate.Event.ListenerType.PreUpdate, new[] { new AuditEventListener() });
services.AddSingleton(cfg);
services.AddSingleton(s => s.GetRequiredService<Configuration>().BuildSessionFactory());
services.AddScoped(s => s.GetRequiredService<ISessionFactory>().WithOptions().Interceptor(new DependencyInjectionInterceptor(s)).OpenSession());
... // you other services setup
}
AuditEventListener.cs:
public class AuditEventListener : IPreUpdateEventListener, IPreInsertEventListener
{
public bool OnPreUpdate(PreUpdateEvent e)
{
var user = ((DependencyInjectionInterceptor)e.Session.Interceptor).GetService<ICurrentUser>();
if (e.Entity is IEntity)
UpdateAuditTrail(user, e.State, e.Persister.PropertyNames, (IEntity)e.Entity, false);
return false;
}
}
So you use interceptor to get your scoped or any other service:
var myService = ((DependencyInjectionInterceptor)e.Session.Interceptor).GetService<IService>();
ICurrentUser in particular is a scoped service which uses HttpContext to get the current user.
I hope it might be helpful for everyone.

Am I using and disposing Entity Framework's Object Context (per request) correctly?

I have a web application where I have just began to use Entity Framework. I read the beginners tutorials, and topics about benefits of object context per request for web apps.
However, I am not sure my context is at the right place...
I found this very useful post (Entity Framework Object Context per request in ASP.NET?) and used the suggested code :
public static class DbContextManager
{
public static MyEntities Current
{
get
{
var key = "MyDb_" + HttpContext.Current.GetHashCode().ToString("x")
+ Thread.CurrentContext.ContextID.ToString();
var context = HttpContext.Current.Items[key] as MyEntities;
if (context == null)
{
context = new MyEntities();
HttpContext.Current.Items[key] = context;
}
return context;
}
}
}
And in Global.asax :
protected virtual void Application_EndRequest()
{
var key = "MyDb_" + HttpContext.Current.GetHashCode().ToString("x")
+ Thread.CurrentContext.ContextID.ToString();
var context = HttpContext.Current.Items[key] as MyEntities;
if (context != null)
{
context.Dispose();
}
}
Then, I am using it in my pages :
public partial class Login : System.Web.UI.Page
{
private MyEntities context;
private User user;
protected void Page_Load(object sender, EventArgs e)
{
context = DbContextManager.Current;
if (Membership.GetUser() != null)
{
Guid guid = (Guid)Membership.GetUser().ProviderUserKey;
user = context.Users.Single(u => (u.Id == guid));
}
}
protected void _Button_Click(object sender, EventArgs e)
{
Item item = context.Items.Single(i => i.UserId == user.Id);
item.SomeFunctionThatUpdatesProperties();
context.SaveChanges();
}
}
I did read a lot but this is still a little bit confused for me.
Is the context getter okay in Page_Load ? Do I still need to use "using" or will disposal be okay with the Global.asax method ?
If I am confusing something I am sorry and I would be really, really grateful if someone could help me understand where it should be.
Thanks a lot !
Edits following nativehr answer and comments :
Here is the DbContextManager:
public static class DbContextManager
{
public static MyEntities Current
{
get
{
var key = "MyDb_" + typeof(MyEntities).ToString();
var context = HttpContext.Current.Items[key] as MyEntities;
if (context == null)
{
context = new MyEntities();
HttpContext.Current.Items[key] = context;
}
return context;
}
}
}
The page :
public partial class Login : System.Web.UI.Page
{
private User user;
protected void Page_Load(object sender, EventArgs e)
{
if (Membership.GetUser() != null)
{
Guid guid = (Guid)Membership.GetUser().ProviderUserKey;
user = UserService.Get(guid);
}
}
protected void _Button_Click(object sender, EventArgs e)
{
if (user != null)
{
Item item = ItemService.GetByUser(user.Id)
item.SomeFunctionThatUpdatesProperties();
ItemService.Save(item);
}
}
}
And the ItemService class :
public static class ItemService
{
public static Item GetByUser(Guid userId)
{
using (MyEntities context = DbContextManager.Current)
{
return context.Items.Single(i => (i.UserId == userId));
}
}
public static void Save(Item item)
{
using (MyEntities context = DbContextManager.Current)
{
context.SaveChanges();
}
}
}
I would not rely on Thread.CurrentContext property.
Firstly, Microsoft says, Context class is not intended to be used directly from your code:
https://msdn.microsoft.com/en-us/library/system.runtime.remoting.contexts.context%28v=vs.110%29.aspx
Secondly, imagine you want to make an async call to the database.
In this case an additional MyEntities instance will be constructed, and it will not be disposed in Application_EndRequest.
Furthermore, ASP.NET itself does not guarantee not to switch threads while executing a request.
I had a similar question, have a look at this:
is thread switching possible during request processing?
I would use "MyDb_" + typeof(MyEntities).ToString() instead.
Disposing db context in Application_EndRequest is OK, but it produces a bit performance hit, 'cause your context will stay not disposed longer than needed, it is better to close it as soon as possible (you actually don't need an open context to render the page, right?)
Context pre request implementation would make sense if it has to be shared between different parts of your code, insted of creating a new instance each time.
For example, if you utilize the Repository pattern, and several repositories share the same db context while executing a request.
Finally you call SaveChanges and all the changes made by different repositories are committed in a single transaction.
But in your example you are calling the database directly from your page's code, in this case I don't see any reason to not create a context directly with using.
Hope this helps.
Update: a sample with Context per request:
//Unit of works acts like a wrapper around DbContext
//Current unit of work is stored in the HttpContext
//HttpContext.Current calls are kept in one place, insted of calling it many times
public class UnitOfWork : IDisposable
{
private const string _httpContextKey = "_unitOfWork";
private MyContext _dbContext;
public static UnitOfWork Current
{
get { return (UnitOfWork) HttpContext.Current.Items[_httpContextKey]; }
}
public UnitOfWork()
{
HttpContext.Current.Items[_httpContextKey] = this;
}
public MyEntities GetContext()
{
if(_dbContext == null)
_dbContext = new MyEntities();
return _dbContext;
}
public int Commit()
{
return _dbContext != null ? _dbContext.SaveChanges() : null;
}
public void Dispose()
{
if(_dbContext != null)
_dbContext.Dispose();
}
}
//ContextManager allows repositories to get an instance of DbContext
//This implementation grabs the instance from the current UnitOfWork
//If you want to look for it anywhere else you could write another implementation of IContextManager
public class ContextManager : IContextManager
{
public MyEntities GetContext()
{
return UnitOfWork.Current.GetContext();
}
}
//Repository provides CRUD operations with different entities
public class RepositoryBase
{
//Repository asks the ContextManager for the context, does not create it itself
protected readonly IContextManager _contextManager;
public RepositoryBase()
{
_contextManager = new ContextManager(); //You could also use DI/ServiceLocator here
}
}
//UsersRepository incapsulates Db operations related to User
public class UsersRepository : RepositoryBase
{
public User Get(Guid id)
{
return _contextManager.GetContext().Users.Find(id);
}
//Repository just adds/updates/deletes entities, saving changes is not it's business
public void Update(User user)
{
var ctx = _contextManager.GetContext();
ctx.Users.Attach(user);
ctx.Entry(user).State = EntityState.Modified;
}
}
public class ItemsRepository : RepositoryBase
{
public void UpdateSomeProperties(Item item)
{
var ctx = _contextManager.GetContext();
ctx.Items.Attach(item);
var entry = ctx.Entry(item);
item.ModifiedDate = DateTime.Now;
//Updating property1 and property2
entry.Property(i => i.Property1).Modified = true;
entry.Property(i => i.Property2).Modified = true;
entry.Property(i => i.ModifiedDate).Modified = true;
}
}
//Service encapsultes repositories that are necessary for request handling
//Its responsibility is to create and commit the entire UnitOfWork
public class AVeryCoolService
{
private UsersRepository _usersRepository = new UsersRepository();
private ItemsRepository _itemsRepository = new ItemsRepository();
public int UpdateUserAndItem(User user, Item item)
{
using(var unitOfWork = new UnitOfWork()) //Here UnitOfWork.Current will be assigned
{
_usersRepository.Update(user);
_itemsRepository.Update(user); //Item object will be updated with the same DbContext instance!
return unitOfWork.Commit();
//Disposing UnitOfWork: DbContext gets disposed immediately after it is not longer used.
//Both User and Item updates will be saved in ome transaction
}
}
}
//And finally, the Page
public class AVeryCoolPage : System.Web.UI.Page
{
private AVeryCoolService _coolService;
protected void Btn_Click(object sender, EventArgs e)
{
var user = .... //somehow get User and Item objects, for example from control's values
var item = ....
_coolService.UpdateUserAndItem(user, item);
}
}
I think you should read a bit more about repository pattern for EntityFramework and UnitofWork pattern.
Implementing the Repository and Unit of Work Patterns in an ASP.NET MVC
I know this is mvc and you are problably using web forms but you can get an idea of how to implement it.
Disposing the context on each request is a bit strange, because there might be requests where you will not touch the database, so you will be doing unnecessary code.
What you should do is get a layer for data access and implement a repository pattern that you will access on whatever method you will need on the code behind of your page.

Castle project per session lifestyle with ASP.NET MVC

I'm really new to Castle Windsor IoC container. I wanted to know if theres a way to store session variables using the IoC container. I was thinking something in the line of this:
I want to have a class to store search options:
public interface ISearchOptions{
public string Filter{get;set;}
public string SortOrder{get;set;}
}
public class SearchOptions{
public string Filter{get;set;}
public string SortOrder{get;set;}
}
And then inject that into the class that has to use it:
public class SearchController{
private ISearchOptions _searchOptions;
public SearchController(ISearchOptions searchOptions){
_searchOptions=searchOptions;
}
...
}
then in my web.config, where I configure castle I want to have something like:
<castle>
<components>
<component id="searchOptions" service="Web.Models.ISearchOptions, Web" type="Web.Models.SearchOptions, Web" lifestyle="PerSession" />
</components>
</castle>
And have the IoC container handle the session object without having to explicitly access it myself.
How can I do this?
Thanks.
EDIT: Been doing some research. Basically, what I want is to have the a session Scoped component. I come from Java and Spring Framework and there I have session scoped beans which I think are very useful to store session data.
this might be what your looking for.
public class PerSessionLifestyleManager : AbstractLifestyleManager
{
private readonly string PerSessionObjectID = "PerSessionLifestyleManager_" + Guid.NewGuid().ToString();
public override object Resolve(CreationContext context)
{
if (HttpContext.Current.Session[PerSessionObjectID] == null)
{
// Create the actual object
HttpContext.Current.Session[PerSessionObjectID] = base.Resolve(context);
}
return HttpContext.Current.Session[PerSessionObjectID];
}
public override void Dispose()
{
}
}
And then add
<component
id="billingManager"
lifestyle="custom"
customLifestyleType="Namespace.PerSessionLifestyleManager, Namespace"
service="IInterface, Namespace"
type="Type, Namespace">
</component>
This solution will work for Windsor 3.0 and above. It;s based on the implementation of PerWebRequest Lifestyle and makes use of the new Scoped Lifestyle introduced in Windsor 3.0.
You need two classes...
An implementation of IHttpModule to handle session management. Adding the ILifetimeScope object into session and disposing it again when the session expires. This is crucial to ensure that components are released properly. This is not taken care of in other solutions given here so far.
public class PerWebSessionLifestyleModule : IHttpModule
{
private const string key = "castle.per-web-session-lifestyle-cache";
public void Init(HttpApplication context)
{
var sessionState = ((SessionStateModule)context.Modules["Session"]);
sessionState.End += SessionEnd;
}
private static void SessionEnd(object sender, EventArgs e)
{
var app = (HttpApplication)sender;
var scope = GetScope(app.Context.Session, false);
if (scope != null)
{
scope.Dispose();
}
}
internal static ILifetimeScope GetScope()
{
var current = HttpContext.Current;
if (current == null)
{
throw new InvalidOperationException("HttpContext.Current is null. PerWebSessionLifestyle can only be used in ASP.Net");
}
return GetScope(current.Session, true);
}
internal static ILifetimeScope YieldScope()
{
var context = HttpContext.Current;
if (context == null)
{
return null;
}
var scope = GetScope(context.Session, true);
if (scope != null)
{
context.Session.Remove(key);
}
return scope;
}
private static ILifetimeScope GetScope(HttpSessionState session, bool createIfNotPresent)
{
var lifetimeScope = (ILifetimeScope)session[key];
if (lifetimeScope == null && createIfNotPresent)
{
lifetimeScope = new DefaultLifetimeScope(new ScopeCache(), null);
session[key] = lifetimeScope;
return lifetimeScope;
}
return lifetimeScope;
}
public void Dispose()
{
}
}
The second class you need is an implementation of IScopeAccessor. This is used to bridge the gap between your HttpModule and the built in Windsor ScopedLifestyleManager class.
public class WebSessionScopeAccessor : IScopeAccessor
{
public void Dispose()
{
var scope = PerWebSessionLifestyleModule.YieldScope();
if (scope != null)
{
scope.Dispose();
}
}
public ILifetimeScope GetScope(CreationContext context)
{
return PerWebSessionLifestyleModule.GetScope();
}
}
Two internal static methods were added to PerWebSessionLifestyleModule to support this.
That's it, expect to register it...
container.Register(Component
.For<ISometing>()
.ImplementedBy<Something>()
.LifestyleScoped<WebSessionScopeAccessor>());
Optionally, I wrapped this registration up into an extension method...
public static class ComponentRegistrationExtensions
{
public static ComponentRegistration<TService> LifestylePerSession<TService>(this ComponentRegistration<TService> reg)
where TService : class
{
return reg.LifestyleScoped<WebSessionScopeAccessor>();
}
}
So it can be called like this...
container.Register(Component
.For<ISometing>()
.ImplementedBy<Something>()
.LifestylePerSession());
It sounds like you are on the right track, but your SearchOptions class needs to implement ISearchOptions:
public class SearchOptions : ISearchOptions { ... }
You also need to tell Windsor that your SearchController is a component, so you may want to register that in the web.config as well, although I prefer to do it from code instead (see below).
To make Windsor pick up your web.config, you should instantiate it like this:
var container = new WindsorContainer(new XmlInterpreter());
To make a new instance of SearchController, you can then simply do this:
var searchController = container.Resolve<SearchController>();
To register all Controllers in a given assembly using convention-based techniques, you can do something like this:
container.Register(AllTypes
.FromAssemblyContaining<MyController>()
.BasedOn<IController>()
.ConfigureFor<IController>(reg => reg.LifeStyle.Transient));
My experience has been that Andy's answer does not work, as the SessionStateModule.End is never raised directly:
Though the End event is public, you can only handle it by adding an event handler in the Global.asax file. This restriction is implemented because HttpApplication instances are reused for performance. When a session expires, only the Session_OnEnd event specified in the Global.asax file is executed, to prevent code from calling an End event handler associated with an HttpApplication instance that is currently in use.
For this reason, it becomes pointless to add a HttpModule that does nothing. I have adapted Andy's answer into a single SessionScopeAccessor class:
public class SessionScopeAccessor : IScopeAccessor
{
private const string Key = "castle.per-web-session-lifestyle-cache";
public void Dispose()
{
var context = HttpContext.Current;
if (context == null || context.Session == null)
return;
SessionEnd(context.Session);
}
public ILifetimeScope GetScope(CreationContext context)
{
var current = HttpContext.Current;
if (current == null)
{
throw new InvalidOperationException("HttpContext.Current is null. PerWebSessionLifestyle can only be used in ASP.Net");
}
var lifetimeScope = (ILifetimeScope)current.Session[Key];
if (lifetimeScope == null)
{
lifetimeScope = new DefaultLifetimeScope(new ScopeCache());
current.Session[Key] = lifetimeScope;
return lifetimeScope;
}
return lifetimeScope;
}
// static helper - should be called by Global.asax.cs.Session_End
public static void SessionEnd(HttpSessionState session)
{
var scope = (ILifetimeScope)session[Key];
if (scope != null)
{
scope.Dispose();
session.Remove(Key);
}
}
}
}
It is important to call the SessionEnd method from your global.asax.cs file:
void Session_OnEnd(object sender, EventArgs e)
{
SessionScopeAccessor.SessionEnd(Session);
}
This is the only way to handle a SessionEnd event.

Categories