Jegyzet
Az oldalhoz való hozzáférés engedélyezést igényel. Próbálhatod be jelentkezni vagy könyvtárat váltani.
Az oldalhoz való hozzáférés engedélyezést igényel. Megpróbálhatod a könyvtár váltását.
A MessageInspectors-minta bemutatja, hogyan implementálhatók és konfigurálhatók az ügyfél- és szolgáltatásüzenet-ellenőrök.
Az üzenetfelügyelő egy bővíthetőségi objektum, amely használható a szolgáltatásmodell ügyfél-futtatókörnyezetében, és programozott módon vagy konfiguráción keresztül küldi el a futtatókörnyezetet, és amely megvizsgálhatja és módosíthatja az üzeneteket a beérkezés után vagy az elküldés előtt.
Ez a minta egy alapszintű ügyfél- és szolgáltatásüzenet-érvényesítési mechanizmust implementál, amely konfigurálható XML-sémadokumentumokkal ellenőrzi a bejövő üzeneteket. Vegye figyelembe, hogy ez a minta nem ellenőrzi az üzeneteket az egyes műveletekhez. Ez szándékos egyszerűsítés.
Üzenetfelügyelő
Az ügyfélüzenet-ellenőrök implementálják az interfészt, és a IClientMessageInspector szolgáltatásüzenet-ellenőrök implementálják a IDispatchMessageInspector felületet. Az implementációk egyetlen osztályba kombinálhatók, így egy mindkét fél számára használható üzenetfelügyelőt alkothatnak. Ez a minta egy ilyen kombinált üzenetfelügyelőt valósít meg. Az ellenőr egy sémagyűjtemény átadásával jön létre, amelyeken a bejövő és kimenő üzenetek érvényesítve vannak, és lehetővé teszi a fejlesztő számára, hogy meghatározza, hogy a bejövő és kimenő üzenetek érvényesítve vannak-e, valamint hogy az ellenőr küldési vagy ügyfél módban van-e, ami hatással van az itt tárgyalt hibakezelésre.
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;
}
Minden szolgáltatás (diszpécser) üzenetfelügyelőnek implementálnia kell a két IDispatchMessageInspector módszert AfterReceiveRequest és BeforeSendReply(Message, Object).
AfterReceiveRequest a diszpécser akkor hívja meg, amikor egy üzenet megérkezett, a csatorna verem által feldolgozásra került, egy szolgáltatáshoz lett rendelve, de még mielőtt deszerializálnák és egy művelethez továbbítanák. Ha a bejövő üzenet titkosítva lett, a rendszer már visszafejti az üzenetet, amikor eléri az üzenetfelügyelőt. A metódus referenciaparaméterként adja át az request üzenetet, amely lehetővé teszi az üzenet vizsgálatát, manipulálását vagy cseréjét igény szerint. A visszatérési érték bármilyen objektum lehet, és korrelációs állapotobjektumként használható, amelyet akkor ad át BeforeSendReply a rendszer, amikor a szolgáltatás választ ad vissza az aktuális üzenetre. Ebben a mintában AfterReceiveRequest delegálja az üzenet ellenőrzését (érvényesítését) a privát, helyi metódusnak ValidateMessageBody , és nem ad vissza korrelációs állapotobjektumot. Ez a módszer biztosítja, hogy ne jutnak át érvénytelen üzenetek a szolgáltatásba.
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) a rendszer akkor hívja meg, ha egy válasz készen áll arra, hogy vissza lehessen küldeni egy ügyfélnek, vagy egyirányú üzenetek esetén, amikor a bejövő üzenet feldolgozása megtörtént. Ez lehetővé teszi, hogy a bővítmények a képviselőktől függetlenül szimmetrikusan legyenek meghívva. Ahogy a AfterReceiveRequest esetében is, az üzenetet referenciaként adják át paraméterként, és megvizsgálható, módosítható vagy lecserélhető. Az ebben a mintában végrehajtott üzenet érvényesítése ismét delegálva lesz a ValidMessageBody metódushoz, de az ellenőrzési hibák kezelése ebben az esetben kissé eltérő.
Ha érvényesítési hiba történik a szolgáltatásban, a ValidateMessageBody metódus FaultException-származtatott kivételeket ad. Ebben AfterReceiveRequesta esetben ezek a kivételek a szolgáltatásmodell-infrastruktúrába helyezhetők, ahol automatikusan SOAP-hibákká alakulnak át, és továbbítják őket az ügyfélnek. Ebben a BeforeSendReplyFaultException esetben a kivételeket nem szabad az infrastruktúrába helyezni, mert a szolgáltatás által kiadott hibakivétel átalakítása az üzenetellenőrző meghívása előtt történik. Ezért a következő implementáció az ismert ReplyValidationFault kivételt észleli, és a válaszüzenetet explicit hibaüzenetre cseréli. Ez a módszer biztosítja, hogy a szolgáltatás implementációja ne adjon vissza érvénytelen üzeneteket.
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);
}
}
Az ügyfélüzenet-felügyelő nagyon hasonló. A IClientMessageInspector-ból implementálandó két módszer a AfterReceiveReply és a BeforeSendRequest.
BeforeSendRequest akkor hívja meg a rendszer, ha az üzenetet az ügyfélalkalmazás vagy a műveletformázási szolgáltató állította össze. A diszpécser üzenetfelügyelőihez hasonlóan az üzenet egyszerűen megvizsgálható vagy teljesen lecserélhető. Ebben a mintában az ellenőr ugyanahhoz a helyi ValidateMessageBody segédmetódushoz delegál, amelyet a küldőüzenet-felügyelők is használnak.
Az ügyfél és a szolgáltatás érvényesítése közötti viselkedésbeli különbség (ahogy azt a konstruktorban megadták) az, hogy az ügyfél érvényesítése olyan helyi kivételeket generál, amelyek a felhasználói kódban fordulnak elő, mivel ezek helyben jelentkeznek, nem pedig valamilyen szolgáltatáshibából adódnak. A szabály általában az, hogy a szolgáltatás-diszpécser ellenőrök hibákat dobnak ki, és hogy az ügyfélfelügyelők kivételeket vetnek ki.
Ez a BeforeSendRequest megvalósítás biztosítja, hogy a rendszer ne küldjön érvénytelen üzeneteket a szolgáltatásnak.
object IClientMessageInspector.BeforeSendRequest(ref System.ServiceModel.Channels.Message request, System.ServiceModel.IClientChannel channel)
{
if (validateRequest)
{
ValidateMessageBody(ref request, true);
}
return null;
}
A AfterReceiveReply megvalósítás biztosítja, hogy a szolgáltatástól kapott érvénytelen üzenetek ne legyenek továbbítva az ügyfél felhasználói kódjának.
void IClientMessageInspector.AfterReceiveReply(ref System.ServiceModel.Channels.Message reply, object correlationState)
{
if (validateReply)
{
ValidateMessageBody(ref reply, false);
}
}
Ennek a konkrét üzenetellenőrzőnek a szíve a ValidateMessageBody metódus. A munkája elvégzéséhez egy érvényesítő XmlReader-t helyez az átadott üzenet törzstartalom-fájának köré. Az olvasó feltöltődik az üzenetfelügyelő által birtokolt sémákkal, az érvényesítési visszahívás pedig egy olyan meghatalmazottra van beállítva, aki hivatkozik a InspectionValidationHandler metódus mellett definiált sémákra. Az ellenőrzés végrehajtásához a rendszer beolvassa és beszúrja az üzenetet egy memóriastreammel támogatott XmlDictionaryWritermemóriába. Ha érvényesítési hiba vagy figyelmeztetés történik a folyamatban, a rendszer meghívja a visszahívási módszert.
Ha nem történik hiba, létrejön egy új üzenet, amely átmásolja a tulajdonságokat és a fejléceket az eredeti üzenetből, és a memóriastreamben a most érvényesített adathalmazt használja, amelyet egy XmlDictionaryReader új üzenet burkol, és hozzáad a helyettesítő üzenethez.
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;
}
}
A InspectionValidationHandler metódust az ellenőrzés XmlReader hívja meg, amikor sémaérvényesítési hiba vagy figyelmeztetés lép fel. Az alábbi implementáció csak hibákkal működik, és figyelmen kívül hagyja az összes figyelmeztetést.
Első ránézésre lehetségesnek tűnhet, hogy érvényesítést XmlReader injektálunk az üzenetbe az üzenetfelügyelővel, és hagyjuk, hogy az ellenőrzés az üzenet feldolgozásakor és az üzenet pufferelése nélkül történjen. Ez azonban azt jelenti, hogy ez a visszahívás az érvényesítési kivételeket valahol a szolgáltatásmodell infrastruktúrájába vagy a felhasználói kódba dobja, mivel a rendszer érvénytelen XML-csomópontokat észlel, ami kiszámíthatatlan viselkedést eredményez. A pufferelési módszer teljes egészében védi a felhasználói kódot az érvénytelen üzenetektől.
Ahogy korábban említettem, a kezelő által megadott kivételek különböznek az ügyfél és a szolgáltatás között. A szolgáltatásban a kivételek a FaultException-ből származnak, míg az ügyfélen a kivételek szokásos egyéni kivételek.
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);
}
}
}
}
Magatartás
Az üzenetfelügyelők az ügyfél futtatókörnyezetéhez vagy a küldési futtatókörnyezethez tartozó bővítmények. Az ilyen kiterjesztések viselkedés alapján vannak konfigurálva. A viselkedés olyan osztály, amely az alapértelmezett konfiguráció módosításával vagy bővítmények (például üzenetfelügyelők) hozzáadásával módosítja a szolgáltatásmodell futtatókörnyezetének viselkedését.
A következő SchemaValidationBehavior osztály az a metódus, amelyet a példa üzenetellenőrzőjének az ügyfélhez vagy az üzenettovábbító futtatókörnyezethez való hozzáadására használnak. A megvalósítás mindkét esetben meglehetősen alapszintű. A ApplyClientBehavior és ApplyDispatchBehavior esetében az üzenetfelügyelő létrehozásra kerül és hozzáadódik a megfelelő futtatókörnyezet MessageInspectors gyűjteményéhez.
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
}
Megjegyzés:
Ez az adott viselkedés nem duplázódik meg attribútumként, ezért nem adható hozzá deklaratív módon egy szolgáltatástípus szerződéstípusához. Ez egy előre megtervezett döntés, mert a sémagyűjtemény nem tölthető be attribútumdeklarációba, és ha egy extra konfigurációs helyre hivatkozik (például az alkalmazásbeállításokra), az azt jelenti, hogy létrehoz egy olyan konfigurációs elemet, amely nem összhangban van a szolgáltatásmodell többi konfigurációjával. Ezért ez a viselkedés csak kóddal és egy szolgáltatásmodell konfigurációs bővítményével adható hozzá.
Az Üzenetfelügyelő hozzáadása konfigurációval
Ahhoz, hogy egyéni viselkedést konfiguráljon egy végponton az alkalmazáskonfigurációs fájlban, a szolgáltatásmodell megköveteli, hogy a implementátorok létrehozzák a konfigurációbővítmény elemét, amelyet egy, a forrásból BehaviorExtensionElementszármaztatott osztály jelöl. Ezt a bővítményt ezután hozzá kell adni a szolgáltatásmodell konfigurációs szakaszához a bővítmények esetében, ahogyan az az ebben a szakaszban tárgyalt következő bővítménynél is látható.
<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>
Bővítmények hozzáadhatók az alkalmazáshoz vagy ASP.NET konfigurációs fájlhoz, amely a leggyakoribb választás, vagy a gép konfigurációs fájljában.
Amikor hozzáadja a bővítményt egy konfigurációs hatókörhöz, a viselkedés hozzáadható egy viselkedéskonfigurációhoz, ahogy az az alábbi kódban is látható. A viselkedéskonfigurációk olyan újrafelhasználható elemek, amelyek szükség szerint több végpontra is alkalmazhatók. Mivel az itt konfigurálandó viselkedés implementálja a IEndpointBehavior-t, csak az adott konfigurációs szakaszban a konfigurációs fájlban érvényes.
<system.serviceModel>
<behaviors>
…
<endpointBehaviors>
<behavior name="HelloServiceEndpointBehavior">
<schemaValidator validateRequest="True" validateReply="True">
<schemas>
<add location="messages.xsd" />
</schemas>
</schemaValidator>
</behavior>
</endpointBehaviors>
…
</behaviors>
</system.serviceModel>
Az <schemaValidator> üzenetfelügyelőt konfiguráló elemet az SchemaValidationBehaviorExtensionElement osztály biztosítja. Az osztály két logikai típusú, nyilvános tulajdonságot tesz elérhetővé, az elnevezésük ValidateRequest és ValidateReply. Mindkettő meg van jelölve egy ConfigurationPropertyAttribute-vel. Ez az attribútum a kódtulajdonságok és az előző XML-konfigurációs elemen látható XML-attribútumok közötti kapcsolatot képezi. Az osztálynak van egy további tulajdonsága Schemas, amely meg van jelölve a ConfigurationCollectionAttribute-vel, és a SchemaCollection típusba tartozik. Ez a tulajdonság szintén része ennek a mintának, de a rövidség kedvéért ki lett hagyva ebből a dokumentumból. Ez a tulajdonság a gyűjtemény és a gyűjteményelem-osztály SchemaConfigElement mellett az <schemas> előző konfigurációs kódrészletben található elemet is háttérbe helyezi, és lehetővé teszi sémagyűjtemény hozzáadását az érvényesítési csoporthoz.
A felülbírált CreateBehavior módszer viselkedési objektummá alakítja a konfigurációs adatokat, amikor a futtatókörnyezet kiértékeli a konfigurációs adatokat egy ügyfél vagy egy végpont létrehozásakor.
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;
}
}
}
Üzenetfelügyelők hozzáadása imperatív módon
A korábban idézett okból nem támogatott attribútumok és konfigurációk kivételével a viselkedések könnyen hozzáadhatók az ügyfél- és szolgáltatás-futtatókörnyezethez imperatív kóddal. Ebben a mintában ez az ügyfélalkalmazásban történik az ügyfélüzenet-felügyelő teszteléséhez. Az GenericClient osztály abból származik ClientBase<TChannel>, amely a végpont konfigurációját teszi elérhetővé a felhasználói kód számára. Az ügyfél implicit megnyitása előtt a végpont konfigurációja módosítható, például az alábbi kódban látható viselkedések hozzáadásával. A szolgáltatás viselkedésének hozzáadása jórészt megegyezik az itt bemutatott ügyféltechnikával, és ezt a szolgáltatás kiszolgáló megnyitása előtt kell elvégezni.
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);
}
A példa beállítása, elkészítése és futtatása
Győződjön meg arról, hogy elvégezte a Windows Communication Foundation-minták One-Time beállítási eljárását.
A megoldás létrehozásához kövesse a Windows Communication Foundation-minták készítésére vonatkozó utasításokat.
Ha a mintát egy vagy több gép közötti konfigurációban szeretné futtatni, kövesse A Windows Communication Foundation-minták futtatásacímű témakör utasításait.