How To Load WF4 Workflow Services from a Database with IIS/AppFabric
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
Comments
- Anonymous
August 11, 2011
Thanks for the great sample. I found that adding the GetCacheDependency override allows you to avoid having the xaml folder, but using sample code gets an error. The link forums.asp.net/.../1289756.aspx said returning null works, which it does. Do you have insight as to why that works?public override CacheDependency GetCacheDependency(string virtualPath, IEnumerable virtualPathDependencies, DateTime utcStart){ if (IsPathVirtual(virtualPath)) return null; else return Previous.GetCacheDependency(virtualPath, virtualPathDependencies, utcStart);} - Anonymous
August 11, 2011
Thanks Jim! No I don't know why returning null works. - Anonymous
August 18, 2011
i am wondering how we would use appfabric with this type of sitiuation, in case we want to monitor or persist a service, since these services are not visible through IIS in Appfabric dashboard - Anonymous
August 26, 2011
@ashish The service would still emit tracking events in the monitoring store but as you point out AppFabric would not display it as a service or offer control operations on it. - Anonymous
September 01, 2011
To answer the question I posted, I found that a wsHttp service is created for the xamlx at http://<host>/xaml/<wfname>.xmalx/System.ServiceModel.Activities_IWorkflowInstanceManagement - Anonymous
November 24, 2011
Hi Ron... this is working like a charm.However, is there a way I can use the WorkflowServiceHostFactory while using this VirtualPathProvider?Any idea, please let me know.Thanks!