すべてのWCF呼び出しにカスタムHTTPヘッダーを追加するにはどうすればよいですか?


162

WindowsサービスでホストされているWCFサービスがあります。このサービスを使用するクライアントは、サービスメソッドを呼び出すたびに識別子を渡す必要があります(その識別子は、呼び出されるメソッドが行うべきことにとって重要であるため)。なんとかしてこの識別子をWCFヘッダー情報に入れるのは良い考えだと思いました。

それが良いアイデアである場合、どのようにして識別子をヘッダー情報に自動的に追加できますか?つまり、ユーザーがWCFメソッドを呼び出すたびに、識別子をヘッダーに自動的に追加する必要があります。

更新: WCFサービスを使用しているクライアントは、WindowsアプリケーションとWindows Mobileアプリケーション(コンパクトフレームワークを使用)の両方です。


1
あなたはあなたの問題を解決することができましたか?
マークグッド

これをCompact Frameworkで動作させることになりましたか?
ヴァッカーノ

回答:


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ヘッダーは機能しませんでした)。ServiceHost AfterReceiveRequestイベントでトークン(名前)を含むヘッダーを取得しますが、値は取得しません(値のプロパティであるように見えませんか?)。何か足りないものはありますか?要求されるヘッダーを作成するときに、名前と値のペアを期待していました:request.Headers.Add(MessageHeader.CreateHeader(name、ns、value));
Program.X 2009

13
+1 OutgoingMessagePropertiesは、HTTPヘッダーにアクセスするために必要なものでOutgoingMessageHeadersあり、SOAPヘッダーではありません。
SliverNinja-MSFT 2012年

1
単に、素晴らしいコード!:)
abhilashca

3
これは、ハードコーディングされたユーザーエージェントのみを許可します。これは、与えられた例によれば、web.configにハードコーディングされています。
KristianB

1
これは素晴らしい答えです。また、HttpRequestMessageProperty.Nameがメッセージプロパティでまだ使用できない場合も処理します。何らかの理由で、コードをデバッグしましたが、タイミングの問題によっては、この値が常に存在するとは限らないことに気付きました。マークありがとう!
carlos357 2018

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

これは、この回答で提供の提案に拡大する例との良好MSDNのリンクです:msdn.microsoft.com/en-us/library/...
atconway

1
おかげで、カスタムクライアントライブラリを使用している場合、これは素晴らしいコードになります。これにより、メッセージインスペクタを実装する必要がなくなります。OperationContextScope内のすべてのクライアント呼び出しをラップする共通のラッパーメソッドを作成するだけです。
JustAMartin 2013

3
ので、あなたは、あなたの呼び出しで非同期のものの任意の並べ替えをやっている場合は注意点としては、これは問題があるOperationContextScope(とOperationContext)しているThreadStatic- マーク・グッドの答えはに依存せずに動作しますThreadStatic項目。
zimdanen 2014

2
これはHTTPヘッダーを追加しません!SOAPエンベロープにヘッダーを追加します。
br3nt

32

サービスへのすべてのリクエストに同じヘッダーを追加したいだけの場合は、コーディングなしでそれを行うことができます!
クライアント構成ファイルのエンドポイントノードの下に、必要なヘッダーを含むヘッダーノードを追加するだけです

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

18
これらはSOAPヘッダー(alaMessageHeader)であり、HTTPヘッダーではありません。
SliverNinja-MSFT 2012年

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 headers = operationContext.RequestContext.RequestMessage.Headers; int headerIndex = headers.FindHeader( "ClientIdentification"、string.Empty); var requestName =(headerIndex <0)?"不明":headers.GetHeader <string>(headerIndex);
granadaCoder 2016

1
@granadaCoderあのサイト大好き!;-)
ΩmegaMan

これにより、HTTPヘッダーではなくヘッダーがSOAPエンベロープに追加されます
br3nt

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

12
これはSOAPメッセージヘッダーであり、HTTPヘッダーではありません。
ルネ

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の3つです。コンテキストプロトコルは基本的に、メッセージヘッダーでキーと値のペアを渡します。チェックアウト耐久性のあるサービスで状態を管理する MSDNの雑誌の記事。


また、サーバーとのセッションを確立する前に、コンテキストを1回だけ設定することにも注意してください。その後、コンテキストは読み取り専用になります。コンテキストの設定をクライアント側で透過的にしたい場合は、クライアントのプロキシクラスから派生させ、コンストラクターでコンテキストを構成する情報を追加できます。その後、クライアントがクライアントプロキシのインスタンスを作成するたびに、コンテキストが自動的に作成され、クライアントプロキシインスタンスに追加されます。
Mehmet Aras、

2

私があなたの要件を正しく理解しているなら、簡単な答えは:あなたはできません。

これは、WCFサービスのクライアントが、サービスを使用するサードパーティによって生成される可能性があるためです。

IFあなたのサービスのクライアントの制御を持っている、あなたが希望ヘッダを追加し、ワーカークラスの振る舞いを継承ベースのクライアントクラスを作成することができます。


1
本当にSOAを構築しているのであれば、すべてのクライアントが.NETベースであると想定することはできません。あなたのビジネスが獲得されるまで待ちます。
SliverNinja-MSFT 2012年

2
これは本当ですか?Java Webサービスクライアントには、SOAPヘッダーに名前/値を追加する機能がありませんか?それを信じるのは難しいと思います。確かに別の実装になりますが、これは相互運用可能なソリューションです
Adam

2

MessageContractでカスタムヘッダーを指定できます

また、構成ファイルに格納され、クライアント/サービスによって送信されるすべてのメッセージのヘッダーに沿ってコピーされる<endpoint>ヘッダーを使用することもできます。これは、静的ヘッダーを簡単に追加するのに役立ちます。


3
これらはSOAPヘッダー(alaMessageHeader)であり、HTTPヘッダーではありません。
SliverNinja-MSFT 2012年

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 Webサービスリファレンスガイドを使用しました)を使用して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

パーティーには少し遅れますが、ジュヴァルロウイは彼のと関連するServiceModelExでこの正確なシナリオを扱いますライブラリます。

基本的に彼は、タイプセーフなヘッダー値を指定できるClientBaseとChannelFactoryの特殊化を定義しています。ソースをダウンロードして、HeaderClientBaseクラスとHeaderChannelFactoryクラスを確認することをお勧めします。

ジョン


1
これは、誰かの仕事を促進することに他なりません。関連する抜粋/アルゴリズムを追加します-つまり質問に答えます-またはあなたが持っている所属を開示しますか?それ以外の場合、これは単なる空想的なスパムです。
モニカの訴訟に資金を提供

それは、彼らが知らないかもしれないアプローチへのポインタによって誰かに答えを与えていると私は言うでしょう。関連リンクを提供しましたが、さらに追加する必要があるのはなぜですか?それはすべて参考文献にあります。そして、ジュヴァルロウイは私がこれまでにできるよりもそれをよりよく説明できると確信しています:-)私の所属については-私は本を購入しました!それでおしまい。私はLowy氏に会ったことがありませんが、彼は素晴らしい人だと確信しています。WCFについてよく知っているようです;-)
BrizzleOwl 2015

回答する前に回答方法を読んで、「対象のサイトにアクセスできない場合や完全にオフラインになる場合に備えて、必ず重要なリンクの最も関連性の高い部分を引用してください」と書かれているため、さらに追加する必要があります。あなたの所属は重要ではありません。答えの質だけです。
モニカの訴訟に資金を提供

いいね。私はポイントには入っていません-あなたはおそらく私のスコアからわかるでしょう!それは便利なポインタかもしれないと思っただけです。
BrizzleOwl 2015

1
それが悪いポインタだと言っているのではありません。それだけでは、良い答えではないと言っています。それは人々を非常に助けるかもしれません、そしてそれは良いことですが、関係するクラスの非常に簡単な説明を与えるよりも、彼が使う方法を説明できれば答えはより良いでしょう。そうすれば、何らかの理由でサイトにアクセスできない場合でも、回答が役に立ちます。
モニカの訴訟に資金を提供する
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.