Click here to Skip to main content
Click here to Skip to main content
Add your own
alternative version

Navigational Workflows Unleashed in WWF/ASP.NET 3.5

, 21 Oct 2008
Case-study on the internals of a Navigational Workflow engine for a fictional dating website called “World Wide Dating”.
datesite.zip
DateSite
App_Data
bin
database setup procedure
DateSite.csproj.user
Global.asax
images
complete.png
critical.png
logo2.png
warning.png
Profile.dbml
Profile.dbml.layout
Properties
Service References
NavigationWorkflow
bin
NavigationWorkflow.layout
Properties
Settings.settings
using System;
using System.Workflow.Activities;

namespace DateSiteNavigation
{
    /// <summary>
    /// Navigational state machine workflow. Marked as serializable so that we have 
    /// the flexibility to mark data members as non serializable. The advantage of
    /// this is that non serialized data members do not get persisted to the sql store.
    /// </summary>
    [Serializable]
    public sealed partial class NavigationWorkflow : StateMachineWorkflowActivity
    {        
        [NonSerialized]
        private PageToGoToEventArgs _pageToGoToEventArgs;

        [NonSerialized]
        private IncomingSynchronizeEventArgs _incomingSynchronizeEventArgs;

        [NonSerialized]
        private OutgoingSynchronizeEventArgs _outgoingSynchronizeEventArgs;

        [NonSerialized]
        private ErrorEventArgs _errorEventArgs;

        private int _heaviestWeight;

        /// <summary>
        /// Constructor.
        /// </summary>
        public NavigationWorkflow()
        {
            InitializeComponent();
        }
        
        /// <summary>
        /// Page to go to args that are sent to the host application.
        /// </summary>
        public PageToGoToEventArgs PageToGoToEventArgs
        {
            get { return _pageToGoToEventArgs; }
            set { _pageToGoToEventArgs = value; }
        }

        /// <summary>
        /// Args received by the workflow used to determine if the workflow is in sync.
        /// </summary>
        public IncomingSynchronizeEventArgs IncomingSynchronizeMessageEventArgs
        {
            get { return _incomingSynchronizeEventArgs; }
            set { _incomingSynchronizeEventArgs = value; }
        }

        /// <summary>
        /// Args sent to host application indicating if the workflow is in sync.
        /// </summary>
        public OutgoingSynchronizeEventArgs OutgoingSynchronizeMessageEventArgs
        {
            get { return _outgoingSynchronizeEventArgs; }
            set { _outgoingSynchronizeEventArgs = value; }
        }

        /// <summary>
        /// Args received by the workflow that contain the type of error that
        /// occurred so that the workflow can determine what page to go to.
        /// </summary>
        public ErrorEventArgs ErrorEventArgs
        {
            get { return _errorEventArgs; }
            set { _errorEventArgs = value; }
        }

        /// <summary>
        /// Initialization code that executes as soon as a new workflow is started.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void codeInitializeInitialState_ExecuteCode(object sender, EventArgs e)
        {
            _heaviestWeight = -1; // default setting for heaviest weight
        }

        /// <summary>
        /// Sets up the page to go to event args to be passed back to the host application.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void InitializeOutgoingMessage(object sender, EventArgs e)
        {
            // initialize the outgoing args to send to the host application
            _pageToGoToEventArgs = new PageToGoToEventArgs(this.WorkflowInstanceId, GetPageToGoTo());

            // each state has a weight associated with it. keep track of the heaviest weight (logically the
            // furthest state in the navigation process) so we can skip there later
            if (_heaviestWeight < NavigationSection.Current.Elements[this.CurrentStateName].Weight)
            {
                _heaviestWeight = NavigationSection.Current.Elements[this.CurrentStateName].Weight;
            }
        }

        /// <summary>
        /// Sets up the page to go to event args with the correct error page to pass 
        /// back to the host application.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void InitializeErrorMessage(object sender, EventArgs e)
        {
            // initialize the outgoing args to send to the host application
            _pageToGoToEventArgs = new PageToGoToEventArgs(this.WorkflowInstanceId, GetErrorPageToGoTo());
        }

        /// <summary>
        /// Executes when the SkipTo[statename] is raised, but the user has not navigated far enough
        /// in the workflow to raise this event yet. Therefore an error page is returned.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void InitializeCannotSkipToStateErrorMessage(object sender, EventArgs e)
        {
            // get the error page to go to
            string pageToGoTo = NavigationSection.Current.Errors[ErrorType.Generic.GetErrorName()].PageName;

            // initialize the outgoing args to send to the host application
            _pageToGoToEventArgs = new PageToGoToEventArgs(this.WorkflowInstanceId, pageToGoTo);
        }

        /// <summary>
        /// Code condition that determines if a skip to the basics state is possible.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void CanSkipToBasicsState(object sender, ConditionalEventArgs e)
        {
            int basicsWeight = NavigationSection.Current.Elements[BasicsState.Name].Weight;
            e.Result = basicsWeight <= _heaviestWeight;
        }

        /// <summary>
        /// Code condition that determines if a skip to the appearance state is possible.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void CanSkipToAppearanceState(object sender, ConditionalEventArgs e)
        {
            int appearanceWeight = NavigationSection.Current.Elements[AppearanceState.Name].Weight;
            e.Result = appearanceWeight <= _heaviestWeight;
        }

        /// <summary>
        /// Code condition that determines if a skip to the lifestyle state is possible.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void CanSkipToLifestyleState(object sender, ConditionalEventArgs e)
        {
            int lifestyleWeight = NavigationSection.Current.Elements[LifestyleState.Name].Weight;
            e.Result = lifestyleWeight <= _heaviestWeight;
        }

        /// <summary>
        /// Code condition that determines if a skip to the interests state is possible.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void CanSkipToInterestsState(object sender, ConditionalEventArgs e)
        {
            int interestsWeight = NavigationSection.Current.Elements[InterestsState.Name].Weight;
            e.Result = interestsWeight <= _heaviestWeight;
        }

        /// <summary>
        /// Code condition that determines if a skip to the complete state is possible.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void CanSkipToCompleteState(object sender, ConditionalEventArgs e)
        {
            int completeWeight = NavigationSection.Current.Elements[CompleteState.Name].Weight;
            e.Result = completeWeight <= _heaviestWeight;
        }

        /// <summary>
        /// Gets the page to go to based off the current state the workflow is in.
        /// </summary>
        /// <returns></returns>
        private string GetPageToGoTo()
        {
            // query the page to go to from the navigation section
            string pageToGoTo = NavigationSection.Current.Elements[this.CurrentStateName].PageName;
            return pageToGoTo;
        }

        /// <summary>
        /// Gets the error page to go to based off the error event arguments that were passed in.
        /// </summary>
        /// <returns></returns>
        private string GetErrorPageToGoTo()
        {
            // query the page to go to from the navigation section
            string errorName = _errorEventArgs.ErrorType.GetErrorName();
            string pageToGoTo = NavigationSection.Current.Errors[errorName].PageName;
            return pageToGoTo;
        }

        /// <summary>
        /// Initializes the outgoing synchronize event args to send to the host application
        /// and contains information on whether the workflow is in sync and if not, what needs
        /// to be done in order for it to become back in sync.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void InitializeOutgoingSynchronizeMessageEventArgs(object sender, EventArgs e)
        {
            NavigationElement expectedElement = NavigationSection.Current.Elements.GetElementByPageName(GetPageToGoTo());
            NavigationElement currentElement = NavigationSection.Current.Elements.GetElementByPageName(_incomingSynchronizeEventArgs.CurrentPage);

            // initialize the outgoing synchronize event args
            _outgoingSynchronizeEventArgs = new OutgoingSynchronizeEventArgs(this.WorkflowInstanceId);

            // check if the workflow is synchronized by comparing the expected page to the current page the user is on
            _outgoingSynchronizeEventArgs.IsSynchronized = expectedElement.PageName.Equals(currentElement.PageName);

            // workflow is synchronized
            if (_outgoingSynchronizeEventArgs.IsSynchronized)
            {
                return;
            }


            /* the below checks are in reference to if the user tried to manipulate the URL by hand */


            // if the weight of the page the user is on is less than or equal to the heaviest weight,
            // then the workflow is able to "self-heal" and transition itself to the state it should be in.
            // the host application will make the transition, but the StateToGoTo below tells it which is
            // the right state to transition to.
            if (currentElement.Weight <= _heaviestWeight)
            {
                _outgoingSynchronizeEventArgs.StateToGoTo = currentElement.StateName;
            }
            // otherwise the user is trying to skip to a page further in the workflow than they
            // can currently go, so set the state of the workflow to be what we expect it should be
            else
            {
                _outgoingSynchronizeEventArgs.StateToGoTo = expectedElement.StateName;
            }
        }
    }
}

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.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

Pero Matić
Software Developer (Senior) Plan A Software
United States United States
No Biography provided

| Advertise | Privacy | Mobile
Web02 | 2.8.140814.1 | Last Updated 21 Oct 2008
Article Copyright 2008 by Pero Matić
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid