Ескертпе
Бұл бетке кіру үшін қатынас шегін айқындау қажет. Жүйеге кіруді немесе каталогтарды өзгертуді байқап көруге болады.
Бұл бетке кіру үшін қатынас шегін айқындау қажет. Каталогтарды өзгертуді байқап көруге болады.
В примере 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);
}
Настройка, сборка и запуск примера
Убедитесь, что вы выполнили процедуру настройки One-Time для образцов Windows Communication Foundation.
Чтобы создать решение, следуйте инструкциям по созданию примеров Windows Communication Foundation.
Чтобы запустить пример в конфигурации с одним или несколькими компьютерами, следуйте инструкциям в запуска примеров Windows Communication Foundation.