Инспекторы сообщений

В примере MessageInspectors показано, как реализовать и настроить инспекторы сообщений клиента и службы.

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

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

Инспектор сообщений

Инспекторы сообщений клиента реализуют интерфейс IClientMessageInspector, а инспекторы сообщений службы реализуют интерфейс IDispatchMessageInspector. Реализации можно объединить в один класс, чтобы сформировать инспектор сообщений, который работает для обеих сторон. Этот пример реализует такой объединенный инспектор сообщений. Инспектор формируется в наборе схем, в отношении которых проверяются входящие и исходящие сообщения, и позволяет разработчику указать, проверяются ли входящие или исходящие сообщения и находится ли инспектор в режиме отправки или клиента, что влияет на обработку ошибок, как описано далее в этом разделе.

public class SchemaValidationMessageInspector : IClientMessageInspector, IDispatchMessageInspector
{
    XmlSchemaSet schemaSet;
    bool validateRequest;
    bool validateReply;
    bool isClientSide;
    [ThreadStatic]
    bool isRequest;

    public SchemaValidationMessageInspector(XmlSchemaSet schemaSet,
         bool validateRequest, bool validateReply, bool isClientSide)
    {
        this.schemaSet = schemaSet;
        this.validateReply = validateReply;
        this.validateRequest = validateRequest;
        this.isClientSide = isClientSide;
    }

Любой инспектор сообщений службы (диспетчер) должен реализовать два метода IDispatchMessageInspectorAfterReceiveRequest и BeforeSendReply(Message, Object).

AfterReceiveRequest вызывается диспетчером, когда сообщение получено, обработано стеком каналов и назначено службе, но до его десериализации и отправки на операцию. Если входящие сообщения были зашифрованы, сообщение уже расшифровывается при достижении инспектора сообщений. Метод получает request сообщение, переданное в качестве эталонного параметра, что позволяет проверять, манипулировать или заменять сообщение по мере необходимости. Возвращаемое значение может быть любым объектом и используется в качестве объекта состояния корреляции, который передается BeforeSendReply , когда служба возвращает ответ текущему сообщению. В этом примере AfterReceiveRequest делегирует проверку (проверку) сообщения частному, локальному методу ValidateMessageBody и не возвращает объект состояния корреляции. Этот метод гарантирует, что недопустимые сообщения не передаются в службу.

object IDispatchMessageInspector.AfterReceiveRequest(ref System.ServiceModel.Channels.Message request, System.ServiceModel.IClientChannel channel, System.ServiceModel.InstanceContext instanceContext)
{
    if (validateRequest)
    {
        // inspect the message. If a validation error occurs,
        // the thrown fault exception bubbles up.
        ValidateMessageBody(ref request, true);
    }
    return null;
}

BeforeSendReply(Message, Object) вызывается всякий раз, когда ответ готов к отправке клиенту, или в случае обработки входящего одностороннего сообщения, когда обработка завершена. Это позволяет дополнениям рассчитывать на симметричный вызов, независимо от MEP. Как и в случае AfterReceiveRequest, сообщение передается в качестве эталонного параметра и может быть проверено, изменено или заменено. Проверка сообщения, выполняемого в этом примере, снова делегируется ValidMessageBody методу, но обработка ошибок проверки немного отличается в этом случае.

Если в службе возникает ошибка проверки ValidateMessageBody, метод создает исключения, производные от FaultException. В AfterReceiveRequestэтом случае эти исключения можно поместить в инфраструктуру модели службы, где они автоматически преобразуются в ошибки SOAP и передаются клиенту. В BeforeSendReplyслучае исключения FaultException не должны быть помещены в инфраструктуру, так как преобразование исключений сбоя, создаваемых службой, происходит перед вызовом инспектора сообщений. Поэтому следующая реализация перехватывает известное ReplyValidationFault исключение и заменяет ответное сообщение явным сообщением об ошибке. Этот метод гарантирует, что недопустимые сообщения не будут возвращены реализацией службы.

void IDispatchMessageInspector.BeforeSendReply(ref System.ServiceModel.Channels.Message reply, object correlationState)
{
    if (validateReply)
    {
        // Inspect the reply, catch a possible validation error
        try
        {
            ValidateMessageBody(ref reply, false);
        }
        catch (ReplyValidationFault fault)
        {
            // if a validation error occurred, the message is replaced
            // with the validation fault.
            reply = Message.CreateMessage(reply.Version,
                    fault.CreateMessageFault(), reply.Headers.Action);
        }
    }

Инспектор сообщений клиента очень похож. Два метода, которые должны быть реализованы из IClientMessageInspector, — это AfterReceiveReply и BeforeSendRequest.

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

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

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

object IClientMessageInspector.BeforeSendRequest(ref System.ServiceModel.Channels.Message request, System.ServiceModel.IClientChannel channel)
{
    if (validateRequest)
    {
        ValidateMessageBody(ref request, true);
    }
    return null;
}

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

void IClientMessageInspector.AfterReceiveReply(ref System.ServiceModel.Channels.Message reply, object correlationState)
{
    if (validateReply)
    {
        ValidateMessageBody(ref reply, false);
    }
}

Сердцем этого конкретного инспектора сообщений является метод ValidateMessageBody. Для выполнения своей работы он упаковывает проверку XmlReader содержимого текста вложенное дерево переданного сообщения. Читатель инициализируется набором схем, который содержит инспектор сообщений, и устанавливается обратный вызов для проверки посредством делегата, ссылающегося на InspectionValidationHandler, определенный вместе с этим методом. Чтобы выполнить проверку, сообщение затем считывается и спулируется в поток памяти, поддерживаемый XmlDictionaryWriter. Если в процессе возникает ошибка проверки или предупреждение, вызывается метод обратного вызова.

Если ошибка не возникает, создается новое сообщение, которое копирует свойства и заголовки из исходного сообщения и использует проверенный набор сведений в потоке памяти, который упаковывается XmlDictionaryReader и добавляется в сообщение замены.

void ValidateMessageBody(ref System.ServiceModel.Channels.Message message, bool isRequest)
{
    if (!message.IsFault)
    {
        XmlDictionaryReaderQuotas quotas =
                new XmlDictionaryReaderQuotas();
        XmlReader bodyReader =
            message.GetReaderAtBodyContents().ReadSubtree();
        XmlReaderSettings wrapperSettings =
                              new XmlReaderSettings();
        wrapperSettings.CloseInput = true;
        wrapperSettings.Schemas = schemaSet;
        wrapperSettings.ValidationFlags =
                                XmlSchemaValidationFlags.None;
        wrapperSettings.ValidationType = ValidationType.Schema;
        wrapperSettings.ValidationEventHandler += new
           ValidationEventHandler(InspectionValidationHandler);
        XmlReader wrappedReader = XmlReader.Create(bodyReader,
                                            wrapperSettings);

        // pull body into a memory backed writer to validate
        this.isRequest = isRequest;
        MemoryStream memStream = new MemoryStream();
        XmlDictionaryWriter xdw =
              XmlDictionaryWriter.CreateBinaryWriter(memStream);
        xdw.WriteNode(wrappedReader, false);
        xdw.Flush(); memStream.Position = 0;
        XmlDictionaryReader xdr =
        XmlDictionaryReader.CreateBinaryReader(memStream, quotas);

        // reconstruct the message with the validated body
        Message replacedMessage =
            Message.CreateMessage(message.Version, null, xdr);
        replacedMessage.Headers.CopyHeadersFrom(message.Headers);
        replacedMessage.Properties.CopyProperties(message.Properties);
        message = replacedMessage;
    }
}

Метод InspectionValidationHandler вызывается проверяющим элементом XmlReader при возникновении ошибки или предупреждения при проверке схемы. Следующая реализация работает только с ошибками и игнорирует все предупреждения.

Во-первых, может показаться возможным внедрить проверку XmlReader в сообщение с инспектором сообщений и позволить проверке произойти по мере обработки сообщения и без буферизации сообщения. Однако это означает, что обратный вызов создает исключения проверки в инфраструктуре модели службы или пользовательском коде, так как обнаружены недопустимые XML-узлы, что приводит к непредсказуемому поведению. Подход буферизации полностью экранирует код пользователя от недопустимых сообщений.

Как упоминалось ранее, исключения, создаваемые обработчиком, отличаются между клиентом и службой. В службе исключения являются производными от FaultException, на клиенте исключения являются регулярными настраиваемыми исключениями.

        void InspectionValidationHandler(object sender, ValidationEventArgs e)
{
    if (e.Severity == XmlSeverityType.Error)
    {
        // We are treating client and service side validation errors
        // differently here. Client side errors cause exceptions
        // and are thrown straight up to the user code. Service side
        // validations cause faults.
        if (isClientSide)
        {
            if (isRequest)
            {
                throw new RequestClientValidationException(e.Message);
            }
            else
            {
                throw new ReplyClientValidationException(e.Message);
            }
        }
        else
        {
            if (isRequest)
            {
                // this fault is caught by the ServiceModel
                // infrastructure and turned into a fault reply.
                throw new RequestValidationFault(e.Message);
             }
             else
             {
                // this fault is caught and turned into a fault message
                // in BeforeSendReply in this class
                throw new ReplyValidationFault(e.Message);
              }
          }
      }
    }

Поведение

Инспекторы сообщений представляют собой расширения, добавляемые в клиентскую среду выполнения или среду выполнения диспетчера. Такие расширения настраиваются с помощью поведенческих шаблонов. Поведение — это класс, который изменяет поведение среды выполнения модели службы, изменив конфигурацию по умолчанию или добавив в него расширения (например, инспекторы сообщений).

SchemaValidationBehavior Следующий класс — это поведение, используемое для добавления инспектора сообщений этого примера в клиент или среду выполнения отправки. Реализация является довольно базовой в обоих случаях. В ApplyClientBehavior и ApplyDispatchBehavior инспектор сообщений создается и добавляется в коллекцию MessageInspectors соответствующей среды выполнения.

public class SchemaValidationBehavior : IEndpointBehavior
{
    XmlSchemaSet schemaSet;
    bool validateRequest;
    bool validateReply;

    public SchemaValidationBehavior(XmlSchemaSet schemaSet, bool
                           inspectRequest, bool inspectReply)
    {
        this.schemaSet = schemaSet;
        this.validateReply = inspectReply;
        this.validateRequest = inspectRequest;
    }
    #region IEndpointBehavior Members

    public void AddBindingParameters(ServiceEndpoint endpoint,
       System.ServiceModel.Channels.BindingParameterCollection
                                            bindingParameters)
    {
    }

    public void ApplyClientBehavior(ServiceEndpoint endpoint,
            System.ServiceModel.Dispatcher.ClientRuntime clientRuntime)
    {
        SchemaValidationMessageInspector inspector =
           new SchemaValidationMessageInspector(schemaSet,
                      validateRequest, validateReply, true);
            clientRuntime.MessageInspectors.Add(inspector);
    }

    public void ApplyDispatchBehavior(ServiceEndpoint endpoint,
         System.ServiceModel.Dispatcher.EndpointDispatcher
                                          endpointDispatcher)
    {
        SchemaValidationMessageInspector inspector =
           new SchemaValidationMessageInspector(schemaSet,
                        validateRequest, validateReply, false);
   endpointDispatcher.DispatchRuntime.MessageInspectors.Add(inspector);
    }

   public void Validate(ServiceEndpoint endpoint)
   {
   }

    #endregion
}

Замечание

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

Добавление инспектора сообщений с помощью конфигурации

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

<system.serviceModel>
…
   <extensions>
      <behaviorExtensions>
        <add name="schemaValidator" type="Microsoft.ServiceModel.Samples.SchemaValidationBehaviorExtensionElement, MessageInspectors, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null"/>
      </behaviorExtensions>
    </extensions>
…
</system.serviceModel>

Расширения можно добавить в приложение или ASP.NET файл конфигурации, который является наиболее распространенным вариантом или в файле конфигурации компьютера.

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

<system.serviceModel>
   <behaviors>
      …
     <endpointBehaviors>
        <behavior name="HelloServiceEndpointBehavior">
          <schemaValidator validateRequest="True" validateReply="True">
            <schemas>
              <add location="messages.xsd" />
            </schemas>
          </schemaValidator>
        </behavior>
      </endpointBehaviors>
      …
    </behaviors>
</system.serviceModel>

Элемент <schemaValidator> , который настраивает инспектор сообщений, поддерживается классом SchemaValidationBehaviorExtensionElement . Класс предоставляет два логических общедоступных свойства с именем ValidateRequest и ValidateReply. Оба они помечены с помощью ConfigurationPropertyAttribute. Этот атрибут представляет собой связь между свойствами кода и атрибутами XML, которые можно увидеть в предыдущем элементе конфигурации XML. Класс также имеет свойство Schemas, которое дополнительно отмечено ConfigurationCollectionAttribute и имеет тип SchemaCollection, что также является частью этого примера, но не указан в этом документе для краткости. Это свойство, а также коллекция и класс элементов SchemaConfigElement коллекции обслуживают элемент <schemas> в предыдущем фрагменте конфигурации и позволяют добавлять коллекцию схем в набор для проверки.

Переопределенный CreateBehavior метод преобразует данные конфигурации в объект поведения, когда среда выполнения оценивает данные конфигурации по мере сборки клиента или конечной точки.

public class SchemaValidationBehaviorExtensionElement : BehaviorExtensionElement
{
    public SchemaValidationBehaviorExtensionElement()
    {
    }

    public override Type BehaviorType
    {
        get
        {
            return typeof(SchemaValidationBehavior);
        }
    }

    protected override object CreateBehavior()
    {
        XmlSchemaSet schemaSet = new XmlSchemaSet();
        foreach (SchemaConfigElement schemaCfg in this.Schemas)
        {
            Uri baseSchema = new
                Uri(AppDomain.CurrentDomain.BaseDirectory);
            string location = new
                Uri(baseSchema,schemaCfg.Location).ToString();
            XmlSchema schema =
                XmlSchema.Read(new XmlTextReader(location), null);
            schemaSet.Add(schema);
        }
     return new
     SchemaValidationBehavior(schemaSet,ValidateRequest,ValidateReply);
    }

[ConfigurationProperty("validateRequest",DefaultValue=false,IsRequired=false)]
public bool ValidateRequest
{
    get { return (bool)base["validateRequest"]; }
    set { base["validateRequest"] = value; }
}

[ConfigurationProperty("validateReply", DefaultValue = false, IsRequired = false)]
        public bool ValidateReply
        {
            get { return (bool)base["validateReply"]; }
            set { base["validateReply"] = value; }
        }

     //Declare the Schema collection property.
     //Note: the "IsDefaultCollection = false" instructs
     //.NET Framework to build a nested section of
     //the kind <Schema> ...</Schema>.
    [ConfigurationProperty("schemas", IsDefaultCollection = true)]
    [ConfigurationCollection(typeof(SchemasCollection),
        AddItemName = "add",
        ClearItemsName = "clear",
        RemoveItemName = "remove")]
    public SchemasCollection Schemas
    {
        get
        {
            SchemasCollection SchemasCollection =
            (SchemasCollection)base["schemas"];
            return SchemasCollection;
        }
    }
}

Добавление инспекторов сообщений императивно

За исключением атрибутов (который не поддерживается в этом примере по причине, приведенной ранее) и конфигурации, поведение может быть легко добавлено в клиент и среду выполнения службы с помощью императивного кода. В этом примере это делается в клиентском приложении для проверки инспектора сообщений клиента. Класс GenericClient наследуется от ClientBase<TChannel>, который предоставляет конфигурацию конечной точки пользовательскому коду. Перед тем, как клиент неявно открывается, конфигурацию конечной точки можно изменить, добавив поведения, как показано в следующем коде. Добавление поведения службы в значительной степени эквивалентно показанному здесь методу клиента и должно выполняться перед открытием хоста службы.

try
{
    Console.WriteLine("*** Call 'Hello' with generic client, with client behavior");
    GenericClient client = new GenericClient();

    // Configure client programmatically, adding behavior
    XmlSchema schema = XmlSchema.Read(new StreamReader("messages.xsd"),
                                                          null);
    XmlSchemaSet schemaSet = new XmlSchemaSet();
    schemaSet.Add(schema);
    client.Endpoint.Behaviors.Add(new
                SchemaValidationBehavior(schemaSet, true, true));

    Console.WriteLine("--- Sending valid client request:");
    GenericCallValid(client, helloAction);
    Console.WriteLine("--- Sending invalid client request:");
    GenericCallInvalid(client, helloAction);

    client.Close();
}
catch (Exception e)
{
    DumpException(e);
}

Настройка, сборка и запуск примера

  1. Убедитесь, что вы выполнили процедуру настройки One-Time для образцов Windows Communication Foundation.

  2. Чтобы создать решение, следуйте инструкциям по созданию примеров Windows Communication Foundation.

  3. Чтобы запустить пример в конфигурации с одним или несколькими компьютерами, следуйте инструкциям в запуска примеров Windows Communication Foundation.