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

我是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中,我配置城堡我希望有类似的东西:

      

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

我怎样才能做到这一点?

谢谢。

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

这可能就是你要找的东西。

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

然后添加

   

此解决方案适用于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(); } } 

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

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

 container.Register(Component .For() .ImplementedBy() .LifestyleScoped()); 

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

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

所以它可以这样调用……

 container.Register(Component .For() .ImplementedBy() .LifestylePerSession()); 

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

 public class SearchOptions : ISearchOptions { ... } 

你还需要告诉Windsor你的SearchController是一个组件,所以你可能也想在web.config中注册它,虽然我更喜欢从代码中做到(见下文)。

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

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

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

 var searchController = container.Resolve(); 

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

 container.Register(AllTypes .FromAssemblyContaining() .BasedOn() .ConfigureFor(reg => reg.LifeStyle.Transient)); 

我的经验是Andy的答案不起作用,因为SessionStateModule.End 永远不会直接引发 :

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

}

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

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

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