c# - WCF:发送带有自定义标头的 SOAP 请求
问题描述
我正在开发 WCF 项目。我对 WCF 没有太多经验。
我的目标是从客户端接收 SOAP 请求并传递到另一台主机(我的目标是 Amadeus Web 服务)。
我按照给定链接遵循本教程:https ://weblogs.asp.net/paolopia/handling-custom-soap-headers-via-wcf-behaviors
这是我的代码
自定义行为.cs
using System;
using System.ServiceModel.Configuration;
using System.ServiceModel.Description;
using System.ServiceModel.Dispatcher;
namespace WCFTest
{
[AttributeUsage(AttributeTargets.Class)]
public class CustomBehavior : Attribute, IEndpointBehavior
{
#region IEndpointBehavior Members
public void AddBindingParameters(ServiceEndpoint endpoint, System.ServiceModel.Channels.BindingParameterCollection bindingParameters)
{
}
public void ApplyClientBehavior(ServiceEndpoint endpoint, System.ServiceModel.Dispatcher.ClientRuntime clientRuntime)
{
CustomMessageInspector inspector = new CustomMessageInspector();
clientRuntime.MessageInspectors.Add(inspector);
}
public void ApplyDispatchBehavior(ServiceEndpoint endpoint, System.ServiceModel.Dispatcher.EndpointDispatcher endpointDispatcher)
{
ChannelDispatcher channelDispatcher = endpointDispatcher.ChannelDispatcher;
if (channelDispatcher != null)
{
foreach (EndpointDispatcher ed in channelDispatcher.Endpoints)
{
CustomMessageInspector inspector = new CustomMessageInspector();
ed.DispatchRuntime.MessageInspectors.Add(inspector);
}
}
}
public void Validate(ServiceEndpoint endpoint)
{
}
#endregion
}
public class CustomBehaviorExtensionElement : BehaviorExtensionElement
{
protected override object CreateBehavior()
{
return new CustomBehavior();
}
public override Type BehaviorType
{
get
{
return typeof(CustomBehavior);
}
}
}
}
自定义标头.cs
using System;
using System.ServiceModel.Channels;
using System.Xml;
namespace WCFTest
{
public class CustomHeader : MessageHeader
{
private String _key;
public String Key
{
get
{
return (this._key);
}
}
public CustomHeader(String key)
{
this._key = key;
}
public override string Name
{
get { return (CustomHeaderNames.CustomHeaderName); }
}
public override string Namespace
{
get { return (CustomHeaderNames.CustomHeaderNamespace); }
}
protected override void OnWriteHeaderContents(System.Xml.XmlDictionaryWriter writer, MessageVersion messageVersion)
{
// Write the content of the header directly using the XmlDictionaryWriter
writer.WriteElementString(CustomHeaderNames.KeyName, this.Key);
}
public static CustomHeader ReadHeader(XmlDictionaryReader reader)
{
// Read the header content (key) using the XmlDictionaryReader
if (reader.ReadToDescendant(CustomHeaderNames.KeyName, CustomHeaderNames.CustomHeaderNamespace))
{
String key = reader.ReadElementString();
return (new CustomHeader(key));
}
else
{
return null;
}
}
}
public static class CustomHeaderNames
{
public const String CustomHeaderName = "CustomHeader";
public const String KeyName = "Key";
public const String CustomHeaderNamespace = "http://schemas.devleap.com/CustomHeader";
}
}
CustomMessageInspector.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.ServiceModel;
using System.ServiceModel.Channels;
using System.ServiceModel.Dispatcher;
using System.Web;
using System.Xml;
namespace WCFTest
{
public class CustomMessageInspector : IDispatchMessageInspector, IClientMessageInspector
{
#region Message Inspector of the Service
public object AfterReceiveRequest(ref Message request, IClientChannel channel, InstanceContext instanceContext)
{
// Look for my custom header in the request
Int32 headerPosition = request.Headers.FindHeader(CustomHeaderNames.CustomHeaderName, CustomHeaderNames.CustomHeaderNamespace);
// Get an XmlDictionaryReader to read the header content
XmlDictionaryReader reader = request.Headers.GetReaderAtHeader(headerPosition);
// Read it through its static method ReadHeader
CustomHeader header = CustomHeader.ReadHeader(reader);
// Add the content of the header to the IncomingMessageProperties dictionary
OperationContext.Current.IncomingMessageProperties.Add("key", header.Key);
return null;
}
public void BeforeSendReply(ref Message reply, object correlationState)
{
}
#endregion
#region Message Inspector of the Consumer
public void AfterReceiveReply(ref Message reply, object correlationState)
{
}
public object BeforeSendRequest(ref Message request, IClientChannel channel)
{
// Prepare the request message copy to be modified
MessageBuffer buffer = request.CreateBufferedCopy(Int32.MaxValue);
request = buffer.CreateMessage();
// Simulate to have a random Key generation process
request.Headers.Add(new CustomHeader(Guid.NewGuid().ToString()));
return null;
}
#endregion
}
}
IService1.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
using System.ServiceModel;
using System.ServiceModel.Web;
using System.Text;
namespace WCFTest
{
[ServiceContract]
public interface IService1
{
}
}
服务1.svc
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
using System.ServiceModel;
using System.ServiceModel.Web;
using System.Text;
namespace WCFTest
{
public class Service1 : IService1
{
}
}
网络配置
<?xml version="1.0"?>
<configuration>
<appSettings>
<add key="aspnet:UseTaskFriendlySynchronizationContext" value="true" />
</appSettings>
<system.web>
<compilation debug="true" targetFramework="4.8" />
<httpRuntime targetFramework="4.8"/>
</system.web>
<system.serviceModel>
<extensions>
<behaviorExtensions>
<add name="customBehavior" type="WCF_Test.CustomBehaviorExtensionElement, DevLeap.WCF.Behaviors.Extensions, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" />
</behaviorExtensions>
</extensions>
<services>
<service name="WCF_Test.Service1" behaviorConfiguration="">
<endpoint
address="net.tcp://localhost:35001/ServiceOne/"
binding="netTcpBinding"
contract="WCF_Test.IService1"
behaviorConfiguration="endpointBehavior" />
</service>
</services>
<behaviors>
<endpointBehaviors>
<behavior name="endpointBehavior">
<customBehavior />
</behavior>
</endpointBehaviors>
<serviceBehaviors>
<behavior>
<!-- To avoid disclosing metadata information, set the values below to false before deployment -->
<serviceMetadata httpGetEnabled="true" httpsGetEnabled="true"/>
<!-- To receive exception details in faults for debugging purposes, set the value below to true. Set to false before deployment to avoid disclosing exception information -->
<serviceDebug includeExceptionDetailInFaults="false"/>
</behavior>
</serviceBehaviors>
</behaviors>
<protocolMapping>
<add binding="basicHttpsBinding" scheme="https" />
</protocolMapping>
<serviceHostingEnvironment aspNetCompatibilityEnabled="true" multipleSiteBindingsEnabled="true" />
</system.serviceModel>
<system.webServer>
<modules runAllManagedModulesForAllRequests="true"/>
<!--
To browse web app root directory during debugging, set the value below to true.
Set to false before deployment to avoid disclosing web app folder information.
-->
<directoryBrowse enabled="true"/>
</system.webServer>
</configuration>
现在我的问题是
如何更改默认的 WCF SOAP 标头和正文消息?我的 SOAP 请求应该如下所示:
<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/" xmlns:sec="http://xml.amadeus.com/2010/06/Security_v1" xmlns:link="http://wsdl.amadeus.com/2010/06/ws/Link_v1" xmlns:ses="http://xml.amadeus.com/2010/06/Session_v3" xmlns:pnr="http://xml.amadeus.com/FLIREQ_07_1_1A"> <s:Header xmlns:s="http://schemas.xmlsoap.org/soap/envelope/"> <add:MessageID xmlns:add="http://www.w3.org/2005/08/addressing">305ec189-1f8c-45c4-ba78-a832d6181b48</add:MessageID> <add:Action xmlns:add="http://www.w3.org/2005/08/addressing">http://webservices.amadeus.com/FLIREQ_07_1_1A</add:Action> <add:To xmlns:add="http://www.w3.org/2005/08/addressing">https://nodeA1.test.webservices.amadeus.com/1ASIWGENOM</add:To> <link:TransactionFlowLink xmlns:link="http://wsdl.amadeus.com/2010/06/ws/Link_v1"> <link:Consumer> <link:UniqueID>iMM42S6y6KWQ6LUpZnqA8Q==</link:UniqueID> </link:Consumer> </link:TransactionFlowLink> <sec:Security xmlns:sec="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd"> <sec:UsernameToken xmlns:oas1="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd" oas1:Id="UsernameToken-1"> <sec:Username>myusername</sec:Username> <sec:Nonce EncodingType="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-soap-message-security-1.0#Base64Binary">UKxRL2MOdKz1k1ik1l76tQ==</sec:Nonce> <sec:Password Type="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordDigest">nmgMLy29QGO+gH7zQSOrOmVji8o=</sec:Password> <oas1:Created>2020-07-06T06:08:17.938Z</oas1:Created> </sec:UsernameToken> </sec:Security> <AMA_SecurityHostedUser xmlns="http://xml.amadeus.com/2010/06/Security_v1"> <UserID POS_Type="1" PseudoCityCode="ULNOM0101" RequestorType="U" /> </AMA_SecurityHostedUser> </s:Header> <s:Body> <Air_FlightInfo xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"> <generalFlightInfo xmlns="http://xml.amadeus.com/FLIREQ_07_1_1A"> <flightDate> <departureDate>160720</departureDate> </flightDate> <companyDetails> <marketingCompany>OM</marketingCompany> </companyDetails> <flightIdentification> <flightNumber>0301</flightNumber> </flightIdentification> </generalFlightInfo> </Air_FlightInfo> </s:Body> </s:Envelope>
目前我的 Service1.svc 是空的。如何调用自定义消息检查器?
Visual Studio 在 web.config 文件上给了我这个错误。Visual Studio 未检测到我创建的自定义扩展。如何解决这个问题?
严重性代码描述项目路径文件行源抑制状态警告元素“行为”具有无效的子元素“自定义行为”。预期的可能元素列表:'clientVia、callbackDebug、callbackTimeouts、clear、clientCredentials、transactedBatching、dataContractSerializer、dispatcherSynchronization、remove、synchronousReceive、webHttp、enableWebScript、endpointDiscovery、soapProcessing'。WCF_Test C:\Users\sambuu-yondon.e\source\repos\AMA_WCF\WCF_Test C:\Users\sambuu-yondon.e\source\repos\AMA_WCF\WCF_Test\Web.config 29 IntelliSense
- 我从 Amadeus 的开发人员站点下载了 WSDL 文件,并将其作为服务参考添加到我的项目中。现在怎么用?WSDL 代理类是我的 SOAP Body 部分。应该包括 Soap:Body 标签。
如果有不清楚的地方,我会给你更多细节。谢谢你。
解决方案
如果要在服务器端添加自定义标头,只需实现 IDispatchMessageInspector 接口即可。
这是我的演示:
public class CustomMessageInspector : IDispatchMessageInspector
{
public object AfterReceiveRequest(ref Message request, IClientChannel channel, InstanceContext instanceContext)
{
MessageHeader header = MessageHeader.CreateHeader("Testrequest", "http://Test", "Test");
OperationContext.Current.IncomingMessageHeaders.Add(header);
Console.WriteLine("request"+request);
return null;
}
public void BeforeSendReply(ref Message reply, object correlationState)
{
MessageHeader header = MessageHeader.CreateHeader("Testreply", "http://Test", "Test");
OperationContext.Current.OutgoingMessageHeaders.Add(header);
Console.WriteLine("reply"+reply);
}
}
CustomMessageInspector 实现了 IDispatchMessageInspector 接口,在获取消息后给消息添加自定义头,在发送消息前也添加自定义头。
[AttributeUsage(AttributeTargets.Interface)]
public class CustomBehavior : Attribute, IContractBehavior
{
public void AddBindingParameters(ContractDescription contractDescription, ServiceEndpoint endpoint, BindingParameterCollection bindingParameters)
{
return;
}
public void ApplyClientBehavior(ContractDescription contractDescription, ServiceEndpoint endpoint, ClientRuntime clientRuntime)
{
return;
}
public void ApplyDispatchBehavior(ContractDescription contractDescription, ServiceEndpoint endpoint, DispatchRuntime dispatchRuntime)
{
dispatchRuntime.MessageInspectors.Add(new CustomMessageInspector());
}
public void Validate(ContractDescription contractDescription, ServiceEndpoint endpoint)
{
return;
}
}
我们将此拦截器添加到服务的行为中。
最后,我们将 Custombehavior 应用到我们的服务中。
更新
我的项目:
namespace Test
{
public class CustomMessageInspector : IDispatchMessageInspector
{
public object AfterReceiveRequest(ref Message request, IClientChannel channel, InstanceContext instanceContext)
{
MessageHeader header = MessageHeader.CreateHeader("Testrequest", "http://Test", "Test");
OperationContext.Current.IncomingMessageHeaders.Add(header);
Console.WriteLine("request"+request);
return null;
}
public void BeforeSendReply(ref Message reply, object correlationState)
{
MessageHeader header = MessageHeader.CreateHeader("Testreply", "http://Test", "Test");
OperationContext.Current.OutgoingMessageHeaders.Add(header);
Console.WriteLine("reply"+reply);
}
}
[AttributeUsage(AttributeTargets.Interface)]
public class CustomBehavior : Attribute, IContractBehavior
{
public void AddBindingParameters(ContractDescription contractDescription, ServiceEndpoint endpoint, BindingParameterCollection bindingParameters)
{
return;
}
public void ApplyClientBehavior(ContractDescription contractDescription, ServiceEndpoint endpoint, ClientRuntime clientRuntime)
{
return;
}
public void ApplyDispatchBehavior(ContractDescription contractDescription, ServiceEndpoint endpoint, DispatchRuntime dispatchRuntime)
{
dispatchRuntime.MessageInspectors.Add(new CustomMessageInspector());
}
public void Validate(ContractDescription contractDescription, ServiceEndpoint endpoint)
{
return;
}
}
class Program
{
[ServiceContract]
[CustomBehavior]
public interface IService1
{
[OperationContract]
string hello();
}
public class Service1 : IService1
{
public string hello()
{
return "hi";
}
}
static void Main(string[] args)
{
// Step 1: Create a URI to serve as the base address.
Uri baseAddress = new Uri("http://localhost:8000/GettingStarted/");
// Step 2: Create a ServiceHost instance.
ServiceHost selfHost = new ServiceHost(typeof(Service1), baseAddress);
try
{
// Step 3: Add a service endpoint.
selfHost.AddServiceEndpoint(typeof(IService1), new BasicHttpBinding(), "CalculatorService");
// Step 4: Enable metadata exchange.
ServiceMetadataBehavior smb = new ServiceMetadataBehavior();
smb.HttpGetEnabled = true;
selfHost.Description.Behaviors.Add(smb);
// Step 5: Start the service.
selfHost.Open();
Console.WriteLine("The service is ready.");
// Close the ServiceHost to stop the service.
Console.WriteLine("Press <Enter> to terminate the service.");
Console.WriteLine();
Console.ReadLine();
selfHost.Close();
}
catch (CommunicationException ce)
{
Console.WriteLine("An exception occurred: {0}", ce.Message);
selfHost.Abort();
}
}
}
}
推荐阅读
- r - 如何根据现有数据在 R 中创建百分比表?
- amazon-web-services - Devops 和 AWS Lambda 函数工具
- video - 在两个单独的接收器上同步视频流
- html - 同一div中的响应式多个三角形CSS
- html - 如何更新/发布/部署网页中不断变化的“常量”部分?
- flutter - ListView 和 BottomNavigationBar 之间的空白
- jquery - 语言切换 - 使用 HTML 而不是 Key
- python - 如何在 Twitter API 上访问特定时间范围内的推文?
- java - 为什么我的数据库连接在尝试执行 JOOQ 生成的 SQL 时关闭?
- omnet++ - 如何在静脉中创建自定义消息