如何在不修改现有调用的情况下为WCF服务调用传递HTTP头
问题背景
我们有一个调用WCF服务的ASP.NET Web应用,现有代码结构如下:
AjaxWebService.svc.cs
[ServiceBehavior(InstanceContextMode = InstanceContextMode.PerSession, ConcurrencyMode = ConcurrencyMode.Multiple)] [ServiceContract(Namespace = "", SessionMode = SessionMode.Allowed)] [AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Allowed)] private Service m_Service; private Service Service { get { if (m_Service == null) { m_Service = new Service(); } return m_Service; } } [OperationContract] internal Users[] getUsers() { return this.Service.getUsers(); } [OperationContract] internal products[] getproducts() { return this.Service.getproducts(); }
Service.cs
private IServiceClient m_ServiceGateway; private IServiceClient ServiceGateway { get { if (m_ServiceGateway == null) { m_ServiceGateway = new IServiceClient(); } return m_ServiceGateway; } } internal Users[] getUsers() { return this.ServiceGateway.getUsers(this.RoleId, this.Token); } internal products[] getproducts() { return this.ServiceGateway.getproducts(this.RoleId, this.Token); }
IServiceGateway.cs
[OperationContract] List<getUsers> getUsers(int RoleId, string Token); [OperationContract] List<products> getProducts(int RoleId, string Token);
需求与问题
我们希望不修改现有方法签名(比如return this.ServiceGateway.getUsers(this.RoleId, this.Token);)的前提下,给所有WCF服务调用传递自定义HTTP头(如User-Agent和Accept)。
之前尝试在ServiceGateway属性初始化时设置头,但未成功,代码如下:
private IServiceClient m_ServiceGateway; private IServiceClient ServiceGateway { get { if (m_ServiceGateway == null) { m_ServiceGateway = new IServiceClient(); using (new OperationContextScope(m_ServiceGateway.InnerChannel)) { // Set HTTP headers HttpRequestMessageProperty httpRequestProperty = new HttpRequestMessageProperty(); httpRequestProperty.Headers["User-Agent"] = "mrt/1.0 (compatible; MSIE 10.0; Windows NT 6.1; Trident/6.0)"; httpRequestProperty.Headers["Accept"] = "application/json"; OperationContext.Current.OutgoingMessageProperties[HttpRequestMessageProperty.Name] = httpRequestProperty; } } return m_ServiceGateway; } }
想知道:
- 是否存在无需修改现有方法调用就能传递头信息的方式?
- 如何集中为所有WCF服务调用应用自定义头,不用逐个修改API?
解决方案
问题根源
之前的代码失败是因为OperationContextScope用了using块,作用域结束后上下文会被销毁,后续调用服务时这个上下文已经不存在,所以头信息没被传递。
最优方案:WCF客户端消息拦截器
这是最适合集中处理的方案,不用修改任何现有服务调用代码,所有请求都会自动带上自定义头。
1. 实现客户端消息拦截器
public class CustomHeaderMessageInspector : IClientMessageInspector { public object BeforeSendRequest(ref Message request, IClientChannel channel) { HttpRequestMessageProperty httpRequestProperty; // 检查是否已有请求属性,没有则新建 if (request.Properties.ContainsKey(HttpRequestMessageProperty.Name)) { httpRequestProperty = (HttpRequestMessageProperty)request.Properties[HttpRequestMessageProperty.Name]; } else { httpRequestProperty = new HttpRequestMessageProperty(); request.Properties.Add(HttpRequestMessageProperty.Name, httpRequestProperty); } // 添加自定义HTTP头 httpRequestProperty.Headers["User-Agent"] = "mrt/1.0 (compatible; MSIE 10.0; Windows NT 6.1; Trident/6.0)"; httpRequestProperty.Headers["Accept"] = "application/json"; return null; } public void AfterReceiveReply(ref Message reply, object correlationState) { // 不需要处理响应的话留空即可 } }
2. 实现行为扩展类
public class CustomHeaderBehavior : IEndpointBehavior { public void AddBindingParameters(ServiceEndpoint endpoint, BindingParameterCollection bindingParameters) { } public void ApplyClientBehavior(ServiceEndpoint endpoint, ClientRuntime clientRuntime) { // 将拦截器添加到客户端运行时 clientRuntime.ClientMessageInspectors.Add(new CustomHeaderMessageInspector()); } public void ApplyDispatchBehavior(ServiceEndpoint endpoint, EndpointDispatcher endpointDispatcher) { } public void Validate(ServiceEndpoint endpoint) { } }
3. 给WCF客户端配置行为
有两种配置方式:
方式A:代码配置(修改Service.cs中的ServiceGateway属性)
private IServiceClient m_ServiceGateway; private IServiceClient ServiceGateway { get { if (m_ServiceGateway == null) { m_ServiceGateway = new IServiceClient(); // 添加自定义行为 m_ServiceGateway.Endpoint.Behaviors.Add(new CustomHeaderBehavior()); } return m_ServiceGateway; } }
方式B:配置文件(web.config/app.config)
如果偏好配置文件管理,可添加如下配置:
<system.serviceModel> <extensions> <behaviorExtensions> <add name="customHeaderBehavior" type="你的命名空间.CustomHeaderBehavior, 你的程序集名称"/> </behaviorExtensions> </extensions> <behaviors> <endpointBehaviors> <behavior name="CustomHeaderBehavior"> <customHeaderBehavior /> </behavior> </endpointBehaviors> </behaviors> <client> <endpoint address="你的WCF服务地址" binding="basicHttpBinding" contract="IServiceGateway" behaviorConfiguration="CustomHeaderBehavior"/> </client> </system.serviceModel>
备选方案:封装通用服务调用方法
如果不想用拦截器,也可以封装通用方法统一处理头信息,改动极小:
public class ServiceWrapper { private static void SetCustomHeaders(IClientChannel channel) { using (new OperationContextScope(channel)) { HttpRequestMessageProperty httpRequestProperty = new HttpRequestMessageProperty(); httpRequestProperty.Headers["User-Agent"] = "mrt/1.0 (compatible; MSIE 10.0; Windows NT 6.1; Trident/6.0)"; httpRequestProperty.Headers["Accept"] = "application/json"; OperationContext.Current.OutgoingMessageProperties[HttpRequestMessageProperty.Name] = httpRequestProperty; } } public static TResult Execute<TClient, TResult>(Func<TClient, TResult> operation) where TClient : IClientChannel, new() { var client = new TClient(); try { SetCustomHeaders(client); return operation(client); } finally { // 正确释放客户端资源 if (client.State == CommunicationState.Faulted) client.Abort(); else client.Close(); } } }
修改Service.cs中的调用代码:
internal Users[] getUsers() { return ServiceWrapper.Execute<IServiceClient, Users[]>(client => client.getUsers(this.RoleId, this.Token)); } internal products[] getproducts() { return ServiceWrapper.Execute<IServiceClient, products[]>(client => client.getproducts(this.RoleId, this.Token)); }
内容的提问来源于stack exchange,提问作者Shashimk
相关产品推荐
相关产品推荐

