Sdílet prostřednictvím


Inspektoři zpráv

Ukázka MessageInspectors ukazuje, jak implementovat a nakonfigurovat kontroly zpráv klienta a služby.

Inspektor zpráv je objekt rozšiřitelnosti, který lze použít v modulu runtime klienta modelu služby a prostřednictvím kódu programu nebo prostřednictvím konfigurace a který může kontrolovat a měnit zprávy po přijetí nebo před jejich odesláním.

Tato ukázka implementuje základní mechanismus ověřování zpráv klienta a služby, který ověřuje příchozí zprávy proti sadě konfigurovatelných dokumentů schématu XML. Všimněte si, že tato ukázka neověřuje zprávy pro každou operaci. Jedná se o záměrné zjednodušení.

Kontrola zpráv

Kontroly klientských zpráv implementují IClientMessageInspector rozhraní a inspektory zpráv služby implementují IDispatchMessageInspector rozhraní. Implementace lze kombinovat do jedné třídy a vytvořit kontrolu zpráv, která funguje pro obě strany. Tato ukázka implementuje takový kombinovaný inspektor zpráv. Inspektor je vytvořený tak, aby předával sadu schémat, pro která se ověřují příchozí a odchozí zprávy, a umožňuje vývojáři určit, jestli jsou příchozí nebo odchozí zprávy ověřeny a jestli je inspektor v režimu odeslání nebo klienta, což má vliv na zpracování chyb, jak je popsáno dále v tomto tématu.

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;
    }

Každá kontrola zpráv služby (dispečera) musí implementovat dvě IDispatchMessageInspector metody AfterReceiveRequest a BeforeSendReply(Message, Object).

AfterReceiveRequest je vyvolána dispečerem, když byla přijata zpráva, zpracována zásobníkem kanálu a přiřazena službě, ale před deserializována a odeslána do operace. Pokud byla příchozí zpráva zašifrovaná, zpráva se už dešifruje, když dorazí do inspektoru zpráv. Metoda získá request zprávu předanou jako referenční parametr, který umožňuje, aby byla zpráva kontrolována, manipulována nebo nahrazena podle potřeby. Návratová hodnota může být libovolný objekt a slouží jako objekt stavu korelace, který se předává BeforeSendReply , když služba vrátí odpověď na aktuální zprávu. V této ukázce AfterReceiveRequest deleguje kontrolu (ověření) zprávy na privátní, místní metodu ValidateMessageBody a nevrátí žádný objekt stavu korelace. Tato metoda zajišťuje, aby se do služby nepředály žádné neplatné zprávy.

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) je vyvolána vždy, když je odpověď připravená k odeslání zpět klientovi nebo v případě jednosměrných zpráv při zpracování příchozí zprávy. To umožňuje, aby rozšíření počítala se symetricky bez ohledu na mep. AfterReceiveRequestStejně jako v případě , zpráva se předává jako referenční parametr a lze ji zkontrolovat, upravit nebo nahradit. Ověření zprávy provedené v této ukázce je znovu delegováno na metodu ValidMessageBody , ale zpracování chyb ověření se v tomto případě mírně liší.

Pokud ve službě dojde k chybě ověření, ValidateMessageBody metoda vyvolá FaultException-odvozené výjimky. V AfterReceiveRequestsystému mohou být tyto výjimky vloženy do infrastruktury modelu služby, kde se automaticky transformují na chyby PROTOKOLU SOAP a předávají se klientovi. FaultException Výjimky BeforeSendReplynesmí být vloženy do infrastruktury, protože transformace výjimek chyb vyvolaných službou nastane před voláním inspektoru zpráv. Následující implementace proto zachytí známou ReplyValidationFault výjimku a nahradí zprávu odpovědi explicitní chybovou zprávou. Tato metoda zajišťuje, že implementace služby nevrátí žádné neplatné zprávy.

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);
        }
    }

Inspektor zpráv klienta je velmi podobný. Dvě metody, ze které musí být implementovány IClientMessageInspector , jsou AfterReceiveReply a BeforeSendRequest.

BeforeSendRequest je vyvolána, pokud byla zpráva složena klientskou aplikací nebo formátovačem operace. Stejně jako u inspektorů zpráv dispečera je možné zprávu jenom zkontrolovat nebo úplně nahradit. V této ukázce inspektor deleguje stejnou místní ValidateMessageBody pomocnou metodu, která se používá také pro kontroly zpráv dispečera.

Rozdíl chování mezi ověřováním klienta a služby (jak je uvedeno v konstruktoru) spočívá v tom, že ověření klienta vyvolá místní výjimky, které jsou vložené do uživatelského kódu, protože k nim dochází místně, a ne kvůli selhání služby. Obecně platí, že pravidlo je, že dispečer služby vyvolává chyby a že klientské inspektory vyvolává výjimky.

Tato BeforeSendRequest implementace zajišťuje, že službě nebudou odeslány žádné neplatné zprávy.

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

Implementace AfterReceiveReply zajišťuje, že se do kódu uživatele klienta nepředají žádné neplatné zprávy přijaté ze služby.

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

Jádrem tohoto konkrétního inspektoru ValidateMessageBody zpráv je metoda. Aby bylo možné tuto práci provést, zalomí ověření XmlReader kolem dílčího stromu obsahu textu předané zprávy. Čtečka je naplněna sadou schémat, která inspektor zpráv uchovává, a ověřovací zpětné volání je nastaveno na delegáta odkazující na InspectionValidationHandler to, který je definován spolu s touto metodou. Chcete-li provést ověření, zpráva se pak přečte a zařadí do datového proudu paměti.XmlDictionaryWriter Pokud v procesu dojde k chybě ověření nebo upozornění, vyvolá se metoda zpětného volání.

Pokud nedojde k žádné chybě, vytvoří se nová zpráva, která zkopíruje vlastnosti a hlavičky z původní zprávy a použije nyní ověřenou informační sadu v datovém proudu paměti, která je zabalena XmlDictionaryReader a přidána do náhradní zprávy.

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;
    }
}

Metoda InspectionValidationHandler se volá ověřováním XmlReader při každé chybě nebo upozornění ověření schématu. Následující implementace funguje pouze s chybami a ignoruje všechna upozornění.

Při prvním zvážení se může zdát, že do zprávy vložíte XmlReader ověření pomocí inspektoru zpráv a necháte ověření provést při zpracování zprávy a bez uložení zprávy do vyrovnávací paměti. To však znamená, že toto zpětné volání vyvolá výjimky ověření někam do infrastruktury modelu služby nebo uživatelského kódu, protože jsou zjištěny neplatné uzly XML, což vede k nepředvídatelným chování. Přístup do vyrovnávací paměti chrání uživatelský kód před neplatnými zprávami zcela.

Jak bylo popsáno dříve, výjimky vyvolané obslužnou rutinou se liší mezi klientem a službou. Ve službě jsou výjimky odvozeny z FaultException, v klientovi jsou výjimky běžné vlastní výjimky.

        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);
              }
          }
      }
    }

Chování

Kontroly zpráv jsou rozšíření modulu runtime klienta nebo modulu runtime dispatch. Taková rozšíření se konfigurují pomocí chování. Chování je třída, která mění chování modulu runtime modelu služby změnou výchozí konfigurace nebo přidáním rozšíření (například inspektorů zpráv).

Následující SchemaValidationBehavior třída je chování použité k přidání inspektoru zpráv této ukázky do klienta nebo modulu runtime dispatch. Implementace je v obou případech spíše základní. In ApplyClientBehavior a ApplyDispatchBehavior, message inspector is created and added to the MessageInspectors collection of the respective 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
}

Poznámka:

Toto konkrétní chování se nedvojuje jako atribut, a proto jej nelze deklarativním způsobem přidat do typu kontraktu typu služby. Jedná se o rozhodnutí podle návrhu, protože kolekci schémat nelze načíst v deklaraci atributu a odkazovat na další umístění konfigurace (například nastavení aplikace) v tomto atributu znamená vytvoření elementu konfigurace, který není konzistentní se zbytkem konfigurace modelu služby. Toto chování lze proto přidat pouze imperativním způsobem prostřednictvím kódu a prostřednictvím rozšíření konfigurace modelu služby.

Přidání kontroly zpráv prostřednictvím konfigurace

Pro konfiguraci vlastního chování koncového bodu v konfiguračním souboru aplikace vyžaduje model služby implementátory k vytvoření elementu rozšíření konfigurace reprezentované třídou odvozenou z BehaviorExtensionElement. Toto rozšíření se pak musí přidat do konfiguračního oddílu modelu služby pro rozšíření, jak je znázorněno pro následující rozšíření probírané v této části.

<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>

Rozšíření lze přidat do aplikace nebo do konfiguračního souboru ASP.NET, což je nejběžnější volba nebo v konfiguračním souboru počítače.

Po přidání rozšíření do oboru konfigurace je možné toto chování přidat do konfigurace chování, jak je znázorněno v následujícím kódu. Konfigurace chování jsou opakovaně použitelné prvky, které je možné použít na více koncových bodů podle potřeby. Vzhledem k tomu, že se zde implementuje IEndpointBehaviorkonkrétní chování, je platné pouze v příslušné části konfigurace v konfiguračním souboru.

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

Element <schemaValidator> , který konfiguruje kontrolu zpráv, je podporován SchemaValidationBehaviorExtensionElement třídou. Třída zveřejňuje dvě logické veřejné vlastnosti pojmenované ValidateRequest a ValidateReply. Obě jsou označeny značkou ConfigurationPropertyAttribute. Tento atribut představuje propojení mezi vlastnostmi kódu a atributy XML, které lze vidět na předchozím konfiguračním elementu XML. Třída má také vlastnost Schemas , která je navíc označena ConfigurationCollectionAttribute a je typu SchemaCollection, který je také součástí této ukázky, ale vynechá z tohoto dokumentu pro stručnost. Tato vlastnost spolu s kolekcí a element kolekce třídy SchemaConfigElement backs <schemas> element v předchozím fragmentu konfigurace a umožňuje přidání kolekce schémat do ověřovací sady.

Přepsaná CreateBehavior metoda změní konfigurační data na objekt chování, když modul runtime vyhodnotí konfigurační data při sestavení klienta nebo koncového bodu.

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;
        }
    }
}

Imperativní přidání inspektorů zpráv

S výjimkou atributů (které nejsou v této ukázce podporovány z důvodu citovaného dříve) a konfigurace je možné chování snadno přidat do klienta a modulu runtime služby pomocí imperativního kódu. V této ukázce se to provádí v klientské aplikaci k otestování kontroly zpráv klienta. Třída GenericClient je odvozena z ClientBase<TChannel>, která zveřejňuje konfiguraci koncového bodu pro uživatelský kód. Než se klient implicitně otevře konfiguraci koncového bodu, může se například změnit přidáním chování, jak je znázorněno v následujícím kódu. Přidání chování služby je z velké části ekvivalentní technikě klienta, která je zde uvedena a musí být provedena před otevřením hostitele služby.

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);
}

Nastavení, sestavení a spuštění ukázky

  1. Ujistěte se, že jste pro ukázky windows Communication Foundation provedli jednorázovou instalační proceduru.

  2. Pokud chcete sestavit řešení, postupujte podle pokynů v části Sestavení ukázek Windows Communication Foundation.

  3. Pokud chcete spustit ukázku v konfiguraci s jedním nebo více počítači, postupujte podle pokynů v části Spuštění ukázek windows Communication Foundation.