Search code examples
c#web-serviceswcfsoapamadeus

WCF: Send SOAP request with custom header


I'm developing WCF project. I don't have much experience on WCF.

My goal is receive SOAP request from client side and pass into another host (my target is Amadeus Web Service).

I followed this tutorial as given link : https://weblogs.asp.net/paolopia/handling-custom-soap-headers-via-wcf-behaviors

Here's my code

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>

Now my questions are

  1. How to change default WCF SOAP Header and Body message? My SOAP request should be like this as shown below:

    <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. Currently my Service1.svc is empty. How to call custom message inspector?

  3. Visual Studio gives me this error on web.config file. My created custom extension didn't detected by Visual Studio. How to resolve this issue?

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. I downloaded WSDL file from Amadeus' developer site and added into my project as Service Reference. Now how to use it? WSDL proxy class is my SOAP Body part. Should included Soap:Body tag.

If something is unclear, i give you more details. Thank you.


Solution

  • If you want to add a custom header on the server-side, just implement the IDispatchMessageInspector interface.

    Here is my Demo:

      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 implements the IDispatchMessageInspector interface, and adds a custom header to the message after getting the message, and also adds a custom header before sending the message.

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

    We add this interceptor to the behavior of the service.

    enter image description here

    Finally we apply Custombehavior to our service.

    UPDATE

    My project:

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