使用ASP.NET MVC的每个会话生活方式的Castle项目

时间:2009-09-02 07:07:51

标签: c# asp.net-mvc session castle-windsor ioc-container

我是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容器处理会话对象,而不必自己显式访问它。

我该怎么做?

感谢。

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

4 个答案:

答案 0 :(得分: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>

答案 1 :(得分: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和内置的Windsor ScopedLifestyleManager类之间的差距。

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

答案 2 :(得分: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));

答案 3 :(得分:1)

我的经验是Andy's answer不起作用,因为SessionStateModule.Endnever raised directly

  

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

由于这个原因,添加一个什么也不做的HttpModule变得毫无意义。我已经将Andy的答案改编成了一个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事件的唯一方法。