Design Details
Workflow modeling components
The Workflow Model layer defines how a UI workflow is created to capture business logic, thus providing navigation for host applications. The goals for this layer are:
- Create a simple state machine like root activity type that can be used to model navigation logic for host applications
- Establish a protocol for host to access workflow data and change workflow state
- Provide UI tools for easy creation of UI workflows
Navigator
A Navigator is a simplified state machine activity. If a child of the Navigator is an InteractionActivity, it represents a user interaction. The Navigator coordinates the execution of interaction activities by interpreting transitions defined between two interaction activities. An InteractionActivity can block when it waits for a user input. The navigator becomes idle when it reaches a blocking state and control is returned to the host at that point.
Different types of non-blocking activities can be children of a Navigator, however the only blocking activities allowed as direct children of a Navigator are interaction activities. Each child activity when added to the navigator is automatically attached with a Transitions property. This property is a collection of Transition objects. Each transition models a link between the source activity and the target activity. Each transition has an associated Condition property that is of type ActivityCondition. Each transition is evaluated at runtime in the order of their position in the collection to determine which target to move to from the source. The first transition that evaluates to true becomes the active link and the workflow moves to the state it points to.
The Navigator executes every child in a separate execution context. This allows a child to be executed more than once, thus enable backward navigation.
The Navigator keeps a History list which contains a list of activities that are executed in the past. The list behaves similarly to a backward stack inside a browser. Every time the navigator moves from one state (Activity) to another state (Activity), an entry is added to the history list. When the host explicitly asks the Navigator to move to an earlier executed state, the history list shrinks to where the target activity had been in the list and all entries after that point are cleared from the list. We do not maintain a forward history stack.
The following is the definition of the Navigator class.
[Designer(typeof(NavigatorDesigner), typeof(IDesigner))]
[DesignerSerializer(typeof(NavigatorCodeDomSerializer), typeof(CodeDomSerializer))]
[ActivityValidator(typeof(NavigatorValidator))]
public class Navigator : CompositeActivity, IActivityEventListener<ActivityExecutionStatusChangedEventArgs>, IActivityEventListener<QueueEventArgs>
{
// Public events
public event EventHandler<NavigatorEventArgs> Navigating{ add; remove; }
// Public properties
public string StartWith { get; set; }
public SynchronizationMode SynchronizationMode { get; set; }
public Permission OwnerPermission { get; set; }
public bool EnforceOwnerPermission { get; set; }
// Property accessor for attached properties
public static object GetTransitions(object dependencyObject);
public static void SetTransitions(object dependencyObject, object value);
// Proptected members
protected Activity Current { get; }
// ProcessCommand implementation
protected virtual void ProcessCommand(Command cmd, ActivityExecutionContext executionContext);
}
The Navigating Event
The Navigating event is fired just before the navigator moves to the next activity from the current activity. Because it fires before any transitions are evaluated, it gives the workflow developer a chance to overwrite the target of the navigation. If the event handler returns a non-empty NavigatorEventArgs.NavigateTo, the navigator will move to this activity without evaluating the transitions specified for the current activity. The NavigatorEventArgs class is defined as followes:
public sealed class NavigatorEventArgs : EventArgs
{
public string NavigateTo { get; set; }
}
Public Properties
Protected Members
- Current: this property always points to the current executing child activity of the navigator
ProcessCommand Implementation
It is necessary for the host application to be able to query for and change workflow state. The Navigator class implements the ProcessCommand virtual method that can perform actions that would support APIs defined on the NavigationManager.
protected virtual void ProcessCommand(object cmd, ActivityExecutionContext executionContext);
The Navigator receives command messages through a message queue created during its initialization process. The name of the queue is the same as the name of the Navigator activity. When a command message arrives in the queue, the Navigator is notified about its arrival. The Navigator takes the message from the queue and calls the virtual function ProcessCommand to execute the command.
The type of the command message enqueued by the host determines what command the host wants to execute. For example, the host can issue a GetCurrentContextCommand if it wants to query for the current workflow state and a SynchronizeCommand if it want to move to a particular state. There is an implicit contract between the host and the navigator as to what types of command message means what.
Transition
The Transition class represents a potential transition between the source and the target. Each transition has an associated Condition, which is evaluated at runtime. The first transition whose condition evaluates to true becomes the actual transition. The target of the actual transition becomes the next activity to be executed.
The following is the definition for the Transition class:
public sealed class Transition : DependencyObject
{
// Public properties
public string Target { get; set; }
public ActivityCondition Condition { get; set; }
public RuleSetReference ValidationRules { get; set; }
public string Name { get; set; }
}
- Target: this is the name of the target activity for this transition
- Condition: this can be code condition or declarative condition. If it evaluates to true, the target activity will be executed next if the validation also passes. If the Condition is null, it’s equivalent to the condition being evaluated to true
- ValidationRules: this is a RuleSet that is executed if the Condition evaluates to true. It can be used to validate the input for the target activity. If the validation fails, the navigator will re-execute the current activity instead of moving on to the target activity. An exception object will be returned to the host application via the InteractionContext class
- Name: this is the string that is displayed in the transition editor dialog as the name of this transition
The TransitionCollection class is a collection class that contains transitions. The definition is as follows
public sealed class TransitionCollection : KeyedCollection<string, Transition>
{
protected override string GetKeyForItem(Transition item)
{
return item.Target;
}
}
Other Supporting Types
The design experience is an important part of building the navigator. The following types are used to provide a rich design experience.
- NavigatorDesigner: The NavigatorDesigner is a free-form designer that supports flexible layout of child activities and the drawing of connectors between child activities
- NavigatorValidator: This validator makes sure that the sematics of a navigator is maintained. The following activities are not supported inside a Navigator:
- StateActivity
- StateInitializationActivity
- StateFinalizationActivity
- SetStateActivity
- Activities that implement the IEventActivity interface
- EventDrivenActivity
- ListenActivity
- WebServiceInputActivity
- WebServiceOutputActivity
- WebServiceFaultActivity
- NavigatorCodeDomSerializer: This CodeDomSerializer ensure the Transitions collection property serialized correctly into code
NavigatorWorkflow
The NavigatorWorkflow is the root activity for a UI workflow. It inherits from Navigator. It has an additional property TimeoutDuration that can be used to automatically clean up abandoned workflow instances.
The following shows the definition for NavigatorWorkflow:
[Designer(typeof(NavigatorWorkflowDesigner), typeof(IDesigner))]
[ActivityValidator(typeof(NavigatorWorkflowValidator))]
public class NavigatorWorkflow : Navigator, IActivityEventListener<QueueEventArgs>
{
public TimeSpan TimeoutDuration { get; set; }
}
- TimeoutDuration: this property specifies a timeout value for the workflow instance. It is frequent that a user might abandon an application process in the middle without explicitly inform the application. For example, close the browser in the middle of a check-out process. Without a cleanup mechanism, the unfinished workflow will be left in the persistence store forever. A timer associated with the workflow would cause the workflow to be cancelled and removed from persistence when the timer fires
Interaction Activities
InteractionBaseActivity
The InteractionBaseActivity class is an abstract class that captures the logic of modeling a user interaction inside the Navigator.
An InteractionBaseActivity is an event activity that unblocks the execution of the workflow when it receives a message from the host. It receives input messages through a message queue created during its initialization process. The name of the queue is the same as the name of the activity. When an InteractionBaseActivity starts executing, it first produces an InteractionContext object that captures the current state of the workflow. The InteractionContext generated is passed to the host application. It contains all the information necessary to display the UI associated with the current interaction activity, including the name of the current interaction and the appropriate data structure that contains the data to be displayed by the UI. The InteractionBaseActivity then checks to see if a message is available in the queue. If a message is available, it picks up the message and continues to execute its children if there are any. If a message is not available in the queue, it subscribe to the message arrive event and blocks the execution. The execution continues when a message arrives in the queue.
The following is the definition of the InteractionBaseActivity:
[ActivityValidator(typeof(InteractionActivityValidator))]
public abstract class InteractionBaseActivity: CompositeActivity, IDynamicPropertyTypeProvider, IActivityEventListener<QueueEventArgs>
{
// Public properties
public bool ReceiveInput { get; set; }
public Type ActivityInputType { get; set; }
public object ActivityInput { get; set; }
public Type ActivityOutputType { get; set; }
public object ActivityOutput { get; set; }
public Permission RequiredPermission { get; set; }
public Exception Error { get; set; }
// Public Events
public event EventHandler<EventArgs> Initialized { add; remove; }
public event EventHandler<InputArrivedEventArgs> InputArrived { add; remove; }
// Protected virtual members
protected internal virtual InteractionContext GetInteractionContext();
protected virtual void OnInitialize(IServiceProvider provider);
protected virtual void OnUninitialize(IServiceProvider provider);
protected virtual ActivityExecutionStatus OnExecute(ActivityExecutionContext executionContext);
protected virtual ActivityExecutionStatus OnCancel(ActivityExecutionContext executionContext);
protected virtual ActivityExecutionStatus OnHandleFault(ActivityExecutionContext executionContext, Exception exception);
protected virtual void OnClose(IServiceProvider provider);
}
Public Properties
- ReceiveInput: this property specifies a Boolean value that indicates whether or not the activity will block and wait for user input. Default is true.
- ActivityInputType: this property specifies the required type of the input data.
- ActivityInput: this property contains the input data that that the host application passes to the workflow to resume the current interaction.
- ActivityOutputType: this property specifies the type of the output data.
- ActivityOutputData: this property contains the output data that the workflow passes to the host application for displaying the UI for the current interaction.
- RequiredPermission: this property specifies a Permission object that restricts the access of the interaction activity. When a message arrives in the queue, the activity will take the IPrincipal object coming with the message and check it against the permissions defined in the RequiredPermission. Message will only be processed if it passes the permission check. Permission is again checked when the next interaction starts executing.
- Error: this property is set when permission check fails or when the validation rule associated with the transition fails. The validation rule is executed after a decision is made by the Navigator which activity is to transition to next. If the validation fails, the transition will be abandoned and the previous activity will be re-executed.
Public Events
- Initialized: this event is fired when the activity begins to execute. Handlers can use this opportunity to initialize any data needed to support the UI that is to be displayed next
- UserInputArrived: this event is fired when a message is delivered to the activity’s message queue and picked up by the activity’s queue event handler, before any child execution is started. The event args for this event is of type UserInputArrivedEventArgs. It is defined as followes:
public class UserInputArrivedEventArgs : EventArgs
{
public string UserInput { get; }
}
Out-of-box we provide two concrete implementations of the InteractionBaseActivity: InteractionActivity and InteractionGroupActivity.
InteractionActivity
An InteractionActivity can receive one single input message. When a message arrives in the queue, the InteractionActivity takes the message, unblocks the workflow and executes its child in sequential order. It closes when the last child is closed.
The following is the definition of the InteractionActivity:
[ToolboxBitmap(typeof(InteractionActivity), "Resources.InteractionActivity.png")]
[ToolboxItem(typeof(InteractionActivityToolboxItem))]
[Designer(typeof(InteractionActivityDesigner))]
public class InteractionActivity: InteractionBaseActivity, IActivityEventListener<ActivityExecutionStatusChangedEventArgs>
{
// Protected member overrides
protected override ActivityExecutionStatus OnExecute(ActivityExecutionContext executionContext);
// Event handler for the Close event on child closing.
void IActivityEventListener<ActivityExecutionStatusChangedEventArgs>.OnEvent(Object sender, ActivityExecutionStatusChangedEventArgs e);
}
InteractionGroupActivity
An InteractionGroupActivity can receive multiple input messages. Every time a message comes to the queue, the InteractionGroupActivity takes the message, and based on the input data in the message activates one of its children. More than one child activities can be active (status = Executing) at the same time. The InteractionActivity then goes back to wait for another message to come in. It does not close until either the FinishCondition is met or all of its children have closed.
The following is the definition of the InteractionGroupActivity:
[ToolboxBitmap(typeof(InteractionGroupActivity), "Resources.InteractionGroup.png")]
[ToolboxItem(typeof(InteractionGroupActivityToolboxItem))]
[Designer(typeof(InteractionGroupDesigner))]
public class InteractionGroupActivity: InteractionBaseActivity, IActivityEventListener<ActivityExecutionStatusChangedEventArgs>
{
// Public Members
public ActivityCondition UntilCondition { get; set; }
// Protected member overrides
protected override ActivityExecutionStatus OnExecute(ActivityExecutionContext executionContext);
// Event handler for the Close event on child closing.
void IActivityEventListener<ActivityExecutionStatusChangedEventArgs>.OnEvent(Object sender, ActivityExecutionStatusChangedEventArgs e)
}
Other Supporting Types
The following types are implemented to support the design time experience for interaction activities.
- InteractionActivityValidator: This validator makes sure that semantics inside the InteractionActivity is correct. The following activities are currently not supported inside a Navigator:
- StateActivity
- StateInitializationActivity
- StateFinalizationActivity
- SetStateActivity
- Activities that implement the IEventActivity interface
- EventDrivenActivity
- ListenActivity
- WebServiceInputActivity
- WebServiceOutputActivity
- WebServiceFaultActivity
- InteractionActivityDesigner: Similar to the SequentialActivityDesigner.
- InteractionActivityToolboxItem: A customized toolbox item.
- InteractionGroupActivityValidator: This validator makes sure only interaction activities and Navigator can be a child activity.
- InteractionGroupActivityDesigner: Similar to the ParallelActivityDesigner.
- InteractionActivityToolboxItem: A customized toolbox item that adds two InteractionActivity branches by default.
Workflow hosting components
NavigationManager
Continuation based web development has been the central scheme for many new web development platforms. It’s based on the premise that web applications can be written using a stateful programming model while at the same time maintain is scalability inherent in the stateless web server.
WF is the perfect platform to implement a continuation server with its ability to pause, resume, load and unload programming states. All we’re missing is an API that exposes the WF capabilities in a more UI-developer-friendly fashion. The NavigationManager class provides exactly that. This class creates an abstraction on top the WF WorkflowRuntime and WorkflowInstance classes. It creates and manages workflows for the running UI application. It also serves as a service container for runtime services.
The NavigationManager allows the UI application to access the workflow but hides the complexities that are associated with the WorkflowRuntime and the WorkflowInstance class.
Its methods reflect the basic operations provided by a navigation service, such as Start, GoFoward, GoBack, NavigateTo, etc. The NavigationManager also provides access to workflow state through the GetCurrentContext and GetHistory methods.
There is a one-to-one relationship between the NavigationManager and the WorkflowRuntime. The NavigationManager object creates and maintains a reference to a WorkflowRuntime object. The NavigationManager expose WorkflowRuntime as a public property. This allows more sophisticated solutions to fully customize the runtime environment. Certain restrictions are enforced by the NavigationManager as to what services can be added to the runtime. For example, only the SynchronizationContextSchedulerService can be used. No other scheduling services are allowed. We’ll take about why we impose this restriction later when we talk about the SynchronizationContextSchedulerService.
The following APIs are exposed on the NavigationManager class.
public sealed class NavigationManager : IManageContinuation
{
// Constructors
public NavigationManager();
public NavigationManager(string configSection);
// Continuation Implementation
public Guid CreateWorkflow(XmlReader xaml, XmlReader rules);
public Guid CreateWorkflow(XmlReader xaml, XmlReader rules, Dictionary<string, object> parameters);
public Guid CreateWorkflow(Type workflowType);
public Guid CreateWorkflow(Type workflowType, Dictionary<string, object> parameters);
public InteractionContext Start(Guid durableHandle);
public InteractionContext GoForward(Guid durableHandle, UserInput input);
public InteractionContext GoForward(Guid durableHandle, Bookmark bookmark, UserInput input);
public InteractionContext GoBack(Guid durableHandle);
public InteractionContext Navigate(Guid durableHandle, Bookmark bookmark);
public void Terminate(Guid durableHandle);
public InteractionContext GetCurrentContext(Guid durableHandle);
public InteractionContext GetContext(Guid durableHandle, Bookmark bookmark);
public ReadOnlyCollection<Bookmark> GetHistory(Guid durableHandle);
// Public Properties
public WorkflowRuntime WorkflowRuntime { get; set; }
}
CreateWorkflow
The Create overloads create a new instance of the workflow. These overloads directly correspond to the CreateWorkflow methods on WorkflowRuntime. We support creating workflow instances from both a precompiled workflow type as well as a XOML document in the forms of XmlReader.
Start
Start calls WorkflowInstance.Start to start running the workflow after it’s created. Start is a synchronous call. The call will not return until the first Idle state is reached by the workflow. In most cases, the initialization and the execution will happen on the same thread as the caller thread.
GoForward
A UI workflow typically consists of a navigator, a set of interaction activities and rules that govern how to move between these interaction activities. The GoForward method allows the host application to submit data to the workflow and signal the workflow to move from the current interaction to the next. The method works by sending a message to the interaction activity that is currently waiting for input. Once the message is received by the interaction activity, the workflow continues its execution until it reaches the next interaction activity, at which point a new InteractionContext will be created and control returns back to the host.
GoBack
GoBack method sends a message to the workflow to cancel the current interaction activity and re-execute the previous activity. GoBack will only succeed if the workflow allows synchronization either to all activities or activities that are in the history list.
Nagivate
NavigateTo method allows the host application to cancel the current interaction activity and to start executing a different activity specified by the host. Navigate will fail if the workflow does not allow synchronization or if the target activity is not in the history list but the workflow only allows synchronization to activities that are in the history list. We use this method to implement client cached backward navigation. When the host submits data from a previously cached page, we first synchronize the workflow with the client UI by navigating to the interaction corresponds to the previous UI, and then submit from there.
GetContext & GetCurrentContext
GetCurrentContext returns the current InteractionContext. An InteractionContext object contains information representing the state of the workflow.
GetContext returns the InteractionContext for a specific Bookmark (i.e. interaction).
GetHistory
The GetHistory method returns the list of past Bookmarks since the beginning of the workflow in the reverse order as they’re performed. A history is only valid when the workflow instance is not completed. We’ll discuss more on how the history stack is created later when we talk about the workflow model.
More about Command Processing
Since host applications can not directly access the workflow object, the only way to query or change workflow state is through messaging. GoBack, Navigate, GetContext, GetCurrentContext and GetHistory are all implemented through message exchanges between the NavigationManager and the workflow. We call this Command Processing.
To implement command processing, the host must know how to send a command message and the workflow must know how to receive this message and process the command. To establish a standard communication of command messages between the host and the workflow, we automatically create a command processing message queue for the root navigator activity. The queue name, which is the name of the root activity, is well-known by both the NavigationManager and the Navigator activity. The queue is created when the Navigator activity is initialized and a subscription to the queue event is added to notify the navigator of message arrivals.
Once a command message arrives, the navigator will need to know how to process it. Our Navigator class implements the ProcessCommand virtual method. This method can differentiate different types of command by inspecting the type of the message. It then process each command that it recognized. If it does not recognize the command, the message is ignored.
InteractionContext
The InteractionContext class captures information needed to represent the current interaction. It is defined as the following:
public class InteractionContext
{
public Bookmark Bookmark { get; }
public object ActivityOutput { get; }
private PrincipalPermission requiredPermission { get; }
private Exception validationError { get; }
}
- Bookmark: This is the name of the current interaction activity.
- ActivityOutput: This is the output data generated by the current interaction activity.
- RequiredPermission: This is the permission required by the current interaction activity. Application can use this information to filter the UI presented to the current user.
- Error: This will be set if the user permission checked failed or if input data is invalid.
For InteractionGroupActivity, we introduce a derived class called InteractionGroupContext to include information about child interactions.
public class InteractionGroupContext : InteractionContext
{
public ReadonlyCollection<ageContext> ActiveChildContexts { get; }
public ReadonlyCollection<ageContext> AvailableChildContexts { get; }
}
- ActiveChildContexts: This is the list of active child contexts. An active child is one that has started executing and is waiting for input. This means the activity is in Executing state.
- AvailableChildContexts: This is the list of available child contexts. An available child is one that either has started executing and is waiting for input or can be started by the parent if a message to start it comes. This means the activity is in either Executing or Initialized state.
UserInput
The UserInput class defines data submitted to the workflow from the host application.
public class UserInput
{
public string Action { get; set; }
public object ActivityInput { get; set; }
public IPrincipal Principal { get; set; }
}
- Action: an application specific string that identifies the user action from the UI, such as “Buy”, “ViewCart”, “CheckOut”, etc.
- ActivityInput: the input data submitted from the UI to the workflow.
- Principal: an IPrincipal object that identifies the current user. The value will be used to check for permissions against the RequiredPermission object on interaction activity.
Bookmark
The Bookmark class defines data needed to identify each interaction activity inside a workflow.
public class Bookmark
{
public string ActivityName { get; set; }
public Guid InteractionId { get; set; }
}
- ActivityName: the name of the interaction activity.
- InteractionId: a value that uniquely identifies each interaction.
Configuration Support
The NavigationManager uses a config handler similar to WorkflowRuntime config handler.
An optional StartOnDemand attribute can be used to specify whether or not to automatically start a new workflow instance when the application starts. The default is “true”. If set to “false”, the application will start a new workflow instance by passing a parameter “StartOnDemand=true” in the query string.
The Workflow element is used to specify which workflow to be associated with the application. The attribute mode can be either “Compiled” or “XOML”. When mode is “Compiled”, the value attribute should specify the type of the compiled workflow; when mode is “XOML”, the value attribute should specify the location of the XOML file, another attribute “RulesFile” is also used to optionally specify the location of a rules file.
The Services element contains types and parameters for workflow runtime services.
Below is an example of the NavigationSettings config section:
<NavigationManagerSettings StartOnDemand="false" >
<Workflow mode="Compiled" value="ASPUIWorkflow.Workflow1, ASPUIWorkflow"/>
<!--<Workflow mode="XOML" value="WebSite/XAMLWorkflow.xoml" rulesFile="WebSite/XAMLWorkflow.rules" />-->
<Services>
<add type="System.Workflow.Runtime.Hosting.SqlWorkflowPersistenceService, System.Workflow.Runtime, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" ConnectionString="Initial Catalog=ASPPersistence;Data Source=localhost;Integrated Security=SSPI;" UnloadOnIdle="true"/>
</Services>
</NavigationManagerSettings>
UI specific hosting components
Hosting inside ASP.Net Application
We provide an out-of-box WFWebHostingModule class that makes hosting PageFlow directly inside an Asp.Net application very easy. The WFWebHostingModule class provides a central point of control where all client requests are initially processed. It acts as a translator between client inputs and the workflow. It translates page actions to workflow commands, causing the workflow to change state, thus producing the next page.
The HttpApplication Pipeline
The following is a list of operations that happens when an http request is processed by an HttpApplication class (for a complete list of events fired during a request/response cycle, see the MSDN article “ASP.NET Application Life Cycle Overview”):
- When ASP.NET receives the first request for any resource in an application, a class named ApplicationManager creates an application domain and a HostingEnvironment.
- The ASP.NET core objects such as HttpContext, HttpRequest, and HttpResponse are created and initialized.
- The application is started by creating an instance of the HttpApplication class.
- Any configured modules are created.
- Request processing starts after the HttpApplication has validated the incoming request and identified the location of the requested resources.
- The BeginRequest event is fired, followed authentication and cache resolution event.
- Based on the file name extension of the requested resource (mapped in the application's configuration file), a class that implements IHttpHandler is selected to process the request. If the request is for an object (page) derived from the Page class and the page needs to be compiled, ASP.NET compiles the page before creating an instance of it.
- IHttpHandler.ProcessRequest is called to generate response to be returned to the client.
- Other post-ProcessRequest events are fired.
- The EndRequest event is fired.
The WFWebHostingModule implements the IHttpModule interface and subscribes to the BeginRequest and the EndRequest events.
BeginRequest
When BeginRequest is called, the module checks to see if there is a workflow already associated with the current request by looking for the “FlowId” parameter in the query string. If it does not find one, it starts a new workflow based on the workflow type specified in the web.config file. The workflow starts running and the first interaction activity will be executed to produce the first InteractionContext. The module then looks up the page url from the config file using the activity name inside the InteractionContext and redirects to the page. The workflow then goes on idle, waiting for user to click the “Next” button, and returns the control to the module. The module exits BeginRequest and the normal HttpApplication pipeline resumes, sending the response down to the client.
If an existing workflow is found to be associated with the request, the module calls NavigationManager.GetCurrentContext to get the current InteractionContext. This causes the workflow being reloaded into the process. This procedure ensures that the user can come back to the page they left off from in a previous session and all the data will be restored automatically.
EndRequest
When EndRequest is called, the module processes the resume event initiated by the user clicking the SubmitButton on the web page. The module first identifies the type of resume action as being either “Submit” or “Back” or “Cancel” and then acts accordingly.
- Submit – the module first verifies that the current web page is in-sync with the workflow. If the web page and the workflow is out of sync, it tries to move the workflow back to be aligned with the web page before asks the workflow to continue. The module makes sure that the current web page is in the workflow’s history list before doing the synchronization. This ensures that the application is running in the pre-defined order, that it does not violate the business logic defined by the workflow.
- Back – the module calls NavigationManager.GoBack. Note that by moving back, the workflow is going to re-execute the previous activity. Thus any initialization code inside the activity and the web page will be called again. Developers should be aware of this fact and not expect the same behavior as when the browser back button is clicked.
- Cancel – the module calls NavigationManager.Terminate to terminate the existing workflow.
- The UserInput class defines data submitted to the workflow from the host application.
AspUserInput Class
The AspUserInput inherits from UserInput. In addition to store input data submitted by the UI components, it provides static methods to be called by the UI components to submit the data.
public class AspNetUserInput : UserInput
{
public IPrincipal Principal { get; set; }
public static void GoForward(object data, IPrincipal principal);
public static void GoForward(string action, object data, IPrincipal principal);
public static void GoBack(IPrincipal principal);
public static void Cancel(IPrincipal principal);
}
These static methods does nothing but creating an AspUserInput class instance and put it in the HttpContext.Items collection. When the request is processed by the WFWebHostingModule, the module looks up the current UserInput and calls the correct method on NavigationManager to pass the user input data to the workflow and moves on to the next destination page.
Configuration Support
The WFWebHostingModule uses a config handler to define associations between interaction activities and web pages. Following is an example:
<AspNavigationSettings>
<PageMappings>
<add bookmark="EnterAlias" location="~/Default.aspx"/>
<add bookmark="EnterProfile" location="~/Page2.aspx"/>
<add bookmark="EnterBilling" location="~/Page3.aspx"/>
<add bookmark="Problem" location="~/Page4.aspx"/>
<add bookmark="Summary" location="~/Page5.aspx"/>
<add bookmark="Confirmation" location="~/LastPage.aspx"/>
</PageMappings>
<ExceptionMappings>
<add type="Microsoft.Samples.Workflow.UI.WorkflowNotFoundException" location="~/ErrorPage.aspx"/>
<add type="Microsoft.Samples.Workflow.UI.WorkflowCanceledException" location="~/ErrorPage.aspx"/>
<add type="System.ArgumentException" location="~/ErrorPage.aspx"/>
<add type="System.Security.SecurityException" location="~/ErrorPage.aspx"/>
</ExceptionMappings>
</AspNavigationSettings>
The PageMappings element contains a list of associations of activity names (“bookmark”) to its corresponding web page names (“location”).
The ExceptionMappings element specifies a list of association of an exception types (“type”) with a web page that is used to display this exception (“localtion”).
SynchronousSchedulerService
The SynchronousSchedulerService uses the .Net SynchronizationContext to implement workflow thread scheduling. Several UI technologies support the .Net SynchronizationContext in their threading model. By building a scheduler service on top of SynchronizationContext, we can delegate complex threading logic to individual UI technology and work seamlessly with all of them. The SynchronousSchedulerService delegate the Schedule() call to SynchronizationContext.Send. We do not care how each UI technology implements Send.
The NavigationManager APIs are entirely synchronous as we call SynchronizationContext.Send for all workflow operations. If the client application needs to optimize user response, it should call the NavigationManager APIs using the standard .Net BeginInvoke async programming model.
The following shows the design and implementation of the SynchronousSchedulerService class:
public sealed class SynchronousSchedulerService : WorkflowSchedulerService
{
protected override void Schedule(WaitCallback callback, Guid workflowInstanceId)
{
if (SynchronizationContext.Current != null)
{
SynchronizationContext.Current.Send(delegate(object o2) { callback(workflowInstanceId); }, null);
}
else
callback(workflowInstanceId);
}
}
Normally, to change the scheduling behavior of WF, the host would write a custom WorkflowSchedulerService. However, a UI developer is more familiar with the already prevalent .Net concept of SynchronizationContext. So instead of letting the host applications to plug in their own WorkflowSchedulerService, we let them change the scheduling behavior through implementing their own SynchronizationContext. The host would implement the Send method to change workflow scheduling.