Mark Everard

Hello, I'm Mark – a PhD physicist turned technologist / architect.

Archive for September, 2010

Developing a custom workflow in EPiServer : Part two

without comments

This is the second post, in a series of five about developing a custom workflow in EPiServer.

Windows Workflow Foundation in EPiServer

Windows workflow is a full runtime that can be hosted by any application running on the Microsoft ASP.NET stack. It defines all of the services that are necessary (persistence, threading and transactions) to manage the steps, rules and decisions that make up any arbitrary workflow task.

EPiServer first implemented Windows Workflow Foundation in CMS 5. The EPiServer application acts as the host for the workflow runtime, telling the workflow runtime the type of workflows to create, and communicating any events to the runtime. The runtime itself manages the workflow lifecycle, including persistence and will notify the host of any important workflow events such as completion or termination.

You can see the configuration for the workflow foundation within the EPiServer web.config file, where the runtime itself is configured and the persistence mechanism is setup, which is either a SQL Server or Oracle database (state is serialised into one of two workflow related tables called InstanceState and CompletedScope).

There are two main types of workflows that can be developed using Windows workflow foundation

  1. StateMachineWorkflows
  2. SequentialWorkflows

A SequentialWorkflow is predictable and can only execute in the given order which is defined within the workflow code itself. In this sense you can describe the workflow itself as being in control of its execution pathway. A StateMachineWorkflow relies on external events to drive it to completion and the external decisions define which state the workflow transitions to, meaning that it is the external events that are in control.

Our requirements can be satisifed by a StateMachineWorkflow as the external decisions that are made by Approvers or Publishers define the workflow completion order.

You can select a new StateMachineWorkflow directly from the Visual Studio ‘Add item’ command, you’ll see that it simply inherits from StateMachineWorkflowActivity:

public sealed partial class TwoStagePageWorkflow : StateMachineWorkflowActivity
{
   //implementation
}

Communication

The most important element to define is the code that specifies how your host application communicates with the workflow runtime.

Workflow foundation diagram

The workflow can either handle external events or invoke external methods. This is defined by the contents of a Service interface that is marked with the [ExternalDataExchange()] attribute.

using System;
using System.Workflow.Activities;

namespace TwoStage.Workflow.Communication
{
	[ExternalDataExchange()]
	public interface ITwoStageService
	{
		event EventHandler<TwoStageEventArgs> PageApproved;
                event EventHandler<TwoStageEventArgs> PageDeclined;
                event EventHandler<TwoStageEventArgs> PagePassedToApprover;
                event EventHandler<TwoStageEventArgs> PagePassedToPublisher;
	}
}

For our two-stage workflow, we need to handle the following external events, which correspond to decisions that a Creator, Approver or Publisher will make within the EPiServer Ui, which will then be raised by a concrete implementation of the above ITwoStageService interface. These events map to our StateMachine moving between the following four states.

  1. WithApprover
  2. WithPublisher
  3. PageDeclined
  4. PageApproved

Also you must define the event arguments that are passed when any of the external events are raised and then handled.


using System;
using System.Workflow.Activities;
using EPiServer.Core;
using TwoStage.Workflow.Domain.TaskOwner;

namespace TwoStage.Workflow.Communication
{
[Serializable]
public class TwoStageEventArgs : ExternalDataEventArgs
{
   public TwoStageEventArgs(Guid instanceId, PageReference pageLink, TaskOwner assignedTo, TaskOwner wasAssignedTo, string message) : base(instanceId)
   {
      PageLink = pageLink;
      IsAssignedTo = assignedTo;
      WasAssignedTo = wasAssignedTo;
      Message = message.Replace(Environment.NewLine, "<br />");
   }

   public PageReference PageLink { get; internal set; }

   public TaskOwner IsAssignedTo { get; internal set; }
   public TaskOwner WasAssignedTo { get; internal set; }

   public string Message { get; internal set; }
   }
}

Our custom EventArgs will hold the following properties, a PageReference so we know which EPiServer page the workflow task has been created against. A custom type allowing us to know who the current actioning user or role is, and also the same type to define who previously was responsible. It also defines a string based message.

Any custom EventArgs must inbherit from ExternalDataEventArgs and must be marked as serializable (as must all types defined within the EventArgs class).

If you look at the default EPiServer web.config file / EPiServer.config, you will see that there are three communication services defined for the out-of-the-box workflows. Any new service should also be registered here.

<externalServices>
   <!-- externalService:      Custom services that is to be registered with workflow runtime-->
   <externalService type= "EPiServer.WorkflowFoundation.Workflows.ApprovalService,EPiServer.WorkflowFoundation" />
   <externalService type= "EPiServer.WorkflowFoundation.Workflows.ReadyForTranslationService,EPiServer.WorkflowFoundation" />
   <externalService type= "EPiServer.WorkflowFoundation.Workflows.RequestForFeedbackService,EPiServer.WorkflowFoundation" />
</externalServices>

Now we’ve defined the communication pathway between the workflow runtime and the EPiServer host application, we can design the workflow itself and create the activities that the workflow must perform.

See you for part 3 😉

Written by mark

September 30th, 2010 at 10:00 pm

Developing a custom workflow in EPiServer : Part one

without comments

This in a first of a series of posts about how to build a custom EPiServer workflow. There is a fair amount of information about EPiServer workflows (most of which are nicely summarised by Frederik Vig in his amazingly useful EPiServer Developer Resources). Hopeful these posts will be able to add to that and also provide further context. Comments are most welcome!

Over the course of five posts I’ll cover the following:

Overview

The out-of-the box workflows in EPiServer cover off some common editor based scenarios such as approval and feedback. However the real world always throws up more exacting requirements that can’t be satisfied by the pre-defined workflows (or any combination of them – remember that you can chain them together).

In these situations you’ll have to build your own custom workflow piece utilising Windows Workflow Foundation and EPiServer’s own library of workflow elements and UI integration.

Requirements

First up we must start with requirements gathering. This is often a difficult process with workflows as you have to map and consolidate real business processes into a sensible technical solution.

Workflow requirements are always best distilled by a Visio flow diagram. Fact! So here’s ours (click to enlarge).

2 Stage Workflow Diagram

Looking at the required process, you can see that it’s a 2-stage approval process, where a page is created, sent for approval, and then finally sent through a further approval step, where upon the page is approved one final time before being published.

There are three user roles who interact with this process:

  • Creators, who initially create the content and kick off the workflow.
  • Approvers, who can decide to approve or decline the content, or pass on the approval decision.
  • Publishers, who can approve or decline the content, and publish the content.

This is only slightly different than the out-of-the-box Sequential Approval workflow, but the following differences necessitate a custom solution:

  1. There are two stages of approval
  2. Approvers can opt to ‘pass-the-buck’ and pass the approval stage to a fellow Approver.
  3. The workflow creator is informed at every decision point.

There are some additional requirements that can’t be shown on the diagram:

  1. Pages can only be published using the workflow
  2. Only one instance of the workflow can be in progress for a page at any one time.

Over the course of the next four posts I’ll piece together a workflow that meets the above requirements, covering the (very) basics of Windows Workflow Foundation and how you can use them and the integration methods that the EPiServer API exposes to allow any custom workflow to be implemented against EPiServer managed content.

Written by mark

September 29th, 2010 at 1:30 pm

Future ASP.NET features for UID / HTML developers

without comments


Slides for a talk I gave to Fortune Cookie’s HTML developers, where I tried to press the following points:

1) WebForms != ASP.NET
2) MVC FTW 🙂

Written by mark

September 17th, 2010 at 12:11 am

Posted in ASP.NET,Opinion

Using the DynamicDataStore to count file downloads

without comments

Increasingly CMS builds are required to deal with more than just standard page-based data and deliver an additional level of content metadata, such as the number of page views or the number of times a file has been downloaded.

The DynamicDataStore (introduced in EPiServer CMS6) provides developers with a convenient and easy-to-use persistence mechanism which is well suited to this type of content metadata.

One of the more simple requirements is to track the number of times a document is downloaded. The best place to handle this using ASP.NET is to create your own HttpHandler which will be responsible for serving the file and also updating the download count. This is better than any page based / codebehind methods as you can be sure that however the document is accessed (even via direct url) you’ll record the count. Remeber though, actually verifying that the file has been downloaded sucessfully is a whole different ball game – the count will give you just the number of times the file has been requested.

Creating a HttpHandler is simply a matter of creating a class that implements the IHttpHandler interface. However as we don’t want to alter any of the underlying behaviour for serving a file, but just supplement a call to the DDS. We’ll just inherit the existing EPiServer StaticFileHandler and override the ProcessRequestInternal and IsReusable methods.

using System.Web;
using DownloadCount.Persistence;

namespace DownloadCount
{
   public class DownloadCountHttpHandler : EPiServer.Web.StaticFileHandler
   {
       public DownloadCountHttpHandler()
       {
          ViewCountStore = new DdsDownloadCountStore();
       }

       protected IDownloadCountStore ViewCountStore { get; set; }

       protected override bool ProcessRequestInternal(HttpContext context)
       {
          var permanentPath = PermanentLinkUtility.MapRequestPathToPermanentLink(context.Request.FilePath);
          ViewCountStore.UpdateDownloadCount(permanentPath);
          return base.ProcessRequestInternal(context);
       }

       public override bool IsReusable
       {
          get { return true; }
       }
    }
}

There are some EPiServer specifics to take into account as we’re dealing with files stored in the VPP using the VirtualPathVersioningProvider. We need a unique handle to each file to accurately record the download path so we’ll map the request path to an internal EPiServer permanent link ID. This ensures that the count will be maintained should the file be renamed through the File Manager interface. I’ve put together a simple utility class with a static method, that attempts to checks whether the requested path is a UnifiedFile (aka lives withing the VPP). If so, it returns the PermanentLinkVirtualPath.

using System.Web.Hosting;
using EPiServer.Web.Hosting;

namespace DownloadCount
{
   public static class PermanentLinkUtility
   {
       public static string MapRequestPathToPermanentLink(string filePath)
       {
          var file = HostingEnvironment.VirtualPathProvider.GetFile(filePath) as UnifiedFile;
          if (file == null) return filePath;
          return string.IsNullOrEmpty(file.PermanentLinkVirtualPath)
                   ? filePath
                   : file.PermanentLinkVirtualPath;
       }
   }
}

The data we need to store is represented by the following object. The class only has two properties, an int to hold the total number of downloads and a string to represent the unique file path to the file. You’ll notice that the FilePath property is marked with the [EPiServerDataIndex] attribute, which tells the DDS to map the property to an indexed database column. We’ll use this index to perform lookups when retrieving data from the store.

using EPiServer.Data.Dynamic;

namespace DownloadCount.Persistence
{
   public class FileDownloadCount
   {
      [EPiServerDataIndex]
      public string FilePath { get; set; }
      public int Count { get; set; }
   }
}

Below is the service layer that is responsible for dealing with the DDS (following the recommended usage pattern) . It implements the two members of the IDownloadCount interface, which provide means of accessing and updating the download count information.elpful

[EDIT – updated as per Paul Smith’s helpful comment below]

using System.Web;
using EPiServer.Data.Dynamic;
using System.Linq;

namespace DownloadCount.Persistence
{
   public class DdsDownloadCountStore : IDownloadCountStore
   {
      protected DynamicDataStore GetDynamicDataStore()
      {
         return DynamicDataStoreFactory.Instance.GetStore(typeof(FileDownloadCount)) ??
               DynamicDataStoreFactory.Instance.CreateStore(typeof(FileDownloadCount));
      }

      public void UpdateDownloadCount(string path)
      {
         path = UrlDecodePath(path);
         using(var store = GetDynamicDataStore())
         {
            var fileCount = store.Find<FileDownloadCount>("FilePath", path).FirstOrDefault()
                            ?? FileDownloadCountFactory.Create(path);

            fileCount.Count++;
            store.Save(fileCount);
         }
      }

      private static string UrlDecodePath(string path)
      {
         return HttpUtility.UrlDecode(path);
      }

      public int GetDownloadCount(string path)
      {
         path = UrlDecodePath(path);
         using (var store = GetDynamicDataStore())
         {
           var fileCount = store.Find<FileDownloadCount>("FilePath", path).FirstOrDefault();
           return fileCount == null ? 0 : fileCount.Count;
         }
      }
   }
}

Finally, to use this handler we need to register it in the web.config file. Here I’m going to record counts for just .pdf and .doc files that are stored in the Globals VPP folder. You need to add one instance of the handler for each type of file you want to track. Don’t forget to chain the handlers appropriately (ie the existing wildcard handler should always be defined last).


<location path="Global">
 <system.webServer>
  <handlers>
   <add name="webresources" path="WebResource.axd" verb="GET" type="System.Web.Handlers.AssemblyResourceLoader"/>
   <add name="countdocs" path="*.doc" verb="*" type="DownloadCount.DownloadCountHttpHandler, DownloadCount"/>
   <add name="countpdfs" path="*.pdf" verb="*" type="DownloadCount.DownloadCountHttpHandler, DownloadCount"/>
   <add name="wildcard" path="*" verb="*" type="EPiServer.Web.StaticFileHandler, EPiServer"/>
  </handlers>
 </system.webServer>
 <staticFile expirationTime="-1.0:0:0"/>
 </location>

Accessing the latest count on page is pretty simple, again using the MapRequestPathToPermanentLink method defined earlier.


var store = new DdsDownloadCountStore();
var permanentPath = PermanentLinkUtility.MapRequestPathToPermanentLink(FilePath);
DownLoadCount.Text = string.Format("Downloaded {0} times", store.GetDownloadCount(permanentPath));

Full source code is available zipped up here, or in the EPiServer World code section.

Written by mark

September 15th, 2010 at 8:06 pm

Posted in ASP.NET,C#,Code,EPiServer