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

C# 使用ASP.NET MVC的Castle项目每会话生活方式,c#,asp.net-mvc,session,castle-windsor,ioc-container,C#,Asp.net Mvc,Session,Castle Windsor,Ioc Container,我对温莎城堡真的很陌生。我想知道是否有一种使用IoC容器存储会话变量的方法。我在想这样的事情: 我想要一个类来存储搜索选项: public interface ISearchOptions{ public string Filter{get;set;} public string SortOrder{get;set;} } public class SearchOptions{ public string Filter{get;set;} public stri

我对温莎城堡真的很陌生。我想知道是否有一种使用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,我希望有如下内容:

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

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

我该怎么做

谢谢


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

听起来你的思路是对的,但是你的SearchOptions类需要实现ISearchOptions:

public class SearchOptions : ISearchOptions { ... }
您还需要告诉Windsor您的SearchController是一个组件,因此您可能也希望在web.config中注册它,尽管我更喜欢从代码中注册(见下文)

要使Windsor获取您的web.config,您应该如下实例化它:

var container = new WindsorContainer(new XmlInterpreter());
container.Register(AllTypes
    .FromAssemblyContaining<MyController>()
    .BasedOn<IController>()
    .ConfigureFor<IController>(reg => reg.LifeStyle.Transient));
要创建SearchController的新实例,只需执行以下操作:

var searchController = container.Resolve<SearchController>();
var searchController=container.Resolve();
要使用基于约定的技术注册给定程序集中的所有控制器,可以执行以下操作:

var container = new WindsorContainer(new XmlInterpreter());
container.Register(AllTypes
    .FromAssemblyContaining<MyController>()
    .BasedOn<IController>()
    .ConfigureFor<IController>(reg => reg.LifeStyle.Transient));
container.Register(所有类型
.FromAssemblyContaining()
.BasedOn()
.ConfigureFor(reg=>reg.lifety.Transient));

这可能就是你想要的

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>

此解决方案适用于Windsor 3.0及以上版本。信息技术s基于PerWebRequest生活方式的实现,并利用温莎3.0中引入的新范围生活方式

你需要两门课

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
的实现。这用于弥合HTTPM模块与内置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();
    }
}
PerWebSessionLifestyleModule
中添加了两个
internalstatic
方法来支持这一点

就这样,希望注册它

container.Register(Component
    .For<ISometing>()
    .ImplementedBy<Something>()
    .LifestyleScoped<WebSessionScopeAccessor>());
container.Register(组件
.对于()
.由()实施
.lifestylescope());
或者,我将此注册打包到扩展方法中

public static class ComponentRegistrationExtensions
{
    public static ComponentRegistration<TService> LifestylePerSession<TService>(this ComponentRegistration<TService> reg)
        where TService : class
    {
        return reg.LifestyleScoped<WebSessionScopeAccessor>();
    }
}
公共静态类ComponentRegistrationExtensions
{
公共静态组件注册生活方式会话(此组件注册注册表)
where-TService:class
{
return reg.LifestyleScoped();
}
}
所以可以这样称呼它

container.Register(Component
    .For<ISometing>()
    .ImplementedBy<Something>()
    .LifestylePerSession());
container.Register(组件
.对于()
.由()实施
.生活方式();

我的经验是,这不起作用,因为
会话状态模块.End
是:

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

因此,添加一个什么都不做的HttpModule变得毫无意义。我已将Andy的答案改编为一个
会话范围访问器
类:

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

global.asax.cs
文件调用
SessionEnd
方法非常重要:

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

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

谢谢,这正是我要找的。我想您应该将字段名从PerRequestObjectID更改为PerSessionObjectID。事实上,我相信查看文档可以让您受益匪浅:)@TigerShark Correct:)我已经修复了它。这是否允许我们编写类似于cr=>cr.lifety.PerSession.Named(cr.Implementation.Name)的内容;这不包括释放组件。必须将某些内容连接到会话结束事件中才能发生这种情况。请看下面我的答案,IIRC会话只会在InProc会话中触发。看起来是这样的。我真的没有考虑过这件事。我认为在我的情况下这是好的。我肯定我只会使用InProc会话。如果会话过期,我管理的对象类型将永远不需要持久化。但这绝对是其他人应该意识到的。我不确定如果没有会话结束事件,您如何处理会话生活方式。事实上,这是不可能的。不久前我为温莎写了一个perwebsession生活方式,请看@Andy,我在app.config(XML)中找不到如何配置它,你知道吗?@John Landheer与PerWebRequest相同。向
和/或
元素添加元素。这将在web.config中