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 http://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

http://blogs.msdn.com/rjacobs

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