다음을 통해 공유


메시지 검사기

MessageInspectors 샘플은 클라이언트 및 서비스 메시지 검사기를 구현하고 구성하는 방법을 보여 줍니다.

메시지 검사기는 서비스 모델의 클라이언트 런타임 및 디스패치 런타임에서 프로그래밍 방식으로 또는 구성을 통해 사용할 수 있으며 메시지를 받은 후 또는 보내기 전에 검사하고 변경할 수 있는 확장성 개체입니다.

이 샘플에서는 구성 가능한 XML 스키마 문서 집합에 대해 들어오는 메시지의 유효성을 검사하는 기본 클라이언트 및 서비스 메시지 유효성 검사 메커니즘을 구현합니다. 이 샘플은 각 작업에 대한 메시지의 유효성을 검사하지 않습니다. 이는 의도적인 단순화입니다.

메시지 검사기

클라이언트 메시지 검사기 인터페이스를 IClientMessageInspector 구현 하 고 서비스 메시지 검사기 인터페이스를 IDispatchMessageInspector 구현 합니다. 구현을 단일 클래스로 결합하여 양쪽 모두에서 작동하는 메시지 검사기를 구성할 수 있습니다. 이 샘플에서는 이러한 결합된 메시지 검사기를 구현합니다. 검사기는 들어오는 메시지와 나가는 메시지의 유효성을 검사하는 스키마 집합을 전달하여 생성되며, 개발자는 들어오는 메시지 또는 나가는 메시지의 유효성을 검사할지 여부와 검사기가 디스패치 또는 클라이언트 모드에 있는지 여부를 지정할 수 있으며, 이는 이 항목의 뒷부분에 설명된 대로 오류 처리에 영향을 줍니다.

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

모든 서비스(디스패처) 메시지 검사기는 IDispatchMessageInspectorAfterReceiveRequest의 두 메서드를 BeforeSendReply(Message, Object) 구현해야 합니다.

AfterReceiveRequest 는 메시지가 수신되고, 채널 스택에서 처리되고, 서비스에 할당될 때 디스패처가 호출하지만, 역직렬화되고 작업에 디스패치되기 전에 호출됩니다. 들어오는 메시지가 암호화된 경우 메시지가 메시지 검사에 도달하면 이미 암호가 해독됩니다. 메서드는 참조 매개 변수로 전달된 메시지를 가져오 request 며, 필요에 따라 메시지를 검사, 조작 또는 바꿀 수 있습니다. 반환 값은 모든 개체일 수 있으며 서비스에서 현재 메시지에 대한 회신을 반환할 BeforeSendReply 때 전달되는 상관 관계 상태 개체로 사용됩니다. 이 샘플 AfterReceiveRequest 에서는 메시지의 검사(유효성 검사)를 프라이빗 로컬 메서드 ValidateMessageBody 에 위임하고 상관 관계 상태 개체를 반환하지 않습니다. 이 메서드는 잘못된 메시지가 서비스에 전달되지 않도록 합니다.

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) 는 회신을 클라이언트로 다시 보낼 준비가 될 때마다 또는 단방향 메시지의 경우 들어오는 메시지가 처리될 때 호출됩니다. 이로 인해 확장은 MEP에 관계없이 신뢰성 있게 대칭적으로 호출될 수 있습니다. 마찬가지로 AfterReceiveRequest메시지는 참조 매개 변수로 전달되며 검사, 수정 또는 바꿀 수 있습니다. 이 샘플에서 수행되는 메시지의 유효성 검사는 메서드에 ValidMessageBody 다시 위임되지만 이 경우 유효성 검사 오류 처리는 약간 다릅니다.

서비스에서 유효성 검사 오류가 발생하면 ValidateMessageBody 메서드는 FaultException에서 파생된 예외를 던집니다. 여기서 AfterReceiveRequest이러한 예외는 자동으로 SOAP 오류로 변환되고 클라이언트에 릴레이되는 서비스 모델 인프라에 넣을 수 있습니다. BeforeSendReply, FaultException 예외는 메시지 검사기가 호출되기 전에 서비스에서 throw된 오류 예외의 변환이 발생하므로 인프라에 놓아서는 안 됩니다. 따라서 다음 구현에서는 알려진 ReplyValidationFault 예외를 catch하고 회신 메시지를 명시적 오류 메시지로 바꿉니다. 이 메서드는 잘못된 메시지가 서비스 구현에서 반환되지 않도록 합니다.

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

클라이언트 메시지 검사기는 매우 유사합니다. IClientMessageInspector에서 구현해야 하는 두 가지 메서드는 AfterReceiveReplyBeforeSendRequest입니다.

BeforeSendRequest 는 클라이언트 애플리케이션 또는 작업 포맷터에서 메시지를 작성할 때 호출됩니다. 디스패처 메시지 검사자와 마찬가지로 메시지를 검사하거나 완전히 바꿀 수 있습니다. 이 샘플에서 검사기는 디스패치 메시지 검사에도 사용되는 동일한 로컬 ValidateMessageBody 도우미 메서드에 위임합니다.

클라이언트와 서비스 유효성 검사 간의 동작 차이(생성자에 지정된 대로)는 클라이언트 유효성 검사에서 서비스 오류로 인한 것이 아니라 로컬에서 발생하기 때문에 사용자 코드에 포함된 로컬 예외를 throw한다는 것입니다. 일반적으로 서비스 검사기는 오류를 발생시키고, 클라이언트 검사기는 예외를 처리하는 것이 규칙입니다.

BeforeSendRequest 구현은 잘못된 메시지가 서비스에 전송되지 않도록 합니다.

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

구현을 AfterReceiveReply 통해 서비스에서 받은 잘못된 메시지가 클라이언트 사용자 코드로 릴레이되지 않습니다.

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

이 특정 메시지 검사기의 핵심은 메서드입니다 ValidateMessageBody . 작업을 수행하기 위해 전달된 메시지의 본문 콘텐츠 하위 트리 주위에 유효성 검사기 XmlReader를 래핑합니다. 판독기는 메시지 검사기에서 보유하는 스키마 집합으로 채워지고 유효성 검사 콜백은 이 메서드와 함께 정의된 것을 참조하는 InspectionValidationHandler 대리자로 설정됩니다. 유효성 검사를 수행하려면 메시지를 읽은 후 메모리 스트림으로 스풀합니다XmlDictionaryWriter. 프로세스에서 유효성 검사 오류 또는 경고가 발생하면 콜백 메서드가 호출됩니다.

오류가 발생하지 않으면 원본 메시지에서 속성과 헤더를 복사하고 메모리 스트림에서 현재 유효성이 검사된 정보 세트를 사용하는 새 메시지가 생성됩니다. 이 인포셋은 메모리 스트림에 XmlDictionaryReader 래핑되어 대체 메시지에 추가됩니다.

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

InspectionValidationHandler 메서드는 스키마 유효성 검사 오류 또는 경고가 발생할 때마다 XmlReader 유효성 검사에 의해 호출됩니다. 다음 구현은 오류에서만 작동하며 모든 경고를 무시합니다.

첫 번째 고려 사항에서는 메시지 검사기를 XmlReader 사용하여 메시지에 유효성 검사를 삽입하고 메시지가 처리될 때 메시지를 버퍼링하지 않고 유효성 검사가 수행되도록 할 수 있습니다. 그러나 이 콜백은 잘못된 XML 노드가 검색되어 예측할 수 없는 동작이 발생하므로 서비스 모델 인프라 또는 사용자 코드에 유효성 검사 예외를 throw합니다. 버퍼링 방법은 사용자 코드를 잘못된 메시지로부터 완전히 보호합니다.

이전에 설명한 것처럼, 처리기에서 발생된 예외는 클라이언트와 서비스 간에 다릅니다. 서비스에서 예외는 클라이언트에서 FaultException파생되며, 클라이언트에서 예외는 일반 사용자 지정 예외입니다.

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

행동

메시지 검사자는 클라이언트 런타임 또는 디스패치 런타임에 대한 확장입니다. 이러한 확장은 동작을 사용하여 구성됩니다. 동작은 기본 구성을 변경하거나 확장(예: 메시지 검사기)을 추가하여 서비스 모델 런타임의 동작을 변경하는 클래스입니다.

다음 SchemaValidationBehavior 클래스는 클라이언트 또는 디스패치 런타임에 이 샘플의 메시지 검사기를 추가하는 데 사용되는 동작입니다. 구현은 두 경우 모두에 다소 기본적입니다. ApplyClientBehaviorApplyDispatchBehavior에서 메시지 검사기가 생성되어 해당 런타임의 MessageInspectors 컬렉션에 추가됩니다.

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
}

비고

이 특정 동작은 특성으로 두 배가 되지 않으므로 서비스 유형의 계약 형식에 선언적으로 추가할 수 없습니다. 이는 스키마 컬렉션을 특성 선언에 로드할 수 없고 이 특성의 추가 구성 위치(예: 애플리케이션 설정)를 참조하는 것은 나머지 서비스 모델 구성과 일치하지 않는 구성 요소를 만드는 것을 의미하기 때문에 의도적으로 결정된 것입니다. 따라서 이 동작은 코드 및 서비스 모델 구성 확장을 통해서만 명령적으로 추가할 수 있습니다.

구성을 통해 메시지 검사기 추가

애플리케이션 구성 파일의 엔드포인트에서 사용자 지정 동작을 구성하려면 서비스 모델을 사용하려면 구현자가 파생 클래스에서 나타내는 구성 BehaviorExtensionElement 만들어야 합니다. 그런 다음, 이 섹션에서 설명한 다음 확장에 표시된 대로 확장에 대한 서비스 모델의 구성 섹션에 이 확장을 추가해야 합니다.

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

가장 일반적인 선택인 애플리케이션 또는 ASP.NET 구성 파일 또는 컴퓨터 구성 파일에서 확장을 추가할 수 있습니다.

확장이 구성 범위에 추가되면 다음 코드와 같이 동작 구성에 동작을 추가할 수 있습니다. 동작 구성은 필요에 따라 여러 엔드포인트에 적용할 수 있는 재사용 가능한 요소입니다. 여기서 구성할 특정 동작이 구현되므로 구성 파일의 해당 구성 섹션에서만 유효합니다 IEndpointBehavior.

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

<schemaValidator> 메시지 검사기를 구성하는 요소는 SchemaValidationBehaviorExtensionElement 클래스에 의해 지원됩니다. 클래스는 두 개의 부울형 공용 속성인 ValidateRequestValidateReply을 제공합니다. 둘 다 .로 ConfigurationPropertyAttribute표시됩니다. 이 특성은 이전 XML 구성 요소에서 볼 수 있는 코드 속성과 XML 특성 간의 링크를 구성합니다. 클래스에는 또한 Schemas으로 표시된 ConfigurationCollectionAttribute 속성이 있으며, 이 속성은 SchemaCollection 형식입니다. 이는 이 샘플의 일부이지만 간결함을 위해 이 문서에서는 생략되었습니다. 컬렉션 및 컬렉션 요소 클래스 SchemaConfigElement 와 함께 이 속성은 <schemas> 이전 구성 코드 조각의 요소를 백업하고 유효성 검사 집합에 스키마 컬렉션을 추가할 수 있습니다.

재정의된 CreateBehavior 메서드는 런타임이 클라이언트 또는 엔드포인트를 빌드할 때 구성 데이터를 평가할 때 구성 데이터를 동작 개체로 바꿉니다.

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

명령적으로 메시지 검사기 추가

특성(앞에서 언급한 이유로 이 샘플에서 지원되지 않음) 및 구성을 제외하고 명령적 코드를 사용하여 클라이언트 및 서비스 런타임에 동작을 쉽게 추가할 수 있습니다. 이 샘플에서는 클라이언트 애플리케이션에서 클라이언트 메시지 검사기를 테스트하기 위해 이 작업을 수행합니다. GenericClient 클래스는 ClientBase<TChannel>로부터 파생되었으며, 이는 엔드포인트 구성을 사용자 코드에 노출합니다. 클라이언트를 암시적으로 열기 전에 다음 코드와 같이 동작을 추가하여 엔드포인트 구성을 변경할 수 있습니다. 서비스에 동작을 추가하는 것은 여기에 표시된 클라이언트 기술과 거의 동일하며 서비스 호스트를 열기 전에 수행해야 합니다.

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

샘플을 설정, 빌드 및 실행하려면

  1. Windows Communication Foundation 샘플 에 대한One-Time 설정 절차를 수행했는지 확인합니다.

  2. 솔루션을 빌드하려면 Windows Communication Foundation 샘플 빌드의 지침을 따릅니다.

  3. 단일 또는 컴퓨터 간 구성에서 샘플을 실행하려면 Windows Communication Foundation 샘플실행의 지침을 따릅니다.