Образ жизни проекта замка на сеанс с ASP.NET MVC

Я действительно новичок в контейнере 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 обрабатывает объект сеанса, не обращаясь к нему лично.

Как я могу это сделать?

Спасибо.

РЕДАКТИРОВАТЬ: Провел небольшое исследование. По сути, я хочу иметь компонент с областью действия сеанса. Я пришел из Java и Spring Framework, и там у меня есть bean-компоненты с сессионной областью, которые, как мне кажется, очень полезны для хранения данных сеанса.


person Carles Company    schedule 02.09.2009    source источник


Ответы (4)


это может быть то, что вы ищете.

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>
person Carl Bergquist    schedule 02.09.2009
comment
Спасибо, это именно то, что я искал. - person Carles Company; 02.09.2009
comment
Думаю, вам следовало изменить имя поля с PerRequestObjectID на PerSessionObjectID. На самом деле я считаю, что просмотр документации может помочь вам :) castleproject .org / container / documentation / trunk / usersguide /. - person Siewers; 29.03.2011
comment
@TigerShark Правильно :) Я исправил это. - person Carl Bergquist; 30.03.2011
comment
Позволяет ли это написать что-то вроде cr = ›cr.LifeStyle.PerSession.Named (cr.Implementation.Name)); - person Yurii Hohan; 05.10.2011
comment
Это не касается выпуска компонентов. Что-то должно быть связано с событием окончания сеанса, чтобы это произошло. См. Мой ответ ниже stackoverflow.com/a/9534959/3362 - person Andy McCluggage; 02.03.2012
comment
В самом деле, этот ответ не должен был быть принят - какой смысл иметь область действия сеанса, если все объекты остаются включенными после истечения времени ожидания сеанса? - person Søren Boisen; 09.04.2015

Это решение будет работать для Windsor 3.0 и выше. Он основан на реализации PerWebRequest Lifestyle и использует новый Scoped Lifestyle, представленный в Windsor 3.0.

Вам нужно два класса ...

Реализация 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<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());
person Andy McCluggage    schedule 02.03.2012
comment
IIRC Session_End будет срабатывать только для сеансов InProc. - person Mauricio Scheffer; 02.03.2012
comment
Так казалось бы. Я действительно не думал об этом. Я думаю, что это нормально в моей ситуации. Я уверен, что буду использовать только сеансы InProc. Тип объектов, для которых я управляю образом жизни, никогда не потребуется сохранять, если сеанс истечет. Но это определенно то, о чем другие должны знать. Я не уверен, как вы могли бы справиться с образом жизни сеанса без события завершения сеанса. - person Andy McCluggage; 02.03.2012
comment
действительно, пути нет. Некоторое время назад я написал постоянный образ жизни для Виндзора, см. bugsquash. blogspot.com/2010/06/ github.com/castleprojectcontrib/Castle.Windsor. Образ жизни - person Mauricio Scheffer; 02.03.2012
comment
@ Энди, я не могу найти, как это настроить в app.config (XML), вы знаете? - person John Landheer; 12.04.2012
comment
@John Landheer то же, что и для PerWebRequest. Добавьте элемент к элементам <modules> и / или <httpModules>. Это будет в файле web.config - person Andy McCluggage; 13.04.2012
comment
Вы проверили, что это работает? Согласно MSDN этого не должно: Хотя событие End является общедоступным, вы можете обработать его, только добавив обработчик события в файл Global.asax. [...] По истечении сеанса выполняется только событие Session_OnEnd, указанное в файле Global.asax, чтобы предотвратить вызов кода обработчика событий End, связанного с экземпляром HttpApplication, который используется в данный момент. - person AlexFoxGill; 20.03.2015
comment
Кроме того, по моему опыту, вы должны вызвать session.Remove(key); в обработчике SessionEnd - person AlexFoxGill; 20.03.2015

Похоже, вы на правильном пути, но ваш класс SearchOptions должен реализовать ISearchOptions:

public class SearchOptions : ISearchOptions { ... }

Вам также необходимо сообщить Windsor, что ваш SearchController является компонентом, поэтому вы можете также зарегистрировать его в файле web.config, хотя я предпочитаю делать это из кода (см. Ниже).

Чтобы Виндзор забрал ваш 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));
person Mark Seemann    schedule 02.09.2009

По моему опыту, ответ Энди не работает, поскольку SessionStateModule.End - это никогда не поднимался напрямую:

Хотя событие End является общедоступным, вы можете обработать его, только добавив обработчик события в файл Global.asax. Это ограничение реализовано, поскольку экземпляры HttpApplication повторно используются для повышения производительности. Когда сеанс истекает, выполняется только событие Session_OnEnd, указанное в файле Global.asax, чтобы предотвратить вызов кода обработчика событий End, связанного с экземпляром HttpApplication, который используется в данный момент.

По этой причине становится бессмысленным добавлять 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);
        }
    }
}

}

Важно вызвать метод SessionEnd из вашего global.asax.cs файла:

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

Это единственный способ обработать событие SessionEnd.

person AlexFoxGill    schedule 20.03.2015