Introduction
I don't like the fact that I need to know the type of a page flow definition. Or even the fact that a page flow definition has a type.
In this article I'll change the Page Flow Application Block to be possible to get page flows by its definition name instead of its definition type.
Contracts
To add this new feature, the contracts (interfaces) of the Page Flow Application Block's components need to be changed.
Page Flow Provider
A page flow provider is a class that implements the Microsoft.Practices.PageFlow.IPageFlowProvider interface and is responsible for processing the HTTP request and provide page flow instance by its type or instance identifier (strange combination of responsibilities, isn't it).
To be able to use page flow definition names instead of types, I'll add a new method to provide a page flow instance by its name:
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);
/// <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
A page flow factory is a class that implements the Microsoft.Practices.PageFlow.IPageFlowFactory interface and is responsible for creating page flow instances based on its type (if we want a new instance) or its type and instance identifier (if we want to retrieve an existing instance).
To be able to use page flow definition names instead of types, I'll add new methods to create a page flow instance by its name:
namespace Microsoft.Practices.PageFlow
{
/// <summary>
/// Creates IPageFlow instances.
/// </summary>
public interface IPageFlowFactory : IDisposable
{
/// <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>
/// <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 <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);
}
}
Page Flow Definition Catalog
A page flow definition catalog is a class that implements the Microsoft.Practices.PageFlow.IPageFlowDefinitionCatalog interface and is responsible for storing page flow definitions (providing Add, Remove and Get operations). A page flow definition can be obtained form the catalog by specifying its type.
To be able to use page flow definition names instead of types, I'll add a new method to obtain a page flow definition by its name:
namespace Microsoft.Practices.PageFlow
{
/// <summary>
/// Defines the interface for a catalog of <see cref="IPageFlowDefinition"/>s.
/// </summary>
public interface IPageFlowDefinitionCatalog
{
/// <summary>
/// Adds an <see cref="IPageFlowDefinition"/> to the catalog.
/// </summary>
/// <param name="definition">The <see cref="IPageFlowDefinition"/> to add to the catalog.</param>
void Add(IPageFlowDefinition definition);
/// <summary>
/// Gets the number of <see cref="IPageFlowDefinition"/>s in the catalog.
/// </summary>
int Count { get; }
/// <summary>
/// Removes an <see cref="IPageFlowDefinition"/> from the catalog.
/// </summary>
/// <param name="definition">The <see cref="IPageFlowDefinition"/> to remove.</param>
void Remove(IPageFlowDefinition definition);
/// <summary>
/// Gets an <see cref="IPageFlowDefinition"/> based on its URL.
/// </summary>
/// <param name="rawUrl">The URL that designates the <see cref="IPageFlowDefinition"/> to retrieve.</param>
/// <returns>The <see cref="IPageFlowDefinition"/> for the given URL.</returns>
IPageFlowDefinition GetByUrl(string rawUrl);
/// <summary>
/// Gets an <see cref="IPageFlowDefinition"/> based on its URL.
/// </summary>
/// <param name="pageFlowName">The name that designates the <see cref="IPageFlowDefinition"/> to retrieve.</param>
/// <returns>The <see cref="IPageFlowDefinition"/> for the given URL.</returns>
IPageFlowDefinition GetByName(string pageFlowName);
}
}
Implementations
Now that I've define the new component contracts, I'll have to upgrade the implementations to the new contracts.
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.
This implementation (as you can guess by its name) is based on Windows Workflow Foundation and its instances will be created using the type of the definition. Because of that, the implementation of the method that provides a page flow instance based on the definition's name just gets the definition from the catalog and calls the method that provides a page flow instance based on the definition type.
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="P:IPageFlowDefinition.Name"/>.
/// </summary>
/// <param name="pageFlowName">The page flow name 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(string pageFlowName)
{
IPageFlowDefinition pageFlowDefinition = PageFlowDirectory.Catalog.GetByName(pageFlowName);
if (pageFlowDefinition == null)
{
throw new PageFlowException(string.Format(CultureInfo.CurrentCulture, Resources.PageFlowDefinitionNotFound, pageFlowName));
}
return GetPageFlow(pageFlowDefinition.PageFlowType);
}
// ...
}
}
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.
As in the case of the page flow provider (and for the same reasons) The implementation of the new methods consists just on getting the page flow definition type and calling the corresponding old methods.
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 <see cref="Type"/>.
/// </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>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(string pageFlowName, Guid instanceId)
{
Type pageFlowType = GetPageFlowType(pageFlowName);
return GetPageFlow(pageFlowType, 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>
public IPageFlow GetPageFlow(string pageFlowName)
{
Type pageFlowType = GetPageFlowType(pageFlowName);
return GetPageFlow(pageFlowType);
}
private static Type GetPageFlowType(string pageFlowName)
{
IPageFlowDefinition pageFlowDefinition = PageFlowDirectory.Catalog.GetByName(pageFlowName);
if (pageFlowDefinition == null)
{
throw new PageFlowException(string.Format(CultureInfo.CurrentCulture, Resources.PageFlowDefinitionNotFound, pageFlowName));
}
return pageFlowDefinition.PageFlowType;
}
// ...
}
}
Page Flow Definition Catalog
The class that implements the Microsoft.Practices.PageFlow.IPageFlowDefinitionCatalog interface supplied with the Page Flow Application Block is the Microsoft.Practices.PageFlow.PageFlowDefinitionCatalog class in the PageFlow project.
The new method that obtains a page flow definition given its name will be implemented in the same way that the old method that obtains a page flow definition given its type:
namespace Microsoft.Practices.PageFlow
{
/// <summary>
/// A catalog of <see cref="IPageFlowDefinition"/>s.
/// </summary>
public class PageFlowDefinitionCatalog : List<IPageFlowDefinition>, IPageFlowDefinitionCatalog
{
// ...
/// <summary>
/// Gets an <see cref="IPageFlowDefinition"/> based on its URL.
/// </summary>
/// <param name="pageFlowName">The name that designates the <see cref="IPageFlowDefinition"/> to retrieve.</param>
/// <returns>The <see cref="IPageFlowDefinition"/> for the given URL.</returns>
public IPageFlowDefinition GetByName(string pageFlowName)
{
return Find(new Predicate<IPageFlowDefinition>(delegate(IPageFlowDefinition definition)
{
return definition.Name == pageFlowName;
}
)
);
}
// ...
}
}
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 = _pageFlowProvider.GetPageFlow("StorePageFlow");
// _storePageFlow = _pageFlowProvider.GetPageFlow(typeof(StorePageFlow));
}
return _storePageFlow;
}
}
}
}
Conclusion
And there I am with a new page flow application block where I don't need to know the page flow definition type (or even that it has one).
Resources
Published
Wed, Oct 17 2007 1:35
by
Paulo Morgado
Filed under: .NET, Architecture, ASP.NET, Community, SoftDev, Microsoft, MSDN, MVP, Web, WCSF, PnP, SoftwareFactories