A Configurable AppFabric Cache Attribute For Your WCF Services

 

Doing a search online for WCF and Cache will eventually lead you to a number of links* demonstrating the use of an IOperationInvoker implementation to manage caching. With AppFabric Cache rapidly becoming a mainstream enterprise technology I felt this to be an ideal time to revisit this concept.

I chose to offer the following features in my implementation

  • Using the default or a named cache
  • Using a cache region
  • Action keyed caching
  • Choice of using one or more parameters to the operation as a key
  • Choice of XML serialized keys or a more compact form
  • Choice of XmlSerializer or DataContractSerializer when using the XML serialization option

Using an IOperationInvoker implementation allows for the following pattern

Using the custom invoker in your code is straightforward and natural as the following snippet from the test service provided with the code attached to this blog demonstrates

         /// <summary>
        /// This method is for testing cache retrieval by key.
        ///
        /// </summary>
        /// <param name="catGuy"></param>
        /// <returns></returns>
        [OperationContract]
        [AppFabricCacheBehavior]
        CatMember GetCat(long catGuy);

        /// <summary>
        /// This method is for testing demonstrating cache by Action.
        /// </summary>
        /// <returns></returns>
        [OperationContract]
        [AppFabricCacheBehavior(CacheByAction=true)]
        List<long> GetIds();

As you  can see it’s only a matter of supplying an additional attribute along with your OperationContract..

The behavior code itself is very simple and contains descriptions of what the attribute parameters do

    [AttributeUsage(AttributeTargets.Method)]
    public class AppFabricCacheBehavior : Attribute, IOperationBehavior
    {
        /// <summary>
        /// If this property is true the only key considered is Action.
        /// A common use case scenario is a method that returns a slowly changing drop down list.
        /// Setting this will cause all other non AppFabric Cache specific options to be ignored.
        /// </summary>
        public bool CacheByAction { get; set; }

        /// <summary>
        /// If this property is true then either DataContractSerializer or XmlSerializer will be used on keys and then concatenated.
        /// By default this is false and the key will be a simple string concatenation 
        /// You should set this to true when using complex type(s) as a key.
        /// Avoid setting this to true when you are using primitive type(s) as a key.
        /// </summary>
        public bool SerializeKeysToXml { get; set; }

        /// <summary>
        /// Substitutes XmlSerializer for DataContractSerializer when SerializeKeysToXml is true.
        /// </summary>
        public bool UseXmlSerializer { get; set; }

        /// <summary>
        /// Retrieves items from the named cache if not null. If CacheName is not specified DefaultCache will be used. 
        /// </summary>
        public string CacheName { get; set; }

        /// <summary>
        /// Retrieves items from the named region.  
        /// </summary>
        public string CacheRegion { get; set; }

        /// <summary>
        /// Indexes of the objects to generate key from. If not specified it will default to KeyIndexes = new int[]{0}.  
        /// </summary>
        public int[] KeyIndexes { get; set; }

        public void AddBindingParameters(OperationDescription operationDescription, BindingParameterCollection bindingParameters)
        {
            
        }

        public void ApplyClientBehavior(OperationDescription operationDescription, ClientOperation clientOperation)
        {
            
        }

        public void ApplyDispatchBehavior(OperationDescription operationDescription, DispatchOperation dispatchOperation)
        {
            dispatchOperation.Invoker = new AppFabricCachingInvoker(this, dispatchOperation.Invoker,dispatchOperation.Action);
        }

        public void Validate(OperationDescription operationDescription)
        {
            
        }
    }

The only real work being done in this class is forwarding the original Invoker and passing in the Action.

Attribute Parameter Use Cases and Observations

While I was researching this article I noticed in some implementations they would unconditionally  serialize all the parameters to XML and then concatenate them into a giant key. I still allow you to do this in my implementation if you choose to but I also give you a choice to use a simpler more compact format that will greatly reduce the memory usage when using primitive types. In simple test cases this cuts memory usage in 1/2 and I suspect in cases with very large payloads the effect is even more dramatic. 

I also noticed some of the implementations did not take into account methods using zero parameters in their key calculations. It is a common occurrence to see Web Services built with zero parameter methods in order to provide for retrieving lists and such. By setting CacheByAction=true in the supplied code you will bypass key calculation logic and immediately use the Action parameter passed in as a key instead.

Test runs on my system ran approximately 3 times faster for cached responses versus having to access the database. As with any performance testing however, you will need to test in your own environment to see the effect a solution like the one presented might have.

Try It Out

Please give the code a test run and drop a comment if you would like a new feature or find what you think is a bug. 

 

*The following links all demonstrate a form of caching using an IOperationInvoker implementation

  Windows Communication Foundation Extensibility 

  Extending WCF with Custom Behaviors by Aaron Skonnard

  Developing Custom OperationInvoker by Sankarsan

  Use AppFabric Cache to cache your WCF Service response by Mikael Håkansson 

 

Thanks to Paolo Salvatori,  Valery Mizonov, and Quoc Bui for their review

 

   Built for Quoc

 

BizTalk Setup XML

If you look at the arguments of the setup.exe to do an install of BizTalk you can install everything by using the following switch: /ADDLOCAL ALL. However, you will notice that you can supply an xml to tell the setup what components to install:

But where is a sample configuration file? (I could not find it anywhere on MSDN)

You could use the following xml file to install all components, which is the tail of the configuration file that is created after the setup.exe completes.

<Configuration>
  <InstalledFeature>MsEDIAS2</InstalledFeature>
  <InstalledFeature>MsEDIAS2StatusReporting</InstalledFeature>
  <InstalledFeature>WCFAdapter</InstalledFeature>
  <InstalledFeature>InfoWorkerApps</InstalledFeature>
  <InstalledFeature>BAMPortal</InstalledFeature>
  <InstalledFeature>WcfAdapterAdminTools</InstalledFeature>
  <InstalledFeature>PAM</InstalledFeature>
  <InstalledFeature>Development</InstalledFeature>
  <InstalledFeature>MsEDISchemaExtension</InstalledFeature>
  <InstalledFeature>MsEDISDK</InstalledFeature>
  <InstalledFeature>MsEDIMigration</InstalledFeature>
  <InstalledFeature>Documentation</InstalledFeature>
  <InstalledFeature>SDK</InstalledFeature>
  <InstalledFeature>WMI</InstalledFeature>
  <InstalledFeature>BizTalk</InstalledFeature>
  <InstalledFeature>MOT</InstalledFeature>
  <InstalledFeature>Engine</InstalledFeature>
  <InstalledFeature>MSMQ</InstalledFeature>
  <InstalledFeature>Runtime</InstalledFeature>
  <InstalledFeature>RfidEventForwarderMessageTransform</InstalledFeature>
  <InstalledFeature>AdminAndMonitoring</InstalledFeature>
  <InstalledFeature>MonitoringAndTracking</InstalledFeature>
  <InstalledFeature>AdminTools</InstalledFeature>
  <InstalledFeature>BizTalkAdminSnapIn</InstalledFeature>
  <InstalledFeature>HealthActivityClient</InstalledFeature>
  <InstalledFeature>BAMTools</InstalledFeature>
  <InstalledFeature>Migration</InstalledFeature>
  <InstalledFeature>BizTalkExplorer</InstalledFeature>
  <InstalledFeature>BizTalkExtensions</InstalledFeature>
  <InstalledFeature>OrchestrationDesigner</InstalledFeature>
  <InstalledFeature>Designer</InstalledFeature>
  <InstalledFeature>PipelineDesigner</InstalledFeature>
  <InstalledFeature>XMLTools</InstalledFeature>
  <InstalledFeature>AdapterImportWizard</InstalledFeature>
  <InstalledFeature>VSTools</InstalledFeature>
  <InstalledFeature>WCFDevTools</InstalledFeature>
  <InstalledFeature>DeploymentWizard</InstalledFeature>
  <InstalledFeature>TrackingProfileEditor</InstalledFeature>
  <InstalledFeature>SSOAdmin</InstalledFeature>
  <InstalledFeature>AdditionalApps</InstalledFeature>
  <InstalledFeature>SSOServer</InstalledFeature>
  <InstalledFeature>RulesEngine</InstalledFeature>
  <InstalledFeature>OLAPNS</InstalledFeature>
  <InstalledFeature>FBAMCLIENT</InstalledFeature>
  <InstalledFeature>BAMEVENTAPI</InstalledFeature>
  <InstalledFeature>ProjectBuildComponent</InstalledFeature>
</Configuration>

And your batch script could look like this (notice you can’t use /quiet or /passive, but it can still be hands free):

"\\BizTalk Server 2010 Enterprise\BizTalk Server\setup.exe" /s "\\BizTalk Server 2010 Enterprise\BizTalk Server\CompleteSetup.xml"  /l c:\biztalksetupSSO.log /CompanyName Company /Username Developer /cabpath "\\BizTalk Server 2010 Enterprise\BizTalk Server\Bts2010Win2K8R2EN64.cab"

Option 2:

If you look deep in the log files you can see that on a full install has the following options:

MSI (s) (B8:0C) [01:30:32:802]: Command Line: ADDLOCAL=MsEDIAS2,MsEDIAS2StatusReporting,WCFAdapter,InfoWorkerApps,BAMPortal,WcfAdapterAdminTools,PAM,Development,MsEDISchemaExtension,MsEDISDK,MsEDIMigration,Documentation,SDK,WMI,BizTalk,MOT,Engine,MSMQ,Runtime,RfidEventForwarderMessageTransform,AdminAndMonitoring,MonitoringAndTracking,AdminTools,BizTalkAdminSnapIn,HealthActivityClient,BAMTools,Migration,BizTalkExplorer,BizTalkExtensions,OrchestrationDesigner,Designer,PipelineDesigner,XMLTools,AdapterImportWizard,VSTools,WCFDevTools,DeploymentWizard,TrackingProfileEditor,SSOAdmin,AdditionalApps,SSOServer,RulesEngine,OLAPNS,FBAMCLIENT,BAMEVENTAPI,ProjectBuildComponent COMPANYNAME=Company USERNAME=Developer FILESINUSEERROR= CEIP=0 CURRENTDIRECTORY=C:\Users\estott\AppData\Local\Temp\EBZ30319.tmp CLIENTUILEVEL=3 MSICLIENTUSESEXTERNALUI=1 CLIENTPROCESSID=5024 

I would like to tell you that I have figured out all of the combinations of what you want to install, but Trace Young beat me to it.

CAB Files for BizTalk 2010

At a recent client I was at, we were installing BizTalk 2010, but the servers did not have access to the internet. None of the development machines were the same build as the servers that were locked down behind the firewall.

This made getting the CAB file for that server type a little challenging.

Here is a list of the version and the links to download the cab files directly

OS

Link

Vista http://go.microsoft.com/fwlink/?LinkID=189405
Win2k8 http://go.microsoft.com/fwlink/?LinkID=189408
Win2k8R2 http://go.microsoft.com/fwlink/?LinkID=189409
Win7 http://go.microsoft.com/fwlink/?LinkID=189404
FutureNT http://go.microsoft.com/fwlink/?LinkId=189409

Does anyone know what FutureNT means?

Create Host, Host Instances, Receive Handlers, Send Handlers, and Start Host Instances WMI

I wanted a script that post a new BizTalk build, I could run and it could create new hosts, host instances, create new receive and send handlers and finally start the host instances

'Create the new host
CreateHost "App2Host", InProcess, "CORP\BTSAppUsers-Dev", False, False, False, False

'Make the new host the default host
UpdateHost "App2Host",True,True

'Turn the tracking off of the initially installed host
UpdateHost "App1Host",False,False

'Create the host instance
FinalizeInstallHostInstance "App2Host","CORP\svcBTSHost-Dev","P4ssw0rd!"

ReConfigureReceiveHandler "App1Host","WCF-SQL"
CreateReceiveHandler "App2Host","WCF-SQL"
CreateSendHandler "App2Host","WCF-SQL"

'Start all of the host instances that are not currently running
StartAllInProcessHostInstance

Sub CreateHost (HostName, HostType, NTGroupName, AuthTrusted, Isdefault, IsHost32BitOnly, HostTracking )
   On Error Resume Next

   Dim objLocator, objService, objHostSetting, objHS

   ' Connects to local server WMI Provider BizTalk namespace
   Set objLocator = Createobject ("wbemScripting.SWbemLocator")
   Set objService = objLocator.ConnectServer(".", "root/MicrosoftBizTalkServer")

   ' Get WMI class MSBTS_HostSetting
   Set objHostSetting = objService.Get ("MSBTS_HostSetting")

   Set objHS = objHostSetting.SpawnInstance_

   objHS.Name = HostName
   objHS.HostType = HostType
   objHS.NTGroupName = NTGroupName
   objHS.AuthTrusted = AuthTrusted
   objHS.Isdefault = IsDefault
   objHS.IsHost32BitOnly = IsHost32BitOnly
   objHS.HostTracking = HostTracking

   ' Create instance
   objHS.Put_(CreateOnly)

   CheckWMIError
   wscript.echo "Host - " & HostName & " - has been created successfully"
   
end Sub

Sub UpdateHost (HostName, HostTracking, IsDefault)
   On Error Resume Next

   Dim objLocator, objService, objHS

   ' Connects to local server WMI Provider BizTalk namespace
   Set objLocator = Createobject ("wbemScripting.SWbemLocator")
   Set objService = objLocator.ConnectServer(".", "root/MicrosoftBizTalkServer")

   ' Look for WMI Class MSBTS_HostSetting with name equals HostName value
   Set objHS = objService.Get("MSBTS_HostSetting.Name='" & HostName & "'")

   objHS.HostTracking = HostTracking
   objHS.IsDefault = IsDefault

   ' Update instance properties
   objHS.Put_(UpdateOnly)

   ' Check for error condition before continuing.
   CheckWMIError
   wscript.echo "Host - " & HostName & " - has been updated successfully"
   
end Sub
Sub FinalizeInstallHostInstance (HostName, uid, pwd)
 On Error Resume Next
 Dim ServerName, objSysInfo
 Set objSysInfo = CreateObject( "WinNTSystemInfo" )
 ServerName = objSysInfo.ComputerName
 CheckWMIError 
 MapInstallHostInstance HostName,ServerName,uid,pwd
end Sub

Sub MapInstallHostInstance (HostName, ServerName, uid, pwd)
'Sub MapInstallHostInstance (HostName, uid, pwd)
   On Error Resume Next

   Dim objLocator, objService, objServerHost, objSH
   Dim objHostInstance, objHI
   'Dim ServerName, wshShell

   ' Connects to local server WMI Provider BizTalk namespace
   Set objLocator = Createobject ("wbemScripting.SWbemLocator")
   Set objService = objLocator.ConnectServer(".", "root/MicrosoftBizTalkServer")
   Set objServerHost = objService.Get ("MSBTS_ServerHost")

   Set objSH = objServerHost.SpawnInstance_

   objSH.HostName = HostName
   objSH.ServerName = ServerName

   ' Invoke MSBTS_ServerHost Map method
   objSH.Map

   CheckWMIError
   wscript.echo "Host - " & HostName & " - has been mapped successfully to server - " & ServerName

   Set objHostInstance = objService.Get ("MSBTS_HostInstance")

   Set objHI = objHostInstance.SpawnInstance_

   objHI.Name = "Microsoft BizTalk Server " & HostName & " " & ServerName
   
   ' Invoke MSBTS_HostInstance Install method
   objHI.Install uid, pwd, true   ' Calling MSBTS_HostInstance::Install(string Logon, string Password, boolean GrantLogOnAsService) method

   CheckWMIError
   wscript.echo "HostInstance - " & HostName & " - has been installed successfully on server - " & ServerName
   
end Sub
Sub ReConfigureReceiveHandler(HostName,AdapterName)
	'error handling is done by explicity checking the err object rather than using
	'the VB ON ERROR construct, so set to resume next on error.
	On Error Resume Next

	'Get the command line arguments entered for the script
	Dim objArgs: Set objArgs = WScript.Arguments

	'Make sure the expected number of arguments were provided on the command line.
	'if not, print usage text and exit.

	Dim objInstSet, objInst, strQuery

	'set up a WMI query to acquire a list of send handlers with the given Name key value.
	'This should be a list of zero or one send handlers.
	strQuery = "SELECT * FROM MSBTS_ReceiveHandler WHERE AdapterName =""" & AdapterName & """"
	Set objInstSet = GetObject("Winmgmts:!root\MicrosoftBizTalkServer").ExecQuery(strQuery)

	'If send handler found, set configuration information, otherwise print error and end.
	If objInstSet.Count > 0 then
		For Each objInst in objInstSet
			objInst.HostNameToSwitchTo=HostName

			
			'Commit the change to the database
			objInst.Put_(UpdateOnly)
			If Err <> 0	Then
				PrintWMIErrorThenExit Err.Description, Err.Number
			End If
			WScript.Echo "The "& AdapterName &" Receive Handler was successfully configured."
		Next
	Else
		WScript.Echo "No Receive Handler was found matching that AdapterName."
	End If
End Sub
' Sample to show MSBTS_ReceiveHandler instance creation with CustomCfg property
Sub CreateReceiveHandler (HostName, AdapterName)
   On Error Resume Next

   Dim objLocator, objService, objReceiveHandler, objRH, objSendHandler, objSH

   ' Connects to local server WMI Provider BizTalk namespace
   Set objLocator = Createobject ("wbemScripting.SWbemLocator")
   Set objService = objLocator.ConnectServer(".", "root/MicrosoftBizTalkServer")

   ' Get WMI class MSBTS_ReceiveHandler
   Set objReceiveHandler = objService.Get ("MSBTS_ReceiveHandler")

   Set objRH = objReceiveHandler.SpawnInstance_

   objRH.AdapterName = AdapterName
   objRH.HostName = HostName

   ' Create instance
   objRH.Put_(CreateOnly)

   CheckWMIError
   wscript.echo "Receive Handler - " & AdapterName & " " & HostName & " - has been created successfully"
   
end Sub
Sub CreateSendHandler (HostName, AdapterName)
   On Error Resume Next

   Dim objLocator, objService, objSendHandler, objSH

   ' Connects to local server WMI Provider BizTalk namespace
   Set objLocator = Createobject ("wbemScripting.SWbemLocator")
   Set objService = objLocator.ConnectServer(".", "root/MicrosoftBizTalkServer")

   ' Get WMI class MSBTS_ReceiveHandler
   'Insert the Send Handler make sure you use SendHandler2 as SendHandler is a throwback to BTS 2004 which does not allow updates
   Set objSendHandler = objService.Get ("MSBTS_SendHandler2")

   Set objSH = objSendHandler.SpawnInstance_

   objSH.AdapterName = AdapterName
   objSH.HostName = HostName

   ' Create instance
   objSH.Put_(CreateOnly)

   CheckWMIError
   wscript.echo "Send Handler - " & AdapterName & " " & HostName & " - has been created successfully"
   
end Sub
Sub StartAllInProcessHostInstance ()
   On Error Resume Next

   Dim Query, HostInstSet, Inst

   ' Enumerate all InProcess type Host Instance
   Query = "SELECT * FROM MSBTS_HostInstance WHERE HostType =1"
   Set HostInstSet = GetObject("Winmgmts:!root\MicrosoftBizTalkServer").ExecQuery(Query)

   For Each Inst in HostInstSet

      ' If host instance is stopped, then it'll start it
      If( HostInstServiceState_Stopped = Inst.ServiceState ) Then
         wscript.echo "Starting host instance..."
             Inst.Start   ' Calling MSBTS_HostInstance::Start() method
         CheckWMIError
         wscript.echo "HostInstance - " & Inst.HostName & " - has been started successfully on server - " & Inst.RunningServer
      End If
   Next

end Sub


'This subroutine deals with all errors using the WbemScripting object.  Error descriptions
'are returned to the user by printing to the console.
Sub   CheckWMIError()

   If Err <> 0   Then
      On Error Resume   Next

      Dim strErrDesc: strErrDesc = Err.Description
      Dim ErrNum: ErrNum = Err.Number
      Dim WMIError : Set WMIError = CreateObject("WbemScripting.SwbemLastError")

      If ( TypeName(WMIError) = "Empty" ) Then
         wscript.echo strErrDesc & " (HRESULT: "   & Hex(ErrNum) & ")."
      Else
         wscript.echo WMIError.Description & "(HRESULT: " & Hex(ErrNum) & ")."
         Set WMIError = nothing
      End   If
      
      wscript.quit 0
   End If

End Sub


				
					

Configuring IIS for UDDI

Here is the components that need to be enabled for UDDI to run properly

Task 3: Install Internet Information Services When installing IIS on Windows Server 2008 R2, you must enable following IIS features:

  • ASP.NET
  • Basic Authentication
  • Windows Authentication
  • IIS 6 Metabase Compatibility

You simply create a batch file with the following command:

start /w pkgmgr /iu:IIS-ASPNET;IIS-BasicAuthentication;IIS-WindowsAuthentication;IIS-IIS6ManagementCompatibility;IIS-Metabase

WCF Adapter Quirkiness

If you are going to install the WFC adapters for SQL, SAP, and Oracle, there is not a way that the install can be complete during a silent install.

The adapters don’t show up in the management console without having to manually add them.

Until now

All you need to do is run this AddAdapter.vbs (BizTalk 2010)

AddAdapter "WCF-SQL", "WCF-SQL adapter", "{59b35d03-6a06-4734-a249-ef561254ecf7}" 
AddAdapter "WCF-SAP", "WCF-SAP adapter", "{a5f15999-8879-472d-8c62-3b5ea9406504}" 
AddAdapter "WCF-OracleDB", "WCF-OracleDB adapter", "{d7127586-e851-412e-8a8a-2428aeddc219}" 
AddAdapter "WCF-OracleEBS", "WCF-OracleEBS adapter", "{f452bb15-7a0d-495d-9395-c630d3fd29cd}" 

Sub AddAdapter(strAdapterName, strAdapterComment, strAdapterMgmtCLSID) 
Set objLocator = CreateObject("WbemScripting.SWbemLocator") 
Set objService = objLocator.ConnectServer(".", "root/MicrosoftBizTalkServer") 
Set objAdapterClass = objService.Get("MSBTS_AdapterSetting") 

On Error Resume Next 
Set objAdapterInstance = objService.Get("MSBTS_AdapterSetting.Name='" & strAdapterName & "'") 
If (objAdapterInstance is Nothing) Then 
On Error Goto 0 
Set objAdapterInstance = objAdapterClass.SpawnInstance_ 
Else 
On Error Goto 0 
End If 

objAdapterInstance.Name = strAdapterName 
objAdapterInstance.Comment = strAdapterComment 
objAdapterInstance.MgmtCLSID = strAdapterMgmtCLSID 

On Error Resume Next 
objAdapterInstance.Put_(0) 
If (Err.Number <> 0) Then 
Else 
End If 
End Sub

So this is what the batch file looks like:

"\\BizTalk Server 2010 Enterprise\WCF-LOB-Adapter-SDK-2010-x86\AdapterFramework.msi" /quiet MUOPTIN="Yes"
"\\BizTalk Server 2010 Enterprise\WCF-LOB-Adapter-SDK-2010-x64\AdapterFramework64.msi" /quiet MUOPTIN="Yes"
msiexec /i "\\BizTalk Server 2010 Enterprise\BizTalk Server\AdapterPack_x86\AdaptersSetup.msi" /qn ADDLOCAL=ALL
msiexec /i "\\BizTalk Server 2010 Enterprise\BizTalk Server\AdapterPack_x64\AdaptersSetup64.msi" /qn ADDLOCAL=ALL
cscript.exe "\\BizTalk Server 2010 Enterprise\BizTalk Server\AddAdapter.vbs"

And you get something that looks like this: