Berichtencontrole
Het messageInspectors-voorbeeld laat zien hoe u client- en serviceberichtencontroles implementeert en configureert.
Een berichtcontrole is een uitbreidbaar object dat kan worden gebruikt in de clientruntime van het servicemodel en programmatisch of via de configuratie kan worden verzonden en die berichten kan inspecteren en wijzigen nadat ze zijn ontvangen of voordat ze worden verzonden.
In dit voorbeeld wordt een eenvoudig validatiemechanisme voor client- en serviceberichten geïmplementeerd waarmee binnenkomende berichten worden gevalideerd op basis van een set configureerbare XML-schemadocumenten. Houd er rekening mee dat in dit voorbeeld geen berichten worden gevalideerd voor elke bewerking. Dit is een opzettelijke vereenvoudiging.
Berichtcontrole
Clientberichtcontrole implementeert de IClientMessageInspector interface en serviceberichtcontrole de IDispatchMessageInspector interface. De implementaties kunnen in één klasse worden gecombineerd om een berichtcontrole te vormen die voor beide zijden werkt. In dit voorbeeld wordt een dergelijke gecombineerde berichtcontrole geïmplementeerd. De inspector wordt samengesteld door een set schema's op te geven waarmee binnenkomende en uitgaande berichten worden gevalideerd en waarmee de ontwikkelaar kan opgeven of binnenkomende of uitgaande berichten worden gevalideerd en of de inspecteur zich in de verzend- of clientmodus bevindt, wat van invloed is op de foutafhandeling, zoals verderop in dit onderwerp wordt besproken.
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;
}
Elke service (dispatcher) berichtcontrole moet de twee IDispatchMessageInspector methoden AfterReceiveRequest implementeren en BeforeSendReply(Message, Object).
AfterReceiveRequest wordt aangeroepen door de dispatcher wanneer een bericht is ontvangen, verwerkt door de kanaalstack en is toegewezen aan een service, maar voordat het wordt gedeserialiseerd en verzonden naar een bewerking. Als het binnenkomende bericht is versleuteld, wordt het bericht al ontsleuteld wanneer het de berichtcontrole bereikt. Met de methode wordt het request
bericht opgehaald dat is doorgegeven als referentieparameter, waarmee het bericht naar behoefte kan worden geïnspecteerd, gemanipuleerd of vervangen. De retourwaarde kan elk object zijn en wordt gebruikt als een correlatiestatusobject dat wordt doorgegeven BeforeSendReply wanneer de service een antwoord op het huidige bericht retourneert. In dit voorbeeld AfterReceiveRequest delegeert u de inspectie (validatie) van het bericht naar de persoonlijke, lokale methode ValidateMessageBody
en retourneert u geen correlatiestatusobject. Deze methode zorgt ervoor dat er geen ongeldige berichten worden doorgegeven aan de service.
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) wordt aangeroepen wanneer een antwoord gereed is om terug te worden verzonden naar een client, of in het geval van berichten in één richting, wanneer het binnenkomende bericht is verwerkt. Hierdoor kunnen extensies op symmetrische wijze worden aangeroepen, ongeacht het MEP. Net als bij AfterReceiveRequest, wordt het bericht doorgegeven als referentieparameter en kan het worden geïnspecteerd, gewijzigd of vervangen. De validatie van het bericht dat in dit voorbeeld wordt uitgevoerd, wordt opnieuw gedelegeerd aan de ValidMessageBody
methode, maar de verwerking van validatiefouten is in dit geval iets anders.
Als er een validatiefout optreedt in de service, genereert FaultExceptionde ValidateMessageBody
methode -afgeleide uitzonderingen. In AfterReceiveRequestkunnen deze uitzonderingen worden geplaatst in de infrastructuur van het servicemodel, waar ze automatisch worden omgezet in SOAP-fouten en worden doorgestuurd naar de client. FaultException Uitzonderingen BeforeSendReplymogen niet in de infrastructuur worden geplaatst, omdat de transformatie van foutuitzondering die door de service wordt gegenereerd, plaatsvindt voordat de berichtcontrole wordt aangeroepen. Daarom wordt met de volgende implementatie de bekende ReplyValidationFault
uitzondering onderschept en wordt het antwoordbericht vervangen door een expliciet foutbericht. Deze methode zorgt ervoor dat er geen ongeldige berichten worden geretourneerd door de service-implementatie.
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);
}
}
De controle van clientberichten is vergelijkbaar. De twee methoden waaruit moet worden geïmplementeerd IClientMessageInspector , zijn AfterReceiveReply en BeforeSendRequest.
BeforeSendRequest wordt aangeroepen wanneer het bericht is samengesteld door de clienttoepassing of door de bewerkingsindeling. Net als bij de berichtencontrole van de dispatcher kan het bericht gewoon worden geïnspecteerd of volledig worden vervangen. In dit voorbeeld delegeert de inspector aan dezelfde lokale ValidateMessageBody
helpermethode die ook wordt gebruikt voor de dispatch-berichtcontrole.
Het gedragsverschil tussen de client- en servicevalidatie (zoals opgegeven in de constructor) is dat de clientvalidatie lokale uitzonderingen genereert die in de gebruikerscode worden geplaatst omdat deze lokaal optreden en niet vanwege een servicefout. Over het algemeen is de regel dat service-dispatcher-inspectors fouten genereren en dat clientcontroleurs uitzonderingen genereren.
Deze BeforeSendRequest implementatie zorgt ervoor dat er geen ongeldige berichten naar de service worden verzonden.
object IClientMessageInspector.BeforeSendRequest(ref System.ServiceModel.Channels.Message request, System.ServiceModel.IClientChannel channel)
{
if (validateRequest)
{
ValidateMessageBody(ref request, true);
}
return null;
}
De AfterReceiveReply
implementatie zorgt ervoor dat er geen ongeldige berichten die van de service worden ontvangen, worden doorgestuurd naar de clientgebruikerscode.
void IClientMessageInspector.AfterReceiveReply(ref System.ServiceModel.Channels.Message reply, object correlationState)
{
if (validateReply)
{
ValidateMessageBody(ref reply, false);
}
}
Het hart van deze specifieke berichtcontrole is de ValidateMessageBody
methode. Om het werk uit te voeren, verpakt het een validatie XmlReader rond de substructuur van de hoofdtekstinhoud van het doorgegeven bericht. De lezer wordt gevuld met de set schema's die de berichtencontrole bevat en de callback voor validatie is ingesteld op een gemachtigde die verwijst naar de InspectionValidationHandler
die naast deze methode is gedefinieerd. Om de validatie uit te voeren, wordt het bericht vervolgens gelezen en in een door een geheugenstroom ondersteund XmlDictionaryWriter. Als er een validatiefout of waarschuwing optreedt in het proces, wordt de callback-methode aangeroepen.
Als er geen fout optreedt, wordt er een nieuw bericht gemaakt waarmee de eigenschappen en headers van het oorspronkelijke bericht worden gekopieerd en de nu gevalideerde infoset in de geheugenstroom wordt gebruikt, die wordt verpakt door een XmlDictionaryReader en toegevoegd aan het vervangende bericht.
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;
}
}
De InspectionValidationHandler
methode wordt aangeroepen door het valideren XmlReader wanneer een schemavalidatiefout of waarschuwing optreedt. De volgende implementatie werkt alleen met fouten en negeert alle waarschuwingen.
Bij de eerste overweging lijkt het misschien mogelijk om een validatie in het bericht te injecteren XmlReader met de berichtcontrole en de validatie te laten plaatsvinden wanneer het bericht wordt verwerkt en zonder het bericht te bufferen. Dit betekent echter dat met deze callback de validatieuitzonderingen ergens in de infrastructuur van het servicemodel worden gegenereerd of dat de gebruikerscode als ongeldige XML-knooppunten worden gedetecteerd, wat resulteert in onvoorspelbaar gedrag. Met de bufferingsbenadering wordt de gebruikerscode volledig afgeschermd van ongeldige berichten.
Zoals eerder besproken, verschillen de uitzonderingen die door de handler worden gegenereerd, tussen de client en de service. Op de service worden de uitzonderingen afgeleid van FaultException, op de client zijn de uitzonderingen reguliere aangepaste uitzonderingen.
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);
}
}
}
}
Gedrag
Berichtcontrole is een uitbreiding voor de clientruntime of de dispatch-runtime. Dergelijke extensies worden geconfigureerd met behulp van gedrag. Een gedrag is een klasse die het gedrag van de servicemodelruntime wijzigt door de standaardconfiguratie te wijzigen of extensies (zoals berichtcontrole) eraan toe te voegen.
De volgende SchemaValidationBehavior
klasse is het gedrag dat wordt gebruikt om de berichtcontrole van dit voorbeeld toe te voegen aan de client of dispatch-runtime. De implementatie is in beide gevallen vrij eenvoudig. In ApplyClientBehavior en ApplyDispatchBehaviorwordt de berichtcontrole gemaakt en toegevoegd aan de MessageInspectors verzameling van de respectieve 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
}
Notitie
Dit specifieke gedrag fungeert niet als een kenmerk en kan daarom niet declaratief worden toegevoegd aan een contracttype van een servicetype. Dit is een ontwerpbeslissing omdat de schemaverzameling niet kan worden geladen in een kenmerkdeclaratie en verwijst naar een extra configuratielocatie (bijvoorbeeld naar de toepassingsinstellingen) in dit kenmerk betekent het maken van een configuratie-element dat niet consistent is met de rest van de configuratie van het servicemodel. Daarom kan dit gedrag alleen imperatief worden toegevoegd via code en via een servicemodelconfiguratie-extensie.
De Berichtcontrole toevoegen via configuratie
Voor het configureren van een aangepast gedrag op een eindpunt in het configuratiebestand van de toepassing vereist het servicemodel implementeerfuncties om een configuratie-extensie-element te maken dat wordt vertegenwoordigd door een klasse die is afgeleid vanBehaviorExtensionElement. Deze extensie moet vervolgens worden toegevoegd aan de configuratiesectie van het servicemodel voor extensies, zoals wordt weergegeven voor de volgende extensie die in deze sectie wordt besproken.
<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>
Extensies kunnen worden toegevoegd in de toepassing of ASP.NET configuratiebestand, wat de meest voorkomende keuze is, of in het configuratiebestand van de computer.
Wanneer de extensie wordt toegevoegd aan een configuratiebereik, kan het gedrag worden toegevoegd aan een gedragsconfiguratie, zoals wordt weergegeven in de volgende code. Gedragsconfiguraties zijn herbruikbare elementen die naar behoefte kunnen worden toegepast op meerdere eindpunten. Omdat het specifieke gedrag dat hier moet worden geconfigureerd, wordt geïmplementeerd IEndpointBehavior, is dit alleen geldig in de respectieve configuratiesectie in het configuratiebestand.
<system.serviceModel>
<behaviors>
…
<endpointBehaviors>
<behavior name="HelloServiceEndpointBehavior">
<schemaValidator validateRequest="True" validateReply="True">
<schemas>
<add location="messages.xsd" />
</schemas>
</schemaValidator>
</behavior>
</endpointBehaviors>
…
</behaviors>
</system.serviceModel>
Het <schemaValidator>
element dat de berichtcontrole configureert, wordt ondersteund door de SchemaValidationBehaviorExtensionElement
klasse. De klasse toont twee booleaanse openbare eigenschappen met de naam ValidateRequest
en ValidateReply
. Beide zijn gemarkeerd met een ConfigurationPropertyAttribute. Dit kenmerk vormt de koppeling tussen de code-eigenschappen en de XML-kenmerken die kunnen worden weergegeven in het voorgaande XML-configuratie-element. De klasse heeft ook een eigenschap Schemas
die aanvullend is gemarkeerd met het ConfigurationCollectionAttribute en is van het type SchemaCollection
, dat ook deel uitmaakt van dit voorbeeld, maar weggelaten uit dit document voor beknoptheid. Deze eigenschap samen met de verzameling en de klasse SchemaConfigElement
van het verzamelingselement maakt een back-up van het <schemas>
element in het voorgaande configuratiefragment en maakt het mogelijk om een verzameling schema's toe te voegen aan de validatieset.
Met de overschreven CreateBehavior
methode worden de configuratiegegevens omgezet in een gedragsobject wanneer de runtime de configuratiegegevens evalueert tijdens het bouwen van een client of een eindpunt.
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;
}
}
}
Berichtcontrole toevoegen imperatief
Met uitzondering van kenmerken (die om de eerder geciteerde reden niet worden ondersteund in dit voorbeeld) en configuratie, kunnen gedrag heel eenvoudig worden toegevoegd aan een client- en serviceruntime met behulp van imperatieve code. In dit voorbeeld wordt dit gedaan in de clienttoepassing om de clientberichtcontrole te testen. De GenericClient
klasse is afgeleid van ClientBase<TChannel>, waarmee de eindpuntconfiguratie beschikbaar wordt gemaakt voor de gebruikerscode. Voordat de client impliciet wordt geopend, kan de eindpuntconfiguratie worden gewijzigd, bijvoorbeeld door gedrag toe te voegen zoals wordt weergegeven in de volgende code. Het toevoegen van het gedrag van de service is grotendeels gelijk aan de clienttechniek die hier wordt weergegeven en moet worden uitgevoerd voordat de servicehost wordt geopend.
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);
}
Het voorbeeld instellen, compileren en uitvoeren
Zorg ervoor dat u de eenmalige installatieprocedure voor de Windows Communication Foundation-voorbeelden hebt uitgevoerd.
Volg de instructies in Het bouwen van de Windows Communication Foundation-voorbeelden om de oplossing te bouwen.
Als u het voorbeeld wilt uitvoeren in een configuratie met één of meerdere computers, volgt u de instructies in Het uitvoeren van de Windows Communication Foundation-voorbeelden.