Bagaimana cara menambahkan header HTTP khusus untuk setiap panggilan WCF?

162

Saya memiliki layanan WCF yang di-host di Layanan Windows. Klien yang menggunakan layanan ini harus melewati pengidentifikasi setiap kali mereka memanggil metode layanan (karena pengenal itu penting untuk apa yang harus dilakukan metode yang dipanggil). Saya pikir itu ide yang baik untuk meletakkan pengidentifikasi ini ke informasi header WCF.

Jika itu ide yang bagus, bagaimana saya bisa menambahkan pengenal secara otomatis ke informasi header. Dengan kata lain, setiap kali pengguna memanggil metode WCF, pengidentifikasi harus ditambahkan secara otomatis ke header.

UPDATE: Klien yang menggunakan layanan WCF adalah aplikasi Windows dan aplikasi Windows Mobile (menggunakan Compact Framework).

mrtaikandi
sumber
1
Apakah Anda dapat menyelesaikan masalah Anda?
Mark Good
Apakah Anda akhirnya mendapatkan ini berfungsi pada Compact Framework?
Vaccano

Jawaban:

185

Keuntungannya adalah ini diterapkan untuk setiap panggilan.

Buat kelas yang mengimplementasikan IClientMessageInspector . Dalam metode BeforeSendRequest, tambahkan tajuk khusus Anda ke pesan keluar. Mungkin terlihat seperti ini:

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

Kemudian buat perilaku titik akhir yang menerapkan inspektur pesan ke runtime klien. Anda bisa menerapkan perilaku melalui atribut atau melalui konfigurasi menggunakan elemen ekstensi perilaku.

Berikut adalah contoh yang bagus tentang cara menambahkan header agen-pengguna HTTP ke semua pesan permintaan. Saya menggunakan ini di beberapa klien saya. Anda juga dapat melakukan hal yang sama di sisi layanan dengan menerapkan IDispatchMessageInspector .

Apakah ini yang ada dalam pikiran Anda?

Pembaruan: Saya menemukan daftar fitur WCF yang didukung oleh kerangka kerja yang ringkas. Saya percaya inspektur pesan diklasifikasikan sebagai 'Channel Perluasan' yang menurut posting ini, yang didukung oleh kerangka kompak.

Tandai Baik
sumber
2
@ Mark, Ini jawaban yang sangat bagus. Terima kasih. Saya sudah mencoba ini melalui net.tcp tetapi saya langsung menggunakan koleksi Header (Header Http tidak berfungsi). Saya mendapatkan Header dengan token (Nama) saya di di acara ServiceHost AfterReceiveRequest, tetapi bukan nilainya (sepertinya tidak ada properti untuk nilainya?). Apakah ada sesuatu yang saya lewatkan? Saya akan mengharapkan pasangan nama / nilai seperti ketika saya membuat header itu meminta saya untuk: request.Headers.Add (MessageHeader.CreateHeader (nama, ns, nilai));
Program.X
13
+1 OutgoingMessagePropertiesadalah apa yang Anda perlukan untuk mengakses HTTP Header - bukan OutgoingMessageHeadersheader SOAP.
SliverNinja - MSFT
1
Cukup, Kode Luar Biasa! :)
abhilashca
3
Ini hanya memungkinkan agen pengguna hardcoded, yang - sesuai dengan contoh yang diberikan - adalah hardcoded di web.config!
KristianB
1
Ini jawaban yang sangat bagus. Ini juga menangani kasus ketika HttpRequestMessageProperty.Name belum tersedia di properti pesan. Untuk beberapa alasan, men-debug kode saya, saya menyadari bahwa tergantung pada beberapa masalah waktu, nilai ini tidak selalu ada. Mark terima kasih!
carlos357
80

Anda menambahkannya ke panggilan menggunakan:

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
}

Dan kemudian, sisi server Anda ambil menggunakan:

MessageHeaders headers = OperationContext.Current.IncomingMessageHeaders;
string identity = headers.GetHeader<string>("Identity", "http://www.my-website.com");
AgileJon
sumber
5
Terima kasih untuk Anda cuplikan kode. Tetapi dengan ini saya harus menambahkan header setiap kali saya ingin memanggil metode. Saya ingin membuat proses ini transparan. Maksud saya dengan menerapkan sekali, setiap kali pengguna membuat klien layanan dan menggunakan metode, header pelanggan secara otomatis ditambahkan ke pesan.
mrtaikandi
Ini adalah tautan MSDN yang baik dengan contoh untuk memperluas saran yang diberikan dalam jawaban ini: msdn.microsoft.com/en-us/library/…
atconway
1
Terima kasih, ini adalah kode yang luar biasa jika Anda menggunakan pustaka klien khusus. Dengan cara ini Anda tidak perlu mengimplementasikan penginspeksi pesan. Cukup buat metode pembungkus umum yang membungkus setiap panggilan klien di OperationContextScope.
JustAMartin
3
Sebagai catatan, ini bermasalah jika Anda melakukan hal-hal async dengan panggilan Anda, karena OperationContextScope(dan OperationContext) adalah ThreadStatic- Jawaban Mark Good akan bekerja tanpa bergantung pada ThreadStaticitem.
zimdanen
2
Ini tidak menambahkan header HTTP! Ini menambahkan header ke amplop SOAP.
br3nt
32

Jika Anda hanya ingin menambahkan tajuk yang sama ke semua permintaan ke layanan, Anda dapat melakukannya tanpa kode apa pun!
Cukup tambahkan node header dengan header yang diperlukan di bawah node titik akhir di file konfigurasi klien Anda

<client>  
  <endpoint address="http://localhost/..." >  
    <headers>  
      <HeaderName>Value</HeaderName>  
    </headers>   
 </endpoint>  
Nimesh Madhavan
sumber
18
Ini adalah SOAP Header ( alaMessageHeader ) - bukan HTTP Header.
SliverNinja - MSFT
18

Berikut ini adalah solusi lain yang bermanfaat untuk menambahkan Header HTTP khusus ke permintaan WCF klien Anda menggunakan ChannelFactorysebagai proxy. Ini harus dilakukan untuk setiap permintaan, tetapi cukup sebagai demo sederhana jika Anda hanya perlu menguji unit proxy Anda dalam persiapan untuk platform non-.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... 
}
SliverNinja - MSFT
sumber
1
Saya mencoba 4 saran serupa lainnya dan ini adalah satu-satunya yang bekerja untuk saya.
JohnOpincar
Ini sebenarnya menambah header HTTP, terima kasih! :) Tapi ya ampun itu kode yang terlihat jelek.
br3nt
11

Ini mirip dengan jawaban NimsDotNet tetapi menunjukkan cara melakukannya secara terprogram.

Cukup tambahkan header ke penjilidan

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();
GamegaMan
sumber
Saya mendapatkan kode ini ditambahkan ke panggilan saya saat ini (sisi klien) .. Bagaimana cara saya mendapatkan nilai kepala ini di System.ServiceModel.OperationContext? (sisi server) (Saya menyilangkan jari saya bahwa ini akan membantu saya)
granadaCoder
1
Mengerti ! Header System.ServiceModel.Channels.MessageHeaders = operationContext.RequestContext.RequestMessage.Headers; int headerIndex = header.FindHeader ("ClientIdentification", string.Empty); var requestName = (headerIndex <0)? "UNKNOWN": header.GetHeader <string> (headerIndex);
granadaCoder
1
@granadaCoder Saya suka situs itu! ;-)
ΩmegaMan
Ini menambahkan header ke amplop SOAP, bukan header HTTP
br3nt
5
var endpoint = new EndpointAddress(new Uri(RemoteAddress),
               new[] { AddressHeader.CreateAddressHeader(
                       "APIKey", 
                       "",
                       "bda11d91-7ade-4da1-855d-24adfe39d174") 
                     });
shepcom
sumber
12
Ini adalah header pesan SOAP, bukan header HTTP.
René
3

Inilah yang berhasil bagi saya, diadaptasi dari Menambahkan HTTP Header ke Panggilan WCF

// 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)
    {
    }
}

Setelah mendeklarasikan kelas-kelas ini, Anda dapat menambahkan perilaku baru ke klien WCF Anda seperti ini:

client.Endpoint.Behaviors.Add(new AddUserAgentEndpointBehavior());
paulwhit
sumber
Ini tidak dapat dikompilasi: Kesalahan CS0136 Lokal atau parameter bernama 'properti' tidak dapat dideklarasikan dalam lingkup ini karena nama itu digunakan dalam lingkup lokal terlampir untuk menentukan lokal atau parameter.
Leszek P
hapus saja yang tidak digunakan
kosnkov
3

Ini bekerja untuk saya

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);
}
Taran
sumber
2

Binding konteks di .NET 3.5 mungkin hanya apa yang Anda cari. Ada tiga dari kotak: BasicHttpContextBinding, NetTcpContextBinding, dan WSHttpContextBinding. Protokol konteks pada dasarnya melewati pasangan nilai kunci di header pesan. Lihat artikel Mengelola Status Layanan Tahan Lama di majalah MSDN.

Mehmet Aras
sumber
Perhatikan juga bahwa Anda hanya menetapkan konteks sekali sebelum membuat sesi dengan server. Maka konteksnya menjadi hanya bisa dibaca. Jika Anda ingin pengaturan konteks menjadi transparan di sisi klien, Anda dapat berasal dari kelas proxt klien dan di konstruktor Anda dapat menambahkan informasi yang membentuk konteks Anda. Kemudian setiap kali klien membuat instance dari proxt klien, konteksnya akan secara otomatis dibuat dan ditambahkan ke instance proxy klien.
Mehmet Aras
2

Jika saya memahami kebutuhan Anda dengan benar, jawaban sederhana adalah: Anda tidak bisa.

Itu karena klien layanan WCF dapat dihasilkan oleh pihak ketiga mana pun yang menggunakan layanan Anda.

JIKA Anda memiliki kendali atas klien layanan Anda, Anda bisa membuat kelas klien dasar yang menambahkan header yang diinginkan dan mewarisi perilaku di kelas pekerja.

Paulo Santos
sumber
1
setuju, jika Anda benar-benar membangun SOA, Anda tidak dapat mengasumsikan bahwa semua klien berbasis .NET. Tunggu sampai bisnis Anda diperoleh.
SliverNinja - MSFT
2
Apakah ini benar? Klien layanan web Java tidak memiliki kemampuan untuk menambahkan nama / nilai ke header SOAP? Saya merasa sulit untuk percaya. Tentu itu akan menjadi implementasi yang berbeda, tetapi ini adalah solusi yang dapat dioperasikan
Adam
2

Anda dapat menentukan header khusus di MessageContract .

Anda juga dapat menggunakan tajuk <endpoint> yang disimpan dalam file konfigurasi dan akan disalin di tajuk semua pesan yang dikirim oleh klien / layanan. Ini berguna untuk menambahkan beberapa tajuk statis dengan mudah.

Philippe
sumber
3
Ini adalah SOAP Header ( alaMessageHeader ) - bukan HTTP Header.
SliverNinja - MSFT
0

Jika Anda ingin menambahkan header HTTP khusus ke setiap panggilan WCF dengan cara yang berorientasi objek, tidak perlu mencari lagi.

Sama seperti dalam jawaban Mark Good dan paulwhit, kita perlu subkelas IClientMessageInspectoruntuk menyuntikkan header HTTP khusus ke dalam permintaan WCF. Namun, mari buat inspektur lebih umum dengan menerima kamus yang berisi tajuk yang ingin kita tambahkan:

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

Sama seperti dalam jawaban Mark Good dan paulwhit, kita perlu subkelas IEndpointBehavioruntuk menyuntikkan HttpHeaderMessageInspectorklien kami ke WCF kami.

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

Bagian terakhir yang diperlukan untuk menyelesaikan pendekatan berorientasi objek kami, adalah membuat subkelas dari klien yang dibuat secara otomatis WCF kami (saya menggunakan Panduan Referensi Layanan Web WCF Microsoft untuk menghasilkan klien WCF).

Dalam kasus saya, saya perlu melampirkan kunci API ke x-api-keyheader HTML.

Subclass melakukan hal berikut:

  • memanggil konstruktor dari kelas dasar dengan parameter yang diperlukan (dalam kasus saya EndpointConfigurationenum dihasilkan untuk masuk ke konstruktor - mungkin implementasi Anda tidak akan memiliki ini)
  • Menentukan header yang harus dilampirkan ke setiap permintaan
  • Menempel perilaku AddHttpHeaderMessageEndpointBehaviorklienEndpoint
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);
    }
}

Akhirnya, gunakan klien Anda!

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

Permintaan HTTP yang dihasilkan harus berisi tajuk HTTP Anda dan terlihat seperti ini:

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>
br3nt
sumber
-1

Agak terlambat ke pesta tetapi Juval Lowy membahas skenario yang tepat ini dalam bukunya dan perpustakaan ServiceModelEx terkait .

Pada dasarnya ia mendefinisikan spesialisasi ClientBase dan ChannelFactory yang memungkinkan menentukan nilai header jenis-aman. Saya suggesst mengunduh sumber dan melihat kelas HeaderClientBase dan HeaderChannelFactory.

John

BrizzleOwl
sumber
1
Ini tidak lebih dari mempromosikan pekerjaan seseorang. Bisakah Anda menambahkan kutipan / algoritma yang relevan - yaitu menjawab pertanyaan - atau mengungkapkan afiliasi yang Anda miliki? Kalau tidak, ini hanya spam yang diinginkan.
Dana Gugatan Monica
Saya akan mengatakan bahwa itu memberi seseorang jawaban dengan cara menunjuk ke pendekatan yang mungkin tidak mereka sadari. Saya telah memberikan tautan yang relevan mengapa saya harus menambahkan lebih banyak? itu semua ada di referensi. Dan saya yakin Juval Lowy dapat menggambarkannya lebih baik daripada yang pernah saya lakukan :-) Adapun afiliasi saya - saya membeli buku itu! Itu dia. Saya belum pernah bertemu Tuan Lowy tapi saya yakin dia orang yang baik. Tahu banyak tentang WCF rupanya ;-)
BrizzleOwl
Anda harus menambahkan lebih banyak karena mungkin Anda membaca Cara Menjawab sebelum menjawab, dan Anda mencatat bagian yang mengatakan "Selalu kutip bagian yang paling relevan dari tautan penting, jika situs target tidak dapat dijangkau atau offline secara permanen." Afiliasi Anda tidak penting. Hanya kualitas jawabannya.
Dana Gugatan Monica
Baik. Saya tidak di dalamnya untuk poin - karena Anda mungkin bisa tahu dari skor saya! Pikir saja itu mungkin pointer yang berguna.
BrizzleOwl
1
Saya tidak mengatakan itu pointer buruk. Saya mengatakan bahwa, dengan sendirinya, itu bukan jawaban yang baik. Ini mungkin sangat membantu orang, dan itu hal yang baik, tetapi jawabannya akan lebih baik jika Anda dapat menggambarkan metode yang digunakannya, daripada memberikan deskripsi yang sangat singkat tentang kelas yang terlibat. Dengan begitu, jika situs tidak dapat diakses - untuk alasan apa pun - jawaban Anda masih membantu.
Dana Gugatan Monica