How To Load WF4 Workflow Services from a Database with IIS/AppFabric
[This article was contributed by the AppFabric team.]
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 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