Sdílet prostřednictvím


Kontroloř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 běhovém prostředí klienta a běhovém prostředí směrování modelu služby, a to programově nebo skrze konfiguraci, a který může kontrolovat a měnit zprávy po jejich 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 tento příklad neověřuje zprávy pro každou operaci. Jedná se o záměrné zjednodušení.

Kontrola zpráv

Inspektoři klientských zpráv implementují rozhraní IClientMessageInspector a inspektoři zpráv služby implementují rozhraní IDispatchMessageInspector. 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ý inspektor zpráv služby (dispečera) musí implementovat dvě metody IDispatchMessageInspectorAfterReceiveRequest a BeforeSendReply(Message, Object).

AfterReceiveRequest je vyvolána dispečerem, když je přijata zpráva, zpracována zásobníkem kanálu a přiřazena službě, ale než je deserializována a odeslána k operaci. 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. Toto umožňuje, aby rozšíření byla volána 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. BeforeSendReply Výjimky FaultExceptionnesmí 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, které musí být implementovány z 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í pravidlo, že inspektoři dispečera služby vyvolávají chyby a že inspektoři klienta vyhazují 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 inspektora zpráv je metoda ValidateMessageBody. Aby bylo možné tuto práci provést, obklopí ověřovací prvek XmlReader kolem podstromu obsahu těla 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, který odkazuje na InspectionValidationHandler definovaný 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 je volána ověřovatelem 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í by se mohlo zdát možné vložit ověřovací XmlReader do zprávy pomocí inspektoru zpráv a nechat ověření provést při zpracování zprávy bez jejího uložení do vyrovnávací paměti. To však znamená, že toto zpětné volání vyvolá výjimky validace do infrastruktury modelu služby nebo uživatelského kódu, při zjištění neplatných uzlů XML, což vede k nepředvídatelnému chování. Přístup s vyrovnávací pamětí zcela chrání uživatelský kód před neplatnými zprávami.

Jak bylo popsáno dříve, výjimky vyvolané zpracovatelem se liší mezi klientem a službou. Ve službě jsou výjimky odvozeny z FaultException, na klientu 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í

Kontroloři zpráv jsou rozšíření klientského runtime prostředí nebo dispečerského runtime prostředí. 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 představuje chování použité k přidání inspektoru zpráv této ukázky do klienta nebo prováděcího runtime. Implementace je v obou případech spíše základní. V ApplyClientBehavior a ApplyDispatchBehavior je inspektor zpráv vytvořen a přidán do kolekce MessageInspectors příslušného 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 služební model vyžaduje, aby implementátoři vytvořili element rozšíření konfigurace, která je reprezentována 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 konkrétní chování implementuje IEndpointBehavior, 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 je vynechána z tohoto dokumentu pro stručnost. Tato vlastnost spolu s kolekcí a elementem třídy kolekce SchemaConfigElement podporuje element <schemas> v předchozím fragmentu konfigurace a umožňuje přidat kolekci 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í do služby je z velké části ekvivalentní technikě klienta, která je zde uvedena, a se musí provést 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);
}

Jak nastavit, sestavit a spustit ukázku

  1. Ujistěte se, že jste provedli instalační proceduru One-Time pro ukázky Windows Communication Foundation.

  2. Pro sestavení řešení postupujte podle pokynů v Sestavení ukázek Windows Communication Foundation.

  3. Pokud chcete spustit ukázku v konfiguraci pro jeden počítač nebo pro více počítačů, postupujte podle pokynů v Spuštění ukázek Windows Communication Foundation.