모든 WCF 호출에 사용자 지정 HTTP 헤더를 추가하는 방법은 무엇입니까?


162

Windows 서비스에서 호스팅되는 WCF 서비스가 있습니다. 이 서비스를 사용하는 클라이언트는 서비스 메소드를 호출 할 때마다 식별자를 전달해야합니다 (호출 된 메소드가 수행해야하는 작업에 식별자가 중요하기 때문에). 이 식별자를 WCF 헤더 정보에 넣는 것이 좋은 생각이라고 생각했습니다.

좋은 생각이라면 식별자를 자동으로 헤더 정보에 추가하는 방법은 무엇입니까? 즉, 사용자가 WCF 메서드를 호출 할 때마다 식별자가 자동으로 헤더에 추가되어야합니다.

업데이트 : WCF 서비스를 사용하는 클라이언트는 Windows 응용 프로그램과 Windows Mobile 응용 프로그램입니다 (Compact Framework 사용).


1
문제를 해결할 수 있었습니까?
— Mark Good

Compact Framework에서 작동하게 되었습니까?
— Vaccano

답변:


185

이것의 장점은 모든 통화에 적용된다는 것입니다.

IClientMessageInspector 를 구현하는 클래스를 작성하십시오 . BeforeSendRequest 메소드에서 사용자 정의 헤더를 발신 메시지에 추가하십시오. 다음과 같이 보일 수 있습니다.

    public object BeforeSendRequest(ref System.ServiceModel.Channels.Message request,  System.ServiceModel.IClientChannel channel)
{
    HttpRequestMessageProperty httpRequestMessage;
    object httpRequestMessageObject;
    if (request.Properties.TryGetValue(HttpRequestMessageProperty.Name, out httpRequestMessageObject))
    {
        httpRequestMessage = httpRequestMessageObject as HttpRequestMessageProperty;
        if (string.IsNullOrEmpty(httpRequestMessage.Headers[USER_AGENT_HTTP_HEADER]))
        {
            httpRequestMessage.Headers[USER_AGENT_HTTP_HEADER] = this.m_userAgent;
        }
    }
    else
    {
        httpRequestMessage = new HttpRequestMessageProperty();
        httpRequestMessage.Headers.Add(USER_AGENT_HTTP_HEADER, this.m_userAgent);
        request.Properties.Add(HttpRequestMessageProperty.Name, httpRequestMessage);
    }
    return null;
}

그런 다음 메시지 관리자를 클라이언트 런타임에 적용하는 엔드 포인트 작동을 작성하십시오. 동작 확장 요소를 사용하여 속성 또는 구성을 통해 동작을 적용 할 수 있습니다.

다음은 모든 요청 메시지에 HTTP 사용자 에이전트 헤더를 추가하는 좋은 예 입니다. 나는 이것을 내 고객 중 일부에서 사용하고 있습니다. IDispatchMessageInspector 를 구현하여 서비스 측에서도 동일한 작업을 수행 할 수 있습니다. .

이것이 당신이 생각한 것입니까?

업데이트 : 컴팩트 프레임 워크에서 지원하는이 WCF 기능 목록 을 찾았습니다 . 이 게시물에 따르면, '채널 확장'으로 분류 된 메시지 사찰 생각 하는 컴팩트 프레임 워크에서 지원합니다.


2
@ 마크, 이것은 정말 좋은 답변입니다. 감사. net.tcp를 통해 이것을 시도했지만 Headers 컬렉션을 직접 사용하고 있습니다 (Http Headers가 작동하지 않았습니다). ServiceHost AfterReceiveRequest 이벤트에서 내 토큰 (Name)이있는 헤더를 얻지 만 값은 없습니다 (값의 속성이 아닌 것 같습니까?). 내가 놓친 것이 있습니까? 헤더를 만들 때 요청하는 이름 / 값 쌍을 예상했을 것입니다. request.Headers.Add (MessageHeader.CreateHeader (name, ns, value));
— Program.X

13
+1 OutgoingMessageProperties은 OutgoingMessageHeadersSOAP 헤더가 아닌 HTTP 헤더에 액세스하는 데 필요한 것 입니다.
— SliverNinja-MSFT

1
간단하게 멋진 코드! :)
— abhilashca

3
이것은 주어진 예에 따라 web.config에 하드 코딩 된 사용자 에이전트 만 허용합니다!
— KristianB

1
이것은 훌륭한 답변입니다. 메시지 속성에서 HttpRequestMessageProperty.Name을 아직 사용할 수없는 경우도 처리합니다. 어떤 이유로 든 코드를 디버깅하면서 타이밍 문제에 따라이 값이 항상 존재하지는 않는다는 것을 깨달았습니다. 고마워 마크!
— carlos357

80

다음을 사용하여 통화에 추가합니다.

using (OperationContextScope scope = new OperationContextScope((IContextChannel)channel))
{
    MessageHeader<string> header = new MessageHeader<string>("secret message");
    var untyped = header.GetUntypedHeader("Identity", "http://www.my-website.com");
    OperationContext.Current.OutgoingMessageHeaders.Add(untyped);

    // now make the WCF call within this using block
}

그런 다음 서버 측에서 다음을 사용하여 가져옵니다.

MessageHeaders headers = OperationContext.Current.IncomingMessageHeaders;
string identity = headers.GetHeader<string>("Identity", "http://www.my-website.com");

5
코드 스 니펫 주셔서 감사합니다. 그러나 이것으로 메소드를 호출 할 때마다 헤더를 추가해야합니다. 이 과정을 투명하게하고 싶었습니다. 즉, 사용자가 서비스 클라이언트를 작성하고 메소드를 사용할 때마다 고객 헤더가 자동으로 메시지에 추가됩니다.
— mrtaikandi 2016 년

이것은이 답변에서 제공하는 제안을 확장하는 예와 좋은 MSDN의 링크입니다 : msdn.microsoft.com/en-us/library/...
— atconway

1
감사합니다. 이것은 사용자 정의 클라이언트 라이브러리를 사용하는 경우 훌륭한 코드입니다. 이 방법으로 메시지 검사기를 구현할 필요가 없습니다. 모든 클라이언트 호출을 OperationContextScope로 래핑하는 공통 래퍼 메소드를 작성하십시오.
— JustAMartin

3
때문에 당신이 당신의 전화와 비동기 물건의 어떤 종류를하고 있다면 참고로,이 문제가 OperationContextScope(하고 OperationContext) 있습니다 ThreadStatic- 마크 좋은 의 대답에 의존하지 않고 작동합니다 ThreadStatic항목.
— zimdanen

2
이것은 HTTP 헤더를 추가하지 않습니다! SOAP 엔벨로프에 헤더를 추가합니다.
— br3nt

32

서비스의 모든 요청에 ​​동일한 헤더를 추가하려는 경우 코딩없이 수행 할 수 있습니다!
클라이언트 구성 파일의 엔드 포인트 노드 아래에 필요한 헤더가있는 헤더 노드 만 추가하십시오.

<client>  
  <endpoint address="http://localhost/..." >  
    <headers>  
      <HeaderName>Value</HeaderName>  
    </headers>   
 </endpoint>  

18
다음은 SOAP 헤더 (있는 알라MessageHeader 하지 HTTP 헤더 -).
— SliverNinja-MSFT

18

다음은 ChannelFactory프록시를 사용하여 클라이언트 WCF 요청에 사용자 지정 HTTP 헤더를 수동으로 추가하는 데 유용한 또 다른 솔루션입니다 . 이것은 각각의 요청에 대해 수행되어야하지만 .NET 이외의 플랫폼을 준비하기 위해 프록시를 단위 테스트해야하는 경우 간단한 데모로 충분합니다.

// create channel factory / proxy ...
using (OperationContextScope scope = new OperationContextScope(proxy))
{
    OperationContext.Current.OutgoingMessageProperties[HttpRequestMessageProperty.Name] = new HttpRequestMessageProperty()
    {
        Headers = 
        { 
            { "MyCustomHeader", Environment.UserName },
            { HttpRequestHeader.UserAgent, "My Custom Agent"}
        }
    };    
    // perform proxy operations... 
}

1
나는 비슷한 모양의 제안 4 가지를 시도했으며 이것이 나를 위해 유일하게 제안 한 것입니다.
— JohnOpincar

이것은 실제로 HTTP 헤더를 추가합니다. 감사합니다! :) 그러나 추악한 코드입니다.
— br3nt

11

이것은 NimsDotNet 답변과 비슷하지만 프로그래밍 방식으로 수행하는 방법을 보여줍니다.

바인딩에 헤더를 추가하기 만하면됩니다.

var cl = new MyServiceClient();

var eab = new EndpointAddressBuilder(cl.Endpoint.Address);

eab.Headers.Add( 
      AddressHeader.CreateAddressHeader("ClientIdentification",  // Header Name
                                         string.Empty,           // Namespace
                                         "JabberwockyClient"));  // Header Value

cl.Endpoint.Address = eab.ToEndpointAddress();

이 코드를 현재 호출 (클라이언트 측)에 추가했습니다. System.ServiceModel.OperationContext 에서이 헤드 값을 얻는 방법은 무엇입니까? (서버 쪽) (이것이 나를 도울 것입니다 내 손가락을 교차 해요)
— granadaCoder

1
알았다 ! System.ServiceModel.Channels.MessageHeaders 헤더 = operationContext.RequestContext.RequestMessage.Headers; int headerIndex = headers.FindHeader ( "ClientIdentification", string.Empty); var requestName = (headerIndex <0)? "UNKNOWN": headers.GetHeader <string> (headerIndex);
— granadaCoder

1
@granadaCoder 나는 그 사이트를 좋아한다! ;-)
— ΩmegaMan

이렇게하면 HTTP 헤더가 아닌 SOAP 엔벨로프에 헤더가 추가됩니다.
— br3nt

5
var endpoint = new EndpointAddress(new Uri(RemoteAddress),
               new[] { AddressHeader.CreateAddressHeader(
                       "APIKey", 
                       "",
                       "bda11d91-7ade-4da1-855d-24adfe39d174") 
                     });

12
이것은 HTTP 헤더가 아닌 SOAP 메시지 헤더입니다.
— René

3

이것이 WCF 호출에 HTTP 헤더 추가 에서 적응 한 것입니다.

// Message inspector used to add the User-Agent HTTP Header to the WCF calls for Server
public class AddUserAgentClientMessageInspector : IClientMessageInspector
{
    public object BeforeSendRequest(ref System.ServiceModel.Channels.Message request, IClientChannel channel)
    {
        HttpRequestMessageProperty property = new HttpRequestMessageProperty();

        var userAgent = "MyUserAgent/1.0.0.0";

        if (request.Properties.Count == 0 || request.Properties[HttpRequestMessageProperty.Name] == null)
        {
            var property = new HttpRequestMessageProperty();
            property.Headers["User-Agent"] = userAgent;
            request.Properties.Add(HttpRequestMessageProperty.Name, property);
        }
        else
        {
            ((HttpRequestMessageProperty)request.Properties[HttpRequestMessageProperty.Name]).Headers["User-Agent"] = userAgent;
        }
        return null;
    }

    public void AfterReceiveReply(ref System.ServiceModel.Channels.Message reply, object correlationState)
    {
    }
}

// Endpoint behavior used to add the User-Agent HTTP Header to WCF calls for Server
public class AddUserAgentEndpointBehavior : IEndpointBehavior
{
    public void ApplyClientBehavior(ServiceEndpoint endpoint, ClientRuntime clientRuntime)
    {
        clientRuntime.MessageInspectors.Add(new AddUserAgentClientMessageInspector());
    }

    public void AddBindingParameters(ServiceEndpoint endpoint, BindingParameterCollection bindingParameters)
    {
    }

    public void ApplyDispatchBehavior(ServiceEndpoint endpoint, EndpointDispatcher endpointDispatcher)
    {
    }

    public void Validate(ServiceEndpoint endpoint)
    {
    }
}

이러한 클래스를 선언하면 다음과 같이 WCF 클라이언트에 새로운 동작을 추가 할 수 있습니다.

client.Endpoint.Behaviors.Add(new AddUserAgentEndpointBehavior());

컴파일되지 않습니다. 오류 CS0136 'property'라는 로컬 또는 매개 변수를이 범위에서 선언 할 수 없습니다.이 이름은 로컬 또는 매개 변수를 정의하기 위해 포함하는 로컬 범위에서 사용되기 때문입니다.
— Leszek P

사용하지 않는 것을 제거하십시오
— kosnkov

3

이것은 나를 위해 작동

TestService.ReconstitutionClient _serv = new TestService.TestClient();

using (OperationContextScope contextScope = new OperationContextScope(_serv.InnerChannel))
{
   HttpRequestMessageProperty requestMessage = new HttpRequestMessageProperty();

   requestMessage.Headers["apiKey"] = ConfigurationManager.AppSettings["apikey"]; 
   OperationContext.Current.OutgoingMessageProperties[HttpRequestMessageProperty.Name] = 
      requestMessage;
   _serv.Method(Testarg);
}

2

.NET 3.5의 컨텍스트 바인딩 은 원하는 것일 수 있습니다. 기본적으로 BasicHttpContextBinding, NetTcpContextBinding 및 WSHttpContextBinding의 세 가지가 있습니다. 컨텍스트 프로토콜은 기본적으로 메시지 헤더에서 키-값 쌍을 전달합니다. MSDN Magazine의 영구 서비스로 상태 관리 기사를 확인하십시오 .


또한 서버와의 세션을 설정하기 전에 컨텍스트를 한 번만 설정하십시오. 그러면 컨텍스트가 읽기 전용이됩니다. 클라이언트 측에서 컨텍스트 설정을 투명하게하려면 클라이언트 프록시 클래스에서 파생 할 수 있으며 컨스트럭터에서 컨텍스트를 구성하는 정보를 추가 할 수 있습니다. 그러면 클라이언트가 클라이언트 프록시 인스턴스를 생성 할 때마다 컨텍스트가 자동으로 생성되어 클라이언트 프록시 인스턴스에 추가됩니다.
— Mehmet Aras 2016 년

2

귀하의 요구 사항을 올바르게 이해하면 간단한 대답은 다음과 같습니다.

WCF 서비스의 클라이언트는 서비스를 사용하는 타사에서 생성 할 수 있기 때문입니다.

경우 당신이 당신의 서비스의 클라이언트 제어 할 수 있습니다, 당신은 원하는 헤더를 추가하고 작업자 클래스의 동작을 상속 기본 클라이언트 클래스를 생성 할 수 있습니다.


1
실제로 SOA를 구축하는 경우 모든 클라이언트가 .NET 기반이라고 가정 할 수 없습니다. 비즈니스가 인수 될 때까지 기다리십시오.
— SliverNinja-MSFT

2
이것이 사실입니까? Java 웹 서비스 클라이언트가 SOAP 헤더에 이름 / 값을 추가 할 수 없습니까? 나는 그것을 믿기가 어렵다. 물론 다른 구현 일 수도 있지만 이것은 상호 운용 가능한 솔루션입니다.
— Adam

2

MessageContract 에서 사용자 정의 헤더를 지정할 수 있습니다 .

구성 파일에 저장되어 있고 클라이언트 / 서비스가 보낸 모든 메시지의 헤더에 모두 복사되는 <endpoint> 헤더 를 사용할 수도 있습니다 . 이것은 정적 헤더를 쉽게 추가하는 데 유용합니다.


3
다음은 SOAP 헤더 (있는 알라MessageHeader 하지 HTTP 헤더 -).
— SliverNinja-MSFT

0

객체 지향 방식으로 모든 WCF 호출에 사용자 지정 HTTP 헤더를 추가하려면 더 이상 보지 마십시오.

Mark Good 및 paulwhit의 답변에서와 IClientMessageInspector같이 사용자 지정 HTTP 헤더를 WCF 요청에 주입하려면 서브 클래스가 필요합니다 . 그러나 추가하려는 헤더가 포함 된 사전을 수락하여 검사기를보다 일반적으로 만들 수 있습니다.

public class HttpHeaderMessageInspector : IClientMessageInspector
{
    private Dictionary<string, string> Headers;

    public HttpHeaderMessageInspector(Dictionary<string, string> headers)
    {
        Headers = headers;
    }

    public object BeforeSendRequest(ref Message request, IClientChannel channel)
    {
        // ensure the request header collection exists
        if (request.Properties.Count == 0 || request.Properties[HttpRequestMessageProperty.Name] == null)
        {
            request.Properties.Add(HttpRequestMessageProperty.Name, new HttpRequestMessageProperty());
        }

        // get the request header collection from the request
        var HeadersCollection = ((HttpRequestMessageProperty)request.Properties[HttpRequestMessageProperty.Name]).Headers;

        // add our headers
        foreach (var header in Headers) HeadersCollection[header.Key] = header.Value;

        return null;
    }

    // ... other unused interface methods removed for brevity ...
}

Mark Good과 paulwhit의 답변 에서처럼 WCF 클라이언트 에 서브 클래스 IEndpointBehavior를 주입 하려면 서브 클래스가 필요합니다 HttpHeaderMessageInspector.

public class AddHttpHeaderMessageEndpointBehavior : IEndpointBehavior
{
    private IClientMessageInspector HttpHeaderMessageInspector;

    public AddHttpHeaderMessageEndpointBehavior(Dictionary<string, string> headers)
    {
        HttpHeaderMessageInspector = new HttpHeaderMessageInspector(headers);
    }

    public void ApplyClientBehavior(ServiceEndpoint endpoint, ClientRuntime clientRuntime)
    {
        clientRuntime.ClientMessageInspectors.Add(HttpHeaderMessageInspector);
    }

    // ... other unused interface methods removed for brevity ...
}

객체 지향 접근 방식을 완성하는 데 필요한 마지막 부분은 WCF 자동 생성 클라이언트의 하위 클래스를 만드는 것입니다 (저는 Microsoft의 WCF 웹 서비스 참조 안내서를 사용했습니다). WCF 클라이언트를 생성하기 를 사용했습니다).

제 경우에는 API 키를 x-api-key HTML 헤더 .

서브 클래스는 다음을 수행합니다.

  • 필요한 매개 변수를 사용하여 기본 클래스의 생성자를 호출합니다 (제 경우에는 EndpointConfiguration 생성자가 전달할 열거 형이 생성되었습니다-아마도 구현에 없을 것입니다)
  • 모든 요청에 ​​첨부해야 할 헤더를 정의합니다
  • AddHttpHeaderMessageEndpointBehavior클라이언트의 Endpoint행동 에 첨부
public class Client : MySoapClient
{
    public Client(string apiKey) : base(EndpointConfiguration.SomeConfiguration)
    {
        var headers = new Dictionary<string, string>
        {
            ["x-api-key"] = apiKey
        };

        var behaviour = new AddHttpHeaderMessageEndpointBehavior(headers);
        Endpoint.EndpointBehaviors.Add(behaviour);
    }
}

마지막으로 클라이언트를 사용하십시오!

var apiKey = 'XXXXXXXXXXXXXXXXXXXXXXXXX';
var client = new Client (apiKey);
var result = client.SomeRequest()

결과 HTTP 요청은 HTTP 헤더를 포함해야하며 다음과 같습니다.

POST http://localhost:8888/api/soap HTTP/1.1
Cache-Control: no-cache, max-age=0
Connection: Keep-Alive
Content-Type: text/xml; charset=utf-8
Accept-Encoding: gzip, deflate
x-api-key: XXXXXXXXXXXXXXXXXXXXXXXXX
SOAPAction: "http://localhost:8888/api/ISoapService/SomeRequest"
Content-Length: 144
Host: localhost:8888

<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/">
  <s:Body>
    <SomeRequestxmlns="http://localhost:8888/api/"/>
  </s:Body>
</s:Envelope>

-1

파티에 약간 늦었지만 Juval Lowy는 그의 책 과 관련 ServiceModelEx 에서이 정확한 시나리오를 해결합니다. 라이브러리 .

기본적으로 그는 유형 안전 헤더 값을 지정할 수있는 ClientBase 및 ChannelFactory 전문화를 정의합니다. 소스를 다운로드하고 HeaderClientBase 및 HeaderChannelFactory 클래스를 살펴볼 것을 제안합니다.

남자


1
이것은 단지 누군가의 작업을 홍보하는 것 외에는 아무것도 아닙니다. 관련 발췌 / 알고리즘을 추가 (예 : 질문에 답변)하거나 소속 관계를 공개 할 수 있습니까? 그렇지 않으면 이것은 단지 스팸입니다.
— Fund Monica의 소송

나는 그것이 모르는 접근법에 대한 포인터를 통해 누군가에게 대답을하고 있다고 말하고 싶습니다. 추가 링크를 추가해야하는 관련 링크를 제공했습니다. 모두 참고 문헌에 있습니다. 그리고 Juval Lowy가 내가 할 수있는 것보다 더 잘 설명 할 수 있다고 확신합니다. 그게 다야. 나는 Lowy 씨를 만난 적이 없지만 나는 그가 훌륭한 녀석이라고 확신합니다. WCF에 대해 많이 알고 있음 ;-)
— BrizzleOwl

응답 하기 전에 응답 방법을 읽고 아마도 "대상 사이트에 도달 할 수 없거나 영구적으로 오프라인 상태가되는 경우 항상 중요한 링크의 가장 관련성있는 부분을 인용하십시오"라는 섹션을 언급 했기 때문에 더 추가해야합니다 . 귀하의 제휴는 중요하지 않습니다. 답변의 품질 만 있습니다.
— Fund Monica의 소송

좋아. 나는 당신이 아마 내 점수에서 알 수 있듯이 포인트를 위해 그것에 있지 않습니다! 유용한 포인터라고 생각했습니다.
— BrizzleOwl

1
나는 그것이 나쁜 포인터라고 말하는 것이 아닙니다. 나는 그 자체로는 좋은 대답이 아니라고 말하고 있습니다. 그것은 사람들에게 매우 도움이 될 수 있지만 좋은 일이지만, 관련된 클래스에 대한 간단한 설명을 제공하는 대신 그가 사용하는 방법을 설명 할 수 있다면 더 나은 답변이 될 것입니다. 어떤 식 으로든 사이트에 액세스 할 수없는 경우에도 귀하의 답변은 여전히 ​​도움이됩니다.
— Fund Monica의 소송
당사 사이트를 사용함과 동시에 당사의 쿠키 정책과 개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.