모든 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 OutgoingMessagePropertiesOutgoingMessageHeadersSOAP 헤더가 아닌 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.