Paulo Morgado

.NET Development & Architecture

This Blog

Syndication

Search

Tags

News

Unit Test Today! Get Typemock Isolator!

Projects

Books

 

Visitors

Visitor Locations

Community

Email Notifications

Archives

Profile

Disclaimer

The opinions and viewpoints expressed in this site are mine and do not necessarily reflect those of Microsoft, my employer or any community that I belong to. Any code or opinions are offered as is. Products or services mentioned are purchased by me, made available to me by my employer or the manufacturer/vendor which doesn't influence my opinion in any way.

Improving The Page Flow Application Block: Current Definition And Current Page Flow

Introduction

There are a number of classes in the in the .NET Framework that expose a static (shared in Visual Basic) property with a reference to an instance valid in a particular scope (usually the current thread).

In this article I'll change the Page Flow Application Block to add properties to the page flow directory in order to take advantage of the work done by the page flow provider. These properties are:

  • CurrentPageFlow - The current page flow. This is usually set by the ProcessRequest or GetPageFlow methods of the page flow provider.
  • CurrentDefinition - The current page flow definition. This can be the definition of the current page flow if one exists or the definition determined by the ProcessRequest method of the page flow provider.

Contracts

To add this new feature, the contracts (interfaces) of the Page Flow Application Block's components need to be changed.

Page Flow Directory

This is the component exposing the new properties.

namespace Microsoft.Practices.PageFlow
{
    /// <summary>
    /// Directory containing information about PageFlows
    /// </summary>
    public static class PageFlowDirectory
    {
        // ...

        /// <summary>
        /// Gets or sets the current <see cref="T:IPageFlowDefinition">page flow definition</see>.
        /// </summary>
        /// <value>The current <see cref="T:IPageFlowDefinition">page flow definition</see>.</value>
        public static IPageFlowDefinition CurrentDefinition{...}

        /// <summary>
        /// Gets or sets the <see cref="T:IPageFlow">current page flow</see>.
        /// </summary>
        /// <value>The <see cref="T:IPageFlow">current page flow</see>.</value>
        public static IPageFlow CurrentPageFlow{...}

        // ...
    }
}

 

Page Flow Provider

Now that it's possible to have a page flow definition without knowing it's name (or type), the page flow provider should take advantage of that and allow for getting page flow instances by specifying its definition:

namespace Microsoft.Practices.PageFlow
{
    /// <summary>
    /// Defines the contract for PageFlow providers.
    /// </summary>
    public interface IPageFlowProvider
    {
        /// <summary>
        /// Gets a <see cref="IPageFlow"/> with a specific <see cref="P:IPageFlowDefinition.Name"/>.
        /// </summary>
        /// <param name="pageFlowName">The <see cref="P:IPageFlowDefinition.Name"/> of <see cref="IPageFlow"/> to get.</param>
        /// <returns>An instance of an <see cref="IPageFlow"/> with the specified <see cref="P:IPageFlowDefinition.Name"/>.</returns>
        IPageFlow GetPageFlow(string pageFlowName);

        // (3) added start
        /// <summary>
        /// Gets a <see cref="IPageFlow"/> with a specific <see cref="T:IPageFlowDefinition">definition</see>.
        /// </summary>
        /// <param name="definition">The <see cref="T:IPageFlowDefinition">definition</see> of <see cref="IPageFlow"/> to get.</param>
        /// <returns>An instance of an <see cref="IPageFlow"/> with the specified <see cref="T:IPageFlowDefinition">definition</see>.</returns>
        IPageFlow GetPageFlow(IPageFlowDefinition definition);

        /// <summary>
        /// Gets a <see cref="IPageFlow"/> of a specific <see cref="Type"/>.
        /// </summary>
        /// <param name="pageFlowType">The <see cref="Type"/> of <see cref="IPageFlow"/> to get.</param>
        /// <returns>An instance of an <see cref="IPageFlow"/> of the specified <see cref="Type"/>.</returns>
        IPageFlow GetPageFlow(Type pageFlowType);

        /// <summary>
        /// Gets a <see cref="IPageFlow"/> of a specific <see cref="Type"/>.
        /// </summary>
        /// <param name="instanceId"></param>
        /// <returns>The instance of an <see cref="IPageFlow"/> with the specified unique identifier.</returns>
        IPageFlow GetPageFlow(Guid instanceId);

        /// <summary>
        /// Handles a request for a specific page.
        /// </summary>
        /// <param name="url">The URL of the page.</param>
        /// <returns>A <see cref="ProcessResult"/> that describes how the request should be handled.</returns>
        ProcessResult ProcessRequest(string url);

        // When you implement derived classes, you may want to consider
        // the following signature for your constructor.
        //public PageFlowProvider(PageFlowInstanceStoreProviderSection storeSection, PageFlowInstanceCorrelationTokenProviderSection tokenProviderSection);
    }
}

Page Flow Factory

The page flow factory should have the same functionality:

namespace Microsoft.Practices.PageFlow
{
    /// <summary>
    /// Creates IPageFlow instances.
    /// </summary>
    public interface IPageFlowFactory : IDisposable
    {
        /// <summary>
        /// Creates an instance of a PageFlow with the specified name.
        /// </summary>
        /// <param name="pageFlowName">The name of the page flow to create.</param>
        /// <param name="instanceId">The instance id of the <see cref="IPageFlow"/>.</param>
        /// <returns>An instance of a <see cref="IPageFlow"/>.</returns>
        IPageFlow GetPageFlow(string pageFlowName, Guid instanceId);

        /// <summary>
        /// Creates an instance of a PageFlow with the specified <see cref="Type"/>.
        /// </summary>
        /// <param name="pageFlowName">The name of the page flow to create.</param>
        /// <returns>An instance of a <see cref="IPageFlow"/>.</returns>
        IPageFlow GetPageFlow(string pageFlowName);

        /// <summary>
        /// Creates an instance of a PageFlow with the specified definition.
        /// </summary>
        /// <param name="pageFlowDefinition">The definition of the page flow to create.</param>
        /// <param name="instanceId">The instance id of the <see cref="IPageFlow"/>.</param>
        /// <returns>An instance of a <see cref="IPageFlow"/>.</returns>
        IPageFlow GetPageFlow(IPageFlowDefinition pageFlowDefinition, Guid instanceId);

        /// <summary>
        /// Creates an instance of a PageFlow with the specified definition.
        /// </summary>
        /// <param name="pageFlowDefinition">The definition of the page flow to create.</param>
        /// <returns>An instance of a <see cref="IPageFlow"/>.</returns>
        IPageFlow GetPageFlow(IPageFlowDefinition pageFlowDefinition);

        /// <summary>
        /// Creates an instance of a PageFlow with the specified <see cref="Type"/>.
        /// </summary>
        /// <param name="pageFlowType">The page flow type to create.</param>
        /// <param name="instanceId">The instance id of the <see cref="IPageFlow"/>.</param>
        /// <returns>An instance of a <see cref="IPageFlow"/>.</returns>
        IPageFlow GetPageFlow(Type pageFlowType, Guid instanceId);

        /// <summary>
        /// Creates an instance of a PageFlow with the specified <see cref="Type"/>.
        /// </summary>
        /// <param name="pageFlowType">The page flow type to create.</param>
        /// <returns>An instance of a <see cref="IPageFlow"/>.</returns>
        IPageFlow GetPageFlow(Type pageFlowType);
    }
}

Implementation

Now that I've define the new component contracts, I'll have to upgrade the implementations to the new contracts.

Page Flow Directory

This is a static class (Microsoft.Practices.PageFlow.PageFlowDirectory), so the implementation and contract are on the same entity.

Most of this type of properties are implemented as thread static, so I opted for the same kind of implementation.

namespace Microsoft.Practices.PageFlow
{
    /// <summary>
    /// Directory containing information about PageFlows
    /// </summary>
    public static class PageFlowDirectory
    {
        // ...

        [ThreadStatic]
        private static IPageFlowDefinition _currentDefinition;

        [ThreadStatic]
        private static IPageFlow _currentPageFlow;

        // ...

        /// <summary>
        /// Gets or sets the current <see cref="T:IPageFlowDefinition">page flow definition</see>.
        /// </summary>
        /// <value>The current <see cref="T:IPageFlowDefinition">page flow definition</see>.</value>
        public static IPageFlowDefinition CurrentDefinition
        {
            get
            {
                return _currentDefinition;
            }
            set
            {
                if (_currentDefinition != value)
                {
                    _currentDefinition = value;
                }
            }
        }

        /// <summary>
        /// Gets or sets the <see cref="T:IPageFlow">current page flow</see>.
        /// </summary>
        /// <value>The <see cref="T:IPageFlow">current page flow</see>.</value>
        public static IPageFlow CurrentPageFlow
        {
            get
            {
                return _currentPageFlow;
            }
            set
            {
                if (_currentPageFlow != value)
                {
                    _currentPageFlow = value;
                }
                if (_currentPageFlow == null)
                {
                    _currentDefinition = null;
                }
                else
                {
                    _currentDefinition = _currentPageFlow.Definition;
                }
            }
        }

        // ...
    }
}

As you can see, these properties are read/write. That can either be helpful or hurtful to you, as in any implementation of this kind. Also, whenever the CurrentPageFlow property is set, the CurrentDefinition is set to reflect the definition of the current page flow.

Page Flow Provider

The class that implements the Microsoft.Practices.PageFlow.IPageFlowProvider interface supplied with the Page Flow Application Block is the Microsoft.Practices.PageFlow.WorkflowFoundation.WorkflowFoundationPageFlowProvider class in the PageFlow.WorkflowFoundation project.

Although the page flow directory is not tied to a particular page flow factory implementation, the actual implementation was done as if the expected factory is the Microsoft.Practices.PageFlow.WorkflowFoundation.WorkflowFoundationPageFlowFactory class. Keeping that in mind, the new method to implement just gets the page flow type and calls the overload that takes a page flow type as a parameter.

The page flow provider also needs to feed the values of the PageFlowDirectory.CurrentPageFlow and PageFlowDirectory.CurrentDefinition properties. This is done as soon as a value is found for these properties (with a few optimizations).

namespace Microsoft.Practices.PageFlow.WorkflowFoundation
{
    /// <summary>
    /// Implementation that uses Windows Workflow Foundation as the page flow engine.
    /// </summary>
    /// <remarks>
    /// <para>This class is provided as a singleton by the <see cref="PageFlowDirectory"/> class</para>
    /// </remarks>
    public class WorkflowFoundationPageFlowProvider : IPageFlowProvider, IDisposable
    {
        // ...
        
        /// <summary>
        /// Gets a <see cref="IPageFlow"/> with a specific <see cref="T:IPageFlowDefinition">definition</see>.
        /// </summary>
        /// <param name="definition">The page flow <see cref="T:IPageFlowDefinition">definition</see> to get.</param>
        /// <returns>An instance of a page flow</returns>
        /// <remarks>
        /// <para>
        /// If there are page flow instances (running or not) in the <see cref="IPageFlowInstanceStore" /> with the <paramref name="pageFlowName"/> specified,
        /// then the provider will get the instance id asociated and retrieve the instance from Workflow Foundation persistence service using the <see cref="WorkflowFoundationPageFlowFactory" />.
        /// </para>
        /// <para>
        /// If there are NO page flow instances in the <see cref="IPageFlowInstanceStore" /> with the <paramref name="pageFlowName"/> specified,
        /// then the provider will create a new one using the <see cref="WorkflowFoundationPageFlowFactory" /> and will add it to the store as a non running page flow.
        /// </para>
        /// </remarks>
        public IPageFlow GetPageFlow(IPageFlowDefinition definition)
        {
            Guard.ArgumentNotNull(definition, "definition");

            return GetPageFlow(definition.PageFlowType);
        }

        // ...
        
        /// <summary>
        /// Returns a new or existing page flow given the page flow type.
        /// </summary>
        /// <example>
        /// <code>
        /// MyPageFlow myPageFlow = PageFlowDirectory.Provider.GetPageFlow(typeof(MyPageFlow));
        /// </code>
        /// </example>
        /// <param name="pageFlowType">The page flow type to get.</param>
        /// <returns>An instance of a page flow</returns>
        /// <remarks>
        /// <para>
        /// If there are page flow instances (running or not) in the <see cref="IPageFlowInstanceStore" /> with the <paramref name="pageFlowType"/> specified,
        /// then the provider will get the instance id asociated and retrieve the instance from Workflow Foundation persistence service using the <see cref="WorkflowFoundationPageFlowFactory" />.
        /// </para>
        /// <para>
        /// If there are NO page flow instances in the <see cref="IPageFlowInstanceStore" /> with the <paramref name="pageFlowType"/> specified,
        /// then the provider will create a new one using the <see cref="WorkflowFoundationPageFlowFactory" /> and will add it to the store as a non running page flow.
        /// </para>
        /// </remarks>
        public IPageFlow GetPageFlow(Type pageFlowType)
        {
            IPageFlow instance = GetPageFlow(pageFlowType, true);

            PageFlowDirectory.CurrentPageFlow = instance;

            return instance;
        }

        // ...

        /// <summary>
        /// Returns an existing page flow given the instance id.
        /// </summary>
        /// <example>
        /// <code>
        /// Guid myPageFlowId = GetPageFlowIdFromSomewhere();
        /// MyPageFlow myPageFlow = PageFlowDirectory.Provider.GetPageFlow(myPageFlowId);
        /// </code>
        /// </example>
        /// <param name="instanceId">The instance id of the page flow to retrieve</param>
        /// <returns>The page flow instance corresponding to the instance id</returns>
        /// <remarks>
        /// <para>
        /// If <paramref name="instanceId"/> specifies an instance that is not currently in the store the method throws.
        /// </para>
        /// </remarks>        
        /// <exception cref="PageFlowException">The page flow instance was not found in the store</exception>
        public IPageFlow GetPageFlow(Guid instanceId)
        {
            string pageFlowTypeName = _store.GetInstanceType(instanceId);
            if (pageFlowTypeName == null)
                throw new PageFlowException(String.Format(CultureInfo.CurrentCulture, Resources.InstanceNotFound, instanceId));
            
            IPageFlow instance = GetPageFlow(Type.GetType(pageFlowTypeName), instanceId);

            PageFlowDirectory.CurrentPageFlow = instance;

            return instance;
        }

        // ...

        /// <summary>
        /// Process the request url with the page flow engine. 
        /// </summary>
        /// <param name="url">The request url</param>
        /// <returns>The <see cref="ProcessResult" /> holding the redirect action to take.</returns>        
        /// <remarks>
        /// <para>If <paramref name="url"/> belongs to a page flow defined in the catalog, the page flow instance will be resumed if it was suspended.</para>
        /// <para>If <paramref name="url"/> is different from the current page of the running page flow instance, 
        /// the user will be redirected to the current page if the page flow is constrained.</para>
        /// <para>If the user is leaving a page flow, the running instance will be suspended if the <see cref="AbandonBehavior"/> is AllowAndSaveInstance</para>
        /// <para>- or -</para>
        /// <para>Will be aborted if the <see cref="AbandonBehavior"/> is AllowAndDiscardInstance.</para>
        /// <para>- or -</para>
        /// <para>The user will be redirected back to the current page if the <see cref="AbandonBehavior"/> is Prevent.</para>
        /// <para>This method is intended to be called by the <see cref="PageFlowHttpModule"/>.</para>
        /// </remarks>
        public ProcessResult ProcessRequest(string url)
        {
            Guid instanceId = _store.GetLastRunningInstance();  
          
            if (instanceId != Guid.Empty)
            {
                IPageFlow instance = GetPageFlow(instanceId);
                PageFlowDirectory.CurrentPageFlow = instance;
                return ProcessWithRunningInstance(instance, url);
            }
            else
            {
                PageFlowDirectory.CurrentPageFlow = null;
                return ProcessWithNoRunningInstance(url);
            }
        }

        /// <summary>
        /// Process the request when there is no instance in the store or the instance is NOT running.
        /// </summary>
        /// <param name="url">The request url.</param>
        /// <returns>The <see cref="ProcessResult" /> holding the redirect action to take.</returns>        
        private ProcessResult ProcessWithNoRunningInstance(string url)
        {
            ProcessResult result;
            IPageFlowDefinition definition = PageFlowDirectory.Catalog.GetByUrl(url);
            if (definition != null)
            {
                IPageFlow instance = GetPageFlow(definition.PageFlowType, false);
                if (instance == null)
                {
                    PageFlowDirectory.CurrentDefinition = definition;
                    result = ProcessWithNonExistingInstance(definition);
                }
                else
                {
                    PageFlowDirectory.CurrentPageFlow = instance;
                    result = ProcessWithExistingInstance(definition, instance);
                }
            }
            else
            {
                result = new ProcessResult(false, string.Empty); ;
            }

            return result;
        }        
        
        // ...
    }
}

Page Flow Factory

The class that implements the Microsoft.Practices.PageFlow.IPageFlowFactory interface supplied with the Page Flow Application Block is the Microsoft.Practices.PageFlow.WorkflowFoundation.WorkflowFoundationPageFlowFactory class in the PageFlow.WorkflowFoundation project.

The new methods needed to implement the new contract just get the page flow type from the definition and call the methods that take a page flow type as a parameter.

namespace Microsoft.Practices.PageFlow.WorkflowFoundation
{
    /// <summary>
    /// Implementation of an <see cref="IPageFlowFactory"/> that uses Windows Workflow Foundation.
    /// </summary>
    public class WorkflowFoundationPageFlowFactory : IPageFlowFactory, IDisposable
    {
        // ...

        /// <summary>
        /// Creates an instance of a PageFlow with the specified definition.
        /// </summary>
        /// <param name="pageFlowDefinition">The definition of the page flow to create.</param>
        /// <param name="instanceId">The instance id of the <see cref="IPageFlow"/>.</param>
        /// <returns>A <see cref="WorkflowFoundationPageFlow"/>.</returns>
        /// <exception cref="PageFlowException">The <see cref="WorkflowInstance"/> that is trying to be deserialized changed its structure.</exception>
        public IPageFlow GetPageFlow(IPageFlowDefinition pageFlowDefinition, Guid instanceId)
        {
            return GetPageFlow(pageFlowDefinition.PageFlowType, instanceId);
        }

        /// <summary>
        /// Creates an instance of a PageFlow with the specified definition.
        /// </summary>
        /// <param name="pageFlowDefinition">The definition of the page flow to create.</param>
        /// <returns>An instance of a <see cref="IPageFlow"/>.</returns>
        public IPageFlow GetPageFlow(IPageFlowDefinition pageFlowDefinition)
        {
            return GetPageFlow(pageFlowDefinition.PageFlowType);
        }

        // ...
    }
}

Page Flow Store QuickStart

(This sample supplied with the Web Client Software Factory will be used to test and demonstrate the improvements made to the Page Flow Application Block.)

Now that I can reference page flows by its definition name instead of its definition type, I can improve the sample application:

namespace PageFlowWithShoppingCartQuickstart.Store.BusinessLogic
{
    public class StoreController
    {
        // ...

        public IPageFlow StorePageFlow
        {
            get
            {
                if (_storePageFlow == null)
                {
                    _storePageFlow = PageFlowDirectory.CurrentPageFlow;
                    if (_storePageFlow == null)
                    {
                        IPageFlowDefinition definition = PageFlowDirectory.CurrentDefinition;
                        if (definition != null)
                        {
                            _storePageFlow = _pageFlowProvider.GetPageFlow(definition);
                        }
                        else
                        {
                            _storePageFlow = _pageFlowProvider.GetPageFlow("StorePageFlow");
                        }
                    }
                }
                return _storePageFlow;
            }
        }

        // ...
    }
}

Conclusion

And there I am with a new page flow application block where there's no need to retrieve the page flow instance from the persistence store on every request.

Resources

Published Mon, Oct 29 2007 2:32 by Paulo Morgado

Comments

# Improving The Page Flow Application Block: Current Definition And Current Page Flow@ Sunday, October 28, 2007 8:46 PM

In this third article of the series I&#39;ll show how you can change the Page Flow Application Block

Paulo Morgado

# Improving The Page Flow Application Block - Paulo Morgado@ Sunday, October 28, 2007 8:54 PM

Pingback from  Improving The Page Flow Application Block - Paulo Morgado

Improving The Page Flow Application Block - Paulo Morgado

# Resources for geekSpeak - Web Client Software Factory with Paulo Morgado@ Monday, February 04, 2008 3:20 PM

Here is a great set of resources from our guest Paulo Morgado. Catch the recording here . Contextual

geekSpeak

# Resources for geekSpeak - Web Client Software Factory with Paulo Morgado@ Monday, February 04, 2008 3:56 PM

Here is a great set of resources from our guest Paulo Morgado. Catch the recording here . Contextual

Noticias externas

Leave a Comment

(required) 
(required) 
(optional)
(required) 
If you can't read this number refresh your screen
Enter the numbers above: