Service Versioning (WCF Routing)

When we built up service versioning mechanism we need to consider the following items;

– service contract changes
– data contract changes
– service binding changes

I have tried to cover all possible scenarios which consist of various service changes. The ways of satisfying backward compatibility as follows:

1. Service address changes are acceptable

If the service address can be changed, the version management is so easy. All the versions of the service can be hosted different addresses which contains version information of the service.

https://application-address:application-port/Services/TestServiceV1.svc
https://application-address:application-port/Services/TestServiceV2.svc

2. Service address changes are NOT acceptable

In this scenario, we can use WCF Routing mechanism to serve many versions over the same address. We need to have a router service that is placed in the following address.

https://application-address:application-port/Services/TestService.svc

This router service sends requests to TestServiceV1 or TestServiceV2 depends on the filters defined in configuration part of router service. Each client application will insert the filter information into the message header and router will this version-specific information contained in the message header to route the incoming message to the appropriate version of the service.

6

The filter types are as follows:

2.1.    Endpoint Name: A custom message filter for use solely in the Routing Service, which filters messages based on the name of the service endpoint.

2.2.    XPath: Used to match header or body elements of SOAP message to a particular value.

2.2.1.     The version info can be kept in the header

2.2.2.     The username can be placed in header therefore version can be done based on user over a username-version mapping.

2.3.    Action: Used to route based on the SOAP action of a message.

2.4.    And: Used to combine filters before a match can be satisfied.

2.5.    Custom: Used to supply a custom filtering component where you have programmatic control of the filter result.

3. Commercial Products in the Market

There are two major products which are generally called XML gateway or firewall. The first one is that leading product Layer7 XML Gateway (http://www.layer7tech.com/products/industry-leading-xml-gateway-overview). It is java based and running over the Linux server. It cannot be applicable for .NET environment. The second one .Net based product which is called Sentinet produced by Nevatech Inc. (https://www.nevatech.com/sentinet/soa-management). It is extension of Microsoft MSE (Managed Services Engine) framework.

Finally I have decided to keep version information in SOAP message header. WCF Router service will route the messages depends on their SOAP header version value. I have made a solution that can be seen following pic.

5

 

 

Let’s begin with implementation of TestServiceV1. The interface of TestServiceV1:


using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
using System.ServiceModel;
using System.ServiceModel.Web;
using System.Text;

namespace WcfPoc.Services.TestServiceV1
{
 [ServiceContract(Namespace="http://WcfPoc.wcfRouting.int/Increment1")]
 public interface ITestService
 {
 [OperationContract]
 List<Query> GetSavedQueries(string missionCode);
 }

[DataContract]
 public class Query
 {
 [DataMember(Order = 1)]
 public string Name { get; set; }

[DataMember(Order = 2)]
 public string Description { get; set; }

[DataMember(Order = 3)]
 public string TestType { get; set; }
 }
}

The second version of TestServiceV1 contains one more method which is called WriteTestEntities and it uses the TestEntity class that is newly added. Finally we have more than one changes which are service contract and data contract changes.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
using System.ServiceModel;
using System.ServiceModel.Web;
using System.Text;

namespace WcfPoc.Services.TestServiceV2
{
 [ServiceContract(Namespace = "http://WcfPoc.wcfRouting.int/Increment1")]
 public interface ITestService
 {
 [OperationContract]
 List<Query> GetSavedQueries(string missionCode);

[OperationContract]
 bool WriteTestEntities (string missionCode, TestEntity entity);
 }

[DataContract]
 public class Query
 {
 [DataMember(Order = 1)]
 public string Name { get; set; }

[DataMember(Order = 2)]
 public string Description { get; set; }

[DataMember(Order = 3)]
 public string TestType { get; set; }

[DataMember(Order = 3)]
 public string TestTypeIdentifier { get; set; }
 }

[DataContract]
 public class TestEntity
 {
 [DataMember(Order = 1)]
 public string Capacity { get; set; }

[DataMember(Order = 2)]
 public string AirRefuelingType { get; set; }

[DataMember(Order = 3)]
 public string TestType { get; set; }

[DataMember(Order = 3)]
 public string TestTypeIdentifier { get; set; }
 }
}

After implementation of services, we need to host these services. In order to host TestServices I have used console applciation. TestServiceV1 and TestServiceV2 host applications as follows:

Host Application of TestServiceV1


using System;
using System.ServiceModel;
using WcfPoc.Services.TestServiceV1;
using WcfPoc.Host.Common;
using System.ServiceModel.Description;

namespace WcfPoc.Host.TestServiceV1
{
class Program
{
static void Main(string[] args)
{
var host = new ServiceHost(typeof(TestService));
ServiceEndpoint endpoint = host.AddServiceEndpoint(typeof(WcfPoc.Services.TestServiceV1.ITestService),
new BasicHttpBinding("TestServiceBinding"),
"http://localhost:8081/TestV1/TestService");
endpoint.Behaviors.Add(new ServiceMessagLogger());

try
{
host.Open();
Console.WriteLine("TestServiceV1 have started.");
Console.WriteLine("Address: http://localhost:8081/TestV1/TestService.svc");
Console.ReadLine();
host.Close();
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
host.Abort();
Console.ReadLine();
}
}
}
}

Host Application of TestServiceV2

using System;
using System.ServiceModel;
using System.ServiceModel.Description;
using WcfPoc.Host.Common;
using WcfPoc.Services.TestServiceV2;

namespace WcfPoc.Host.TestServiceV2
{
 class Program
 {
 static void Main(string[] args)
 {
 var host = new ServiceHost(typeof(TestService));
 ServiceEndpoint endpoint = host.AddServiceEndpoint(typeof(WcfPoc.Services.TestServiceV2.ITestService),
 new BasicHttpBinding("TestServiceBinding"),
 "http://localhost:8082/TestV2/TestService");
 endpoint.Behaviors.Add(new ServiceMessagLogger());

 try
 {
 host.Open();
 Console.WriteLine("TestServiceV2 have started.");
 Console.WriteLine("Address: http://localhost:8082/TestV2/TestService.svc");
 Console.ReadLine();
 host.Close();
 }
 catch (Exception ex)
 {
 Console.WriteLine(ex.Message);
 host.Abort();
 Console.ReadLine();
 }
 }
 }
}

The TestService versions are not known by the end user. The user just know Router service that I am gonna tell the details. The user knows interfaces of TestService V1 and V2. The users who are using both v1 and v2 send the requests to the same address not to http://localhost:8081/TestV1 or http://localhost:8082/TestV2.

Host Application of Router Service

using System;
using System.ServiceModel;
using System.ServiceModel.Description;
using System.ServiceModel.Routing;
using WcfPoc.Host.Common;

namespace WcfPoc.Host.TestRouterService
{
 class Program
 {
 static void Main(string[] args)
 {
 var host = new ServiceHost(typeof(RoutingService));
 ServiceEndpoint endpoint = host.AddServiceEndpoint(typeof(System.ServiceModel.Routing.IRequestReplyRouter),
 new BasicHttpBinding(),
 "http://localhost:8080/TestService/router");
 endpoint.Behaviors.Add(new ServiceMessagLogger());

 try
 {
 host.Open();
 Console.WriteLine("TestRoutingService have started.");
 Console.WriteLine("Address: http://localhost:8080/TestService.svc");
 Console.ReadLine();
 host.Close();
 }
 catch (Exception ex)
 {
 Console.WriteLine(ex.Message);
 host.Abort();
 Console.ReadLine();
 }
 }
 }
}

As you can see Router service not so much different from TestService, it is implementation of System.ServiceModel.Routing.IRequestReplyRouter interface and hosted in http://localhost:8080/TestService.svc. The tricky point and routing mechanism are in the RouterService configuration file.

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
 <system.serviceModel>
 <services>
 <service name="System.ServiceModel.Routing.RoutingService" 
 behaviorConfiguration="routerConfig">
 <!--<endpoint address="http://localhost:8080/TestService/router"
 binding="basicHttpBinding"
 contract="System.ServiceModel.Routing.IRequestReplyRouter"
 name="reqReplyEndpoint" />-->
 </service>
 </services>
 <behaviors>
 <serviceBehaviors>
 <behavior name="routerConfig">
 <routing filterTableName="RoutingTable" routeOnHeadersOnly="false" />
 <serviceDebug includeExceptionDetailInFaults="true"/>
 </behavior>
 </serviceBehaviors>
 </behaviors>
 <client>
 <endpoint name="TestServiceV1"
 address="http://localhost:8081/TestV1/TestService"
 binding="basicHttpBinding"
 contract="*" />
 <endpoint name="TestServiceV2" 
 address="http://localhost:8082/TestV2/TestService" 
 binding="basicHttpBinding" 
 contract="*" />
 </client>
 <routing>
 <namespaceTable>
 <add prefix="s" namespace="http://schemas.xmlsoap.org/soap/envelope/"/>
 <add prefix="cn" namespace="http://WcfPoc.wcfRouting.int/Increment1"/>
 </namespaceTable>
 <filters>
 <filter name="firstVersionFilter" 
 filterType="XPath" 
 filterData="/s:Envelope/s:Header/cn:Version ='v1.0'"/>
 <filter name="secondVersionFilter" 
 filterType="XPath" 
 filterData="/s:Envelope/s:Header/cn:Version ='v2.0'"/>
 </filters>
 <filterTables>
 <filterTable name="RoutingTable">
 <add filterName="firstVersionFilter" endpointName="TestServiceV1"/>
 <add filterName="secondVersionFilter" endpointName="TestServiceV2"/>
 </filterTable>
 </filterTables>
 </routing>
 </system.serviceModel>
</configuration>

In the filters part there are two filters are defined as firversionFilter and secondVersionFilter. If the request has Version info is equal to v1.0 than the router service will route the request to the TestServiceV1. Also I handle the case that the request doesnt have version info inth request header. In that case the router service will throw an exception to the user. The router service usage scenarios are as follows;

1. TestClientV1 and TestClientV2 are sending request to router service.
3
2. The request has no header info.

4

Have nice coding…

Advertisements

Publish Orchestration as WCF Service

Depending on our SOA solution architectuıre,   sometimes we have decided to communicate with clients using with http or https protocol. In order to client is able to send requests to BizTalk orchestration, we need to expose BizTalk Orchestrations as service (wcf or .asmx) in some SOA scenarios. BizTalk WCF Service Publishing Wizard can help us on this issue.

There is one important point which the assembly of Biztalk project should be placed in GAC. You can find the steps of wizard as follows:

1

2

3

 

“Add a Service Bus endpoint” should be selected if you want to deploy Biztalk project to Cloud.

 

 

4

5

 

BizTalk assembly should also be placed in GAC in order to process BizTalk orchestration correctly.

6

7

8

9

10

12

13

14

15

16

WCF Custom Header

In some business cases, we need to change header of SOAP message that is sent to WCF service. Depending our scenario, we need to override some properties of header such as “security” element which is required with username and password with encryption. Maybe we should add some properties that are required by service provider. Whatever our scenario is that implementing WCF client endpoint behavior provides a solution.The following classes implement IEndpointBehavior interface which enables adding binding parameters and client behavior. IClientMessageInspector also enables interposition of request before it is sent. We can create CustomHeader instance which is derived by MessageHeader (System.ServiceModel.Channels).


public class AddCustomHeaderBehavior : IEndpointBehavior
{

public void AddBindingParameters(ServiceEndpoint endpoint, BindingParameterCollection bindingParameters)
{ }

public void ApplyClientBehavior(ServiceEndpoint endpoint, ClientRuntime clientRuntime)
{
CustomHeaderMessageInspector headerInspector = new CustomHeaderMessageInspector();
clientRuntime.MessageInspectors.Add(headerInspector);
}

public void ApplyDispatchBehavior(ServiceEndpoint endpoint, EndpointDispatcher endpointDispatcher)
{ }

public void Validate(ServiceEndpoint endpoint)
{ }
 }

public class CustomHeaderMessageInspector : IClientMessageInspector
{

  public void AfterReceiveReply(ref Message reply, object correlationState)
  { }

  public object BeforeSendRequest(ref Message request, IClientChannel channel)
  {
    CustomHeader ch = new CustomHeader();
    request.Headers.Add(ch);
    return request;
  }
}

public class CustomHeader : MessageHeader
{
   protected override void OnWriteHeaderContents(XmlDictionaryWriter writer, MessageVersion messageVersion)
   {
    writer.WriteValue(DataContext.KurumKodu);
   }

 public override string Name
 {
    get { return "KurumKodu"; }
 }

 public override string Namespace
 {
   get { return String.Empty; }
 }
}

The service proxy client instance is created and OrdersByCustomer method is invoked without endpoint behavior.

4

The request and response is captured by Fiddler as follows. The result is no header information is passed to server.

5

After adding client endpoint behavior;

1

Viola 🙂 The header information which contains “KurumKodu” element is passed to server.

3

 

Soap Address Location is machine name not domain name(website)

Today I have encountered with a problem which is about soap address location of WCF service WSDL. While we were developing service there were no error about this issue. Whenever service is needed to publish to test environment which is configured with ssl binding, i have relaized that there were some addresses that are not accesible over the internet in wsdl of service. For example soap address location of service should be like that
<soap:address location=”https://uygulamalar.mydomain.com/EbelgeServis/EBelgeServis.svc”/>  instead of  <soap:address location=”https://machinename/EbelgeServis/EBelgeServis.svc”/>.

The address which contains machine name is not resolved by client who are trying to access web service over internet.

To solve this problem, we need to find the solution in IIS hosted web services There may be more than one web site in your IIS. Therefore you need to find your WebsiteId, you can find your websiteId while trying numbers begin from 0.

%systemroot%\system32\cscript.exe //nologo
%systemdrive%\inetpub\adminscripts\adsutil.vbs
enum /w3svc/WebsiteId

1

2

The following code sets the server bindings with name what your domain is.

%systemroot%\system32\cscript.exe //nologo
%systemdrive%\inetpub\adminscripts\adsutil.vbs
set /w3svc/WebsiteId/ServerBindings “:443:uygulamalar.mydomain.com”

3

WCF – Trace Log

While developing BizTalk projects that interact between WCF services, solution of the problem sometimes can be WCF service side. After constructing integration BizTalk and WCF service generally we need to know which message taken by service or what the structure of message is. The message which is sent by BizTalk can be seen using with Administration Console. But what about the message is taken by WCF service. At this point monitoring and tracing of service becomes very important to get runtime information of service.

1. Quick trace

Use the following config in your service config file. The service will log all requests and responses into the messages.svclog file. This file can be opened by service trace viewer tool. You can find more information about tool this link

<system.diagnostics>
 <sources>
 <source name="System.ServiceModel.MessageLogging">
 <listeners>
 <add name="messages"
 type="System.Diagnostics.XmlWriterTraceListener"
 initializeData="c:\logs\messages.svclog">
 </add>
 </listeners>
 </source>
 </sources>
 </system.diagnostics>

<system.serviceModel>

<diagnostics>
 <messageLogging
 logEntireMessage="true"
 logMalformedMessages="true"
 logMessagesAtServiceLevel="true"
 logMessagesAtTransportLevel="true"
 maxMessagesToLog="3000"
 maxSizeOfMessageToLog="2000">
 </messageLogging>
 </diagnostics>

<system.serviceModel>

2. Appfabric trace

Appfabric is great extension of IIS in order to manage and monitor services deployed in IIS. The services shoul be compiled .NET 4.0 or above otherwise Appfabric can’t watch services.
11
Have a look at “Configure WCF and WF for Application” screen, Monitoring section provides ability to write events to database. It also has diagnostic tracing and message logging subsection.
Select WCF tracing level, enter trace file name also enable message logging, enter file name.

22

PS: After sending requests which required to be logged, application pool of service should be stopped for producing svclogfile.

WCF – username password authentication

In asmx (old fashion web service) we could easily create client credentials in code. The authentication information is pushed to soap request’s header. The authentication code can be implemented as follows:

 TestService qs = new TestService ();
 AuthHeader credentials = new AuthHeader ();
 credentials.UserName   = "testUser";
 credentials.Password   = "pass";
 qs.AuthHeaderValue     = credentials; 

We need to write custom validator in WCF in order to validate client’s identity information. In custom validator class, validate method which gives username nad pass to us must be overrided.

// Username and password validator class

using System;
using System.Data;
using System.IdentityModel.Selectors;
using System.ServiceModel;

namespace BTS.Services.ValidationDemo
{
    public class CustomUserNamePasswordValidator : UserNamePasswordValidator
    {
        public override void Validate(string userName, string password)
        {
	       // The authentication logic here
	    }
    }
}

Service configuration

 <behaviors>
      <serviceBehaviors>
        <behavior name="BTS.Services.ValidationDemo.Behavior">
          <serviceMetadata httpGetEnabled="true"/>
          <serviceDebug includeExceptionDetailInFaults="true"/>
          <serviceThrottling maxConcurrentCalls="483647"
            maxConcurrentInstances="483647"
            maxConcurrentSessions="483647"/>
          <serviceCredentials>
            <userNameAuthentication
        userNamePasswordValidationMode="Custom"
        customUserNamePasswordValidatorType="BTS.Services.ValidationDemo.CustomUserNamePasswordValidator, BTS.Services.ValidationDemo"/>
          </serviceCredentials>
        </behavior>
      </serviceBehaviors>
</behaviors>

Additionally, If we should get username and pass info outside of the validator class, they can be taken from security tokens as follows:

// To get username and password inside of service

UserNameSecurityToken securityToken = OperationContext.Current.IncomingMessageProperties.Security.IncomingSupportingTokens[0].SecurityToken as System.IdentityModel.Tokens.UserNameSecurityToken; 

string username = securityToken.UserName; 
string password = securityToken.Password;

Have good coding…