使用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事件的唯一方法。