Cross Post from Ron Jacobs blog

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 http://twitter.com/ronljacobs