【问题标题】:Castle project per session lifestyle with ASP.NET MVC使用 ASP.NET MVC 的每个会话生活方式的城堡项目
【发布时间】:2010-11-24 20:54:59
【问题描述】:

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

我该怎么做?

谢谢。

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

【问题讨论】:

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


    【解决方案1】:

    这可能就是您要找的。​​p>

    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>
    

    【讨论】:

    • 谢谢,这正是我想要的。
    • 我猜您应该将字段名称从 PerRequestObjectID 更改为 PerSessionObjectID。实际上,我相信查看文档可以让您走得很远:) castleproject.org/container/documentation/trunk/usersguide/…
    • @TigerShark 正确 :) 我已经修好了。
    • 这是否允许我们编写类似 cr => cr.LifeStyle.PerSession.Named(cr.Implementation.Name));
    • 这不负责发布组件。为了发生这种情况,必须将某些东西连接到 Session End 事件中。请参阅下面stackoverflow.com/a/9534959/3362 的回答
    【解决方案2】:

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

    【讨论】:

    • IIRC Session_End 只会在 InProc 会话中触发。
    • 看起来是这样。我真的没有考虑过这个。我认为在我的情况下很好。我确定我只会使用 InProc 会话。如果会话过期,我管理生活方式的对象类型将永远不需要保留。但这绝对是其他人应该意识到的。我不确定在没有会话结束事件的情况下如何处理会话生活方式。
    • 确实,没有办法。前段时间我为 Windsor 写了一个 perwebsession 生活方式,见bugsquash.blogspot.com/2010/06/…github.com/castleprojectcontrib/Castle.Windsor.Lifestyles
    • @Andy,我在 app.config (XML) 中找不到如何配置它,你知道吗?
    • @John Landheer 与 PerWebRequest 相同。将元素添加到 &lt;modules&gt; 和/或 &lt;httpModules&gt; 元素。这将在 web.config 中
    【解决方案3】:

    听起来你是在正确的轨道上,但你的 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));
    

    【讨论】:

      【解决方案4】:

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

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

      因此,添加一个什么都不做的 HttpModule 变得毫无意义。我已将安迪的答案改编成一个 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 事件的唯一方法。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多