Устойчивый контекст экземпляраDurable Instance Context

В этом примере демонстрируется настройка среды выполнения Windows Communication Foundation (WCF) для включения устойчивых контекстов экземпляра.This sample demonstrates how to customize the Windows Communication Foundation (WCF) runtime to enable durable instance contexts. В качестве резервного хранилища в этом примере используется SQL Server 2005, а именно SQL Server 2005 Express.It uses SQL Server 2005 as its backing store (SQL Server 2005 Express in this case). Этот сервер также предоставляет возможность доступа к пользовательским механизмам хранения.However, it also provides a way to access custom storage mechanisms.

Примечание

Процедура настройки и инструкции по построению для данного образца приведены в конце этого раздела.The setup procedure and build instructions for this sample are located at the end of this topic.

Этот пример включает расширение уровня канала и уровня модели службы WCF.This sample involves extending both the channel layer and the service model layer of the WCF. Поэтому прежде чем перейти к реализации, необходимо ознакомиться с основными понятиями.Therefore it is necessary to understand the underlying concepts before going into the implementation details.

Устойчивые контексты экземпляров довольно часто встречаются в реальных сценариях.Durable instance contexts can be found in the real world scenarios quite often. Например, приложение покупательской корзины может приостанавливать покупки на посередине по и продолжать работу в течение другого дня.A shopping cart application, for example, has the ability to pause shopping halfway through and continue it on another day. Таким образом, при обращении к покупательской корзине на следующий день восстанавливается исходный контекст.So that when we visit the shopping cart the next day, our original context is restored. Важно отметить, что приложение (на сервере) не хранит экземпляр покупательской корзины, когда пользователь отключен от приложения.It is important to note that the shopping cart application (on the server) does not maintain the shopping cart instance while we are disconnected. Вместо этого оно сохраняет его состояние на устойчивом носителе, который затем используется, чтобы создать новый экземпляр для восстановленного контекста.Instead, it persists its state into a durable storage media and uses it when constructing a new instance for the restored context. Поэтому экземпляр службы, используемый для того же контекста, отличается от предыдущего экземпляра (т. е. имеет другой адрес памяти).Therefore the service instance that may service for the same context is not the same as the previous instance (that is, it does not have the same memory address).

Устойчивый контекст экземпляра обеспечивается несложным протоколом, с помощью которого выполняется обмен ИД контекста между клиентом и службой.Durable instance context is made possible by a small protocol that exchanges a context ID between the client and service. Этот ИД контекста создается на клиенте и передается службе.This context ID is created on the client and transmitted to the service. При создании экземпляра службы среда выполнения службы пытается загрузить сохраненное состояние, соответствующее данному ИД контекста, из постоянного хранилища (которым по умолчанию является база данных SQL Server 2005).When the service instance is created, the service runtime tries to load the persisted state that corresponds to this context ID from a persistent storage (by default it is a SQL Server 2005 database). Если состояние недоступно, новый экземпляр имеет состояние по умолчанию.If no state is available, the new instance has its default state. Реализация службы использует пользовательский атрибут для отметки операций, изменяющих состояние реализации службы, чтобы среда выполнения могла сохранять экземпляр службы после выполнения этих операций.The service implementation uses a custom attribute to mark operations that change the state of the service implementation so that the runtime can save the service instance after invoking them.

Для достижения этой цели необходимо выполнить два действия:By the previous description, two steps can easily be distinguished to achieve the goal:

  1. изменить сообщение, передаваемое по линии связи, включив в него ИД контекста;Change the message that goes on the wire to carry the context ID.

  2. изменить локальное поведение службы для реализации пользовательской логики создания экземпляров.Change the service local behavior to implement custom instancing logic.

Поскольку первая из них в списке влияет на сообщения в сети, она должна быть реализована как пользовательский канал и подключена к уровню канала.Because the first one in the list affects the messages on the wire, it should be implemented as a custom channel and be hooked up to the channel layer. Последнее действие влияет только на локальное поведение службы и поэтому может быть реализовано путем расширения нескольких точек расширяемости службы.The latter only affects the service local behavior and therefore can be implemented by extending several service extensibility points. Каждое из таких расширений рассматривается в следующих разделах.In the next few sections, each of these extensions are discussed.

Устойчивый канал InstanceContextDurable InstanceContext Channel

Прежде всего необходимо обратить внимание на расширение уровня канала.The first thing to look at is a channel layer extension. Первый этап создания пользовательского канала - определение структуры взаимодействия канала.The first step in writing a custom channel is to decide the communication structure of the channel. После того как появился новый протокол передачи, канал должен работать почти с любым другим каналом в стеке каналов.As a new wire protocol is being introduced, the channel should work with almost any other channel in the channel stack. Поэтому он должен поддерживать все шаблоны обмена сообщениями.Therefore it should support all the message exchange patterns. Однако основная функциональность канала остается прежней несмотря на его структуру взаимодействия.However, the core functionality of the channel is the same regardless of its communication structure. Конкретнее, на стороне клиента он должен записывать ИД контекста в сообщения, а на стороне службы он должен считывать этот ИД контекста из сообщений и передавать его на верхние уровни.More specifically, from the client it should write the context ID to the messages and from the service it should read this context ID from the messages and pass it to the upper levels. Поэтому создается класс DurableInstanceContextChannelBase, являющийся абстрактным базовым классом для всех реализаций канала устойчивого контекста экземпляра.Because of that, a DurableInstanceContextChannelBase class is created that acts as the abstract base class for all durable instance context channel implementations. Этот класс содержит функции управления на основе общего конечного автомата и два защищенных члена для применения сведений контекста к сообщениям и чтения этих сведений из них.This class contains the common state machine management functions and two protected members to apply and read the context information to and from messages.

class DurableInstanceContextChannelBase
{
  //…
  protected void ApplyContext(Message message)
  {
    //…
  }
  protected string ReadContextId(Message message)
  {
    //…
  }
}

Эти два метода используют реализации IContextManager для записи ИД контекста в сообщения и чтения его из них.These two methods make use of IContextManager implementations to write and read the context ID to or from the message. ( IContextManager — это пользовательский интерфейс, используемый для определения контракта для всех диспетчеров контекста.) Канал может либо включать идентификатор контекста в пользовательский заголовок SOAP, либо в заголовок HTTP cookie.(IContextManager is a custom interface used to define the contract for all context managers.) The channel can either include the context ID in a custom SOAP header or in an HTTP cookie header. Каждая реализация диспетчера контекста наследуется от класса ContextManagerBase, в котором содержатся общие функции для всех диспетчеров контекста.Each context manager implementation inherits from the ContextManagerBase class that contains the common functionality for all context managers. Метод GetContextId в этом классе используется для получения ИД контекста от клиента.The GetContextId method in this class is used to originate the context ID from the client. При первом получении ИД контекста этот метод сохраняет его в текстовый файл, имя которого создается на основе адреса удаленной конечной точки (недопустимые символы имени файла в типичных URI заменяются символами "@").</span><span class="sxs-lookup">When a context ID is originated for the first time, this method saves it into a text file whose name is constructed by the remote endpoint address (the invalid file name characters in the typical URIs are replaced with @ characters).

Затем при запросе ИД контекста той же удаленной конечной точкой этот метод проверяет, существует ли соответствующий файл.Later when the context ID is required for the same remote endpoint, it checks whether an appropriate file exists. Если файл существует, метод считывает и возвращает ИД контекста.If it does, it reads the context ID and returns. В противном случае он возвращает вновь созданный ИД контекста и сохраняет его в файл.Otherwise it returns a newly generated context ID and saves it to a file. В конфигурации по умолчанию эти файлы помещаются в каталог с именем Контекстсторе, который находится в каталоге Temp текущего пользователя.With the default configuration, these files are placed in a directory called ContextStore, which resides in the current user's temp directory. Однако это расположение настраивается с помощью элемента привязки.However this location is configurable using the binding element.

Механизм, используемый для передачи ИД контекста, является настраиваемым.The mechanism used to transport the context ID is configurable. Он может быть записан в заголовок файла cookie HTTP или в пользовательский заголовок SOAP.It could be either written to the HTTP cookie header or to a custom SOAP header. В случае подхода на основе пользовательского заголовка SOAP можно использовать этот протокол с протоколами, отличными от HTTP (например, с протоколом TCP или протоколом именованных каналов).The custom SOAP header approach makes it possible to use this protocol with non-HTTP protocols (for example, TCP or Named Pipes). Эти две возможности реализуют два класса - MessageHeaderContextManager и HttpCookieContextManager.There are two classes, namely MessageHeaderContextManager and HttpCookieContextManager, which implement these two options.

Оба класса позволяют записывать ИД контекста в сообщение соответствующим образом.Both of them write the context ID to the message appropriately. Например, класс MessageHeaderContextManager позволяет записать его в заголовок SOAP с помощью метода WriteContext.For example, the MessageHeaderContextManager class writes it to a SOAP header in the WriteContext method.

public override void WriteContext(Message message)
{
  string contextId = this.GetContextId();

  MessageHeader contextHeader =
    MessageHeader.CreateHeader(DurableInstanceContextUtility.HeaderName,
      DurableInstanceContextUtility.HeaderNamespace,
      contextId,
      true);

  message.Headers.Add(contextHeader);
}

Методы ApplyContext и ReadContextId класса DurableInstanceContextChannelBase вызывают IContextManager.ReadContext и IContextManager.WriteContext соответственно.Both the ApplyContext and ReadContextId methods in the DurableInstanceContextChannelBase class invoke the IContextManager.ReadContext and IContextManager.WriteContext, respectively. Однако эти диспетчеры контекста не создаются непосредственно с помощью класса DurableInstanceContextChannelBase.However, these context managers are not directly created by the DurableInstanceContextChannelBase class. Для выполнения этой задачи используется класс ContextManagerFactory.Instead it uses the ContextManagerFactory class to do that job.

IContextManager contextManager =
                ContextManagerFactory.CreateContextManager(contextType,
                this.contextStoreLocation,
                this.endpointAddress);

Метод ApplyContext вызывается отправляющими каналами.The ApplyContext method is invoked by the sending channels. Он вставляет ИД контекста в исходящие сообщения.It injects the context ID to the outgoing messages. Метод ReadContextId вызывается получающими каналами.The ReadContextId method is invoked by the receiving channels. Этот метод обеспечивает доступность ИД контекста во входящих сообщениях и добавляет его в коллекцию Properties класса Message.This method ensures that the context ID is available in the incoming messages and adds it to the Properties collection of the Message class. Кроме того, в случае ошибки чтения ИД контекста метод вызывает исключение CommunicationException, что приводит к прерыванию работы канала.It also throws a CommunicationException in case of a failure to read the context ID and thus causes the channel to be aborted.

message.Properties.Add(DurableInstanceContextUtility.ContextIdProperty, contextId);

Прежде чем продолжить, важно понять принцип использования коллекции Properties в классе Message.Before proceeding, it is important to understand the usage of the Properties collection in the Message class. Как правило, коллекция Properties используется при передаче данных с нижнего на верхние уровни канала.Typically, this Properties collection is used when passing data from lower to the upper levels from the channel layer. Таким образом требуемые данные можно согласованно передавать на верхние уровни независимо от подробностей работы протокола.This way the desired data can be provided to the upper levels in a consistent manner regardless of the protocol details. Иными словами, уровень канала может отправлять и получать идентификатор контекста как заголовок SOAP или заголовок HTTP cookie.In other words, the channel layer can send and receive the context ID either as a SOAP header or an HTTP cookie header. Однако знание этих подробностей на верхних уровнях не требуется, поскольку уровень канала делает эту информацию доступной в коллекции Properties.But it is not necessary for the upper levels to know about these details because the channel layer makes this information available in the Properties collection.

Теперь, при наличии класса DurableInstanceContextChannelBase, следует реализовать все десять необходимых интерфейсов (IOutputChannel, IInputChannel, IOutputSessionChannel, IInputSessionChannel, IRequestChannel, IReplyChannel, IRequestSessionChannel, IReplySessionChannel, IDuplexChannel, IDuplexSessionChannel).Now with the DurableInstanceContextChannelBase class in place all ten of the necessary interfaces (IOutputChannel, IInputChannel, IOutputSessionChannel, IInputSessionChannel, IRequestChannel, IReplyChannel, IRequestSessionChannel, IReplySessionChannel, IDuplexChannel, IDuplexSessionChannel) must be implemented. Они похожи на каждый доступный шаблон обмена сообщениями (Datagram, симплексная, дуплексный и их варианты сеансов).They resemble every available message exchange pattern (datagram, simplex, duplex, and their sessionful variants). Каждая из этих реализаций наследует базовый класс, описанный ранее, и вызывает ApplyContext и ReadContextId соответствующим образом.Each of these implementations inherits the base class previously described and calls ApplyContext and ReadContextId appropriately. Например, канал DurableInstanceContextOutputChannel, реализующий интерфейс IOutputChannel, вызывает метод ApplyContext для каждого метода, который отправляет сообщения.For example, DurableInstanceContextOutputChannel - which implements the IOutputChannel interface - calls the ApplyContext method from each method that sends the messages.

public void Send(Message message, TimeSpan timeout)
{
    // Apply the context information before sending the message.
    this.ApplyContext(message);
    //…
}

С другой стороны, DurableInstanceContextInputChannel —, реализующий IInputChannel интерфейс, вызывает ReadContextId метод в каждом методе, который получает сообщения.On the other hand, DurableInstanceContextInputChannel - which implements the IInputChannel interface - calls the ReadContextId method in each method, which receives the messages.

public Message Receive(TimeSpan timeout)
{
    //…
      ReadContextId(message);
      return message;
}

Кроме того, эти реализации каналов делегируют вызовы методов каналу, расположенному ниже них в стеке каналов.Apart from this, these channel implementations delegate the method invocations to the channel below them in the channel stack. Однако у связанных с сеансами разновидностей имеется базовая логика, обеспечивающая отправку и чтение ИД контекста только для первого сообщения, которое приводит к созданию сеанса.However, sessionful variants have a basic logic to make sure that the context ID is sent and is read only for the first message that causes the session to be created.

if (isFirstMessage)
{
//…
    this.ApplyContext(message);
    isFirstMessage = false;
}

Эти реализации каналов затем добавляются в среду выполнения канала WCF DurableInstanceContextBindingElement классом и DurableInstanceContextBindingElementSection классом соответствующим образом.These channel implementations are then added to the WCF channel runtime by the DurableInstanceContextBindingElement class and DurableInstanceContextBindingElementSection class appropriately. Дополнительные сведения об элементах привязки и разделах привязки элементов см. в образце документации по каналу хттпкукиесессион .See the HttpCookieSession channel sample documentation for more details about binding elements and binding element sections.

Расширения уровня модели службService Model Layer Extensions

Теперь, когда ИД контекста прошел через уровень канала, можно реализовать поведение службы для настройки создания экземпляров.Now that the context ID has traveled through the channel layer, the service behavior can be implemented to customize the instantiation. В этом образце диспетчер хранилища используется для загрузки состояния из постоянного хранилища, а также сохранения состояния в него.In this sample, a storage manager is used to load and save state from or to the persistent store. Как отмечалось ранее, этот образец предоставляет диспетчер хранилища SQL Server 2005 в качестве резервного хранилища.As explained previously, this sample provides a storage manager that uses SQL Server 2005 as its backing store. Однако также можно добавить к этому расширению пользовательские механизмы хранения.However, it is also possible to add custom storage mechanisms to this extension. Для этого объявляется открытый интерфейс, который должен быть реализован всеми диспетчерами хранилища.To do that a public interface is declared, which must be implemented by all storage managers.

public interface IStorageManager
{
    object GetInstance(string contextId, Type type);
    void SaveInstance(string contextId, object state);
}

Класс SqlServerStorageManager содержит реализацию IStorageManager по умолчанию.The SqlServerStorageManager class contains the default IStorageManager implementation. В своем SaveInstance методе данный объект сериализуется с помощью XmlSerializer и сохраняется в базе данных SQL Server.In its SaveInstance method, the given object is serialized using the XmlSerializer and is saved to the SQL Server database.

XmlSerializer serializer = new XmlSerializer(state.GetType());
string data;

using (StringWriter writer = new StringWriter(CultureInfo.InvariantCulture))
{
    serializer.Serialize(writer, state);
    data = writer.ToString();
}

using (SqlConnection connection = new SqlConnection(GetConnectionString()))
{
    connection.Open();

    string update = @"UPDATE Instances SET Instance = @instance WHERE ContextId = @contextId";

    using (SqlCommand command = new SqlCommand(update, connection))
    {
        command.Parameters.Add("@instance", SqlDbType.VarChar, 2147483647).Value = data;
        command.Parameters.Add("@contextId", SqlDbType.VarChar, 256).Value = contextId;

        int rows = command.ExecuteNonQuery();

        if (rows == 0)
        {
            string insert = @"INSERT INTO Instances(ContextId, Instance) VALUES(@contextId, @instance)";
            command.CommandText = insert;
            command.ExecuteNonQuery();
        }
    }
}

В GetInstance методе сериализованные данные считываются для заданного идентификатора контекста, а объект, созданный из него, возвращается вызывающему объекту.In the GetInstance method, the serialized data is read for a given context ID and the object constructed from it is returned to the caller.

object data;
using (SqlConnection connection = new SqlConnection(GetConnectionString()))
{
    connection.Open();

    string select = "SELECT Instance FROM Instances WHERE ContextId = @contextId";
    using (SqlCommand command = new SqlCommand(select, connection))
    {
        command.Parameters.Add("@contextId", SqlDbType.VarChar, 256).Value = contextId;
        data = command.ExecuteScalar();
    }
}

if (data != null)
{
    XmlSerializer serializer = new XmlSerializer(type);
    using (StringReader reader = new StringReader((string)data))
    {
        object instance = serializer.Deserialize(reader);
        return instance;
    }
}

Пользователи этих диспетчеров хранилища не должны создавать их экземпляры непосредственно.Users of these storage managers are not supposed to instantiate them directly. Они используют класс StorageManagerFactory, абстрагирующий их от подробностей создания диспетчера хранилища.They use the StorageManagerFactory class, which abstracts from the storage manager creation details. В этом классе имеется один статический член, GetStorageManager, который создает экземпляр заданного типа диспетчера хранилища.This class has one static member, GetStorageManager, which creates an instance of a given storage manager type. Если параметр типа имеет значение null, этот метод создает и возвращает экземпляр класса по умолчанию SqlServerStorageManager.If the type parameter is null, this method creates an instance of the default SqlServerStorageManager class and returns it. Он также проверяет, что заданный тип реализует интерфейс IStorageManager.It also validates the given type to make sure that it implements the IStorageManager interface.

public static IStorageManager GetStorageManager(Type storageManagerType)
{
IStorageManager storageManager = null;

if (storageManagerType == null)
{
    return new SqlServerStorageManager();
}
else
{
    object obj = Activator.CreateInstance(storageManagerType);

    // Throw if the specified storage manager type does not
    // implement IStorageManager.
    if (obj is IStorageManager)
    {
        storageManager = (IStorageManager)obj;
    }
    else
    {
        throw new InvalidOperationException(
                  ResourceHelper.GetString("ExInvalidStorageManager"));
    }

    return storageManager;
}
}

Инфраструктура, необходимая для чтения и записи экземпляров из постоянного хранилища, реализована.The necessary infrastructure to read and write instances from the persistent storage is implemented. Теперь следует выполнить необходимые шаги по изменению поведения службы.Now the necessary steps to change the service behavior have to be taken.

На первом шаге этого процесса нужно сохранить ИД контекста, поступивший через уровень канала в текущий контекст InstanceContext.As the first step of this process we have to save the context ID, which came through the channel layer to the current InstanceContext. InstanceContext — это компонент среды выполнения, который выступает в качестве связи между диспетчером WCF и экземпляром службы.InstanceContext is a runtime component that acts as the link between the WCF dispatcher and the service instance. Его можно использовать для предоставления дополнительного состояния и поведения экземпляру службы.It can be used to provide additional state and behavior to the service instance. Это важно, поскольку в связанном с сеансами взаимодействии ИД контекста отправляется только с первым сообщением.This is essential because in sessionful communication the context ID is sent only with the first message.

WCF позволяет расширять компонент среды выполнения InstanceContext, добавляя новое состояние и поведение с помощью шаблона расширяемых объектов.WCF allows extending its InstanceContext runtime component by adding a new state and behavior using its extensible object pattern. Шаблон расширяемого объекта используется в WCF для расширения существующих классов среды выполнения с новыми функциональными возможностями или для добавления новых функций состояния в объект.The extensible object pattern is used in WCF to either extend existing runtime classes with new functionality or to add new state features to an object. В расширяемых шаблонах объектов-IExtensibleObject <T> , IExtension и иекстенсионколлектион есть три интерфейса <T> <T> :There are three interfaces in the extensible object pattern - IExtensibleObject<T>, IExtension<T>, and IExtensionCollection<T>:

  • Интерфейс IExtensibleObject <T> реализуется объектами, разрешающими расширения, которые настраивают их функциональность.The IExtensibleObject<T> interface is implemented by objects that allow extensions that customize their functionality.

  • Интерфейс IExtension <T> реализуется объектами, которые являются расширениями классов типа T.The IExtension<T> interface is implemented by objects that are extensions of classes of type T.

  • Интерфейс Иекстенсионколлектион <T> — это коллекция иекстенсионс, которая позволяет извлекать иекстенсионс по типу.The IExtensionCollection<T> interface is a collection of IExtensions that allows for retrieving IExtensions by their type.

Таким образом, необходимо создать класс InstanceContextExtension, реализующий интерфейс IExtension и определяющий требуемое состояние для сохранения идентификатора контекста.Therefore an InstanceContextExtension class should be created that implements the IExtension interface and defines the required state to save the context ID. Этот класс также предоставляет состояние для фиксации используемого диспетчера хранилища.This class also provides the state to hold the storage manager being used. После сохранения нового состояния его должно быть невозможно изменить.Once the new state is saved, it should not be possible to modify it. Поэтому состояние предоставляется и сохраняется в экземпляре в момент его создания, после чего доступ к нему осуществляется только с помощью свойств, доступных только для чтения.Therefore the state is provided and saved to the instance at the time it is being constructed and then only accessible using read-only properties.

// Constructor
public DurableInstanceContextExtension(string contextId,
            IStorageManager storageManager)
{
    this.contextId = contextId;
    this.storageManager = storageManager;
}

// Read only properties
public string ContextId
{
    get { return this.contextId; }
}

public IStorageManager StorageManager
{
    get { return this.storageManager; }
}

Класс InstanceContextInitializer реализует интерфейс IInstanceContextInitializer и добавляет расширение контекста экземпляра в коллекцию Extensions создаваемого контекста InstanceContext.The InstanceContextInitializer class implements the IInstanceContextInitializer interface and adds the instance context extension to the Extensions collection of the InstanceContext being constructed.

public void Initialize(InstanceContext instanceContext, Message message)
{
    string contextId =
  (string)message.Properties[DurableInstanceContextUtility.ContextIdProperty];

    DurableInstanceContextExtension extension =
                new DurableInstanceContextExtension(contextId,
                     storageManager);
    instanceContext.Extensions.Add(extension);
}

Как отмечалось ранее, ИД контекста считывается из коллекции Properties класса Message и передается конструктору класса расширения.As described earlier the context ID is read from the Properties collection of the Message class and passed to the constructor of the extension class. Это демонстрирует, как можно согласованно обмениваться информацией между уровнями.This demonstrates how information can be exchanged between the layers in a consistent manner.

Следующий важный шаг - переопределение процесса создания экземпляров службы.The next important step is overriding the service instance creation process. WCF позволяет реализовать поведение пользовательских экземпляров и присоединить их к среде выполнения с помощью интерфейса Иинстанцепровидер.WCF allows implementing custom instantiation behaviors and hooking them up to the runtime using the IInstanceProvider interface. Для выполнения этой задачи реализуется новый класс InstanceProvider.The new InstanceProvider class is implemented to do that job. Тип службы, ожидаемый от поставщика экземпляра, принимается в конструкторе.The service type expected from the instance provider is accepted in the constructor. Затем он используется для создания новых экземпляров.Later this is used to create new instances. В GetInstance реализации создается экземпляр диспетчера хранилища, который ищет сохраняемый экземпляр.In the GetInstance implementation, an instance of a storage manager is created looking for a persisted instance. Если он возвращает null , создается новый экземпляр типа службы, который возвращается вызывающему объекту.If it returns null, then a new instance of the service type is instantiated and returned to the caller.

public object GetInstance(InstanceContext instanceContext, Message message)
{
    object instance = null;

    DurableInstanceContextExtension extension =
    instanceContext.Extensions.Find<DurableInstanceContextExtension>();

    string contextId = extension.ContextId;
    IStorageManager storageManager = extension.StorageManager;

    instance = storageManager.GetInstance(contextId, serviceType);

    instance ??= Activator.CreateInstance(serviceType);
    return instance;
}

Следующим важным шагом является установка InstanceContextExtension InstanceContextInitializer классов, и InstanceProvider в среду выполнения модели службы.The next important step is to install the InstanceContextExtension, InstanceContextInitializer, and InstanceProvider classes into the service model runtime. С помощью пользовательского атрибута можно отметить классы реализации службы для установки поведения.A custom attribute could be used to mark the service implementation classes to install the behavior. DurableInstanceContextAttribute содержит реализацию этого атрибута и реализует интерфейс IServiceBehavior для расширения всей среды выполнения службы.The DurableInstanceContextAttribute contains the implementation for this attribute and it implements the IServiceBehavior interface to extend the entire service runtime.

В этом классе имеется свойство, принимающее тип используемого диспетчера хранилища.This class has a property that accepts the type of the storage manager to be used. Таким образом, реализация позволяет пользователям указать собственную IStorageManager реализацию в качестве параметра этого атрибута.In this way, the implementation enables the users to specify their own IStorageManager implementation as parameter of this attribute.

В ApplyDispatchBehavior реализации выполняется InstanceContextMode ServiceBehavior Проверка текущего атрибута.In the ApplyDispatchBehavior implementation, the InstanceContextMode of the current ServiceBehavior attribute is being verified. Если это свойство имеет значение Singleton, устойчивое создание экземпляров невозможно и для уведомления узла создается исключение InvalidOperationException.If this property is set to Singleton, enabling durable instancing is not possible and an InvalidOperationException is thrown to notify the host.

ServiceBehaviorAttribute serviceBehavior =
    serviceDescription.Behaviors.Find<ServiceBehaviorAttribute>();

if (serviceBehavior != null &&
     serviceBehavior.InstanceContextMode == InstanceContextMode.Single)
{
    throw new InvalidOperationException(
       ResourceHelper.GetString("ExSingletonInstancingNotSupported"));
}

После этого экземпляры диспетчера хранилища, инициализатор контекста экземпляра и поставщик экземпляров создаются и устанавливаются в объекте DispatchRuntime, созданном для каждой конечной точки.After this the instances of the storage manager, instance context initializer, and the instance provider are created and installed in the DispatchRuntime created for every endpoint.

IStorageManager storageManager =
    StorageManagerFactory.GetStorageManager(storageManagerType);

InstanceContextInitializer contextInitializer =
    new InstanceContextInitializer(storageManager);

InstanceProvider instanceProvider =
    new InstanceProvider(description.ServiceType);

foreach (ChannelDispatcherBase cdb in serviceHostBase.ChannelDispatchers)
{
    ChannelDispatcher cd = cdb as ChannelDispatcher;

    if (cd != null)
    {
        foreach (EndpointDispatcher ed in cd.Endpoints)
        {
            ed.DispatchRuntime.InstanceContextInitializers.Add(contextInitializer);
            ed.DispatchRuntime.InstanceProvider = instanceProvider;
        }
    }
}

Итак, на данный момент создан канал, который обеспечивает пользовательский протокол передачи данных по линии связи для обмена пользовательским ИД контекста и переопределяет поведение создания экземпляров по умолчанию для загрузки экземпляров из постоянного хранилища.In summary so far, this sample has produced a channel that enabled the custom wire protocol for custom context ID exchange and it also overwrites the default instancing behavior to load the instances from the persistent storage.

Остается найти способ сохранения экземпляра службы в хранилище сохраняемости.What is left is a way to save the service instance to the persistent storage. Как отмечалось ранее, уже имеется требуемая функциональность для сохранения состояния в реализации IStorageManager.As discussed previously, there is already the required functionality to save the state in an IStorageManager implementation. Теперь это необходимо интегрировать со средой выполнения WCF.We now must integrate this with the WCF runtime. Требуется другой атрибут, применимый к методам в классе реализации службы.Another attribute is required that is applicable to the methods in the service implementation class. Этот атрибут должен применяться к методам, изменяющим состояние экземпляра службы.This attribute is supposed to be applied to the methods that change the state of the service instance.

Класс SaveStateAttribute реализует данную функциональность.The SaveStateAttribute class implements this functionality. Он также реализует IOperationBehavior класс для изменения среды выполнения WCF для каждой операции.It also implements IOperationBehavior class to modify the WCF runtime for each operation. Если метод помечен с помощью этого атрибута, среда выполнения WCF вызывает ApplyBehavior метод при DispatchOperation создании соответствующего.When a method is marked with this attribute, the WCF runtime invokes the ApplyBehavior method while the appropriate DispatchOperation is being constructed. В реализации этого метода существует одна строка кода:There is a single line of code in this method implementation:

dispatch.Invoker = new OperationInvoker(dispatch.Invoker);

Эта инструкция создает экземпляр типа OperationInvoker и присваивает его свойству Invoker создаваемого объекта DispatchOperation.This instruction creates an instance of OperationInvoker type and assigns it to the Invoker property of the DispatchOperation being constructed. Класс OperationInvoker является оболочкой для средства вызова операции по умолчанию, созданного для DispatchOperation.The OperationInvoker class is a wrapper for the default operation invoker created for the DispatchOperation. Этот класс реализует интерфейс IOperationInvoker .This class implements the IOperationInvoker interface. В Invoke реализации метода фактический вызов метода делегируется внутреннему вызывающему операциям.In the Invoke method implementation, the actual method invocation is delegated to the inner operation invoker. Однако перед возвратом результатов диспетчер хранилища в InstanceContext используется для сохранения экземпляра службы.However, before returning the results the storage manager in the InstanceContext is used to save the service instance.

object result = innerOperationInvoker.Invoke(instance,
    inputs, out outputs);

// Save the instance using the storage manager saved in the
// current InstanceContext.
InstanceContextExtension extension =
    OperationContext.Current.InstanceContext.Extensions.Find<InstanceContextExtension>();

extension.StorageManager.SaveInstance(extension.ContextId, instance);
return result;

Использование расширенияUsing the Extension

Расширения уровня канала и модели службы выполняются, и теперь их можно использовать в приложениях WCF.Both the channel layer and service model layer extensions are done and they can now be used in WCF applications. Службы должны добавить канал в стек каналов с помощью пользовательской привязки, а затем пометить классы реализации службы соответствующими атрибутами.Services must add the channel into the channel stack using a custom binding and then mark the service implementation classes with the appropriate attributes.

[DurableInstanceContext]
[ServiceBehavior(InstanceContextMode=InstanceContextMode.PerSession)]
public class ShoppingCart : IShoppingCart
{
//…
     [SaveState]
     public int AddItem(string item)
     {
         //…
     }
//…
 }

Клиентским приложениям необходимо добавить канал DurableInstanceContextChannel в стек каналов с помощью пользовательской привязки.Client applications must add the DurableInstanceContextChannel into the channel stack using a custom binding. Чтобы настроить канал декларативно в файле конфигурации, необходимо добавить раздел элемента привязки в коллекцию расширений элемента привязки.To configure the channel declaratively in the configuration file, the binding element section has to be added to the binding element extensions collection.

<system.serviceModel>
 <extensions>
   <bindingElementExtensions>
     <add name="durableInstanceContext"
type="Microsoft.ServiceModel.Samples.DurableInstanceContextBindingElementSection, DurableInstanceContextExtension, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null"/>
   </bindingElementExtensions>
 </extensions>
</system.serviceModel>

Теперь элемент привязки можно использовать с пользовательской привязкой аналогично другим стандартным элементам привязки.Now the binding element can be used with a custom binding just like other standard binding elements:

<bindings>
 <customBinding>
   <binding name="TextOverHttp">
     <durableInstanceContext contextType="HttpCookie"/>
     <reliableSession />
     <textMessageEncoding />
     <httpTransport />
   </binding>
 </customBinding>
</bindings>

ЗаключениеConclusion

В этом образце показано, как создать пользовательский канал протокола и настроить поведение службы, чтобы задействовать этот канал.This sample showed how to create a custom protocol channel and how to customize the service behavior to enable it.

Расширение можно усовершенствовать, позволив пользователям указывать реализацию IStorageManager с помощью раздела конфигурации.The extension can be further improved by letting users specify the IStorageManager implementation using a configuration section. Это делает возможным изменение резервного хранилища без повторной компиляции кода службы.This makes it possible to modify the backing store without recompiling the service code.

Более того, можно попытаться реализовать класс (например, StateBag), инкапсулирующий состояние экземпляра.Furthermore you could try to implement a class (for example, StateBag), which encapsulates the state of the instance. Этот класс отвечает за сохранение состояния при каждом его изменении.That class is responsible for persisting the state whenever it changes. Таким образом можно избегать использования атрибута SaveState и более точно выполнять операции сохранения (например, можно сохранять состояние при его фактическом изменении, а не при каждом вызове метода с атрибутом SaveState).This way you can avoid using the SaveState attribute and perform the persisting work more accurately (for example, you could persist the state when the state is actually changed rather than saving it each time when a method with the SaveState attribute is called).

При выполнении образца получается следующий результат.When you run the sample, the following output is displayed. Клиент добавляет две единицы продукции в свою покупательскую корзину и получает список имеющихся в ней единиц продукции от службы.The client adds two items to its shopping cart and then gets the list of items in its shopping cart from the service. Нажмите клавишу ВВОД в каждом окне консоли, чтобы закрыть службу и клиент.Press ENTER in each console window to shut down the service and client.

Enter the name of the product: apples
Enter the name of the product: bananas

Shopping cart currently contains the following items.
apples
bananas
Press ENTER to shut down client

Примечание

Повторное построение службы приводит к перезаписи файла базы данных.Rebuilding the service overwrites the database file. Для просмотра состояния, сохраняемого между несколькими запусками образца, не следует выполнять повторная сборка образца между запусками.To observe state preserved across multiple runs of the sample, be sure not to rebuild the sample between runs.

Настройка, сборка и выполнение образцаTo set up, build, and run the sample

  1. Убедитесь, что вы выполнили однократную процедуру настройки для Windows Communication Foundation примеров.Ensure that you have performed the One-Time Setup Procedure for the Windows Communication Foundation Samples.

  2. Чтобы выполнить сборку решения, следуйте инструкциям в разделе Создание примеров Windows Communication Foundation.To build the solution, follow the instructions in Building the Windows Communication Foundation Samples.

  3. Чтобы запустить пример в конфигурации с одним или несколькими компьютерами, следуйте инструкциям в разделе выполнение примеров Windows Communication Foundation.To run the sample in a single- or cross-machine configuration, follow the instructions in Running the Windows Communication Foundation Samples.

Примечание

Для выполнения этого образца необходимо использовать SQL Server 2005 или SQL Express 2005.You must be running SQL Server 2005 or SQL Express 2005 to run this sample. При использовании SQL Server 2005 необходимо изменить конфигурацию строки подключения службы.If you are running SQL Server 2005, you must modify the configuration of the service's connection string. При выполнении примера на нескольких компьютерах использование SQL Server требуется только на компьютере-сервере.When running cross-machine, SQL Server is only required on the server machine.

Важно!

Образцы уже могут быть установлены на компьютере.The samples may already be installed on your machine. Перед продолжением проверьте следующий каталог (по умолчанию).Check for the following (default) directory before continuing.

<InstallDrive>:\WF_WCF_Samples

Если этот каталог не существует, перейдите к примерам Windows Communication Foundation (WCF) и Windows Workflow Foundation (WF) для .NET Framework 4 , чтобы скачать все Windows Communication Foundation (WCF) и WFWF примеры.If this directory does not exist, go to Windows Communication Foundation (WCF) and Windows Workflow Foundation (WF) Samples for .NET Framework 4 to download all Windows Communication Foundation (WCF) and WFWF samples. Этот образец расположен в следующем каталоге.This sample is located in the following directory.

<InstallDrive>:\WF_WCF_Samples\WCF\Extensibility\Instancing\Durable