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());