Nota
L'accesso a questa pagina richiede l'autorizzazione. È possibile provare ad accedere o modificare le directory.
L'accesso a questa pagina richiede l'autorizzazione. È possibile provare a modificare le directory.
L'esempio MessageInspectors illustra come implementare e configurare controlli messaggi client e di servizio.
Un controllo messaggi è un oggetto di estendibilità che può essere usato nel runtime client e nell'invio del modello di servizio a livello di codice o tramite la configurazione e in grado di esaminare e modificare i messaggi dopo la ricezione o prima dell'invio.
Questo esempio implementa un meccanismo di convalida dei messaggi client e di servizio di base che convalida i messaggi in arrivo rispetto a un set di documenti XML Schema configurabili. Si noti che questo esempio non convalida i messaggi per ogni operazione. Si tratta di una semplificazione intenzionale.
Ispettore dei messaggi
I controlli messaggi client implementano l'interfaccia IClientMessageInspector e i controlli messaggi del servizio implementano l'interfaccia IDispatchMessageInspector . Le implementazioni possono essere combinate in una singola classe per formare un controllo messaggi che funziona per entrambi i lati. In questo esempio viene implementato un controllo messaggi combinato. Il controllo viene costruito passando un set di schemi su cui vengono convalidati i messaggi in ingresso e in uscita e consente allo sviluppatore di specificare se i messaggi in ingresso o in uscita vengono convalidati e se il controllo è in modalità dispatch o client, che influisce sulla gestione degli errori, come descritto più avanti in questo argomento.
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;
}
Qualsiasi ispettore di messaggi del servizio (dispatcher) deve implementare i due IDispatchMessageInspector metodi AfterReceiveRequest e BeforeSendReply(Message, Object).
AfterReceiveRequest viene invocato dal dispatcher quando un messaggio è stato ricevuto, elaborato dallo stack di canali e assegnato a un servizio, ma prima che sia deserializzato e venga inviato a un'operazione. Se il messaggio in arrivo è stato crittografato, il messaggio viene già decrittografato quando raggiunge il controllo messaggi. Il metodo ottiene il request messaggio passato come parametro di riferimento, che consente di controllare, modificare o sostituire il messaggio in base alle esigenze. Il valore restituito può essere qualsiasi oggetto e viene utilizzato come oggetto stato di correlazione passato a BeforeSendReply quando il servizio restituisce una risposta al messaggio corrente. In questo esempio delega AfterReceiveRequest l'ispezione (convalida) del messaggio al metodo ValidateMessageBody locale privato e non restituisce alcun oggetto stato di correlazione. Questo metodo garantisce che non vengano passati messaggi non validi al servizio.
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) viene richiamato ogni volta che una risposta è pronta per essere inviata a un client o nel caso di messaggi unidirezionale, quando il messaggio in arrivo è stato elaborato. Questo consente alle estensioni di essere chiamate simmetricamente, indipendentemente dal MEP. Come con AfterReceiveRequest, il messaggio viene passato come parametro di riferimento e può essere controllato, modificato o sostituito. La convalida del messaggio eseguito in questo esempio viene delegata nuovamente al ValidMessageBody metodo , ma la gestione degli errori di convalida è leggermente diversa in questo caso.
Se si verifica un errore di convalida nel servizio, il ValidateMessageBody metodo genera eccezioni derivate da FaultException. In AfterReceiveRequestqueste eccezioni possono essere inserite nell'infrastruttura del modello di servizio in cui vengono trasformate automaticamente in errori SOAP e inoltrate al client. In BeforeSendReply, FaultException le eccezioni non devono essere inserite nell'infrastruttura, perché la trasformazione delle eccezioni di errore generate dal servizio si verifica prima che venga chiamato l'ispettore dei messaggi. Di conseguenza, l'implementazione seguente intercetta l'eccezione nota ReplyValidationFault e sostituisce il messaggio di risposta con un messaggio di errore esplicito. Questo metodo garantisce che non vengano restituiti messaggi non validi dall'implementazione del servizio.
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);
}
}
L'ispettore di messaggi client è molto simile. I due metodi da cui è necessario implementare IClientMessageInspector sono AfterReceiveReply e BeforeSendRequest.
BeforeSendRequest viene richiamato quando il messaggio è stato composto dall'applicazione client o dal formattatore dell'operazione. Come per gli ispettori dei messaggi del dispatcher, il messaggio può essere ispezionato o completamente sostituito. In questo esempio, l'ispettore delega allo stesso metodo helper locale ValidateMessageBody utilizzato anche per gli ispettori di messaggi di dispatch.
La differenza comportamentale tra il client e la convalida del servizio (come specificato nel costruttore) è che la convalida client genera eccezioni locali inserite nel codice utente perché si verificano localmente e non a causa di un errore del servizio. In genere, la regola è che gli ispettori dispatcher del servizio generano errori e che gli ispettori client generano eccezioni.
Questa BeforeSendRequest implementazione garantisce che non vengano inviati messaggi non validi al servizio.
object IClientMessageInspector.BeforeSendRequest(ref System.ServiceModel.Channels.Message request, System.ServiceModel.IClientChannel channel)
{
if (validateRequest)
{
ValidateMessageBody(ref request, true);
}
return null;
}
L'implementazione AfterReceiveReply garantisce che nessun messaggio non valido ricevuto dal servizio venga inoltrato al codice utente client.
void IClientMessageInspector.AfterReceiveReply(ref System.ServiceModel.Channels.Message reply, object correlationState)
{
if (validateReply)
{
ValidateMessageBody(ref reply, false);
}
}
Il cuore di questo particolare ispettore di messaggi è il metodo ValidateMessageBody. Per eseguire il suo lavoro, esegue il wrapping di una convalida XmlReader intorno al sottoalbero del contenuto del corpo del messaggio passato. Il lettore viene popolato con il set di schemi che l'ispezione del messaggio contiene e il callback di convalida viene impostato su un delegato che fa riferimento al InspectionValidationHandler definito insieme a questo metodo. Per eseguire la convalida, il messaggio viene quindi letto e memorizzato in uno stream di memoria supportato da XmlDictionaryWriter. Se si verifica un errore o un avviso di convalida nel processo, viene richiamato il metodo di callback.
Se non si verifica alcun errore, viene costruito un nuovo messaggio che copia le proprietà e le intestazioni dal messaggio originale e utilizza l'infoset ora convalidato nel flusso di memoria, avvolto da un oggetto XmlDictionaryReader e aggiunto al messaggio sostitutivo.
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;
}
}
Il InspectionValidationHandler metodo viene chiamato dal componente di validazione XmlReader ogni volta che si verifica un errore o un avviso durante la convalida dello schema. L'implementazione seguente funziona solo con errori e ignora tutti gli avvisi.
In primo luogo, potrebbe sembrare possibile inserire una convalida XmlReader nel messaggio con il controllo del messaggio e lasciare che la convalida venga eseguita durante l'elaborazione del messaggio e senza memorizzare nel buffer il messaggio. Ciò significa tuttavia che questo callback genera le eccezioni di convalida in un punto qualsiasi nell'infrastruttura del modello di servizio o nel codice utente perché vengono rilevati nodi XML non validi, causando un comportamento imprevedibile. L'approccio di buffering protegge completamente il codice utente dai messaggi non validi.
Come illustrato in precedenza, le eccezioni generate dal gestore differiscono tra il client e il servizio. Nel servizio le eccezioni derivano da FaultException, nel client le eccezioni sono eccezioni personalizzate regolari.
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);
}
}
}
}
Comportamento
Gli ispettori di messaggi sono estensioni del runtime del client o del runtime di dispatch. Tali estensioni vengono configurate usando i comportamenti. Un comportamento è una classe che modifica il comportamento del runtime del modello di servizio modificando la configurazione predefinita o aggiungendo estensioni(ad esempio i controlli messaggi) a essa.
La classe seguente SchemaValidationBehavior è il comportamento usato per aggiungere l'ispettore di messaggi di questo esempio al client o al runtime di dispatch. L'implementazione è piuttosto semplice in entrambi i casi. In ApplyClientBehavior e ApplyDispatchBehavior, il controllo messaggi viene creato e aggiunto alla MessageInspectors raccolta del rispettivo runtime.
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
}
Annotazioni
Questo particolare comportamento non raddoppia come attributo e pertanto non può essere aggiunto in modo dichiarativo a un tipo di contratto di un tipo di servizio. Si tratta di una decisione predefinita presa perché la raccolta di schemi non può essere caricata in una dichiarazione di attributo e facendo riferimento a un percorso di configurazione aggiuntivo (ad esempio alle impostazioni dell'applicazione) in questo attributo significa creare un elemento di configurazione che non è coerente con il resto della configurazione del modello di servizio. Pertanto, questo comportamento può essere aggiunto solo in modo imperativo tramite il codice e tramite un'estensione di configurazione del modello di servizio.
Aggiunta di Message Inspector tramite la configurazione
Per configurare un comportamento personalizzato in un endpoint nel file di configurazione dell'applicazione, il modello di servizio richiede agli implementatori di creare un elemento dell'estensione di configurazione rappresentato da una classe derivata da BehaviorExtensionElement. Questa estensione deve quindi essere aggiunta alla sezione di configurazione del modello di servizio per le estensioni, come illustrato per l'estensione seguente descritta in questa sezione.
<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>
Le estensioni possono essere aggiunte nell'applicazione o ASP.NET file di configurazione, ovvero la scelta più comune o nel file di configurazione del computer.
Quando l'estensione viene aggiunta a un ambito di configurazione, il comportamento può essere aggiunto a una configurazione del comportamento come illustrato nel codice seguente. Le configurazioni del comportamento sono elementi riutilizzabili che possono essere applicati a più endpoint in base alle esigenze. Poiché il comportamento specifico da configurare in questo caso implementa IEndpointBehavior, è valido solo nella rispettiva sezione di configurazione nel file di configurazione.
<system.serviceModel>
<behaviors>
…
<endpointBehaviors>
<behavior name="HelloServiceEndpointBehavior">
<schemaValidator validateRequest="True" validateReply="True">
<schemas>
<add location="messages.xsd" />
</schemas>
</schemaValidator>
</behavior>
</endpointBehaviors>
…
</behaviors>
</system.serviceModel>
L'elemento <schemaValidator> che configura il controllo messaggi è supportato dalla SchemaValidationBehaviorExtensionElement classe . La classe espone due proprietà pubbliche booleane denominate ValidateRequest e ValidateReply. Entrambi sono contrassegnati con un oggetto ConfigurationPropertyAttribute. Questo attributo costituisce il collegamento tra le proprietà del codice e gli attributi XML visualizzabili nell'elemento di configurazione XML precedente. La classe dispone inoltre di una proprietà Schemas contrassegnata anche con ConfigurationCollectionAttribute e è del tipo SchemaCollection, che fa anche parte di questo esempio, ma omesso da questo documento per brevità. Questa proprietà insieme alla raccolta e alla classe SchemaConfigElement dell'elemento della raccolta supporta l'elemento <schemas> nel frammento di configurazione precedente e consente di aggiungere una raccolta di schemi al set di convalida.
Il metodo CreateBehavior sottoposto a override trasforma i dati di configurazione in un oggetto di comportamento quando il runtime valuta i dati di configurazione nel processo di compilazione di un client o un endpoint.
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;
}
}
}
Aggiunta di controllori di messaggi in modo imperativo
Ad eccezione degli attributi (che non sono supportati in questo esempio per il motivo citato in precedenza) e la configurazione, i comportamenti possono essere facilmente aggiunti a un client e a un runtime del servizio usando codice imperativo. In questo esempio questa operazione viene eseguita nell'applicazione client per testare il controllo messaggi client. La GenericClient classe è derivata da ClientBase<TChannel>, che espone la configurazione dell'endpoint al codice utente. Prima che il client venga aperto in modo implicito, è possibile modificare la configurazione dell'endpoint, ad esempio aggiungendo comportamenti come illustrato nel codice seguente. L'aggiunta del comportamento nel servizio è per lo più equivalente alla tecnica client illustrata qui e deve essere eseguita prima dell'apertura dell'host del servizio.
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);
}
Per configurare, compilare ed eseguire l'esempio
Assicurati di aver eseguito la procedura di installazione di One-Time per gli esempi di Windows Communication Foundation.
Per compilare la soluzione, seguire le istruzioni riportate in Compilazione degli esempi di Windows Communication Foundation.
Per eseguire l'esempio in una configurazione con computer singolo o incrociato, seguire le istruzioni riportate in Esecuzione degli esempi di Windows Communication Foundation.