Castle project per session lifestyle with ASP.NET MVC

落花浮王杯 提交于 2019-12-03 08:42:40

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

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.

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!