Состояние веб-приложений

Данная статья поясняет способ содержания состояния веб-приложения.

Введение

Состояние веб-приложения

Веб-страница повторно создается каждый раз, как она отсылается обратно на сервер. В среде традиционного веб-программирования это означает, что вся информация в пределах страницы и в элементах управления теряется при каждом пробеге "туда обратно". Чтобы преодолеть данное препятствие традиционного веб-программирования, структуры страниц ASP.NET включает в себя различные опции, помогающие сохранить изменения. Одна из опций включают в себя сохранение информации у клиента, напрямую в странице или файле куки, а другая опция подразумевает сохранение информации на сервере между пробегами. Следующие объекты были разработаны для содержания состояния приложения: HttpSessionState, HttpApplicationState, ViewState, HttpCookie, HiddenFiled и также строки запросов и базы данных могут служить хранителями состояния. Итак, мы можем обобщить все сказав, что существуют два различных способа управления состоянием веб-приложения: со стороны клиента и со стороны сервера.

Управление состоянием клиентской стороны

Файлы куки (Cookies)

Файл куки - это маленький объем данных, который хранится либо в текстовом файле клиентской файловой системы, или же в оперативной памяти сессии клиентского обозревателя. Файлы куки в основном используются для отслеживания настроек данных. Например, допустим, нам необходимо специализировать домашнюю веб-страницу - когда пользователь запрашивает стандартную веб-страницу, приложение сначала определяет авторизовался ли пользователь на сайте раньше. В таком случае мы можем получить пользовательскую информацию из файлов куки.

Скрытые поля

Стандартным способом сохранения состояния данных является использование скрытых HTML-полей. Скрытое поле хранит данные посредством кода HTML <input type="hidden">. Скрытое поле не будет отображаться в обозревателе, но мы можем установить его свойства таким же образом, как устанавливаем свойства для других стандартных элементов управления. Когда страница отсылается на сервер, содержимое скрытого поля отсылается в коллекции Form вместе со значениями других элементов управления. Для того, чтобы данные скрытые значения были доступны во время обработки страницы, нам необходимо отослать страницу.

ViewState

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

Строки запроса

Строки запроса предоставляют простой, но ограниченный способ сохранения некоторой информации о состоянии. Вы с легкостью можете передавать информацию с одной страницы на другую, но большинство обозревателей и клиентских устройств имеют предел в 255 символов относительно длины ссылки URL. В дополнение, значения запроса раскрываются всей сети посредством ссылки URL, потому в некоторых случаях это может угрожать безопасности. Ссылка с запросом может выглядеть следующим образом: http://myapplication.com/listing.aspx?group=1&item=1.

Управление состоянием серверной стороны Server-side State Management

Объект Application

Объект Application предоставляет механизм для хранения данных, которые доступны всем кодам, запущенным в веб-приложении. Идеальными данными, которые стоит внести в переменные состояния приложения, являются те, которые разделены между множеством сессий и которые не изменяются часто. И поскольку они видны всему приложению, вам стоит использовать методы Lock и UnLock для того, чтобы избежать конфликтов значений.

Объект Session

Объект Session может быть использован для хранения информации по конкретной сессии, которая должна быть сохранена между пробегами к серверу и между запросами страниц. Объект Session основан на клиентах, то есть различные клиенты генерируют различные объекты Session. Идеальными данными на хранение в переменных сессионного состояния является кратковременная, чувствительная информация, которая специфична для каждой индивидуальной сессии.

Использование базы данных

Сохранение состояний, используя базы данных, является стандартным способом хранения информации о пользователе, когда сохраняемая информация довольно объемна. Хранилище базы данных, в частности, пригодно для хранения долгосрочных состояний или тех состояний, который должны быть сохранены даже в том случае, если сервер должен быть перегружен. Подход, использующий базы данных, зачастую используется с файлами куки. К примеру, когда пользователь получает доступ к приложению, ему может понадобиться ввести логин/пароль для авторизации. Вы можете найти его учетную запись в базе данных и затем передать файл куки обратно пользователю. Файл куки может содержать только идентификационные данные (ID) пользователя, хранимые в вашей базе данных. Вы затем можете использовать данный файл в последующих запросах для того, чтобы при необходимости найти информацию в базе данных.

Задача

Как мы уже писали, веб-приложения по своей сути не сохраняют состояния, поэтому вам необходимо реализовать фиксаторы данных для предоставления сохранения состояния веб-приложениям. К примеру, следующий код демонстрирует базовое использование HttpSessionState.

MyDtoObject dtoObject = new MyDtoObject();  
dtoObject.FirstName = Session["FirstName"] as string;
dtoObject.LastName = Session["LastName"] as string;
dtoObject.Login = Session["Login"] as string;
//и т.д.

Но что делать, если требования заставляют нас хранить MyDtoObject в базе данных, но временно? Тогда нам необходимо хранить MyDtoObject в HttpApplicationState для того, чтобы он был доступным между пользовательскими сессиями. Нелегко изменять пакеты файлов одновременно. Что нам необходимо, так это возможность определить то, как мы будет хранить MyDtoObject, без необходимости повторного кодирования.

Решение

Как нам кажется, использование HttpSessionState, HttpCookie и HttpApplicationState напрямую не совсем удобно, потому мы разработали некоторый тип упаковщиков для таких объектов, следуя принципам Объектно-ориентированного программирования. К примеру, нам необходимо иметь объект ApplicationSettings , который будет хранить состояние в файле куки, а также предполагается, что это будет одноэлементный объект. Все, что нам необходимо сделать, так это заставить ApplicationSettings класс наследовать SingletonStateObject<ApplicationSettings, CookieStorage<ApplicationSettings>>.

public class ApplicationSettings : SingletonStateObject<ApplicationSettings, 
             CookieStorage<ApplicationSettings>>
{
    private string _hostName;
    private int _counter;


    public int Counter
    {
        get { return _counter; }
        set { _counter = value; }
    }

    public string HostName
    {
        get { return _hostName; }
        set { _hostName = value; }
    }
}
   
protected override void OnLoad(EventArgs e)
{
    base.OnLoad(e);
    ApplicationSettings settings = ApplicationSettings.Get();
    Response.Write("ApplicationSettings:");
    Response.Write("<br/>");
    Response.Write("HostName: " + settings.HostName);
    Response.Write("<br/>");
    Response.Write("Counter: " + settings.Counter);
    
}

protected void uxSave_Click(object sender, EventArgs e)
{
    ApplicationSettings settings = ApplicationSettings.Get();
    settings.HostName = uxHostName.Text;
    settings.Counter++;
    settings.Save();
    Response.Redirect("~/SingletonDefault.aspx");
}

SingletonStateObject и StateObject являются базовыми классами для любого объекта состояния. Оба этих класса работают с реализаторами IStorage<T>.

Интерфейс IStorage определяет контракт для сохранения, возврата и удаления данных из хранилища.

 

Реализаторы IStorage

Реализаторы StateObject, SingletonStateObject и IStorage демонстрируют шаблон проектирования "Мост", который позволяет определять ваш собственный объект состояния в более гибкой манере. В зависимости от ваших нужд, вы можете использовать конкретный реализатор хранилища или же реализовать собственный.

  • Используйте CookieStorage в случае, если вам необходимо сохранять малые объемы данных на клиенте, при этом проблемы с безопасностью не рассматриваются как возможные.
  • Используйте ApplicationStorage в случае, если вам необходимо хранить небольшие объемы данных, которые будут часто изменяться и доступны различным пользователям.
  • Используйте SessionStorage в том случае, если вам необходимо хранить кратковременную информацию, специфическую для индивидуальной сессии, при этом проблемы с безопасностью не рассматриваются как возможные. Не храните большие объемы информации в объекте состояния сессии. Будьте внимательны - объект состояния сессии будет создан и сохранен на весь жизненный цикл каждой сессии в вашем приложении. В приложениях, содержащих множество пользователей, это может занимать значительные серверные ресурсы и повлиять на масштабируемость.
  • Используйте DoaStorage тогда, когда вам необходимо хранить большие объемы информации при управлении транзакциями или же когда информация должна пережить перезапуски приложений и сессий, а безопасность необходимо учитывать.

Реализация DaoStorage

Давайте вернемся к объекту ApplicationSettings. Что если требования заставят нас изменить хранилище, а ApplicationSettings уже не будет одноэлементым объектом? Требования принуждают хранилище приспособиться к перезапускам Application (приложения) и Session (сессий), поэтому нам необходимо использовать базу данных для сохранения состояния. Существует таблица Heap ("куча") , которая имеет колонки Key ("ключ") и Value ("значения") в базе данных MS Access, которая доступна вместе с прикрепленным к данной статье исходным кодом. Все что нам необходимо выполнить, так это сериализовать объект и сохранить его, используя предоставленный ключ.

public class DaoStorage<T> : IStorage<T> where T : class
{

    public void Save(object key, T obj)
    {
        string value = Serializer<T>.Serialize(obj);

        using (OleDbConnection connection =
               new OleDbConnection(Config.ConnectionString))
        {
            connection.Open();
            if (IsExist(connection, key))
                Update(connection, key, value);
            else
                Insert(connection, key, value);
        }
    }


    public T Get(object key)
    {
        using (OleDbConnection connection =
               new OleDbConnection(Config.ConnectionString))
        {
            connection.Open();
            string obj = Get(connection, key);
            return Serializer<T>.Deserialize(obj);
        }
    }

    public void Delete(object key)
    {
        using (OleDbConnection connection =
               new OleDbConnection(Config.ConnectionString))
        {
            connection.Open();
            Delete(connection, key);
        }
    }


    public List<T> GetAll(Type type)
    {
        using (OleDbConnection connection =
               new OleDbConnection(Config.ConnectionString))
        {
            connection.Open();
            return GetAll(connection);
        }
    }


    public void Clear()
    {
        using (OleDbConnection connection =
               new OleDbConnection(Config.ConnectionString))
        {
            connection.Open();
            Clear(connection);
        }
    }
....

В данном классе мы используем XmlSerialazer для занесения объектов в базу данных, поэтому нам необходимо помнить о том, что ApplicationSettigs должно быть XmlSerializable. Давайте модифицируем класс ApplicationSettings:

public class ApplicationSettings : StateObject<ApplicationSettings, 
             DaoStorage<ApplicationSettings>>
{
    private string _hostName;
    private int _counter;


    public int Counter
    {
        get { return _counter; }
        set { _counter = value; }
    }

    public string HostName
    {
        get { return _hostName; }
        set { _hostName = value; }
    }
}

Теперь класс ApplicationSettings не является одноэлементным, а также не зависит от перезагрузок ApplicationSession (сессий). Вместо использования HttpSessionState или HttpApplicationState он сериализует свое состояние в базу данных. (приложения) и

Масштабирование

Использование HttpSessionState обладает некоторыми проблемами производительности, а использование файлов куки страдает от проблем с безопасностью. Существует множество других альтернативных хранилищ, которые вы можете использовать, но если вам необходимо иметь настраиваемую стратегию хранения для группы объектов - что делать? Давайте разработаем ConfigurableStorage , который позволяет нам определить стратегию хранения исходя из конфигурации.

public class ConfigurableStorage<T> : IStorage<T> where T : class
{
    private readonly IStorage<T> _storage = ResolveStorage();

    private static IStorage<T> ResolveStorage()
    {
        switch (Config.StorageType)
        {
            case StorageType.AppicationStorage:
                return new ApplicationStorage<T>();
            case StorageType.SessionStorage:
                return new SessionStorage<T>();
            case StorageType.FileStorage:
                return new FileStorage<T>();
            case StorageType.DaoStorage:
                return new DaoStorage<T>();
            case StorageType.CookieStorage:
                return new CookieStorage<T>();
            default:
                throw new ApplicationException("StorageType");
        }
    }

    public void Save(object key, T obj)
    {
        _storage.Save(key, obj);
    }

    public T Get(object key)
    {
        return _storage.Get(key);
    }
....

Как вы могли убедиться, ConfigurableStorage является простым классом, который создает другой тип хранилища и использует его для сохранения состояний объектов. Он также демонстрирует метод Proxy. Давайте изменим класс: ApplicationSettings

public class ApplicationSettings : StateObject<ApplicationSettings, 
             ConfigurableStorage<ApplicationSettings>>
{
    private string _hostName;
    private int _counter;


    public int Counter
    {
        get { return _counter; }
        set { _counter = value; }
    }

    public string HostName
    {
        get { return _hostName; }
        set { _hostName = value; }
    }
}

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

Автор: Andrew Golik

Загрузить исходный код - 738 KB