How To Load WF4 Workflow Services from a Database with IIS/AppFabric

This morning I saw a message post on the .NET 4 Windows Workflow Foundation Forum titled Load XAMLX from database.  I’ve been asked this question many times.

How can I store my Workflow Service definitions (xamlx files) in a database with IIS and AppFabric?

Today I decided to create a sample to answer this question.  Lately I’ve been picking up ASP.NET MVC 3 so my sample code is written with it and EntityFramework 4.1 using a code first approach with SQL Server Compact Edition 4.

Download Windows Workflow Foundation (WF4) - Workflow Service Repository Example

AppFabric.tv - How To Build Workflow Services with a Database Repository

Step 1: Create a Virtual Path Provider

Implementing a VirtualPathProvider is fairly simple.  The thing you have to keep in mind is that it will be called whenever ASP.NET wants to resolve a file or directory anywhere on the website.  You will need a way to determine if you want to provide virtual content.  For my example I created a folder in the web site called XAML.  This folder is empty but I found that it has to be there or the WCF Activation code will throw an exception.

When I want to activate a Workflow Service that is stored in the database I use a URI that will point to this directory like this https://localhost:34372/xaml/Service1.xamlx

 public class WorkflowVirtualPathProvider : VirtualPathProvider
 {
     #region Public Methods
  
     public override bool FileExists(string virtualPath)
     {
         return IsPathVirtual(virtualPath)
                     ? GetWorkflowFile(virtualPath).Exists
                     : this.Previous.FileExists(virtualPath);
     }
  
     public override VirtualFile GetFile(string virtualPath)
     {
         return IsPathVirtual(virtualPath)
                     ? GetWorkflowFile(virtualPath)
                     : this.Previous.GetFile(virtualPath);
     }
  
     #endregion
  
     #region Methods
  
     private static WorkflowVirtualFile GetWorkflowFile(string path)
     {
         return new WorkflowVirtualFile(path);
     }
  
     // TODO (01.1) Create a folder that will be used for your virtual path provider
     // Note: System.ServiceModel.Activation code will throw an exception if there is not a real folder with this name
     private static bool IsPathVirtual(string virtualPath)
     {
         var checkPath = VirtualPathUtility.ToAppRelative(virtualPath);
         return checkPath.StartsWith("~/xaml", StringComparison.InvariantCultureIgnoreCase);
     }
  
     #endregion
 }

Step 2: Create a VirtualFile class

The VirtualFile class has to load content from somewhere (in this case a database) and then return a stream to ASP.NET.  Performance is a concern so you should definitely make use of caching when doing this.

 public class WorkflowVirtualFile : VirtualFile
     {
         #region Constants and Fields
  
         private Workflow workflow;
  
         #endregion
  
         #region Constructors and Destructors
  
         public WorkflowVirtualFile(string virtualPath)
             : base(virtualPath)
         {
             this.LoadWorkflow();
         }
  
         #endregion
  
         #region Properties
  
         public bool Exists
         {
             get { return this.workflow != null; }
         }
  
         #endregion
  
         #region Public Methods
  
         public void LoadWorkflow()
         {
             var id = Path.GetFileNameWithoutExtension(this.VirtualPath);
  
             if (string.IsNullOrWhiteSpace(id))
             {
                 throw new InvalidOperationException(string.Format("Cannot find workflow definition for {0}", id));
             }
  
             // TODO (02.1) Check the Cache for workflow definition
  
             this.workflow = (Workflow)HostingEnvironment.Cache[id];
  
             if (this.workflow == null)
             {
                 // TODO (02.2) Load it from the database
  
                 // Note: I'm using EntityFramework 4.1 with a Code First approach
                 var db = new WorkflowDBContext();
  
                 this.workflow = db.Workflows.Find(id);
  
                 if (this.workflow == null)
                 {
                     throw new InvalidOperationException(string.Format("Cannot find workflow definition for {0}", id));
                 }
  
                 // TODO (02.3) Save it in the cache
                 HostingEnvironment.Cache[id] = this.workflow;
             }
         }
  
         /// <summary>
         ///   When overridden in a derived class, returns a read-only stream to the virtual resource.
         /// </summary>
         /// <returns>
         ///   A read-only stream to the virtual file.
         /// </returns>
         public override Stream Open()
         {
             if (this.workflow == null)
             {
                 throw new InvalidOperationException("Workflow definition is null");
             }
  
             // TODO (02.4) Return a stream with the workflow definition
             var stream = new MemoryStream(this.workflow.WorkflowDefinition.Length);
             var writer = new StreamWriter(stream);
             writer.Write(this.workflow.WorkflowDefinition);
             writer.Flush();
             stream.Seek(0, SeekOrigin.Begin);
  
             return stream;
         }
  
         #endregion
     }

Step 3: Register the Virtual Path Provider

Finally you register the provider and you will be on your way.

 protected void Application_Start()
 {
     Database.SetInitializer(new WorkflowInitializer());
     AreaRegistration.RegisterAllAreas();
  
     RegisterGlobalFilters(GlobalFilters.Filters);
     RegisterRoutes(RouteTable.Routes);
  
     // TODO (03) Register the Virtual Path Provider
     HostingEnvironment.RegisterVirtualPathProvider(new WorkflowVirtualPathProvider());
 }

Ron Jacobs

https://blogs.msdn.com/rjacobs

Twitter: @ronljacobs https://twitter.com/ronljacobs

Comments

  • Anonymous
    June 15, 2011
    Ron, this is great. Do you see this leading to storing different versions of a XAMLX in the database and using filters, similar to WCF routing, to get the correct version for each client?

  • Anonymous
    June 15, 2011
    Many Workflow customers are ISVs which allow their customers to create/modify workflow definitions.  Most of the time they want to store the Workflow definitions in a database effectively treating them like data.  With this approach it becomes easier to do this. We like to listen to our customers and if there is demand for product features to make this easier we will definitely consider them.