|
using System;
using DateSiteNavigation;
using System.Workflow.Runtime;
using System.Workflow.Activities;
using System.Workflow.Runtime.Hosting;
/// <summary>
/// Wrapper class used by the asp.net host application to communicate
/// with the navigational workflow.
/// </summary>
public class WorkflowManager
{
private string _pageToGoTo;
private string _stateToGoTo;
private bool _isSynchronized;
private WorkflowRuntime _workflowRuntime;
private NavigationService _navigationService;
public const string WorkflowInstanceIdKey = "WorkflowInstanceId";
public const string WorkflowRuntimeKey = "WorkflowRuntime";
public const string LocalServicesKey = "LocalServices";
private readonly System.Web.HttpApplicationState Application;
private readonly System.Web.SessionState.HttpSessionState Session;
private readonly System.Web.HttpRequest Request;
private readonly System.Web.HttpResponse Response;
/// <summary>
/// Constructor that initializes all variables used to communicate with
/// the navigational workflow.
/// </summary>
/// <param name="application"></param>
/// <param name="session"></param>
/// <param name="request"></param>
/// <param name="response"></param>
public WorkflowManager(System.Web.HttpApplicationState application, System.Web.SessionState.HttpSessionState session, System.Web.HttpRequest request, System.Web.HttpResponse response)
{
// get reference to asp.net members
this.Application = application;
this.Session = session;
this.Request = request;
this.Response = response;
// get workflow references
_pageToGoTo = string.Empty;
_workflowRuntime = GetWorkflowRuntime();
_navigationService = _workflowRuntime.GetService<INavigationService>() as NavigationService;
// add event handlers to receive messages from local service
_navigationService.PageToGoToReceived += new EventHandler<PageToGoToEventArgs>(_navigationService_PageToGoToReceived);
_navigationService.OutgoingSynchronizeReceived += new EventHandler<OutgoingSynchronizeEventArgs>(_navigationService_OutgoingSynchronizeReceived);
}
/// <summary>
/// Gets or sets the workflow instance id currently in use.
/// </summary>
public Guid CurrentWorkflowInstanceId
{
get { return Session[WorkflowInstanceIdKey] == null ? Guid.Empty : (Guid)Session[WorkflowInstanceIdKey]; }
set { Session[WorkflowInstanceIdKey] = value; }
}
/// <summary>
/// Gets the name of the current state activity for the
/// current workflow instance in use.
/// </summary>
public string CurrentWorkflowState
{
get
{
// check if the current workflow instance id is empty
if (this.CurrentWorkflowInstanceId == Guid.Empty)
{
return string.Empty;
}
// get a reference to the current state machine workflow instance
StateMachineWorkflowInstance instance = new StateMachineWorkflowInstance(_workflowRuntime, this.CurrentWorkflowInstanceId);
// return the current state name
return instance.CurrentStateName;
}
}
/// <summary>
/// Get the name of the page the user is currently on. This method is
/// independent of the page they should be on.
/// </summary>
/// <returns></returns>
public string GetCurrentPageName()
{
return System.IO.Path.GetFileName(Request.PhysicalPath);
}
/// <summary>
/// Creates a new workflow instance and returns the page name
/// of the initial state to go to (which is default.aspx).
/// </summary>
/// <returns></returns>
public string StartNewWorkflow()
{
// create and start the navigational workflow instance
WorkflowInstance instance = _workflowRuntime.CreateWorkflow(typeof(NavigationWorkflow));
instance.Start();
// execute the workflow synchronously on the current thread
RunWorkflow(instance.InstanceId);
// put the workflow instance id in session for this user
this.CurrentWorkflowInstanceId = instance.InstanceId;
// execute the workflow on the current thread and return the page to go to
return GetPageToGoTo(instance.InstanceId);
}
/// <summary>
/// Ensures the current workflow is synchronized, meaning the page the user is on is
/// the page they should be on. To test that this works, you can hardcode the url to
/// skip to a different page than the next logical one in the workflow. HOWEVER, our
/// workflow is also self healing, and if the page the user goes to, whether hacked
/// or natural, the workflow will kick off its self healing mechanism (if possible)
/// and auto-transition its state to the state that matches the given page name.
/// </summary>
public void SynchronizeWorkflow()
{
// check if there is a workflow in session
if (this.CurrentWorkflowInstanceId == Guid.Empty)
{
// redirect to the start page
ManagedRedirect(GetStartPage());
}
// the page to go to if the workflow is not synchronized
string pageToGoTo = string.Empty;
try
{
// raise event to ask the workflow if it is synchronized
IncomingSynchronizeEventArgs args = new IncomingSynchronizeEventArgs(this.CurrentWorkflowInstanceId, GetCurrentPageName());
_navigationService.OnIncomingSynchronize(args);
// execute the workflow synchronously on the current thread
RunWorkflow(this.CurrentWorkflowInstanceId);
// return if the workflow is synchronized
if (_isSynchronized)
{
return;
}
// get the name of the state that the user should be in
StateMachineWorkflowInstance instance = new StateMachineWorkflowInstance(_workflowRuntime, this.CurrentWorkflowInstanceId);
instance.SetState(_stateToGoTo);
// get the name of the page that the user should be on
pageToGoTo = Rehydrate(this.CurrentWorkflowInstanceId);
}
catch (Exception ex)
{
// could not synchronize the workflow
ManagedRedirect(GetStartPage());
}
// if the page the user should be on is not the same as the page that they currently
// are on then redirect the user to the page they should be on
if (!pageToGoTo.Equals(GetCurrentPageName()))
{
ManagedRedirect(pageToGoTo);
}
}
/// <summary>
/// Raise the previous event inside the navigational workflow
/// and then return the page to go to.
/// </summary>
/// <returns></returns>
public string Previous()
{
// prepare external data exchange arguments
ExternalDataEventArgs args = new ExternalDataEventArgs(this.CurrentWorkflowInstanceId);
// make local service call into Navigation Workflow
_navigationService.OnPrevious(args);
// execute the workflow on the current thread and return the page to go to
return GetPageToGoTo(args.InstanceId);
}
/// <summary>
/// Raise the next event inside the navigational workflow
/// and then return the page to go to.
/// </summary>
/// <returns></returns>
public string Next()
{
// prepare external data exchange arguments
ExternalDataEventArgs args = new ExternalDataEventArgs(this.CurrentWorkflowInstanceId);
// make local service call into Navigation Workflow
_navigationService.OnNext(args);
// execute the workflow on the current thread and return the page to go to
return GetPageToGoTo(args.InstanceId);
}
/// <summary>
/// Rehydrate the workflow with the given instance id by raising the rehydrate
/// event inside the navigational workflow and then return the page to go to.
/// </summary>
/// <param name="instanceId"></param>
/// <returns></returns>
public string Rehydrate(Guid instanceId)
{
try
{
// prepare external data exchange arguments
ExternalDataEventArgs args = new ExternalDataEventArgs(instanceId);
// make local service call into Navigation Workflow to retrieve step user left off at
_navigationService.OnRehydrated(args);
// put the workflow instance id in session
this.CurrentWorkflowInstanceId = instanceId;
// execute the workflow on the current thread and return the page to go to
return GetPageToGoTo(args.InstanceId);
}
catch (Exception ex)
{
// the given workflow instance could not be rehydrated
return GetStartPage();
}
}
/// <summary>
/// Raise the error event of the given type inside the navigational workflow
/// and then return the page to go to.
/// </summary>
/// <param name="errorType"></param>
/// <returns></returns>
public string Error(ErrorType errorType)
{
// prepare external data exchange arguments
ErrorEventArgs args = new ErrorEventArgs(this.CurrentWorkflowInstanceId, errorType);
// make local service call into Navigational Workflow
_navigationService.OnError(args);
// execute the workflow on the current thread and return the page to go to
return GetPageToGoTo(args.InstanceId);
}
/// <summary>
/// Raise the skip to basics event inside the navigational workflow
/// and then return the page to go to.
/// </summary>
/// <returns></returns>
public string SkipToBasics()
{
// prepare external data exchange arguments
ExternalDataEventArgs args = new ExternalDataEventArgs(this.CurrentWorkflowInstanceId);
// make local service call into Navigation Workflow
_navigationService.OnSkipToBasics(args);
// execute the workflow on the current thread and return the page to go to
return GetPageToGoTo(args.InstanceId);
}
/// <summary>
/// Raise the skip to appearance event inside the navigational workflow
/// and then return the page to go to.
/// </summary>
/// <returns></returns>
public string SkipToAppearance()
{
// prepare external data exchange arguments
ExternalDataEventArgs args = new ExternalDataEventArgs(this.CurrentWorkflowInstanceId);
// make local service call into Navigation Workflow
_navigationService.OnSkipToAppearance(args);
// execute the workflow on the current thread and return the page to go to
return GetPageToGoTo(args.InstanceId);
}
/// <summary>
/// Raise the skip to lifestyle event inside the navigational workflow
/// and then return the page to go to.
/// </summary>
/// <returns></returns>
public string SkipToLifestyle()
{
// prepare external data exchange arguments
ExternalDataEventArgs args = new ExternalDataEventArgs(this.CurrentWorkflowInstanceId);
// make local service call into Navigation Workflow
_navigationService.OnSkipToLifestyle(args);
// execute the workflow on the current thread and return the page to go to
return GetPageToGoTo(args.InstanceId);
}
/// <summary>
/// Raise the skip to interests event inside the navigational workflow
/// and then return the page to go to.
/// </summary>
/// <returns></returns>
public string SkipToInterests()
{
// prepare external data exchange arguments
ExternalDataEventArgs args = new ExternalDataEventArgs(this.CurrentWorkflowInstanceId);
// make local service call into Navigation Workflow
_navigationService.OnSkipToInterests(args);
// execute the workflow on the current thread and return the page to go to
return GetPageToGoTo(args.InstanceId);
}
/// <summary>
/// Raise the skip to complete event inside the navigational workflow
/// and then return the page to go to.
/// </summary>
/// <returns></returns>
public string SkipToComplete()
{
// prepare external data exchange arguments
ExternalDataEventArgs args = new ExternalDataEventArgs(this.CurrentWorkflowInstanceId);
// make local service call into Navigation Workflow
_navigationService.OnSkipToComplete(args);
// execute the workflow on the current thread and return the page to go to
return GetPageToGoTo(args.InstanceId);
}
/// <summary>
/// Return the starting page of all navigational workflows.
/// </summary>
/// <returns></returns>
public string GetStartPage()
{
// this is a special function in that it knows where to look in the web.config to retrieve the initial page
return NavigationSection.Current.Elements["InitialState"].PageName;
}
/// <summary>
/// Calls Response.Redirect on the given page to go to.
/// </summary>
/// <param name="pageToGoTo"></param>
public void ManagedRedirect(string pageToGoTo)
{
if (pageToGoTo != null && pageToGoTo != string.Empty)
{
Response.Redirect(pageToGoTo);
}
}
/// <summary>
/// Get a reference to the workflow runtime.
/// </summary>
/// <returns></returns>
private WorkflowRuntime GetWorkflowRuntime()
{
WorkflowRuntime workflowRuntime = Application[WorkflowRuntimeKey] as WorkflowRuntime;
return workflowRuntime;
}
/// <summary>
/// Get a reference to the manual workflow scheduler service.
/// </summary>
/// <returns></returns>
private ManualWorkflowSchedulerService GetWorkflowSchedulerService()
{
ManualWorkflowSchedulerService scheduler = _workflowRuntime.GetService(typeof(ManualWorkflowSchedulerService)) as ManualWorkflowSchedulerService;
return scheduler;
}
/// <summary>
/// Run the workflow with the given instance id on the current thread.
/// </summary>
/// <param name="instanceId"></param>
/// <returns></returns>
private bool RunWorkflow(Guid instanceId)
{
ManualWorkflowSchedulerService scheduler = GetWorkflowSchedulerService();
return scheduler.RunWorkflow(instanceId);
}
/// <summary>
/// Runs the given workflow and returns the page to go to.
/// </summary>
/// <param name="instanceId"></param>
/// <returns></returns>
private string GetPageToGoTo(Guid instanceId)
{
// execute the workflow on the current asp.net thread
RunWorkflow(instanceId);
// return where the user should go
return _pageToGoTo;
}
/// <summary>
/// After calling OnPageToGoTo, which the navigational workflow is listening
/// for, the workflow with then raise the event MessageReceived to return the
/// page to go to to the host application.
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void _navigationService_PageToGoToReceived(object sender, PageToGoToEventArgs e)
{
// get the page to go to
_pageToGoTo = e.PageToGoTo;
}
/// <summary>
/// After calling OnIncomingSynchronize, which the navigational workflow is listening
/// for, the workflow will then raise the event OutgoingSynchronize to return the result to the host
/// application. It is here we capture if the workflow is synchronzied and the state to go to.
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void _navigationService_OutgoingSynchronizeReceived(object sender, OutgoingSynchronizeEventArgs e)
{
_isSynchronized = e.IsSynchronized;
_stateToGoTo = e.StateToGoTo;
}
}
|
By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.
If a file you wish to view isn't highlighted, and is a text file (not binary), please
let us know and we'll add colourisation support for it.