如何添加一个自定义的 HTTP 头到每个 WCF 调用?

我有一个 WCF 服务托管在一个 Windows 服务。使用此服务的客户端每次调用服务方法时都必须传递一个标识符(因为该标识符对于被调用的方法应该执行的操作非常重要)。我认为以某种方式将这个标识符放到 WCF 头信息中是一个好主意。

如果这是一个好主意,我怎样才能自动添加标识符的标题信息。换句话说,每当用户调用 WCF 方法时,标识符必须自动添加到标头中。

更新: 使用 WCF 服务的客户端既是 Windows 应用程序,也是 WindowsMobile 应用程序(使用 CompactFramework)。

202277 次浏览

将其添加到调用中,使用:

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

上下文 束缚在。NET 3.5可能正是你想要的。现成的有三种: BasicHttpContextBinding、 NetTcpContextBinding 和 WSHttpContextBinding。上下文协议基本上传递消息头中的键-值对。看看 MSDN 杂志上的 使用持久服务管理状态文章。

如果我正确理解了你的要求,简单的答案是: 你不能。

这是因为 WCF 服务的客户端可能由使用您的服务的任何第三方生成。

如果 您已经控制了服务的客户端,那么您可以创建一个基本客户端类来添加所需的头并继承辅助类的行为。

您可以在 信息合同中指定自定义标头。

您还可以使用存储在配置文件中的 < 端点 > 标题,它将被复制到客户机/服务发送的所有消息的头中。这对于轻松地添加一些静态头非常有用。

这样做的好处是可以应用于每个调用。

创建一个实现 信息检查员的类。在 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 用户代理头的一个很好的 例子。我在我的一些客户身上用到了这个。您还可以通过实现 消息检查器在服务端执行相同的操作。

这就是你想要的吗?

更新: 我发现了紧凑框架支持的 WCF 特性的 名单。我相信消息检查员被归类为“通道扩展性”,根据这篇文章,它是由紧凑框架支持的

如果您只想向服务的所有请求添加相同的头部,那么不需要任何编码即可完成!
只需在客户端配置文件的端点节点下添加带有必需头的头节点即可

<client>
<endpoint address="http://localhost/..." >
<headers>
<HeaderName>Value</HeaderName>
</headers>
</endpoint>
var endpoint = new EndpointAddress(new Uri(RemoteAddress),
new[] { AddressHeader.CreateAddressHeader(
"APIKey",
"",
"bda11d91-7ade-4da1-855d-24adfe39d174")
});

下面是另一个有用的解决方案,可以使用 ChannelFactory作为代理手动将自定义 HTTP Header 添加到客户机 WCF 请求中。对于每个请求都必须这样做,但是如果您只需要对代理进行单元测试,以便为非.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...
}

这类似于 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();

虽然有点晚了,但是 Juval Lowy 在他的 和相关的 服务 ModelEx库中解决了这个问题。

基本上,他定义了允许指定类型安全标头值的 ClientBase 和 ChannelFactory 专门化。我建议下载源代码并查看 HeaderClientBase 和 HeaderChannelFactory 类。

约翰

这是什么工作为我,改编自 向 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());

这对我有用

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

如果您希望以面向对象的方式将自定义 HTTP 头添加到每个 WCF 调用中,那么不需要进一步查看。

正如 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 的答案中一样,我们需要子类 IEndpointBehaviorHttpHeaderMessageInspector注入到我们的 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 自动生成客户机的子类(我使用微软的 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>

找到了另一种方法 给你:

SoapServiceClient client = new SoapServiceClient();
using(new OperationContextScope(client.InnerChannel))
{
HttpRequestMessageProperty requestMessage = new HttpRequestMessageProperty();
requestMessage.Headers["MyHttpHeader"] = "MyHttpHeaderValue";
OperationContext.Current.OutgoingMessageProperties[HttpRequestMessageProperty.Name] = requestMessage;
var result = client.MyClientMethod();
}