Dela via


Meddelandekontrollanter

Exemplet MessageInspectors visar hur du implementerar och konfigurerar klient- och tjänstmeddelandekontrollanter.

En meddelandekontroll är ett utökningsobjekt som kan användas i tjänstmodellens klientkörning och sändningskörning programmatiskt eller via konfiguration och som kan inspektera och ändra meddelanden när de har tagits emot eller innan de skickas.

Det här exemplet implementerar en grundläggande mekanism för validering av klient- och tjänstmeddelanden som validerar inkommande meddelanden mot en uppsättning konfigurerbara XML-schemadokument. Observera att det här exemplet inte validerar meddelanden för varje åtgärd. Detta är en avsiktlig förenkling.

Meddelandekontroll

Klientmeddelandekontrollanter implementerar IClientMessageInspector gränssnittet och tjänstmeddelandekontrollanterna implementerar IDispatchMessageInspector gränssnittet. Implementeringarna kan kombineras till en enda klass för att bilda en meddelandekontroll som fungerar för båda sidor. Det här exemplet implementerar en sådan kombinerad meddelandekontroll. Inspektören konstrueras för att skicka in en uppsättning scheman mot vilka inkommande och utgående meddelanden verifieras och gör det möjligt för utvecklaren att ange om inkommande eller utgående meddelanden ska verifieras och om inspektören är i sändnings- eller klientläge, vilket påverkar felhanteringen enligt beskrivningen senare i det här avsnittet.

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

Alla tjänstmeddelanden (dispatcher) måste implementera de två IDispatchMessageInspector metoderna AfterReceiveRequest och BeforeSendReply(Message, Object).

AfterReceiveRequest anropas av avsändaren när ett meddelande har tagits emot, bearbetats av kanalstacken och tilldelats till en tjänst, men innan det deserialiseras och skickas till en åtgärd. Om det inkommande meddelandet krypterades dekrypteras meddelandet redan när det når meddelandekontrollen. Metoden hämtar meddelandet request som skickas som en referensparameter, vilket gör att meddelandet kan inspekteras, manipuleras eller ersättas efter behov. Returvärdet kan vara valfritt objekt och används som ett korrelationstillståndsobjekt som skickas till BeforeSendReply när tjänsten returnerar ett svar på det aktuella meddelandet. I det här exemplet AfterReceiveRequest delegerar du kontrollen (valideringen) av meddelandet till den privata, lokala metoden ValidateMessageBody och returnerar inget korrelationstillståndsobjekt. Den här metoden säkerställer att inga ogiltiga meddelanden skickas till tjänsten.

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) anropas när ett svar är redo att skickas tillbaka till en klient, eller när det gäller enkelriktade meddelanden, när det inkommande meddelandet har bearbetats. Detta gör att tillägg kan räkna med att kallas symmetriskt, oavsett parlamentsledamot. Precis som med AfterReceiveRequestskickas meddelandet som en referensparameter och kan inspekteras, ändras eller ersättas. Verifieringen av meddelandet som utförs i det här exemplet delegeras återigen till ValidMessageBody metoden, men hanteringen av valideringsfel skiljer sig något i det här fallet.

Om ett valideringsfel inträffar på tjänsten ValidateMessageBody genererar FaultExceptionmetoden -derived exceptions. I AfterReceiveRequestkan dessa undantag placeras i tjänstmodellinfrastrukturen där de automatiskt omvandlas till SOAP-fel och vidarebefordras till klienten. FaultException I BeforeSendReplyfår undantag inte placeras i infrastrukturen eftersom omvandlingen av felundanstag som genereras av tjänsten sker innan meddelandekontrollen anropas. Därför fångar följande implementering det kända ReplyValidationFault undantaget och ersätter svarsmeddelandet med ett explicit felmeddelande. Den här metoden säkerställer att inga ogiltiga meddelanden returneras av tjänstimplementeringen.

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

Klientmeddelandekontrollen är mycket lik. De två metoder som måste implementeras från IClientMessageInspector är AfterReceiveReply och BeforeSendRequest.

BeforeSendRequest anropas när meddelandet har skapats antingen av klientprogrammet eller av åtgärdsformaterare. Precis som med avsändarens meddelandekontrollanter kan meddelandet bara inspekteras eller ersättas helt. I det här exemplet delegerar inspektören till samma lokala ValidateMessageBody hjälpmetod som också används för meddelandekontrollanterna.

Den beteendemässiga skillnaden mellan klient- och tjänstvalidering (enligt beskrivningen i konstruktorn) är att klientvalideringen genererar lokala undantag som placeras i användarkoden eftersom de inträffar lokalt och inte på grund av ett tjänstfel. I allmänhet är regeln att service dispatcher-inspektörer utlöser fel och att klientkontrollanter utlöser undantag.

Den här BeforeSendRequest implementeringen säkerställer att inga ogiltiga meddelanden skickas till tjänsten.

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

Implementeringen AfterReceiveReply säkerställer att inga ogiltiga meddelanden som tas emot från tjänsten vidarebefordras till klientanvändarkoden.

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

Kärnan i den här meddelandekontrollen är ValidateMessageBody metoden. För att utföra sitt arbete omsluter det en validering XmlReader runt underträdet för brödtextinnehållet i det skickade meddelandet. Läsaren fylls i med den uppsättning scheman som meddelandekontrollen innehåller och valideringsåteranropet är inställt på ett ombud som refererar till InspectionValidationHandler det som definieras tillsammans med den här metoden. För att utföra verifieringen läss meddelandet och buffrades sedan till en minnesströmsbaserad XmlDictionaryWriter. Om ett valideringsfel eller en varning inträffar i processen anropas återanropsmetoden.

Om inget fel uppstår skapas ett nytt meddelande som kopierar egenskaperna och rubrikerna från det ursprungliga meddelandet och använder den nu verifierade informationsuppsättningen i minnesströmmen, som omsluts av ett XmlDictionaryReader och läggs till i ersättningsmeddelandet.

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

Metoden InspectionValidationHandler anropas av valideringen XmlReader när ett schemaverifieringsfel eller en varning inträffar. Följande implementering fungerar bara med fel och ignorerar alla varningar.

Först kan det verka möjligt att mata in en validering XmlReader i meddelandet med meddelandekontrollen och låta verifieringen ske när meddelandet bearbetas och utan att buffra meddelandet. Det innebär dock att återanropet genererar valideringsundantag någonstans i tjänstmodellinfrastrukturen eller användarkoden som ogiltiga XML-noder identifieras, vilket resulterar i oförutsägbart beteende. Buffringsmetoden skyddar användarkoden från ogiltiga meddelanden, helt och hållet.

Som tidigare diskuterats skiljer sig undantagen som genereras av hanteraren mellan klienten och tjänsten. I tjänsten härleds undantagen från FaultException, på klienten är undantagen vanliga anpassade undantag.

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

Funktionssätt

Meddelandekontrollanter är tillägg till klientkörningen eller sändningskörningen. Sådana tillägg konfigureras med hjälp av beteenden. Ett beteende är en klass som ändrar beteendet för tjänstmodellens körning genom att ändra standardkonfigurationen eller lägga till tillägg (till exempel meddelandekontrollanter) i den.

Följande SchemaValidationBehavior klass är det beteende som används för att lägga till det här exemplets meddelandekontroll i klienten eller sändningskörningen. Implementeringen är ganska grundläggande i båda fallen. I ApplyClientBehavior och ApplyDispatchBehaviorskapas meddelandekontrollen och läggs till i MessageInspectors samlingen för respektive körning.

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
}

Kommentar

Det här beteendet fungerar inte som ett attribut och kan därför inte läggas till deklarativt i en kontraktstyp av en tjänsttyp. Det här är ett beslut som fattas avsiktligt eftersom schemasamlingen inte kan läsas in i en attributdeklaration och refererar till en extra konfigurationsplats (till exempel till programinställningarna) i det här attributet innebär att skapa ett konfigurationselement som inte är konsekvent med resten av konfigurationen av tjänstmodellen. Därför kan det här beteendet bara läggas till imperativt via kod och via ett konfigurationstillägg för tjänstmodell.

Lägga till meddelandekontroll via konfiguration

För att konfigurera ett anpassat beteende på en slutpunkt i programkonfigurationsfilen kräver tjänstmodellen att implementerare skapar ett konfigurationstilläggselement som representeras av en klass som härletts från BehaviorExtensionElement. Det här tillägget måste sedan läggas till i tjänstmodellens konfigurationsavsnitt för tillägg som visas för följande tillägg som beskrivs i det här avsnittet.

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

Tillägg kan läggas till i programmet eller ASP.NET konfigurationsfilen, vilket är det vanligaste valet, eller i datorkonfigurationsfilen.

När tillägget läggs till i ett konfigurationsomfång kan beteendet läggas till i en beteendekonfiguration som det visas i följande kod. Beteendekonfigurationer är återanvändbara element som kan tillämpas på flera slutpunkter efter behov. Eftersom det specifika beteende som ska konfigureras här implementerar IEndpointBehaviorär det endast giltigt i respektive konfigurationsavsnitt i konfigurationsfilen.

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

Elementet <schemaValidator> som konfigurerar meddelandekontrollen backas upp av SchemaValidationBehaviorExtensionElement klassen. Klassen exponerar två booleska offentliga egenskaper med namnet ValidateRequest och ValidateReply. Båda dessa markeras med en ConfigurationPropertyAttribute. Det här attributet utgör länken mellan kodegenskaperna och XML-attributen som kan visas i föregående XML-konfigurationselement. Klassen har också en egenskap Schemas som dessutom är markerad med ConfigurationCollectionAttribute och är av typen SchemaCollection, som också är en del av det här exemplet men utelämnas från det här dokumentet för korthet. Den här egenskapen tillsammans med samlingen och samlingselementklassen SchemaConfigElement backar elementet <schemas> i föregående konfigurationsfragment och gör det möjligt att lägga till en samling scheman i verifieringsuppsättningen.

Den åsidosatta CreateBehavior metoden omvandlar konfigurationsdata till ett beteendeobjekt när körningen utvärderar konfigurationsdata när en klient eller en slutpunkt skapas.

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

Lägga till meddelandekontrollanter imperativt

Förutom genom attribut (som inte stöds i det här exemplet av den anledning som angavs tidigare) och konfiguration, kan beteenden enkelt läggas till i en klient- och tjänstkörning med imperativ kod. I det här exemplet görs detta i klientprogrammet för att testa klientmeddelandekontrollen. Klassen GenericClient härleds från ClientBase<TChannel>, vilket exponerar slutpunktskonfigurationen för användarkoden. Innan klienten öppnas implicit kan slutpunktskonfigurationen ändras, till exempel genom att lägga till beteenden som visas i följande kod. Att lägga till beteendet på tjänsten motsvarar till stor del klienttekniken som visas här och måste utföras innan tjänstvärden öppnas.

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

Så här konfigurerar du, skapar och kör exemplet

  1. Kontrollera att du har utfört engångsinstallationsproceduren för Windows Communication Foundation-exempel.

  2. Skapa lösningen genom att följa anvisningarna i Skapa Windows Communication Foundation-exempel.

  3. Om du vill köra exemplet i en konfiguration med en eller flera datorer följer du anvisningarna i Köra Windows Communication Foundation-exempel.