Як додати користувацький HTTP-заголовок до кожного дзвінка WCF?


162

У мене є служба WCF, яка розміщується в службі Windows. Клієнти, які користуються цією послугою, повинні передавати ідентифікатор щоразу, коли вони викликають методи обслуговування (тому що цей ідентифікатор важливий для того, що повинен робити викликаний метод). Я думав, що це гарна ідея якось поставити цей ідентифікатор до інформації заголовка WCF.

Якщо це гарна ідея, як я можу автоматично додати ідентифікатор до інформації заголовка. Іншими словами, кожен раз, коли користувач викликає метод WCF, ідентифікатор повинен бути автоматично доданий до заголовка.

ОНОВЛЕННЯ: Клієнтами, які користуються послугою WCF, є як програми Windows, так і програма Windows Mobile (використовуючи Compact Framework).


1
Чи змогли ви вирішити свою проблему?
Добре

Ви в кінцевому підсумку змусили це працювати над Компактною рамкою?
Ваккано

Відповіді:


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, які підтримуються компактною рамкою. Я вважаю , що повідомлення інспекторів класифікуються як «Channel розширюваність» , який, згідно з цим повідомленням, які підтримуються в компактній структурі.


2
@ Марк, це справді чудова відповідь. Дякую. Я спробував це через net.tcp, але використовую колекцію заголовків безпосередньо (заголовки Http не спрацювали). Я отримую заголовка зі своїм маркером (ім'ям) у події ServiceHost AfterReceiveRequest, але не значення (навіть не здається, що властивість має значення?). Щось мені не вистачає? Я б очікував, що пара імен / значень, як коли я створюю заголовок, запитує мене: request.Headers.Add (MessageHeader.CreateHeader (ім'я, нс, значення));
Program.X

13
+1 - OutgoingMessagePropertiesце те, що вам потрібно для доступу до заголовків HTTP, а не OutgoingMessageHeadersзаголовків SOAP.
SliverNinja - MSFT

1
Просто, Awesome Code! :)
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

Це хороший посилання MSDN з прикладом для розширення пропозицій, наведених у цій відповіді: msdn.microsoft.com/en-us/library/…
atconway

1
Дякую, це чудовий фрагмент коду, якщо ви використовуєте користувацьку бібліотеку клієнтів. Таким чином, вам не потрібно реалізувати інспектор повідомлень. Просто створіть загальний метод обгортки, який обробляє кожен виклик клієнта в trhe OperationContextScope.
JustAMartin

3
Як зауваження, це проблематично, якщо ви робите якісь асинхронні речі з вашими дзвінками, тому що OperationContextScopeOperationContext) є 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

Ось ще одне корисне рішення для вручну додати користувацькі заголовки HTTP до запиту WCF вашого клієнта, використовуючи ChannelFactoryяк проксі. Це потрібно зробити для кожного запиту, але достатньо простого демо, якщо вам просто потрібно перевірити свій проксі-сервер під час підготовки до не -.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, дякую! :) Але jeez це некрасиво виглядає код.
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? (сторона сервера) (я
схрещую

1
Зрозумів ! System.ServiceModel.Channels.MessageHeaders заголовки = OperationContext.RequestContext.RequestMessage.Headers; int headerIndex = headers.FindHeader ("ClientIdentification", рядок.Empty); var requestName = (headerIndex <0)? "НЕЗНАЧЕНО": headers.GetHeader <string> (headerIndex);
granadaCoder

1
@granadaCoder Мені подобається цей сайт! ;-)
ΩmegaMan

Це додає заголовка до конверта SOAP, а не заголовка HTTP
br3nt

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

12
Це заголовок повідомлення SOAP, а не заголовок HTTP.
Рене

3

Це те, що працювало для мене, адаптоване від додавання заголовків HTTP до дзвінків 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)
    {
    }
}

Після оголошення цих класів ви можете додати нову поведінку до свого клієнта WCF таким чином:

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

Це не компілюється: Помилка CS0136 Локальний або параметр з назвою "властивість" не може бути оголошений у цій області, оскільки це ім'я використовується у локальній області, що додається, для визначення локального або параметра.
Лешек 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.


Також зауважте, що ви встановлюєте контекст лише один раз перед встановленням сеансу з сервером. Тоді контекст стає лише прочитаним. Якщо ви хочете, щоб конфігурація контексту була прозорою на стороні клієнта, ви можете вивести з класу proxt клієнт і в кондукторі ви можете додати інформацію, що становить ваш контекст. Потім кожен раз, коли клієнт створює екземпляр клієнтського проксі, контекст автоматично створюється і додається до екземпляра проксі-клієнта.
Мехмет Арас

2

Якщо я правильно розумію Вашу вимогу, проста відповідь: ви не можете.

Це тому, що клієнт послуги WCF може бути створений будь-якою третьою стороною, яка використовує вашу послугу.

Якщо у вас є контроль над клієнтами вашої послуги, ви можете створити базовий клас клієнтів, який додасть потрібний заголовок і успадкує поведінку на класах робітників.


1
погодились, якщо ви справді будуєте SOA, ви не можете припустити, що всі клієнти базуються на .NET. Зачекайте, поки ваш бізнес придбається.
SliverNinja - MSFT

2
Це справді правда? Клієнти веб-служб Java не мають можливості додавати ім’я / значення в заголовки SOAP? Мені важко повірити. Звичайно, це буде інша реалізація, але це сумісне рішення
Адам

2

Ви можете вказати власні заголовки в MessageContract .

Ви також можете використовувати заголовки <endpoint> , які зберігаються у файлі конфігурації і будуть скопійовані в заголовку всіх повідомлень, що надсилаються клієнтом / службою. Це корисно для легкого додавання статичного заголовка.


3
Це заголовки SOAP ( алаMessageHeader ) - не заголовки HTTP.
SliverNinja - MSFT

0

Якщо ви хочете додати спеціальні заголовки HTTP до кожного дзвінка WCF об'єктно-орієнтованим способом, не слід більше шукати.

Так само, як і у відповіді Марка Гуда та 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 ...
}

Так само, як і у відповіді Марка Гуда та paulwhit, нам потрібно підклас, IEndpointBehaviorщоб ввести нас HttpHeaderMessageInspectorу наш клієнт 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 ...
}

Остання частина, необхідна для завершення нашого об'єктно-орієнтованого підходу, - це створення підкласу нашого клієнта, створеного автоматично з використанням 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

Трохи запізнюючись на вечірку, але Юваль Лоуй розглядає цей точний сценарій у своїй книзі та пов’язаній із ним бібліотеці ServiceModelEx .

В основному він визначає спеціалізацію ClientBase і ChannelFactory, що дозволяють вказати безпечні для типу значення заголовків. Я пропоную завантажити джерело та переглянути класи HeaderClientBase та HeaderChannelFactory.

Джон


1
Це майже нічого, крім сприяння чиїйсь роботі. Чи можете ви додати відповідний уривок / алгоритм - тобто відповісти на питання - або розкрити будь-яку приналежність до вас? В іншому випадку це просто придуманий спам.
Фонд позову Моніки

Я б сказав, що це дає комусь відповідь за допомогою вказівника на підхід, якого він може не усвідомлювати. Я дав відповідне посилання, чому мені потрібно додати більше? це все в посиланнях. І я впевнений, що Ювал Лоуї міг би описати це краще, ніж я міг коли-небудь зробити :-) Щодо моєї приналежності - я купив книгу! Це воно. Я ніколи не зустрічав містера Лоуї, але впевнений, що він чудовий хлопець. Мабуть, знає багато про WCF ;-)
BrizzleOwl

Вам слід додати більше, тому що, ймовірно, ви читали, як відповісти, перш ніж відповісти, і ви відзначили розділ, у якому сказано: "Завжди цитуйте найбільш релевантну частину важливого посилання, якщо ви хочете, що цільовий сайт недоступний або виходить постійно в офлайн". Ваша приналежність не важлива. Лише якість відповіді.
Фонд позову Моніки

Чудово. Я не в цьому для балів - як ви, напевно, можете сказати з моєї оцінки! Просто подумав, що це може бути корисним вказівником.
BrizzleOwl

1
Я не кажу, що це поганий покажчик. Я кажу, що сама по собі це не є гарною відповіддю. Це може дуже допомогти людям, і це добре, але відповідь буде кращою, якщо ви зможете описати метод, який він використовує, а не давати дуже короткий опис занять. Таким чином, у випадку, коли з будь-якої причини не можна отримати доступ до сайту, ваша відповідь все ще допомагає.
Фонд позову Моніки
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.