我是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对于存储会话数据非常有用。
答案 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.End
是never 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事件的唯一方法。