Watermarking Images using the Razor WebImage helper.

There’s many cases when you may want to watermark images on your website including:

Protecting copyrighted content
Showing previews of premium content
Identifying the origin of original content

And many more..
We’ll take a look at using the Razor WebImage helper to watermark images on your website easily and efficiently.
Adding the Microsoft.Web.Helpers Library
To add the Microsoft.Web.Helpers library, […]

Getting the "windows azure web role entry point has stopped working" when using Visual Studio 2010 and the Windows Azure Tools for Visual Studio

Getting the "windows azure web role entry point has stopped working" when using Visual Studio 2010 and the Windows Azure Tools for Visual Studio

I’ve been doing more and more Azure work, and because of my partner Brian
Randell I’ve adopted the TFS lifestyle.

After I updated to the most recent version of the Windows Azure Tools for Visual Studio
I started to get this error:

"windows azure web role entry point has stopped working"

azureerror

If I debugged the project I got

System.ServiceModel.CommunicationObjectFaultedException was unhandled

  Message=The communication object, System.ServiceModel.Channels.ServiceChannel,
cannot be used for communication because it is in the Faulted state.

  Source=mscorlib

  StackTrace:

    Server stack trace:

       at System.ServiceModel.Channels.CommunicationObject.Close(TimeSpan
timeout)

    Exception rethrown at [0]:

       at System.Runtime.Remoting.Proxies.RealProxy.HandleReturnMessage(IMessage
reqMsg, IMessage retMsg)

       at System.Runtime.Remoting.Proxies.RealProxy.PrivateInvoke(MessageData&
msgData, Int32 type)

       at System.ServiceModel.ICommunicationObject.Close(TimeSpan
timeout)

       at System.ServiceModel.ClientBase`1.System.ServiceModel.ICommunicationObject.Close(TimeSpan
timeout)

       at Microsoft.WindowsAzure.Hosts.WaIISHost.Program.Main(String[]
args)

  InnerException:

Either way I was forced to shutdown Visual Studio and reopen it to get the project
to deploy to the local dev fabric again.

Of course I turned off the “Just My Code” debugging setting and set the debugger to
break on all exceptions and low and behold I got the real exception:

realerror

The problem is that the web.config is marked as read-only because it is checked into
source control.

 

Moral of this story – if you are developing with Windows Azure and using source control
– you always have to keep your web.config checked out.  Not sure if a fix is
coming from MS – but I thought this might be helpful to someone out there.



Check out my new book on REST.

My first SSO implementation

Okay, I admit it, I have never spent the time to learn the intricacies of Enterprise Single Sign On. I guess I had a hard time seeing the value when the only description I heard for quite a long time is that it is where BizTalk stores credentials.

I finally sat down and created a sample application that I could see would be useful.

I want to create a generic pipeline that interrogates the target namespace of the xml document in the decode stage, and applies a map. I actually have this requirement where I need to change the document into a different xml document that I can actually apply an envelope schema to.

<Rant>

I have always seen tutorials about how easy it is to create an envelope schema, reference a document schema and viola, BizTalk is splitting documents like crazy! I watched the podcasts, seen the demos eleventeen times and so I was tasked with doing it. I thought, hey, this is going to be easy!

NOT SO

This is a representation of what the order came in as

<orders xmlns="http://WebSiteVendor">
  <order>
    <Header>
      Stuff
    </Header>
    <Detail>
      Crap
    </Detail>
    <Details>
      Even More Crap
    </Details>
  </order>
  <order>
    <Header>
      More Stuff
    </Header>
    <Detail>
      Yes, Even More Crap
    </Detail>
    <Details>
      Hard to believe, but yes, More Crap
    </Details>
  </order>
</orders>

How am I supposed to create an envelope schema with a document node out of this type of document? I can’t go back to the Website Vendor and have them change it.

However, for the DASM, I require an xml document that looks like this:

<orders xmlns="http://WebSiteVendor">
  <order xmlsn="http://WebSiteVendorOrder">
    <Header>
      Stuff
    </Header>
    <Detail>
      Crap
    </Detail>
    <Details>
      Even More Crap
    </Details>
  </order>
  <order xmlsn="http://WebSiteVendorOrder">
    <Header>
      More Stuff
    </Header>
    <Detail>
      Yes, Even More Crap
    </Detail>
    <Details>
      Hard to believe, but yes, More Crap
    </Details>
  </order>
</orders>

Probably the worst thing is that a resolution (how to split this type of document up) has not been explained very well, if at all anywhere on the web.

</Rant>

After exploring all sorts of ways I can prep the data by removing name spaces, adding them back in, etc…, I started feeling like Thomas Edison. When Thomas Edison was interviewed by a young reporter who boldly asked Mr. Edison if he felt like a failure and if he thought he should just give up by now. Perplexed, Edison replied, “Young man, why would I feel like a failure? And why would I ever give up? I now know definitively over 9,000 ways that an electric light bulb will not work. Success is almost in my grasp.” And shortly after that, and over 10,000 attempts, Edison invented the light bulb.

Finally it dawned on me, that if I simply had a map that would execute in the Decode stage, I could then use the XML Disassembler to break up the ’modified’ message into individual orders. I had created the envelope schema and imported the ’document’ schema and made it a child.

Oddly enough, what is not terribly documented well is that the Body Xpath attribute needs to be pointing to the ’parent’ of the document that you want to split up. (What is that about?)

So I looked around and voil%u00e0: there is a pipeline component in the SDK that does EXACTLY what I need. (SDK\Samples\Pipelines\XslTransformComponent)

So here is what I wanted to do:

  1. I wanted it to be on the receive side, in the decode stage.
  2. I wanted it to be dynamic so that if multiple messages came in, it would be able to figure out what the correct map was and invoke it.

So I decided that the SSO would be the perfect place to store the following key values:

Key Value
http://targetnamespace.com#node c:\{path}\map.xsl

Obvious, that this table would increase as more and more documents started to be processed by this splitting component.

The first thing I did was download the Enterprise Single Sign On application to allow you to put key value pairs in the SSO Database. You can download the SSO Configuration Application MMC Snap-In. When you install it, it creates a new menu in your Start Menu. You have to register it, but then you can start placing key value pairs.

I created an application called DecodeTransformations. and here is what my dictionary looks like:

So now I have to take the existing code and have it use ESSO to resolve the xslt instead of hard coding it in the properties of the pipeline.

As part of the download, Microsoft in their kindness, included sample code on how to retrieve values. You have to supply two values:

  1. Application Name (DecodeTransformations)
  2. Key ({targetnamespace})

I included the class and referenced it in the primary class. Instead of giving you bits and pieces, I am going to tell you what lines I modified, and then show the resulting code (so you can use it yourself).

  1. I changed the property bag to get the SSOApplication (line 151)
  2. I commented out the code to ensure that there was value during build (line 248)
  3. Instead of loading the xsl first, I wanted to load the xml document up so I can determine what is its namespace. (line 91)
  4. I then determined what the document schema is it, by calling the ReturnTargetNamespace method. (line 94 -> 257)
  5. Now that I had the namespace, I then passed the Application in from the pipeline properties along with the targetnamespace to return the xsl path and returned the xsl path. (line 95)
  1: using System;
  2: using System.IO;
  3: using System.Xml;
  4: using System.Xml.Xsl;
  5: using System.Xml.Linq;
  6: using System.ComponentModel;
  7: using System.Collections;
  8: using Microsoft.BizTalk.Message.Interop;
  9: using Microsoft.BizTalk.Component.Interop;
 10: using Microsoft.Win32;
 11: using SSO.Utility;
 12: 
 13: namespace Microsoft.BizTalk.SDKSamples.Pipelines.XslTransformComponent
 14: {
 15: 	/// <summary>
 16: 	/// Implements a pipeline component that applies Xsl Transformations to XML messages
 17: 	/// </summary>
 18: 	/// <remarks>
 19: 	/// XslTransformer class implements pipeline components that can be used in send pipelines
 20: 	/// to convert XML messages to HTML format for sending using SMTP transport. Component can
 21: 	/// be placed only in Encoding stage of send pipeline
 22: 	/// </remarks>
 23: 	[ComponentCategory(CategoryTypes.CATID_PipelineComponent)]
 24: 	[ComponentCategory(CategoryTypes.CATID_Decoder)]
 25: 	[System.Runtime.InteropServices.Guid("FA7F9C55-6E8E-4855-8DAC-FA1BC8A499E2")]
 26: 	public class XslTransformer		: Microsoft.BizTalk.Component.Interop.IBaseComponent, 
 27: 									  Microsoft.BizTalk.Component.Interop.IComponent, 
 28: 									  Microsoft.BizTalk.Component.Interop.IPersistPropertyBag,
 29: 									  Microsoft.BizTalk.Component.Interop.IComponentUI
 30: 	{		
 31: 		#region IBaseComponent
 32: 		/// <summary>
 33: 		/// Name of the component.
 34: 		/// </summary>
 35: 		[Browsable(false)]
 36: 		public string Name
 37: 		{
 38: 			get {	return "XSL Transform Component";	}
 39: 		}
 40: 		/// <summary>
 41: 		/// Version of the component.
 42: 		/// </summary>
 43: 		[Browsable(false)]
 44: 		public string Version
 45: 		{
 46: 			get	{	return "1.0";	}
 47: 		}
 48: 		/// <summary>
 49: 		/// Description of the component.
 50: 		/// </summary>
 51:         [Browsable(false)]
 52:         public string Description
 53:         {
 54:             get { return "XSL Transform Pipeline Component"; }
 55:         }
 56: 		#endregion
 57: 		#region IComponent
 58: 		/// <summary>
 59: 		/// Implements IComponent.Execute method.
 60: 		/// </summary>
 61: 		/// <param name="pc">Pipeline context</param>
 62: 		/// <param name="inmsg">Input message.</param>
 63: 		/// <returns>Converted to HTML input message.</returns>
 64: 		/// <remarks>
 65: 		/// IComponent.Execute method is used to convert XML messages
 66: 		/// to HTML messages using provided Xslt file.
 67: 		/// It also sets the content type of the message part to be "text/html"
 68: 		/// which is necessary for client mail applications to correctly render
 69: 		/// the message
 70: 		/// </remarks>
 71: 		public IBaseMessage Execute(IPipelineContext pc, IBaseMessage inmsg)
 72: 		{
 73: 			inmsg.BodyPart.Data = TransformMessage(inmsg.BodyPart.Data);
 74: 			inmsg.BodyPart.ContentType = "text/html";
 75: 			return inmsg;
 76: 		}
 77: 		#endregion
 78: 		#region Helper function
 79: 		/// <summary>
 80: 		/// Transforms XML message in input stream to xml message
 81: 		/// </summary>
 82: 		/// <param name="stm">Stream with input XML message</param>
 83: 		/// <returns>Stream with output xml message</returns>
 84: 		private Stream TransformMessage(Stream stm)
 85: 		{
 86: 			MemoryStream ms = null;
 87: 			string validXsltPath = null;
 88: 			try 
 89: 			{
 90: 				//Load Xml stream in XmlDocument.
 91: 				XmlDocument doc = new XmlDocument();
 92: 				doc.Load(stm);
 93:                 // Get the target name space
 94:                 string tns = ReturnTargetNamespace(doc);
 95:                 string xsltPath = SSOClientHelper.Read(this.SSOApplication, tns);
 96: 				// Get the full path to the Xslt file
 97: 				validXsltPath = GetValidXsltPath(xsltPath);
 98:                 //Create memory stream to hold transformed data.
 99:                 ms = new MemoryStream();
100: 				// Load transform
101: 				XslTransform transform = new XslTransform();
102: 				transform.Load(validXsltPath);
103: 				//Preform transform
104: 				transform.Transform(doc, null, ms, null);
105: 				ms.Seek(0, SeekOrigin.Begin);
106: 			}
107: 			catch(Exception e) 
108: 			{
109: 				System.Diagnostics.Trace.WriteLine(e.Message);
110: 				System.Diagnostics.Trace.WriteLine(e.StackTrace);
111: 				throw e;
112: 			}
113: 			return ms;
114: 		}
115: 		/// <summary>
116: 		/// Get a valid full path to a Xslt file
117: 		/// </summary>
118: 		/// <param name="path">Path provided by user in Pipeline Designer</param>
119: 		/// <returns>The full path</returns>
120: 		/// <remarks>
121: 		/// If user provides absolute path then it is used as long as the file can be opened there
122: 		/// If user provides just a name of file or relative path then we try to open a file in 
123: 		/// [Install foder]\Pipeline Components
124: 		/// </remarks>
125: 		private string GetValidXsltPath(string path)
126: 		{
127: 			string validPath = path;
128: 			if (!System.IO.File.Exists(path))
129: 			{
130: 						RegistryKey rk = Registry.LocalMachine.OpenSubKey(@"SOFTWARE\Microsoft\BizTalk Server\3.0");
131: 						string InstallPath = string.Empty;
132: 				
133: 						if (null != rk)
134: 					InstallPath = (String)rk.GetValue("InstallPath");
135: 						
136: 						validPath = InstallPath + @"Pipeline Components\" + path;
137: 				if (!System.IO.File.Exists(validPath))
138: 				{
139: 					throw new ArgumentException("The XSL transformation file " + path + " can not be found");
140: 				}
141: 			}	
142: 			return validPath;
143: 		}
144: 		private string GetXSLPathFromSSO(string Application,string MessageType)
145: 		{
146: 			string MapName = SSOClientHelper.Read(Application, MessageType);
147: 			return MapName;
148: 		}
149: 		#endregion	
150: 		#region IPersistPropertyBag
151: 		private string _SSOApplication;
152: 		public string SSOApplication
153: 		{
154: 			get { return _SSOApplication; }
155: 			set { _SSOApplication = value; }
156: 		}
157: 		/// <summary>
158: 		/// Gets class ID of component for usage from unmanaged code.
159: 		/// </summary>
160: 		/// <param name="classid">Class ID of the component.</param>
161: 		public void GetClassID(out Guid classid)
162: 		{
163: 			classid = new System.Guid("FA7F9C55-6E8E-4855-8DAC-FA1BC8A499E2");
164: 		}	
165: 		/// <summary>
166: 		/// Not implemented.
167: 		/// </summary>
168: 		public void InitNew()
169: 		{
170: 		}	
171: 		public void Load(Microsoft.BizTalk.Component.Interop.IPropertyBag pb, Int32 errlog)
172: 		{
173: 			string val = (string)ReadPropertyBag(pb, "SSOApplication");
174: 			if (val != null) SSOApplication = val;
175: 		}		
176: 		/// <summary>
177: 		/// Saves the current component configuration into the property bag.
178: 		/// </summary>
179: 		/// <param name="pb">Configuration property bag.</param>
180: 		/// <param name="fClearDirty">Not used.</param>
181: 		/// <param name="fSaveAllProperties">Not used.</param>
182: 		public void Save(Microsoft.BizTalk.Component.Interop.IPropertyBag pb, Boolean fClearDirty, Boolean fSaveAllProperties)
183: 		{
184: 			object val = (object) SSOApplication;
185: 			WritePropertyBag(pb, "SSOApplication", val);            
186: 		}
187: 		/// <summary>
188: 		/// Reads property value from property bag.
189: 		/// </summary>
190: 		/// <param name="pb">Property bag.</param>
191: 		/// <param name="propName">Name of property.</param>
192: 		/// <returns>Value of the property.</returns>
193: 		private static object ReadPropertyBag(Microsoft.BizTalk.Component.Interop.IPropertyBag pb, string propName)
194: 		{
195: 			object val = null;
196: 			try
197: 			{
198: 				pb.Read(propName,out val,0);
199: 			}
200: 			catch(System.ArgumentException)
201: 			{
202: 				return val;
203: 			}
204: 			catch(Exception ex)
205: 			{
206: 				throw new ApplicationException( ex.Message);
207: 			}
208: 			return val;
209: 		}
210: 		private static void WritePropertyBag(Microsoft.BizTalk.Component.Interop.IPropertyBag pb, string propName, object val)
211: 		{
212: 			try
213: 			{
214: 				pb.Write(propName, ref val);
215: 			}
216: 			catch(Exception ex)
217: 			{
218: 				throw new ApplicationException( ex.Message);
219: 			}
220: 		}
221: 		#endregion
222: 		#region IComponentUI
223: 		/// <summary>
224: 		/// Component icon to use in BizTalk Editor.
225: 		/// </summary>
226: 		[Browsable(false)]
227: 		public IntPtr Icon
228: 		{
229: 			get	{	return IntPtr.Zero;	}
230: 		}
231: 		/// <summary>
232: 		/// The Validate method is called by the BizTalk Editor during the build 
233: 		/// of a BizTalk project.
234: 		/// </summary>
235: 		/// <param name="obj">Project system.</param>
236: 		/// <returns>
237: 		/// A list of error and/or warning messages encounter during validation
238: 		/// of this component.
239: 		/// </returns>
240: 		public IEnumerator Validate(object projectSystem)
241: 		{
242: 			if (projectSystem==null)
243: 				throw new System.ArgumentNullException("No project system");
244: 			IEnumerator enumerator = null;
245: 			ArrayList   strList  = new ArrayList();
246: 			try
247: 			{
248: 				//GetValidXsltPath(SSOApplication);
249: 			}
250: 			catch(Exception e)
251: 			{
252: 				strList.Add(e.Message);
253: 				enumerator = strList.GetEnumerator();
254: 			}
255: 			return enumerator;
256: 		}
257:         private string ReturnTargetNamespace(XmlDocument xdoc)
258:         {
259:             XmlNodeReader xmlndRdr = new XmlNodeReader(xdoc);
260:             string TargetNamespace = XElement.Load(xmlndRdr).GetDefaultNamespace().ToString();
261:             TargetNamespace = TargetNamespace + "#" + xdoc.DocumentElement.Name;
262:             return TargetNamespace;
263:         }
264: 		#endregion	
265: 	}
266: }
267: 

Once that is completed, simply place the dll in the pipeline components folder.

I created the pipeline that has the XSLTransform pipeline component and the XML Disassembler pipeline component and it works like a charm!

Now I can use the same component in multiple locations and can receive multiple messages in the same location and based on the application deployed to SSO, it will resolve where the xsl lives and transform it. Unlike the ESB dispatcher pipeline component, I actually don’t even need to deploy the map, I simply need to place it somewhere and I am ready to go.

Here is what the pipeline looks like

Gravatar HtmlHelper added to Round Table : Armory

As part of my efforts around introducing Round Table : Squire, I’ve started a project to centralize useful pieces of code which have application across many projects.  One of these was my code I wrote about some time ago, a Gravatar HtmlHelper extension.  I’ve now centralized that code, the body of which can be seen below, into the Round Table : Armory project on github.  If you’d like the full code, with all the tests, then just head over to the repository and download or clone it.  If you have improvements, please feel free to send me a pull request.

Error Message: The database ".:BizTalkRuleEngineDb" associated with the deployment driver does not match the database ".:BizTalkRuleEngineDb" specified during product configuration.

So I created a script that created a silent install of BizTalk from a server that had SQL server installed to having BizTalk, UDDI, and ESB installed. I figured I was pretty ’tricky’ by using the dot as the database server name.

However, when I went to deploy a rule from within the Business Rule Engine, I received the following error: Error Message: The database “.:BizTalkRuleEngineDb” associated with the deployment driver does not match the database “.:BizTalkRuleEngineDb” specified during product configuration

I looked over a few blog entries, where the registry needed to be altered:

Open the Registry Editor (regedit) and locate the following key:

HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\BusinessRules\3.0

If you have installed the rule processing components on a 64-bit version of Windows, they key will be at:

HKEY_LOCAL_MACHINE\SOFTWARE\Wow6432Node\Microsoft\BusinessRules\3.0

Change the following keys DeploymentDriverAssembly and DeploymentDriverClass values under the key.

  • Microsoft.RuleEngine, Version=3.0.1.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35
  • Microsoft.RuleEngine.RuleSetDeploymentDriver

NADA

The ultimate solution was to go into the adm_Group table and change the server name

There are two other databases that also refer the DB Server for the rules engine:

  1. adm_OtherDatabases
  2. adm_OtherBackupDatabases

Don’t bother changing them;it does not matter.

Once I made that single entry, I could deploy policies again.

BizTalk 2010 Installation and Configuration – Configure the Application Event Log (Part 13)

BizTalk 2010 Installation and Configuration – Configure the Application Event Log (Part 13)

This is an important step and it should be configured or validate before BizTalk Server Setup (part 8 ) because: BizTalk Server setup keeps a record of events in the Application Event Log. Depending on the BizTalk Server features installed, the amount of space required in the log may exceed its limit. If the application […]

Using AppFabric Cache as WF Persistence store

Paolo Salvatori and I, had this idea about using AppFabric Cache as a workflow persistence provider. As we’ve been working on this for quite some time now, we’re happy to finally share the work with everyone else thinking about how workflow persistence work, or perhaps thinking of building your own provider.

Developing your own persistence provider is not the simplest task you can take on, but it’s perfectly doable. I was surprised by the absence of persistence provider samples on the web. The only helpful one I could find was the MemoryStore, which helped me get started.

The funny thing with this sample was that I spent most of the time, trying to figure out why it worked, not why it didn’t.

I want to thank Paolo Salvatori, Manu Srivastava and Ruppert Koch from the AppFabric CAT and Product group for helping out and clarifying how the underlying plumbing works. More on that at the end of this post.

What is Workflow Persistence?

Unless we enable persistence, a workflow instance is only stored in memory. If the host goes down, it will take any evidence of your workflow instance with it. The process of persisting a workflow to a durable repository or storage is known as dehydration while restoring it, is generally referred to as rehydration. If you are a BizTalk person, you are probably aware that the persistence store for BizTalk is the BizTalkMsgBoxDb database.

Much like BizTalk, Workflow Foundation comes with a SQL persistence store, – the SqlWorkflowInstanceStoreProvider. However, with WF it’s optional. -Not only to use persistence, but you are also free to choose other providers, or even build one yourself.

You can enable the persistence either through configuration or code. If you’re hosting your workflow in IIS/AppFabric you can even manage your persistence through the IIS Manager.

Why and when would we use persistence?

You might end up using persistence for different reasons, which might also have an impact on which provider you’d choose. As I’m a “BizTalk guy”, I generally think of persistence in terms of 1) A safety-net in case something goes wrong and 2) Resource management, off-loading running instances to prevent memory consumption. In most cases you’re looking for a safe and robust provider to make sure you are not going to lose sensitive data along with the internal state of the workflow instance.

AppFabric Cache might not be your first choice if you are looking for a secure and reliable store provider. As Stephen W. Thomas put it: “Never put anything in the Cache that MUST not be lost – it is a non-transactional cache, not a database”.

However, persistence is also about scalability. If you, for example, are using WF to control the page flow of an ASP.Net application running on multiple web servers, you’d need persistence to make sure one server can continue a workflow which initiated on another server. Furthermore, in any scenarios where workflow services are accessed in a session-less manner, it is also important to persist immediately when the workflows gets idle. Otherwise the second request to the workflow might be re-directed to the other server, while it’s still active in memory on the first server. We can control this behavior using the WorkflowIdleBehavior.TimeToUnload setting in the web-/app.config:

<behaviors>
<serviceBehaviors>
<behavior>
<sqlWorkflowInstanceStore connectionStringName="persistenceStore" />
<serviceMetadata httpGetEnabled="true"/>
<workflowIdle timeToUnload="00:00:00"/>
<serviceDebug includeExceptionDetailInFaults="true"/>
</behavior>
</serviceBehaviors>
</behaviors>

The default value for the property is 1 minute. Unloading a workflow implies that it is also persisted. If TimeToUnload is set to zero the workflow instance is persisted and unloaded immediately after the workflow becomes idle. Setting TimeToUnload to MaxValue() effectively disables the unload operation. Idle workflow instances are never unloaded.

In some cases, for example when using WF to control the UI of your application, , you may consider using a less reliable provider. In such cases storing your workflow state in AppFabric Cache could be a good fit. In fact I would not be surprised if Microsoft shipped an AppFabric Cache provider in a near future.

But I won’t lie to you, – I think AppFabric Cache is super cool, and would make up any excuse to play with it!

What is correlation?

First lets have a look at the sample workflow. It’s a very simple sample to simulate a process of working with some sort of document form. The user may save the document, to come back later to continue working on its content.

  1. The client calls the workflow service through the CreateDocument method. This will cause the runtime to create a new instance of the workflow.
  2. After receiving the new Document, the workflow assigns it to a local variable.
  3. As the response is sent back to the client, the workflow initializes the correlation on the Document.Id (string)
  4. While the workflow waits for the client to send an updated document it will become idle, and therefore persisted to the AppFabric Cache store.
  5. The client invokes the UpdateDocument method. As the workflow instance is not in memory, the runtime will ask the persistence provider for the serialized workflow and will then rehydrate it.
  6. The workflow instance will perform the following actions:
      • update the local document variable;
      • return the response;
      • persist its internal state,
      • continue to listen for updates until the user submits “I’m done”. 

The only “complex” part of this workflow is making the second call (and all subsequent calls) to be “routed” to the right workflow instance. Keep in mind that there might be any number of concurrent documents being processed in the system. This is solved using the WF Correlation and in particular Content-Based Correlation.

If you’re a BizTalk guy, this is nothing new; however when I teach BizTalk classes people seems to have a bit of trouble to understand the concept. A correlation set is basically a “primary key” for the instance of the workflow. The id is created on the first call to the workflow, and needs to be passed on to the runtime for all subsequent calls. Using WF, this is very simple, all you need to do is to set the CorrelationInitializers for instance a SendActivity:

This will cause the Workflow runtime to create the instanceId (Guid), and add it to the binding context of the client. This way the instanceId is passed along with the soap header.  Although this might work fine in most situations, there are some limitations. -If the client does not have the instanceId, as when different clients are interacting with the same workflow, this won’t work. You are therefore better off using the Content based correlation type; Query correlation initalizer. For more information about the different kinds of correlation that WF provides, have a look at Paolos post.

This correlation type lets you define the id of your workflow, based on the content of the message. But keep in mind it needs to be unique.

So how does a persistence provider work?

The simple answer is: It serializes the workflow along with its metadata to a store. The persistence provider could use any durable media such as a file, database or queue. The AppFabric Cache does not fall in the category of being a durable media, but we will ignore Stephens lame warnings for now, and just concentrate on the sweet coolness of AppFabric Cache.

To begin with, a persistence provider needs to inherit from InstanceStore (System.Runtime.DurableInstancing), which is an abstract class with some operations you need to overload. The most important one is the BeginTryCommand. This is a universal operation which will be called from the runtime. With it comes an InstancePersistenceCommand parameter which tells you what the runtime expects you to do. This can be any of the following:

(there are more commands, but this will do for now)

If the last SaveWorkflowCommand has the CompleteInstance set to true, this indicates the workflow is done. In my case, this is where I clean up the cache, even though the cache will eventually clean itself up, as I add to the cache using the timeout parameter. You can set this yourself in the web.config.

...
<behaviors>
<serviceBehaviors>
<behavior>
<bLogicalPersistenceStore cacheName="bLogical" timeout="00:05:00"/>
<serviceMetadata httpGetEnabled="true"/>
<workflowIdle timeToUnload="00:00:01"/>
<serviceDebug includeExceptionDetailInFaults="true"/>
</behavior>
</serviceBehaviors>
</behaviors>

The LoadWorkflowByInstanceKeyCommand is where I got stuck. As I said in the beginning, -it worked, I just couldn’t figure out why. Below is the code in my LoadWorkflowByInstanceKey method:

private IAsyncResult LoadWorkflowByInstanceKey(InstancePersistenceContext context, 
LoadWorkflowByInstanceKeyCommand command,
TimeSpan timeout,
AsyncCallback callback,
object state)
{

Key key = _cacheHelper.GetKey(command.LookupInstanceKey);
Instance instance = _cacheHelper.GetInstance(key.Instance);

I’m loading the workflow using the LookupInstanceKey value, which is a Guid. Where did that come from? I was expecting my correlation key, – the Document.Id. I was starting to believe the correlation key was stored (together with the LookupInstanceKey) somewhere else. I started to trace the message to see if there was anything hidden in the header, but there wasn’t. I tested different client, collaborating on the same workflow instance, and that worked too. I even set up an environment with multiple servers -AND IT STILL WORKED! How was this possible?

As I was losing sleep over this, I turned my faith to Paolo Salvatori, hoping he could share some light on this. Eventually, Manu Srivastava and Ruppert Koch explained how the magic works. It turns out the LookupInstanceKey is a 128-bit hash of the actual content based key.

Manu Srivastava:

“The Hash == InstanceKey.Value == CorrelationKey == LookupKey. These terms all mean the same thing. InstanceKey.Value contains the value of the hash, which is of type GUID and represents the correlation / lookup key [http://msdn.microsoft.com/en-us/library/system.runtime.durableinstancing.instancekey.aspx]”

“It is the responsibility of the Provider to store the Workflow Instance, the Correlation Keys *and* the mapping between the two. The LoadWorkflowByInstanceKeyCommand has a LookupKey as an argument. This is the correlation key. This is the key the custom provider implementation must use to identify and then return the Workflow Instance. The hashing algorithm used is irrelevant to the implementation of the Provider. The Provider only has to worry about storing the InstanceKey.Value and retrieving an instance via the InstanceKey.Value. The transformation between Document.Id and the InstanceKey is a WF runtime detail that you do not need to worry about for your implementation; thus, you do not need to worry about the hashing algorithm itself. When you persist an instance, save the InstanceKey.Value and its mapping to InstanceId. When you load an instance, use the LookupKey to find the correct InstanceId and return the WorkflowInstance. That's it. You don't need to go from Document.ID to hash or vice-versa…thats handled at the WF Runtime Layer. Just focus on saving InstanceKey.Values and loading by LookupKey. =) InstanceKey.Value == LookupKey in this particular scenario”

In other words: – Concentrate on the problem, and leave the plumbing to us! -Point taken.

Running the sample

Download and install:

Before running the sample, you need to properly configure the environment and in particular you have to create the cache used by the custom Persistence Provider.

To accomplish this task, you can perform the following steps:

1. Download the Windows Server AppFabric.

2. Read Scott Hanselman’s post as to how to set it up.

3. Open Caching Administration Windows PowerShell through the Start menu, and run the following commands:

  • Start-CacheCluster
  • New-Cache bLogical

4. Download and run the sample the sample.

This is probably a good time to point out, that the sample provided with this post is not production ready. However, If you want to take it further, I’d be happy to help out!(</disclaimer>)

Again, many thanks to Paolo Salvatori, Manu Srivastava and Ruppert Koch from the AppFabric CAT and Product group.

HTH