Làm cách nào để thêm tiêu đề HTTP tùy chỉnh vào mỗi cuộc gọi WCF?


162

Tôi có dịch vụ WCF được lưu trữ trong Dịch vụ Windows. Các khách hàng sử dụng dịch vụ này phải vượt qua một mã định danh mỗi khi họ gọi các phương thức dịch vụ (bởi vì định danh đó rất quan trọng đối với những gì phương thức được gọi nên làm). Tôi nghĩ rằng đó là một ý tưởng tốt để bằng cách nào đó đưa định danh này vào thông tin tiêu đề WCF.

Nếu đó là một ý tưởng tốt, làm thế nào tôi có thể tự động thêm định danh vào thông tin tiêu đề. Nói cách khác, bất cứ khi nào người dùng gọi phương thức WCF, mã định danh phải được tự động thêm vào tiêu đề.

CẬP NHẬT: Các khách hàng đang sử dụng dịch vụ WCF là cả ứng dụng Windows và ứng dụng Windows Mobile (sử dụng Compact Framework).


1
Bạn có thể giải quyết vấn đề của bạn?
Đánh dấu

Bạn đã kết thúc việc này để làm việc trên Compact Framework chưa?
Núi lửa

Câu trả lời:


185

Ưu điểm của việc này là nó được áp dụng cho mọi cuộc gọi.

Tạo một lớp thực hiện IClientMessageInspector . Trong phương thức BeforeSendRequest, thêm tiêu đề tùy chỉnh của bạn vào tin nhắn gửi đi. Nó có thể trông giống như thế này:

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

Sau đó tạo một hành vi điểm cuối áp dụng trình kiểm tra thông báo cho thời gian chạy của máy khách. Bạn có thể áp dụng hành vi thông qua một thuộc tính hoặc thông qua cấu hình bằng cách sử dụng phần tử mở rộng hành vi.

Dưới đây là một ví dụ tuyệt vời về cách thêm tiêu đề tác nhân người dùng HTTP vào tất cả các thông báo yêu cầu. Tôi đang sử dụng điều này trong một số khách hàng của tôi. Bạn cũng có thể làm tương tự ở phía dịch vụ bằng cách triển khai IDispatchMessageInspector .

Đây có phải là những gì bạn có trong tâm trí?

Cập nhật: Tôi tìm thấy danh sách các tính năng WCF này được hỗ trợ bởi khung nhỏ gọn. Tôi tin rằng các thanh tra tin nhắn được phân loại là 'Khả năng mở rộng kênh', theo bài đăng này, được hỗ trợ bởi khung nhỏ gọn.


2
@Mark, Đây là một câu trả lời thực sự tuyệt vời. Cảm ơn. Tôi đã thử điều này qua net.tcp nhưng đang sử dụng trực tiếp bộ sưu tập Tiêu đề (Tiêu đề http không hoạt động). Tôi nhận được một Tiêu đề có mã thông báo (Tên) trong sự kiện Servicehost AfterReceiveRequest, nhưng không phải là giá trị (thậm chí dường như không phải là một tài sản cho một giá trị?). Có cái gì tôi đang thiếu? Tôi đã mong đợi một cặp tên / giá trị như khi tôi tạo tiêu đề, nó yêu cầu tôi: request.Headers.Add (MessageHeader.CreateHeader (name, ns, value));
Chương trình.X

13
+1 OutgoingMessagePropertieslà những gì bạn cần để truy cập Tiêu đề HTTP - không phải OutgoingMessageHeaderslà tiêu đề SOAP.
SliverNinja - MSFT

1
Đơn giản, Mã tuyệt vời! :)
abhilashca

3
Điều này chỉ cho phép một tác nhân người dùng được mã hóa cứng, mà - theo ví dụ đã cho - được mã hóa cứng trong web.config!
KristianB

1
Đây là một câu trả lời tuyệt vời. Nó cũng xử lý trường hợp này khi httpRequestMessageProperty.Name chưa có sẵn trong các thuộc tính thông báo. Vì một số lý do, gỡ lỗi mã của tôi, tôi nhận ra rằng tùy thuộc vào một số vấn đề về thời gian, giá trị này không phải lúc nào cũng có. Cảm ơn Mark!
carlos357

80

Bạn thêm nó vào cuộc gọi bằng cách sử dụng:

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
}

Và sau đó, phía máy chủ bạn lấy nó bằng cách sử dụng:

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

5
Cảm ơn bạn đã mã-đoạn. Nhưng với điều này, tôi phải thêm tiêu đề mỗi lần tôi muốn gọi một phương thức. Tôi muốn làm cho quá trình này minh bạch. Ý tôi là với việc thực hiện một lần, mỗi khi người dùng tạo một ứng dụng khách dịch vụ và sử dụng một phương thức, tiêu đề khách hàng sẽ tự động thêm vào thông báo.
mrtaikandi

Đây là một liên kết MSDN tốt với một ví dụ để mở rộng về đề xuất được cung cấp trong câu trả lời này: msdn.microsoft.com/en-us/l
Library / trộm

1
Cảm ơn, đây là một đoạn mã tuyệt vời nếu bạn đang sử dụng thư viện máy khách tùy chỉnh. Bằng cách này, bạn không cần phải thực hiện trình thông báo. Chỉ cần tạo một phương thức trình bao bọc chung để kết thúc mọi cuộc gọi của khách hàng trong trhe ActivityContextScope.
JustAMartin

3
Lưu ý, đây là vấn đề nếu bạn đang thực hiện bất kỳ loại nội dung không đồng bộ nào với các cuộc gọi của mình, bởi vì OperationContextScope(và OperationContext) là ThreadStatic- Câu trả lời của Mark Good sẽ hoạt động mà không cần dựa vào ThreadStaticcác mục.
zimdanen

2
Điều này không thêm tiêu đề HTTP! Nó thêm các tiêu đề vào phong bì SOAP.
br3nt

32

Nếu bạn chỉ muốn thêm cùng một tiêu đề cho tất cả các yêu cầu cho dịch vụ, bạn có thể thực hiện nó với bất kỳ mã hóa nào!
Chỉ cần thêm nút tiêu đề với các tiêu đề cần thiết dưới nút điểm cuối trong tệp cấu hình máy khách của bạn

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

18
Đây là các Tiêu đề SOAP ( alaMessageHeader ) - không phải Tiêu đề HTTP.
SliverNinja - MSFT

18

Dưới đây là một giải pháp hữu ích khác để thêm Thủ công HTTP tùy chỉnh vào yêu cầu WCF khách của bạn bằng cách sử dụng ChannelFactorylàm proxy. Điều này sẽ phải được thực hiện cho từng yêu cầu, nhưng là một bản demo đơn giản nếu bạn chỉ cần đơn vị kiểm tra proxy của mình để chuẩn bị cho các nền tảng không phải .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
Tôi đã thử 4 gợi ý tương tự khác và đây là gợi ý duy nhất phù hợp với tôi.
JohnOpincar

Điều này thực sự thêm tiêu đề HTTP, cảm ơn! :) Nhưng đó là mã trông xấu xí.
br3nt

11

Điều này tương tự như câu trả lời của NimsDotNet nhưng chỉ ra cách thực hiện theo chương trình.

Chỉ cần thêm tiêu đề vào liên kết

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

Tôi đã nhận được mã này được thêm vào cuộc gọi hiện tại của mình (phía máy khách) .. Làm cách nào để tôi nhận được giá trị đầu này trong System.ServiceModel.OperationContext? (phía máy chủ) (Tôi đang vượt qua các ngón tay của mình rằng điều này sẽ giúp tôi)
granadaCoder

1
Hiểu rồi ! System.ServiceModel.Channels.MessageHeaders headers = operContext.RequestContext.RequestMessage.Headers; int headerIndex = headers.FindHeader ("ClientIdentification", string.Empty); var requestName = (tiêu đề <0)? "UNKNOWN": tiêu đề.GetHhead <chuỗi> (tiêu đề);
granadaCoder

1
@granadaCoder Tôi thích trang web đó! ;-)
ΩmegaMan

Điều này thêm một tiêu đề vào phong bì SOAP, không phải tiêu đề HTTP
br3nt

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

12
Đây là tiêu đề thư SOAP, không phải tiêu đề HTTP.
René

3

Đây là những gì làm việc cho tôi, được điều chỉnh từ Thêm tiêu đề HTTP vào các cuộc gọi 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)
    {
    }
}

Sau khi khai báo các lớp này, bạn có thể thêm hành vi mới vào ứng dụng khách WCF của mình như thế này:

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

Điều này sẽ không được biên dịch: Lỗi CS0136 Một địa phương hoặc tham số có tên 'property' không thể được khai báo trong phạm vi này vì tên đó được sử dụng trong phạm vi cục bộ kèm theo để xác định một tham số cục bộ hoặc tham số.
Leszek P

chỉ cần xóa cái không được sử dụng
kosnkov

3

Điều này làm việc cho tôi

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

Các ràng buộc bối cảnh trong .NET 3.5 có thể chỉ là những gì bạn đang tìm kiếm. Có ba trong số các hộp: BasicHttpContextBinding, NetTcpContextBinding và WSHttpContextBinding. Giao thức bối cảnh về cơ bản vượt qua các cặp khóa-giá trị trong tiêu đề thư. Kiểm tra bài viết Quản lý trạng thái với dịch vụ lâu bền trên tạp chí MSDN.


Cũng lưu ý rằng bạn chỉ đặt bối cảnh một lần trước khi thiết lập phiên với máy chủ. Sau đó, bối cảnh trở nên chỉ đọc. Nếu bạn muốn thiết lập bối cảnh trong suốt ở phía máy khách, bạn có thể xuất phát từ lớp proxt của máy khách và trong trình điều khiển, bạn có thể thêm thông tin tạo nên ngữ cảnh của mình. Sau đó, mỗi khi máy khách tạo một thể hiện của proxt máy khách, ngữ cảnh sẽ được tự động tạo và thêm vào thể hiện proxy máy khách.
Mehmet Aras

2

Nếu tôi hiểu đúng yêu cầu của bạn, câu trả lời đơn giản là: bạn không thể.

Đó là bởi vì khách hàng của dịch vụ WCF có thể được tạo bởi bất kỳ bên thứ ba nào sử dụng dịch vụ của bạn.

NẾU bạn có quyền kiểm soát các máy khách của dịch vụ của mình, bạn có thể tạo một lớp máy khách cơ sở để thêm tiêu đề mong muốn và kế thừa hành vi trên các lớp worker.


1
đồng ý, nếu bạn thực sự xây dựng SOA, bạn không thể cho rằng tất cả các máy khách đều dựa trên .NET. Chờ cho đến khi doanh nghiệp của bạn có được.
SliverNinja - MSFT

2
Điều này có thực sự đúng không? Các máy khách dịch vụ web Java không có khả năng thêm tên / giá trị vào các tiêu đề SOAP? Tôi thấy khó tin. Chắc chắn đó sẽ là một triển khai khác, nhưng đây là một giải pháp có thể tương tác
Adam

2

Bạn có thể chỉ định các tiêu đề tùy chỉnh trong MessageContract .

Bạn cũng có thể sử dụng các tiêu đề <endpoint> được lưu trữ trong tệp cấu hình và sẽ được sao chép allong trong tiêu đề của tất cả các tin nhắn được gửi bởi máy khách / dịch vụ. Điều này là hữu ích để thêm một số tiêu đề tĩnh dễ dàng.


3
Đây là các Tiêu đề SOAP ( alaMessageHeader ) - không phải Tiêu đề HTTP.
SliverNinja - MSFT

0

Nếu bạn muốn thêm các tiêu đề HTTP tùy chỉnh vào mỗi cuộc gọi WCF theo cách hướng đối tượng, không tìm đâu xa.

Giống như trong câu trả lời của Mark Good và paulwhit, chúng ta cần phân lớp IClientMessageInspectorđể đưa các tiêu đề HTTP tùy chỉnh vào yêu cầu WCF. Tuy nhiên, hãy làm cho trình kiểm tra chung chung hơn bằng cách chấp nhận một từ điển có chứa các tiêu đề mà chúng tôi muốn thêm:

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

Giống như trong câu trả lời của Mark Good và paulwhit, chúng ta cần phải phân lớp IEndpointBehaviorđể đưa chúng ta HttpHeaderMessageInspectorvào ứng dụng khách WCF.

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

Phần cuối cùng cần thiết để hoàn thành cách tiếp cận hướng đối tượng của chúng tôi, là tạo một lớp con của ứng dụng khách được tạo tự động WCF của chúng tôi (Tôi đã sử dụng Hướng dẫn tham khảo dịch vụ web WCF của Microsoft để tạo ứng dụng khách WCF).

Trong trường hợp của tôi, tôi cần đính kèm khóa API vào x-api-key tiêu đề HTML.

Lớp con thực hiện như sau:

  • gọi hàm tạo của lớp cơ sở với các tham số cần thiết (trong trường hợp của tôi là EndpointConfiguration enum được tạo để truyền vào hàm tạo - có thể việc triển khai của bạn sẽ không có điều này)
  • Xác định các tiêu đề nên được đính kèm với mọi yêu cầu
  • Gắn liền AddHttpHeaderMessageEndpointBehaviorvới Endpointhành vi của khách hàng
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);
    }
}

Cuối cùng, sử dụng khách hàng của bạn!

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

Yêu cầu HTTP kết quả phải chứa các tiêu đề HTTP của bạn và trông giống như thế này:

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

Đến bữa tiệc muộn một chút nhưng Juval Lowy giải quyết tình huống chính xác này trong cuốn sách của anh ấy và thư viện ServiceModelEx liên quan .

Về cơ bản, ông xác định các chuyên môn của ClientBase và ChannelFactory cho phép chỉ định các giá trị tiêu đề an toàn loại. Tôi tìm cách tải xuống nguồn và xem các lớp HeaderClientBase và HeaderChannelFactory.

John


1
Điều này là không có gì nhiều ngoài việc thúc đẩy công việc của một ai đó. Bạn có thể thêm một đoạn trích / thuật toán có liên quan - tức là trả lời câu hỏi - hoặc tiết lộ bất kỳ liên kết nào bạn có? Nếu không thì đây chỉ là thư rác.
Vụ kiện của Quỹ Monica

Tôi sẽ nói rằng nó sẽ cho ai đó một câu trả lời bằng cách đưa con trỏ đến một cách tiếp cận mà họ có thể không biết. Tôi đã đưa ra các liên kết có liên quan tại sao tôi cần phải thêm nhiều hơn? đó là tất cả trong tài liệu tham khảo. Và tôi chắc chắn rằng Juval Lowy có thể mô tả nó tốt hơn tôi từng làm :-) Về phần liên kết của tôi - Tôi đã mua cuốn sách! Đó là nó. Tôi chưa bao giờ gặp Mr Lowy nhưng tôi chắc chắn anh ấy là một người tuyệt vời. Biết rất nhiều về WCF rõ ràng ;-)
Bri MuffOwl

Bạn nên thêm nhiều hơn vì có lẽ bạn đã đọc Cách trả lời trước khi trả lời và bạn lưu ý phần có nội dung "Luôn trích dẫn phần có liên quan nhất của một liên kết quan trọng, trong trường hợp trang đích không thể truy cập được hoặc ngoại tuyến vĩnh viễn." Liên kết của bạn không quan trọng. Chỉ có chất lượng của câu trả lời là.
Vụ kiện của Quỹ Monica

Khỏe. Tôi không ở trong đó cho điểm - như bạn có thể nói từ điểm của tôi! Chỉ cần nghĩ rằng nó có thể là một con trỏ hữu ích.
Bri MuffOwl

1
Tôi không nói đó là một con trỏ xấu. Tôi đang nói rằng, về bản thân nó, nó không phải là một câu trả lời tốt. Nó có thể giúp ích rất nhiều cho mọi người, và đó là một điều tốt, nhưng câu trả lời sẽ tốt hơn nếu bạn có thể mô tả phương pháp anh ta sử dụng, thay vì mô tả rất ngắn gọn về các lớp liên quan. Bằng cách đó, nhân dịp trang web không thể được truy cập - vì bất kỳ lý do gì - câu trả lời của bạn vẫn có ích.
Vụ kiện của Quỹ Monica
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.