WCF:使用自定义 header 发送 SOAP 请求

WCF: Send SOAP request with custom header

我正在开发 WCF 项目。我对 WCF 的经验不多。

我的目标是从客户端接收 SOAP 请求并传递到另一台主机(我的目标是 Amadeus Web 服务)。

我按照给出的本教程进行操作 link : https://weblogs.asp.net/paolopia/handling-custom-soap-headers-via-wcf-behaviors

这是我的代码

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

CustomHeader.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
{

}
}

Service1.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
   {

   }
}

web.config

    <?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>

现在我的问题是

  1. 如何更改默认的 WCF SOAP Header 和 Body 消息?我的 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>
    
  2. 目前我的Service1.svc是空的。如何调用自定义消息检查器?

  3. Visual Studio 在 web.config 文件上给我这个错误。 Visual Studio 未检测到我创建的自定义扩展。如何解决这个问题?

Severity Code Description Project Path File Line Source Suppression State Warning The element 'behavior' has invalid child element 'customBehavior'. List of possible elements expected: '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

  1. 我从 Amadeus 的开发者网站下载了 WSDL 文件并作为服务参考添加到我的项目中。现在怎么用呢? WSDL 代理 class 是我的 SOAP Body 部分。应该包含 Soap:Body 标签。

如果有什么不清楚的地方,我会给你更多的细节。谢谢。

如果要在 server-side 上添加自定义 header,只需实现 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 接口,在获取消息后添加自定义header,发送消息前也添加自定义header

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