Using an IIS hosted MEX endpoint with an in-process WCF receive location in BizTalk Server

Introduction

Receiving messages and requests using WCF endpoints and receive locations is a very well-known practice that is used in many BizTalk integration projects. These WCF services can be either a published orchestration or using specific input and output schemas. In any of these there are two known messaging patterns that can be used either in-process or isolated. In-process means that the BizTalk server host instance will be responsible for hosting the WCF host and any requests will be directly received by the BizTalk host. Isolated model means that the WCF service host will be hosted inside IIS and a request received by this service will then be processed inside IIS W3WP worker process and once it requires persistence it will be submitted to the BizTalk Message Box database for normal publish and subscribe processing to occur. In this case this request can be picked up by any of the BizTalk processing host instances and hence will traverse the processes boundaries. In this post I will not go any further in describing these two models but I want to concentrate on a specific problem I faced.  

So imagine the following scenario, you want to implement an in-process WCF receive service but you want the MEX endpoint to be published on IIS so you want the consumer of this service to be able to submit specific requests and expect specific responses rather than submitting and receiving Message objects. This can be done easily if you want to host the processing or receiving endpoint inside IIS, so working in isolated mode. But unfortunately this is not supported for in-process. Usually we do this by using the WCF publishing wizard and publish a metadata exchange endpoint only to IIS and point to the receive location required to process the request which is usually hosted inside IIS (isolated). Now if you select an in-process receive location it will publish the endpoint as usual and you will be able to create the MEX WCF service on IIS but when you try to use it you will receive the following error.  

clip_image002

This is normal and expected as the in-process receive location have to be specified using an absolute URL rather than a relative one as you usually would configure an isolated one. Since the URL is absolute the MEX host will try to add it as a new base address. Now the problem is that this address is not supported by IIS. Now if you try to configure the service using the suggested configuration in this error this will not work in any way.  

Solution  

Usually this is not a supported configuration and hence there is no out of the box method to get this to work. Now I wanted this configuration to work so I started reflecting the BizTalk assemblies to figure out the problem.

Now if you open the MEX endpoint service SVC file you will find the following in it.  

<%@ ServiceHost Language="c#" Factory="Microsoft.BizTalk.Adapter.Wcf.Metadata.MexServiceHostFactory, Microsoft.BizTalk.Adapter.Wcf.Runtime, Version=3.0.1.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" %>

So as you can see here it is using a custom host factory to create the WCF service host. This custom host factory is inside an assembly called “Microsoft.BizTalk.Adapter.Wcf.Runtime” that is hosted in the GAC. So I reflected this assembly to see what is happening there and here is what I found inside the CreateServiceHost. 

clip_image003

So as expected, it adds the URL to the list of base addresses if the address of the receive location is in absolute URL format. Now this is correct and needed if the processing endpoint is hosted inside the IIS but since this is hosted inside BizTalk worker process this is not needed and hence we would not need such a line.  

So what I ended up doing is to create new service host factory. I started by thinking of inheriting from the already existing host factory, well you guessed it, this class is sealedL. That leaved me with the only other option which is to create a new class with exactly the same code (reflected) but with these lines removed. So the CreateServiceHost function is now as follows:  

removed. So the CreateServiceHost function is now as follows:

public override ServiceHostBase CreateServiceHost(string constructorString, Uri[] baseAddresses)

{

   BizTalkConfigurationSection bizTalkConfigurationSection = MexServiceHostFactory.GetBizTalkConfigurationSection();

   if (bizTalkConfigurationSection.MexServiceHostFactoryConfiguration.Debug)

   {

        System.Diagnostics.Debugger.Launch();

    }

   if (baseAddresses == null)

   {

        throw new ArgumentNullException("baseAddresses");

   }

   TraceParameters(constructorString, baseAddresses);

   TraceHostingEnvironment();

   Uri uri = baseAddresses[0];

   string receiveLocationName = null;

   Uri publicBaseAddress = null;

   ReceiveLocationMappingCollection receiveLocationMappings = bizTalkConfigurationSection.MexServiceHostFactoryConfiguration.ReceiveLocationMappings;

   if (receiveLocationMappings != null)

   {

        string strService = uri.Segments[uri.Segments.Length - 1];

        string strChannel = uri.Segments[uri.Segments.Length - 2];

        ReceiveLocationMapping mapping = receiveLocationMappings[strChannel.ToLower() + strService.ToLower()];

        if (mapping == null)

        {

            throw new ApplicationException(string.Format("ReceiveLocationMappingNotFound for Service {0} and Channel {1}.", strService, strChannel));

        }

        receiveLocationName = mapping.ReceiveLocationName;

        publicBaseAddress = mapping.PublicBaseAddress;

   }

   ReceiveLocationProxy proxy = !string.IsNullOrEmpty(receiveLocationName) ? ConfigurationInfo.GetReceiveLocation(receiveLocationName) : null;

   if (proxy == null)

   {

        throw new ApplicationException(string.Format("ReceiveLocationForMetadataNotFound {0}", new object[] { receiveLocationName }));

   }

   RLConfig rlConfig = proxy.CreateRLConfig();

   OperationFlow operationFlow = !string.IsNullOrEmpty(proxy.ReceivePortName) ? ConfigurationInfo.GetReceivePortOperationFlow(proxy.ReceivePortName) : OperationFlow.None;

   List<Uri> list = new List<Uri>(baseAddresses);

   /*if (Uri.IsWellFormedUriString(proxy.Address, UriKind.Absolute))

   {

        Uri item = new Uri(proxy.Address);

        list.Add(item);

   }*/

   Type BizTalkServiceInstanceType = typeof(ReceiveLocationMappingCollection).Assembly.GetType("Microsoft.BizTalk.Adapter.Wcf.Runtime.BizTalkServiceInstance");

   object BizTalkServiceInstance = Activator.CreateInstance(BizTalkServiceInstanceType);

   Type MexServiceHostType = typeof(ReceiveLocationMappingCollection).Assembly.GetType("Microsoft.BizTalk.Adapter.Wcf.Metadata.MexServiceHost");

   return (ServiceHost)Activator.CreateInstance(MexServiceHostType, new object[] { operationFlow, rlConfig, publicBaseAddress, BizTalkServiceInstance, list.ToArray() });

}

And as you can see I have commented out the lines to add the receive location URL as a base address. Of course I needed to reflect more classes as they were also sealed and private (or internal) so I could not get my new host factory to compile. I could have used reflection to create objects of these classes but I thought it would be simpler to simply reflect them and add them to the project. These classes were:

· ConfigurationInfo

· ReceiveLocationProxy

Now the last thing I need to do is to change the SVC file of the MEX endpoint service to use the new host factory instead of the out of the box one as follows:

<%@ ServiceHost Language="c#" Factory="<Your namespace here>.MexServiceHostFactory,< your assembly name here>, Version=1.0.0.0, Culture=neutral, PublicKeyToken=<your assembly token here>" %>

Now GAC the DLL and reset IIS (or recycle the app pool) and vola now it is working and you can submit messages to the in-process receive location using a MEX endpoint hosted inside IIS.

clip_image005    

Happy BizTalking.