6

我对 Castle Windsor IoC 容器真的很陌生。我想知道是否有一种方法可以使用 IoC 容器存储会话变量。我在想这样的事情:

我想要一个类来存储搜索选项:

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;}
}

然后将其注入到必须使用它的类中:

public class SearchController{
    private ISearchOptions _searchOptions;
    public SearchController(ISearchOptions searchOptions){
        _searchOptions=searchOptions;
    }
    ...
}

然后在我的 web.config 中,我配置城堡的地方,我想要类似的东西:

<castle>
    <components>
        <component id="searchOptions" service="Web.Models.ISearchOptions, Web" type="Web.Models.SearchOptions, Web" lifestyle="PerSession" />
    </components>
</castle>

并让 IoC 容器处理会话对象,而无需自己显式访问它。

我怎样才能做到这一点?

谢谢。

编辑:一直在做一些研究。基本上,我想要的是拥有一个 session Scoped 组件。我来自 Java 和 Spring Framework,我有会话范围的 bean,我认为它们对于存储会话数据非常有用。

4

4 回答 4

14

这可能是您正在寻找的。

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()
    {
    }
}

然后添加

<component
        id="billingManager"  
        lifestyle="custom"  
        customLifestyleType="Namespace.PerSessionLifestyleManager, Namespace"  
        service="IInterface, Namespace"
        type="Type, Namespace">
</component>
于 2009-09-02T09:08:11.927 回答
4

此解决方案适用于 Windsor 3.0 及更高版本。它基于 PerWebRequest Lifestyle 的实现,并利用 Windsor 3.0 中引入的新 Scoped Lifestyle。

你需要两节课...

IHttpModule处理会话管理的实现。将ILifetimeScope对象添加到会话中,并在会话到期时再次处理它。这对于确保正确发布组件至关重要。到目前为止,这里给出的其他解决方案都没有考虑到这一点。

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()
    {
    }
}

您需要的第二个类是IScopeAccessor. 这用于弥合 HttpModule 和内置 WindsorScopedLifestyleManager类之间的差距。

public class WebSessionScopeAccessor : IScopeAccessor
{
    public void Dispose()
    {
        var scope = PerWebSessionLifestyleModule.YieldScope();
        if (scope != null)
        {
            scope.Dispose();
        }
    }

    public ILifetimeScope GetScope(CreationContext context)
    {
        return PerWebSessionLifestyleModule.GetScope();
    }
}

添加了两种internal static方法PerWebSessionLifestyleModule来支持这一点。

就是这样,期待注册它...

container.Register(Component
    .For<ISometing>()
    .ImplementedBy<Something>()
    .LifestyleScoped<WebSessionScopeAccessor>());

或者,我将此注册包装到扩展方法中......

public static class ComponentRegistrationExtensions
{
    public static ComponentRegistration<TService> LifestylePerSession<TService>(this ComponentRegistration<TService> reg)
        where TService : class
    {
        return reg.LifestyleScoped<WebSessionScopeAccessor>();
    }
}

所以可以这样称呼...

container.Register(Component
    .For<ISometing>()
    .ImplementedBy<Something>()
    .LifestylePerSession());
于 2012-03-02T14:31:46.997 回答
1

听起来您在正确的轨道上,但是您的 SearchOptions 类需要实现 ISearchOptions:

public class SearchOptions : ISearchOptions { ... }

您还需要告诉 Windsor 您的 SearchController 是一个组件,因此您可能还想在 web.config 中注册它,尽管我更喜欢从代码中进行注册(见下文)。

为了让 Windsor 选择你的 web.config,你应该像这样实例化它:

var container = new WindsorContainer(new XmlInterpreter());

要创建 SearchController 的新实例,您可以简单地执行以下操作:

var searchController = container.Resolve<SearchController>();

要使用基于约定的技术在给定程序集中注册所有控制器,您可以执行以下操作:

container.Register(AllTypes
    .FromAssemblyContaining<MyController>()
    .BasedOn<IController>()
    .ConfigureFor<IController>(reg => reg.LifeStyle.Transient));
于 2009-09-02T07:25:02.307 回答
1

我的经验是安迪的回答不起作用,因为从未直接提出SessionStateModule.End

虽然 End 事件是公开的,但您只能通过在 Global.asax 文件中添加事件处理程序来处理它。实现此限制是因为 HttpApplication 实例被重用以提高性能。当会话到期时,仅执行 Global.asax 文件中指定的 Session_OnEnd 事件,以防止代码调用与当前正在使用的 HttpApplication 实例关联的 End 事件处理程序。

因此,添加一个什么都不做的 HttpModule 变得毫无意义。我已将安迪的答案改编成一个SessionScopeAccessor班级:

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

}

SessionEnd从文件中调用方法很重要global.asax.cs

void Session_OnEnd(object sender, EventArgs e)
{
    SessionScopeAccessor.SessionEnd(Session);
}

这是处理 SessionEnd 事件的唯一方法。

于 2015-03-20T14:09:49.907 回答