Синглтоны и ASP.NET MVC

Прямо сейчас у меня возникла проблема с синглтоном, который я только что написал для использования в ASP.NET MVC. Мой синглтон выглядит так:

public sealed class RequestGenerator : IRequestGenerator
{
    // Singleton pattern
    private RequestGenerator() 
    {
        requestList = new Stack<Request>();
        appSettings = new WebAppSettings();
    }
    private static volatile RequestGenerator instance = new RequestGenerator();
    private static Stack<Request> requestList = new Stack<Request>();
    // abstraction layer for accessing web.config
    private static IAppSettings appSettings = new WebAppSettings();
    // used for "lock"-ing to prevent race conditions
    private static object syncRoot = new object();
    // public accessor for singleton        
    public static IRequestGenerator Instance
    {
        get
        {
            if (instance == null)
            {
                lock (syncRoot)
                {
                    if (instance == null)
                    {
                        instance = new RequestGenerator();
                    }
                }
            }
            return instance;
        }
    }     
    private const string REQUESTID = "RequestID";

    // Find functions
    private Request FindRequest(string component, string requestId)
    private List<Request> FindAllRequests(string component, string requestId)

    #region Public Methods required by Interface
    // Gets and increments last Request ID from Web.Config, creates new Request, and returns RequestID
    public string GetID(string component, string userId)

    // Changes state of Request to "submitted"
    public void SetID(string component, string requestId)

    // Changes state of Request to "success" or "failure" and records result for later output
    public void CloseID(string component, string requestId, bool success, string result)

    // Verifies that Component has generated a Request of this ID
    public bool VerifyID(string component, string requestId)

    // Verifies that Component has generated a Request of this ID and is owned by specified UserId
    public bool VerifyID(string component, string userId, string requestId)

    // Returns State of Request ID (Open, Submitted, etc.)
    public Status GetState(string component, string requestId)

    // Returns Result String of Success or Failure.
    public string GetResult(string component, string requestId)
    #endregion
}

И мой код контроллера выглядит так:

public ViewResult SomeAction()
{
    private IRequestGenerator reqGen = RequestGenerator.Instance;
    string requestId = reqGen.GetID(someComponentName, someUserId);
    return View(requestId);
}

Все работает нормально с первого раза, когда я нажимаю на контроллер. «reqGen» назначается экземпляру Singleton. Новый экземпляр запроса добавляется во внутренний список синглтона. И затем мы возвращаем View(). В следующий раз, когда я нажму SomeAction() этого контроллера, я ожидаю, что Singleton будет содержать список с экземпляром SomeClass, который я только что добавил, но вместо этого список пуст.

Что случилось? Сборка мусора поглотила мой объект? Есть ли что-то особенное, что мне нужно учитывать при реализации шаблона Singleton в ASP.NET MVC?

Спасибо!

EDIT: Ааа, лампочка только что загорелась. Таким образом, каждый новый запрос страницы происходит в совершенно новом процессе! Понятно. (мой опыт связан с разработкой настольных приложений, так что для меня это другая парадигма...)

EDIT2: Конечно, вот еще одно уточнение. Моему приложению требовалась система нумерации запросов, где что-то запрашиваемое нуждалось в уникальном идентификаторе, но у меня не было доступной БД. Но он должен был быть доступен каждому пользователю для регистрации состояния каждого запроса. Я также понял, что это может использоваться как способ регулирования сеанса, скажем, если пользователь дважды щелкнул кнопку запроса. Синглтон казался подходящим вариантом, но понимание того, что каждый запрос находится в своем собственном процессе, в основном исключает синглтон. И я предполагаю, что это также устраняет статический класс, верно?

EDIT3: хорошо, я добавил фактический код, с которым я работаю (за вычетом реализации каждого метода, для простоты...) Я надеюсь, что это понятнее.

EDIT4: я награждаю Криса зеленой галочкой, поскольку начинаю понимать, что синглтон на уровне приложения — это то же самое, что наличие Global (а глобальные — это зло, верно?) — All шутки в сторону, лучший вариант на самом деле - иметь БД, и SQLite кажется лучшим вариантом на данный момент, хотя я определенно вижу, что в будущем перейду на экземпляр Oracle. К сожалению, тогда лучшим вариантом было бы использование ORM, но это еще одна кривая обучения. жук.

EDIT5: Последнее редактирование, клянусь. :-)

Поэтому я попытался использовать HttpRuntime.Cache, но был удивлен, обнаружив, что мой кеш постоянно очищается/аннулируется, и я не мог понять, что происходит. Ну, я был сбит с толку побочным эффектом чего-то еще, что я делал: записи в "Web.config"

Ответ --> Без моего ведома, когда "web.config" каким-либо образом изменяется, приложение ПЕРЕЗАПУСКАЕТСЯ! Да, все выбрасывается. Мой синглтон, мой кеш, все. Гах. Неудивительно, что ничего не работало правильно. Похоже, что писать обратно в web.config, как правило, плохая практика, от которой я теперь воздержусь.

Еще раз спасибо всем, кто помог мне в этой беде.


person Pretzel    schedule 15.10.2010    source источник
comment
Если вы делаете это для кэширования, изучите использование HttpContext.Current.Cache.   -  person Todd Smith    schedule 16.10.2010
comment
Это большой вопрос! Наблюдение за тем, как Pretzel проходит через веревки перехода к веб-разработке, будет полезно для многих будущих программистов.   -  person Chris McCall    schedule 02.12.2010
comment
@ Крис, да, я стараюсь подробно объяснить все в своих вопросах и ответах, чтобы каждый мог извлечь уроки из моих уроков (а также, чтобы, когда я неизбежно забуду какой-то урок через несколько месяцев, я мог быстро напомнить себе о том, что я узнал. .. ;-)   -  person Pretzel    schedule 02.12.2010


Ответы (2)


Синглтон специфичен для экземпляра обработки. Для каждого запроса страницы создается новый экземпляр. Запросы страниц, как правило, считаются не имеющими состояния, поэтому данные из одного не просто сохраняются для другого.

Чтобы заставить это работать на уровне приложения, переменная экземпляра должна быть объявлена ​​там. См. этот вопрос, чтобы узнать, как создать переменную уровня приложения. . Обратите внимание, что это сделает его доступным для всех запросов... что не всегда то, что вам нужно.

Конечно, если вы пытаетесь реализовать какое-либо состояние сеанса, вы можете просто использовать сеанс или какую-либо процедуру кэширования.

ОБНОВЛЕНИЕ
На основе ваших правок: статический класс не должен поддерживать данные. Его цель состоит в том, чтобы просто сгруппировать некоторые общие методы вместе, но он не должен хранить данные между вызовами методов. Синглтон — это совсем другое дело, поскольку это класс, в котором вы хотите, чтобы для запроса был создан только один объект.

Ни то, ни другое не похоже на то, что вы хотите.

Теперь наличие синглтона на уровне приложения будет доступно для всего приложения, но это пересекает запросы и должно быть закодировано соответствующим образом.

Это почти звучит так, как будто вы пытаетесь создать хранилище данных в памяти. Вы можете пойти по пути использования одного из различных механизмов кэширования, например .NET Page .Cache, MemCache или блок приложения кэширования корпоративной библиотеки.

Тем не менее, все они имеют проблему очистки в случае перезапуска рабочего процесса, в котором размещено приложение. Это может произойти в самые неподходящие времена. количество перекомпиляций страниц и т. д.

Вместо этого я настоятельно рекомендую использовать какое-либо постоянное хранилище. Будь то просто xml-файлы, которые вы читаете/записываете, или встраивание чего-то вроде SQL Lite в приложение. SQL Lite — очень легкая база данных, не требующая установки на сервер; вам просто нужны сборки.

person NotMe    schedule 15.10.2010
comment
Итак, это один из тех экземпляров в сравнении статического класса с одноэлементным, где вместо этого я должен реализовать статический класс? - person Pretzel; 16.10.2010
comment
@Pretzel: трудно сказать, не зная, что это за класс или зачем вам нужна именно одна его копия. Что делает класс? Если вам действительно нужно, чтобы он был контейнером данных, то, вероятно, лучше использовать механизм кэширования или сеанс. Хотя я бы не хранил классы в сеансе. Если вы предоставите подробную информацию о том, чего вы пытаетесь достичь, многие люди здесь могут дать вам некоторое направление о том, как этого добиться. - person NotMe; 16.10.2010
comment
Спасибо за обновления. Я добавил фактический код, с которым я работаю, чтобы у вас было более четкое представление о том, что я делаю. Да, я создаю хранилище данных в памяти. Я не планировал хранить запросы в БД или XML (ну, пока нет). Запросы отправляются пользователям через SMTP. Я посмотрю на SQL Lite. Должен ли я изучить использование NHibernate или Entity Framework? Или я должен просто кодировать SQL Lite напрямую? - person Pretzel; 18.10.2010
comment
@Pretzel: я не фанат инструментов ORM, но это, безусловно, варианты. Лично я бы сразу перешел к SQL Lite. - person NotMe; 18.10.2010
comment
Проверьте мое последнее редактирование выше. Классический фейс-палм здесь. Но откуда мне было знать? - person Pretzel; 20.10.2010

Вы можете использовать Dependency Injection для управления жизнью класса. Вот строка, которую вы могли бы добавить в свой web.config, если бы использовали Castle Windsor.

<component id="MySingleton" service="IMySingleton, MyInterfaceAssembly" 
type="MySingleton, MyImplementationAssembly" lifestyle="Singleton" />

Конечно, тема подключения вашего приложения к использованию DI выходит за рамки моего ответа, но либо вы его используете, и этот ответ вам поможет, либо вы можете опробовать концепцию и влюбиться в нее. :)

person Mayo    schedule 15.10.2010
comment
Я начал читать о DI несколько месяцев назад и теперь у меня появилась привычка кодировать интерфейсы (а не конкретные классы), поэтому в некоторых местах моего кода я делаю ручную инъекцию, но мне до сих пор не удалось внедрить контейнер DI, такой как Castle Windsor или Ninject. (Я еще не до конца понял, как его использовать...) Возможно, это будет мой проект на этих выходных. - person Pretzel; 16.10.2010
comment
Я попытался сделать DI с Ninject, и мне удалось заставить его успешно внедрить синглтон, но как только процесс умер, синглтон умер вместе с ним, так что это не сработало для меня. - person Pretzel; 18.10.2010