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.