2009-09-02 11 views
6

Jestem naprawdę nowy dla kontenera Castle Windsor IoC. Chciałem wiedzieć, czy istnieje sposób przechowywania zmiennych sesji za pomocą kontenera IoC. Myślałam coś w linii to:Projekt zamku na sesję lifestyle z ASP.NET MVC

chcę mieć klasę do przechowywania opcji wyszukiwania:

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

A potem wstrzyknąć że do klasy, która ma z niego korzystać:

public class SearchController{ 
    private ISearchOptions _searchOptions; 
    public SearchController(ISearchOptions searchOptions){ 
     _searchOptions=searchOptions; 
    } 
    ... 
} 

następnie w moim pliku web.config, gdzie skonfigurować zamek Chcę mieć coś takiego:

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

I niech kontener IoC obsługuje obiekt sesji bez konieczności jawnego uzyskiwania dostępu do niego.

Jak mogę to zrobić?

Dzięki.

EDYCJA: Przeprowadziłem badania. Zasadniczo, chcę mieć komponent Scoped sesji. Pochodzę z Java i Spring Framework i tam mam zogniskowane sesje, które moim zdaniem są bardzo przydatne do przechowywania danych sesji.

Odpowiedz

14

to może być to, czego szukasz.

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

a następnie dodać

<component 
     id="billingManager" 
     lifestyle="custom" 
     customLifestyleType="Namespace.PerSessionLifestyleManager, Namespace" 
     service="IInterface, Namespace" 
     type="Type, Namespace"> 
</component> 
+0

Dzięki, właśnie tego szukałem. –

+0

Ja też! Dzięki. –

+1

Chyba powinieneś zmienić nazwę pola z PerRequestObjectID na PerSessionObjectID. Wierzę, że patrząc na dokumentację można uzyskać długą drogę :) http://www.castleproject.org/container/documentation/trunk/usersguide/lifestyles.html – Siewers

1

Brzmi jak jesteś na dobrej drodze, ale klasa SearchOptions musi wdrożyć ISearchOptions:

public class SearchOptions : ISearchOptions { ... } 

Trzeba także powiedzieć Windsor że SearchController jest składnikiem, więc możesz się zarejestrować, że w web.config również, chociaż wolę zrobić to z kodu (patrz poniżej).

Aby Windsor odebrać web.config, powinieneś oznacz ją tak:

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

Aby utworzyć nową instancję SearchController, można po prostu to zrobić:

var searchController = container.Resolve<SearchController>(); 

Aby zarejestrować wszystkie kontrolery w danym zestawie przy użyciu technik opartych na konwencjach, można wykonać następujące czynności:

container.Register(AllTypes 
    .FromAssemblyContaining<MyController>() 
    .BasedOn<IController>() 
    .ConfigureFor<IController>(reg => reg.LifeStyle.Transient)); 
4

Rozwiązanie to będzie pracować dla Windsor 3.0 i wyżej. Jest on oparty na implementacji PerWebRequest Lifestyle i wykorzystuje nowy Scoped Lifestyle wprowadzony w Windsor 3.0.

Musisz dwie klasy ...

Implementacja IHttpModule do obsługi zarządzania sesją. Dodawanie obiektu ILifetimeScope do sesji i usuwanie go ponownie po wygaśnięciu sesji. Ma to kluczowe znaczenie dla prawidłowego wydania komponentów. Nie zajmujemy się tym w dotychczasowych rozwiązaniach.

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

Druga klasa trzeba to implementacja IScopeAccessor. Służy on do wypełnienia luki między Twoim HttpModule a wbudowaną klasą 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(); 
    } 
} 

Dwa internal static metody dodano PerWebSessionLifestyleModule aby wspierać.

To jest to, spodziewać się go zarejestrować ...

container.Register(Component 
    .For<ISometing>() 
    .ImplementedBy<Something>() 
    .LifestyleScoped<WebSessionScopeAccessor>()); 

Opcjonalnie, owinąłem się do tej rejestracji metodę rozszerzenia ...

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

Tak to można nazwać w ten sposób. ..

container.Register(Component 
    .For<ISometing>() 
    .ImplementedBy<Something>() 
    .LifestylePerSession()); 
+1

IIRC Session_End uruchomi tylko sesje ** InProc **. –

+0

Tak się wydaje. Tak naprawdę nie zastanawiałem się nad tym. Myślę, że jest w porządku w mojej sytuacji. Jestem pewien, że będę korzystać tylko z sesji InProc. Rodzaj obiektów, którymi zarządzam, nie będzie trwał dłużej, jeśli sesja wygasa. Ale na pewno jest to coś, o czym inni powinni wiedzieć.Nie wiem, jak poradzić sobie z trybem sesji bez zakończenia sesji. –

+0

w rzeczy samej, nie ma mowy. Napisałem jakiś styl życia dla Windsor jakiś czas temu, zobacz http://bugsquash.blogspot.com/2010/06/hybrid-lifestyles-in-windsor.html https://github.com/castleprojectcontrib/Castle.Windsor.Lifestyles –

1

Moje doświadczenie jest takie, że Andy's answer nie działa, ponieważ SessionStateModule.End to never raised directly:

Mimo że zdarzenie End jest publiczne, można sobie z nim tylko poradzić, dodając procedurę obsługi zdarzeń do pliku Global.asax. To ograniczenie jest implementowane, ponieważ instancje HttpApplication są ponownie wykorzystywane w celu uzyskania wydajności. Gdy sesja wygasa, tylko zdarzenie Session_OnEnd określone w pliku Global.asax jest wykonywane, aby uniemożliwić kodowi wywołanie procedury obsługi zdarzenia End skojarzonej z wystąpieniem HttpApplication, które jest aktualnie używane.

Z tego powodu nie ma sensu dodawać modułu Http, który nie robi nic. Mam dostosowany odpowiedź Andy'ego w jednym SessionScopeAccessor Klasa:

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

}

ważne jest, aby wywołać metodę SessionEnd z pliku global.asax.cs:

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

Jest to jedyny sposób, aby obsłużyć wydarzenie SessionEnd.