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
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>
Currently my Service1.svc is empty. How to call custom message inspector?
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
If something is unclear, i give you more details. Thank you.
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.
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();
}
}
}
}