Using AppFabric Cache in BizTalk

A common requirement in many development scenarios is caching.  In BizTalk implementations, this can be the case, mainly for performance reasons.

As a test I wrote 2 pipelinecomponents that handle 2 common issues with caching inside BizTalk.  These sample components are performing similar tasks to two components of the Codit implementation framework.  This is a framework we use at a lot of our customers.

  1. Code table mappings : limit access to SQL database to perform Code table mappings.  In our framework, this is similar to the Transco component.
  2. Duplicate message : stop messages that come into BizTalk multiple times within a specific timeframe.  In our framework, this is similar to the Double checker component.

CacheHelper

Because both pipelinecomponents use the AppFabric cache, I wrote a small class that takes care of this.

public class CacheHelper : IDisposable
    {
        private string _cacheName = "default";
        private string _region;

        public CacheHelper(string region)
        {
            _region = region;
            CreateRegion(_cacheName,region);
        }

        /// 
        /// Creates a Region in a specified cache.
        /// 
        /// Cache name
        /// Region name
        private void CreateRegion(string cacheName,string region)
        {
            DataCacheFactory dcf=ConnectToCache();

            if (dcf != null)
            {
                DataCache dc=dcf.GetCache(cacheName);
                dc.CreateRegion(region);
            }
        }

        /// 
        /// Connect to a Cache server
        /// 
        /// The Datacache
        private DataCacheFactory ConnectToCache()
        {
            //This can also be kept in a config file
            var config = new DataCacheFactoryConfiguration();
            config.SecurityProperties = new DataCacheSecurity(DataCacheSecurityMode.None, DataCacheProtectionLevel.None);
            config.Servers = new List
            {
                new DataCacheServerEndpoint(Environment.MachineName, 22233)
            };

            return new DataCacheFactory(config);
        }

        ~CacheHelper()
        {
            Dispose(false);
        }

        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }

        protected virtual void Dispose(bool disposing)
        {
        }

        /// 
        /// Gets a value from the cache in the specified region (class level)
        /// 
        /// Key linked to the data
        /// The found data. If null --> not found in the cache
        public string GetLookUpCacheData(string keyValue)
        {
            DataCacheFactory dcf = ConnectToCache();

            var cache = dcf.GetCache(_cacheName);
            string data = cache.Get(keyValue,_region) as string;

            return data;
        }

        /// 
        /// Store a value in the cache
        /// 
        /// Key
        /// Data
        public void StoreLookUpCacheData(string keyValue, object value)
        {
            DataCacheFactory dcf = ConnectToCache();

            var cache = dcf.GetCache(_cacheName);
            cache.Add(keyValue, value, _region);
        }

        /// 
        /// Stores a value in the cache for a specified amount of time
        /// 
        /// Key
        /// Data
        /// Time to keep the data in the cache
        public void StoreLookUpCacheData(string keyValue, object value,TimeSpan expires)
        {
            DataCacheFactory dcf = ConnectToCache();

            var cache = dcf.GetCache(_cacheName);
            cache.Add(keyValue, value, expires, _region);
        }        
    }

This is a very simple implementation that will store values in the default cache and in a specified region.

CodeTable Mapper

Codetable mapping is a very common scenario in BizTalk implementations. In my example we will be translating countrycodes to the country name.
The values are stored in a SQL table. But every time we get a value, we are going to save it to the AppFabric cache.
When we want to get the same value again, we are not going to the database but we will get the stored value from the AppFabric Cache.

 

public IBaseMessage Execute(IPipelineContext pContext, IBaseMessage pInMsg)
        {
            // Set variables
            biztalkMessage = pInMsg;
            XmlReader reader = XmlReader.Create(pInMsg.BodyPart.Data);
            XPathCollection xpaths = new XPathCollection();
            //For this example we are going to use 1 xpath expression
            xpaths.Add(this.XPath);

            ValueMutator vm = new ValueMutator(handleXpathFound);
            pInMsg.BodyPart.Data = new XPathMutatorStream(reader, xpaths, vm);
            return pInMsg;
        }

        private void handleXpathFound(int matchIdx, XPathExpression matchExpr, string origVal, ref string finalVal)
        {
            CacheHelper ch = new CacheHelper("Countries");
            string data = ch.GetLookUpCacheData(origVal);
            if (data == null)
            {
                finalVal = GetCountryFromDB(origVal);
                ch.StoreLookUpCacheData(origVal, finalVal);
            }
            else
                finalVal = ch.GetLookUpCacheData(origVal);
        }

        private string GetCountryFromDB(string countryCode)
        {
            string country = string.Empty;
            SqlConnection conn = null;
            SqlCommand comm = null;

            try
            {
                //Connect to look up database and retrieve the names of the products.
                conn = new SqlConnection("Data Source=(local);Initial Catalog=CacheTest;Integrated Security=SSPI;");
                conn.Open();

                comm = new SqlCommand();
                comm.Connection = conn;
                comm.CommandText = string.Format("SELECT Country FROM Countries WHERE CountryCode='{0}'", countryCode);
                comm.CommandType = CommandType.Text;

                country = (string)comm.ExecuteScalar();
                if(string.IsNullOrEmpty(country))
                    throw new Exception(string.Format("No country found for code {0}",countryCode));
            }
            catch (Exception e)
            {
                throw new Exception(e.Message + e.StackTrace);
            }
            finally
            {
                comm.Dispose();
                conn.Close();
                conn.Dispose();
            }


            return country;
        }

 

Duplicate Message Checker

As a sample scenario I took one I read about a few months ago. BizTalk had to stop messages that come in multiple times within 2 minutes.
So if there are more then 2 minutes between the messages, they should continue.

Normally this would involve a SQL table to store some information and some job to do the cleanup of this table.
But for my example I use AppFabric cache. There you have the option to store something in the cache for a certain timespan.
It is automatically deleted after this period.

 

public Microsoft.BizTalk.Message.Interop.IBaseMessage Execute(IPipelineContext pContext, Microsoft.BizTalk.Message.Interop.IBaseMessage pInMsg)
        {
            //Create hash
            VirtualStream input = new VirtualStream(pInMsg.BodyPart.GetOriginalDataStream());
            MD5 md5 = MD5.Create();
            byte[] hash = md5.ComputeHash(input);
            string hashString = Convert.ToBase64String(hash);

            //check Cache
            CacheHelper ch = new CacheHelper("DuplicateMessages");
            string date=ch.GetLookUpCacheData(hashString);
            if (string.IsNullOrEmpty(date))
            {
                //If not in cache yet, store it --> lifetime is 2 minutes
                ch.StoreLookUpCacheData(hashString, DateTime.Now.ToString(), new TimeSpan(0, 2, 0));
            }
            else
            {
                //Throw error
                throw new ApplicationException(string.Format("Duplicate Message. Already received at {0}",date));
            }

            //Put stream back to beginning
            input.Seek(0, SeekOrigin.Begin);
            return pInMsg;
        }

This makes the implementation very easy and you will not need a SQL table or anything to store the information.
You could say that you can do this with a custom caching solution as well. But what about HA environments with multiple BizTalk servers?
AppFabric is a distributed cache. So it doesn’t mather on which server the message is processed. It will end up in the same cache and will be accessible on all the servers.

Conclusion

As you see, AppFabric caching has some advantages in BizTalk as well. The API is very easy to use and I got this to work quite quickly.

Tim D’haeyer, CODit

 

ESB 2.1 Toolkit Setup

After much digging while trying to have a 100% silent install of BizTalk, I have determined that a silent install of the ESB 2.1 Toolkit is not possible.

I have made it as hands free as I think is possible. The below scripts are designed for a single server installation.

Here is the batch script that needs to be run:

  1: start /w pkgmgr /iu:IIS-ASPNET;IIS-BasicAuthentication;IIS-WindowsAuthentication;IIS-IIS6ManagementCompatibility;IIS-Metabase
  2: msiexec.exe /qn /i "\\BizTalk Server 2010 Enterprise\BizTalk ESB Toolkit 2.1-x64.msi"
  3: "C:\Program Files (x86)\Microsoft Visual Studio 10.0\Common7\IDE\VSIXInstaller.exe" "C:\Program Files (x86)\Microsoft BizTalk ESB Toolkit 2.1\Tools\Itinerary Designer\Microsoft.Practices.Services.Itinerary.DslPackage.vsix" /q /s:Ultimate /v:10.0
  4: move "C:\Program Files (x86)\Microsoft BizTalk ESB Toolkit 2.1\Bin\ESBConfigurationTool.exe.config" "C:\Program Files (x86)\Microsoft BizTalk ESB Toolkit 2.1\Bin\ESBConfigurationTool.exe.config.old"
  5: copy "\\BizTalk Server 2010 Enterprise\BizTalk Server\ESBConfigurationTool.exe.config" "C:\Program Files (x86)\Microsoft BizTalk ESB Toolkit 2.1\Bin\ESBConfigurationTool.exe.config"
  6: echo "Run ESBConfigurationTool.exe and press <Enter> when complete"
  7: pause
  8: "C:\Program Files (x86)\Microsoft BizTalk Server 2010\BTSTask.exe" ImportApp -Package:"C:\Program Files (x86)\Microsoft BizTalk ESB Toolkit 2.1\Microsoft.Practices.ESB.CORE_NOBINDING64.msi" -ApplicationName:Microsoft.Practices.ESB -Overwrite
  9: "C:\Program Files (x86)\Microsoft BizTalk Server 2010\BTSTask.exe" ImportBindings /Source:"\\BizTalk Server 2010 Enterprise\BizTalk Server\Microsoft.Practices.ESB.CORE_Bindings.xml" -Application:Microsoft.Practices.ESB
 10: "C:\Program Files (x86)\Microsoft BizTalk Server 2010\BTSTask.exe" ImportApp -Package:"C:\Program Files (x86)\Microsoft BizTalk ESB Toolkit 2.1\Microsoft.Practices.ESB.ExceptionHandling_NOBINDING64.msi" -ApplicationName:Microsoft.Practices.ESB -Overwrite
 11: "C:\Program Files (x86)\Microsoft BizTalk Server 2010\BTSTask.exe" ImportBindings /Source:"\\BizTalk Server 2010 Enterprise\BizTalk Server\Microsoft.Practices.ESB.ExceptionHandling_Bindings.xml" -Application:Microsoft.Practices.ESB
 12: msiexec.exe /qn /i "C:\Program Files (x86)\Microsoft BizTalk ESB Toolkit 2.1\Microsoft.Practices.ESB.CORE_NOBINDING64.msi"
 13: msiexec.exe /qn /i "C:\Program Files (x86)\Microsoft BizTalk ESB Toolkit 2.1\Microsoft.Practices.ESB.ExceptionHandling64.msi"
 14: iisreset
 15: pause

Line 1 adds the necessary additions to IIS

Line 2 actually installs the ESB components, which in turn adds the items to the start menu and unpacks the BizTalk msi’s etc.

Line 3 installs the Itinerary Designer into Visual Studio

Line 4 archives the default configuration file for the ESBConfigurationTool.exe application

Line 5 copies a new configuration file for the ESBConfigurationTool.exe to use

Line 6 echoes that it is time to run the configuration tool

Line 7 waits until the configuration has been run

Line 8 imports the ESB Core application into the BizTalk Administration Console

Line 9 applies the bindings (this needs to be customized from the samples provided during the install (Line 2))

Line 10 import the ESB Exception application into the BizTalk Administration Console

Line 11 applies the bindings like Line 9

Line 12 installs the ESB Core application into the GAC

Line 13 installs the ESB Exception application into the GAC

Line 14 resets IIS since modifications were made in step 1

Here is a sample of the configuration file, so when you open up the ESBConfiguration tool, you simply need to enable each of the features, press the Apply Configuration tool and walk through it.

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <appSettings>    
    <!-- Management Database -->
    <add key="ServerInstance" value="."/>
    <add key="DatabaseName" value="ESBManagementDB"/>
    <add key="Username" value="CORP\svcBTSHost"/>
    <add key="Password" value="P4ssw0rd!"/>
    <add key="BizTalkAppGroup" value="CORP\AppUsers"/>
    <add key="BizTalkAdminGroup" value="CORP\ServerAdmin"/>
    <add key="UseSqlAuthentication" value="False"/>
    <!-- Itinerary Database -->
    <add key="ItineraryServerInstance" value="."/>
    <add key="ItineraryDatabaseName" value="ExceptionDB"/>
    <add key="ItineraryUsername" value="CORP\svcBTSHost"/>
    <add key="ItineraryPassword" value="P4ssw0rd!"/>
    <add key="ItineraryBizTalkAppGroup" value="CORP\AppUsers"/>
    <add key="ItineraryBizTalkAdminGroup" value="CORP\ServerAdmin"/>
    <add key="BizTalkIsolatedHostGroup" value="CORP\IsolatedUsers"/>
    <add key="ItineraryUseSqlAuthentication" value="False"/>    
    <!-- Core Services-->
    <add key="CoreWebSiteName" value="."/>
    <add key="CoreUserAccount" value="CORP\svcBTSIsoHost"/>
    <add key="CoreUserAccountPassword" value="P4ssw0rd!"/>
    <add key="CoreBizTalkIsolatedHostGroup" value="CORP\IsolatedUsers"/>
    <!-- Exception Services-->
    <add key="ExceptionWebSiteName" value="."/>
    <add key="ExceptionUserAccount" value="CORP\svcBTSIsoHost"/>
    <add key="ExceptionUserAccountPassword" value="P4ssw0rd!"/>
    <add key="ExceptionBizTalkIsolatedHostGroup" value="CORP\IsolatedUsers"/>
    <!-- Configuration-->
    <add key="ConfigurationSource" value="False" />
    <add key="ApplicationName" value="ESB Toolkit" />
    <add key="ContactInformation" value="[email protected]" />
    <add key="AdminGroupName" value="CORP\SSOAdmin" />
    <add key="UserGroupName" value="CORP\AppUsers" />
    <add key="ConfigurationFilePath" value="C:\Program Files (x86)\Microsoft BizTalk ESB Toolkit 2.1\esb.config" />
  </appSettings>
  <system.diagnostics>
    <switches>
      <add name="ESBConfigTool" value="4" />
    </switches>
    <trace autoflush="true" indentsize="4">
      <listeners>
        <add name="FileListener" 
             type="System.Diagnostics.TextWriterTraceListener"
             initializeData="EsbConfigurationTool.log"/>
      </listeners>
    </trace>    
  </system.diagnostics>
</configuration>

Learning Windows Server AppFabric

Interested in learning Windows Server AppFabric? I hope so. It will be the backbone of the on-premise version of BizTalk in the coming years and based on news out of the PDC it looks like it will be moving to Azure (in some form) as well.

There are many resources out there for learning Windows Server AppFabric. However, not all of them are equal and you can spend a lot of time meandering through material without making a lot of progress.

Below is my suggested path for coming up to speed on it with the least amount of effort. First, you need to be familiar with some foundation items:

  • There are new features in WCF 4.0. This whitepaper is a great way to become familiar with it: http://msdn.microsoft.com/en-us/library/ee354381.aspx
    • One thing to keep in mind is that not all of the concepts are that relevant to Windows Server AppFabric. Focus on the new configuration features if you don’t have a lot of time.
  • Also note that both of the whitepapers mentioned above have sections on Workflow Services. Workflow Services existed in .NET 3.5 but have been improved in .NET 4.0.
  • You must also understand IIS 7.0/7.5. If you are not familiar with it, head on over to http://learn.iss.net You’ll need to be familiar with things like Sites, ApplicationPools, Applications, AutoStart, Bindings, MSDeploy, hierarchical configuration with web.configs, etc.

Now you have the foundation you need to start learning about Windows Server AppFabric. This foundation is very important because if you don’t have it, you won’t be able to understand what Windows Server AppFabric is bringing to the table. It will also be more difficult to troubleshoot with out this foundation.

After you have the foundation items covered move on to learning about AppFabric itself:

  • Next, there are some videos that you should watch that should help you with some things that aren’t covered in the documentation in enough detail:
    • http://blogs.msdn.com/b/endpoint/archive/2010/04/22/endpoint-tv-windows-server-appfabric-configuring-monitoring-data.aspx (resilience and scalability)
    • http://blogs.msdn.com/b/endpoint/archive/2010/04/30/endpoint-tv-ten-tips-for-troubleshooting-with-the-windows-server-appfabric-dashboard.aspx
    • http://www.msteched.com/2010/NorthAmerica/ASI301

Whew! That’s a lot of material. You will not be an expert after this but you should have a solid understanding of Windows Server AppFabric and be able to use it.

If have you have other suggestions let me know.

SharePoint 2010: Breeze SharePoint 2010 Bootcamp in Brisbane next week.

Hi folks, I’ve got a lot of requests for when/where these are on, so we’re off and
running next week in Brisbane with 2 seats left.

Just a quick blurb on the course –



The Breeze SharePoint 2010 Bootcamp has been designed to provide just that. Our customers
asked for an in-depth, technical, customized course that, if they were to spend $$s
on just one SharePoint  2010 course this year, would give them enough knowledge
of the technology to build real world solutions.

These bootcamps have been written for the ITPro & Developer
who need to upgrade their SharePoint skills, or are just starting out with SharePoint
2010.

Check
them out HERE

Are you using WCF within your organization?

Do you use WCF within your organization either for internal apis for exposing services and functionality to 3rd parties? We’re looking to connect with customers using our stuff in the real world so we can understand your use cases, the things you’ve liked about WCF and where you’d like to see us improve / go further. If interested, please contact us.

We look forward to hearing from you.

Versioning long running Workflow Services in WF4, the code

In my previous WF4 post I described the principal of how to version workflow services using the WCF 4 RoutingService. In that post I described the general problem and solution without going into a lot of detail and showing any code. In this blog post I will add an actual implementation you can use for reference purposes.

 

The basic layout

The solution has three parts.

  1. The workflow service with 2 different versions of same workflow. The second version of the workflow has more activities in the tree so it can’t load workflow instances from version 1. Each workflow is hosted in a different XAMLX file and the second was created by copying the first and making the required changes to it.
  2. The client application that is calling the service. It knows about one existing workflow instance previously created and will create a new one. Next it will send several requests to both workflows without knowing that these are implemented using different version.
  3. The routing service which receives all requests from the client and forwards them to the different workflow services depending on the data in the request.

 

The Workflow Services

The workflow version 1

The workflow version 2

 

The client application

The basic setup is that the first request, creating the new workflow instance, returns the version identifier and this is a mandatory item in each subsequent request. The client code that does this looks like this:

 

using System;
using TheClient.ServiceReference1;
 
namespace TheClient
{
    class Program
    {
        static void Main(string[] args)
        {
            var proxy = new ServiceClient();
 
            var version = proxy.StartOrder(2);
 
            for (int i = 0; i < 5; i++)
            {
                Console.WriteLine(proxy.AddItem(1, "1.0"));
                Console.WriteLine(proxy.AddItem(2, version));
            }
 
            proxy.Stop(2, version);
 
            Console.ReadLine();
        }
    }
}

Along with this config file:

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <system.serviceModel>
    <client>
      <endpoint address="http://localhost:8080/Service"
                binding="basicHttpBinding"
                contract="ServiceReference1.IService"
                name="BasicHttpBinding_IService" />
    </client>
  </system.serviceModel>
</configuration>

The address “http://localhost:8080/Service” used here is the address of the routing service.

 

The WCF RoutingService

This is where the magic really happens. It checks the versionId parameter from each request and sends the request to the corresponding service. And if no version is found, i.e. a new workflow is started, the request is routed to the last version.

 

The code is pretty simple and looks like this:

using System;
using System.ServiceModel;
using System.ServiceModel.Routing;
 
namespace TheRouter
{
    class Program
    {
        static void Main(string[] args)
        {
            var host = new ServiceHost(
                typeof(RoutingService), 
                new Uri("http://localhost:8080/Service"));
 
            host.Open();
 
            Console.WriteLine("The RoutingService is listening");
            Console.ReadLine();
            
            host.Close();
        }
    }
}

 

Most of the work is done in the configuration file:

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <system.serviceModel>
    <client>
      <endpoint address="http://localhost:26606/Service_V1.xamlx"
                binding="basicHttpBinding"
                contract="*"
                name="serviceV1"/>
      <endpoint address="http://localhost:26606/Service_V2.xamlx"
                binding="basicHttpBinding"
                contract="*"
                name="serviceV2"/>
    </client>
    <services>
      <service name="System.ServiceModel.Routing.RoutingService">
        <endpoint address=""
                  binding="basicHttpBinding"
                  contract="System.ServiceModel.Routing.IRequestReplyRouter"/>
      </service>
    </services>
    <behaviors>
      <serviceBehaviors>
        <behavior>
          <routing filterTableName="serviceRouting"
                   routeOnHeadersOnly="false"/>
        </behavior>
      </serviceBehaviors>
    </behaviors>
    <routing>
      <filters>
        <filter filterType="XPath"
                name="serviceV1"
                filterData="//ns:versionId='1.0'"/>
        <filter filterType="XPath"
                name="serviceV2"
                filterData="//ns:versionId='2.0'"/>
        <filter filterType ="MatchAll"
                name="all"/>
      </filters>
      <namespaceTable>
        <add prefix="ns"
             namespace="http://tempuri.org/"/>
      </namespaceTable>
      <filterTables>
        <filterTable name="serviceRouting">
          <add endpointName="serviceV1"
               filterName="serviceV1"
               priority="2"/>
          <add endpointName="serviceV2"
               filterName="serviceV2"
               priority="2"/>
          <add endpointName="serviceV2"
               filterName="all"
               priority="1"/>
        </filterTable>
      </filterTables>
    </routing>
  </system.serviceModel>
</configuration>

 

 

Running this shows the following output showing that the different requests where send to the correct workflow

 

 

You can download the complete VS2010 solution with all code here.

 

Enjoy!

www.TheProblemSolver.nl

Wiki.WindowsWorkflowFoundation.eu

BizTalk 2010 Installation and Configuration – Installing and configuring local SMTP for Reporting Server (Part 9)

BizTalk 2010 Installation and Configuration – Installing and configuring local SMTP for Reporting Server (Part 9)

Installing SMTP Server Feature on Windows Server 2008 R2 is an easy process requiring only few steps to complete. To install SMTP Server Features Open “Server Manager Console” Start %u2192 Administrative Tools %u2192 Server Manager Under “Features” select “Add Features” In “Select Features” screen, select “SMTP Server” option In “Add role services and features required […]