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.

July 2007 - Posts

WCF: Error Code vs Exception?

There's an interesting discussion going on on the Architecture General MSDN Forum. Check it out.

Posted Fri, Jul 13 2007 0:45 by Paulo Morgado | 1 comment(s)

TypeTypeConverter - The Type TypeConverter

I've been searching high and low for a TypeConverter for Types and only found private or internal implementations.

It wasn't a hard task, but I think that the .NET Framework should provide one out of the box.

Here is the one I wrote:

/// <summary>
/// Provides a type converter to convert <see cref="T:System.Type"/> objects to and from various other representations.
/// </summary>
public class TypeTypeConverter : System.ComponentModel.TypeConverter
{
    /// <summary>
    /// Returns whether this converter can convert an object of the given type to the type of this converter, using the specified context.
    /// </summary>
    /// <param name="context">An <see cref="T:System.ComponentModel.ITypeDescriptorContext"></see> that provides a format context.</param>
    /// <param name="sourceType">A <see cref="T:System.Type"></see> that represents the type you want to convert from.</param>
    /// <returns>
    /// true if this converter can perform the conversion; otherwise, false.
    /// </returns>
    public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
    {
        return ((sourceType == typeof(string)) || base.CanConvertFrom(context, sourceType));
    }

    /// <summary>
    /// Converts from.
    /// </summary>
    /// <param name="context">The context.</param>
    /// <param name="culture">The culture.</param>
    /// <param name="value">The value.</param>
    /// <returns></returns>
    public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
    {
        if (value is string)
        {
            Type type = Type.GetType((string)value);
            if (type == null)
            {
                throw new ArgumentException("Type not found.", "value");
            }
            return type;
        }
        return base.ConvertFrom(context, culture, value);
    }

    /// <summary>
    /// Returns whether this converter can convert the object to the specified type, using the specified context.
    /// </summary>
    /// <param name="context">An <see cref="T:System.ComponentModel.ITypeDescriptorContext"></see> that provides a format context.</param>
    /// <param name="destinationType">A <see cref="T:System.Type"></see> that represents the type you want to convert to.</param>
    /// <returns>
    /// true if this converter can perform the conversion; otherwise, false.
    /// </returns>
    public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType)
    {
        return ((destinationType == typeof(string)) || base.CanConvertTo(context, destinationType));
    }

    /// <summary>
    /// Converts to.
    /// </summary>
    /// <param name="context">The context.</param>
    /// <param name="culture">The culture.</param>
    /// <param name="value">The value.</param>
    /// <param name="destinationType">Type of the destination.</param>
    /// <returns></returns>
    public override object ConvertTo(ITypeDescriptorContext context, CultureInfo culture, object value, Type destinationType)
    {
        if (destinationType != typeof(string))
        {
            return base.ConvertTo(context, culture, value, destinationType);
        }
        return ((Type)value).AssemblyQualifiedName;
    }
}

Posted Mon, Jul 9 2007 0:31 by Paulo Morgado | 4 comment(s)

WebBrowserControl for the .NET Framework 1.1 Updated

"My" WebBrowserControl for the .NET Framework 1.1 has been updated.

A few bugs were fixed and a few changes were made to the API (still thinking of a .NET 2.0 version).

I managed to solve all the problems with the design time but, unfortunately, the PropertyGrid throws an exception when the WebBrowserControl is bound to it:

System.NullReferenceException: Object reference not set to an instance of an object.
   at System.Windows.Forms.PropertyGridInternal.GridEntry.Create(PropertyGridView view, Object[] rgobjs, IServiceProvider baseProvider, IDesignerHost currentHost, PropertyTab tab, PropertySort initialSortType)
   at System.Windows.Forms.PropertyGrid.UpdateSelection()
   at System.Windows.Forms.PropertyGrid.RefreshProperties(Boolean clearCached)
   at System.Windows.Forms.PropertyGrid.Refresh(Boolean clearCached)
   at System.Windows.Forms.PropertyGrid.set_SelectedObjects(Object[] value)
   at System.Windows.Forms.PropertyGrid.set_SelectedObject(Object value)

Does anyone have a clue?

Posted Sun, Jul 8 2007 23:48 by Paulo Morgado | with no comments

Reference Implementation With Page Flow Navigation

In a previous post, I talked about Page Flow Navigation vs. Page Navigation.

This is an adaptation of the Reference Implementation to work with Page Flow Navigation. (You’ll need the changed version of the Page Flow Application Block.)

Downloads

Posted Sun, Jul 1 2007 12:02 by Paulo Morgado | with no comments

PageFlowWithShoppingCartQuickStart Sample With Page Flow Navigation

In a previous post, I talked about Page Flow Navigation vs. Page Navigation.

This is an adaptation of the PageFlowWithShoppingCartQuickStart sample to work with Page Flow Navigation. (You’ll need the changed version of the Page Flow Application Block.)

Downloads

Posted Sun, Jul 1 2007 11:27 by Paulo Morgado | 2 comment(s)

Page Flow Navigation vs. Page Navigation

Introduction

I've been working with page flows for the past days and the idea of navigating to a page flow instead of navigating between pages belonging to a pageflow is making more sense each time I think about it.

The Web Client Software Factory comes with a Page Flow Application Block that governs the navigation between pages of the same page flow.

The way the Page Flow Application Block identifies a particular page flow is by the URLs of the pages that belong to that page flow. This is possible because you are still navigating to those pages. This also makes it impossible to reuse the same page between page flows.

In this post I will change the Page Flow Application Block (keeping the changes to a minimum) to provide page flow navigation instead of page navigation.

Changing the Page Flow Definition

In order to keep changes to a minimum, we'll use the Name property of the page flow as its URL. This will be the virtual address of the page flow and will also be used as the AbortPage's URL if one is not provided.

Since the individual pages will no longer be directly accessible, we can drop all methods that correlate pages with URLs:

  • IPageFlowDefinition.ContainsUrl(string url);
  • IPageFlowDefinition.GetPageFromUrl(string url);

Changing the Page Flow Definition Catalog

Since we are navigating to page flows, we don't need to keep track of the page's URLs, which makes the definition catalog simpler:

/// <summary>
/// A catalog of <see cref="IPageFlowDefinition"/>s.
/// </summary>
public class PageFlowDefinitionCatalog : IPageFlowDefinitionCatalog
{
    private Dictionary<string, IPageFlowDefinition> catalog = new Dictionary<string, IPageFlowDefinition>();

    /// <summary>
    /// Gets the number of <see cref="IPageFlowDefinition"/>s in the catalog.
    /// </summary>
    /// <value>The number of <see cref="IPageFlowDefinition"/>s in the catalog.</value>
    public int Count
    {
        get { return this.catalog.Count; }
    }

    /// <summary>
    /// Removes an <see cref="IPageFlowDefinition"/> from the catalog.
    /// </summary>
    /// <param name="definition">The definition to remove.</param>
    public void Remove(IPageFlowDefinition definition)
    {
        this.catalog.Remove(definition.Name);
    }

    /// <summary>
    /// Adds an <see cref="IPageFlowDefinition"/> to the catalog.
    /// </summary>
    /// <param name="definition">The <see cref="IPageFlowDefinition"/> to add to the catalog.</param>
    public void Add(IPageFlowDefinition definition)
    {
        this.catalog.Add(definition.Name, definition);
    }

    /// <summary>
    /// Get an <see cref="IPageFlowDefinition"/> with a specific URL from the catalog.
    /// </summary>
    /// <param name="rawUrl">The URL to search for.</param>
    /// <returns>The <see cref="IPageFlowDefinition"/> with a matching URL.</returns>
    public IPageFlowDefinition GetByUrl(string rawUrl)
    {
        IPageFlowDefinition definition;
        this.catalog.TryGetValue(rawUrl, out definition);
        return definition;
    }
}
Changing the WorkflowFoundationPageFlowDefinition

In order to comply to these changes, the provided implementation (WorkflowFoundationPageFlowDefinition) will need to be changed.

If an abort page URL is not provided it will default to the not running URL.

/// <summary>
/// Implementation of a page flow definition that relies on the <see cref="Activities.PageFlow">PageFlow activity</see>.
/// </summary>
/// <remarks>This class is used to query the page flow metadata and is accessible through the <see cref="WorkflowFoundationPageFlow.Definition"></see> propert
public class WorkflowFoundationPageFlowDefinition : IPageFlowDefinition
{
    // ...

    /// <summary>
    /// Initializes a new instance of the <see cref="WorkflowFoundationPageFlowDefinition"></see> given a <see cref="Activities.PageFlow">PageFlow activity</s
    /// </summary>
    /// <param name="definition">A <see cref="Activities.PageFlow">PageFlow</see></param>
    /// <exception cref="ArgumentNullException"><paramref name="key"/> is <see langword="null"></see></exception>
    public WorkflowFoundationPageFlowDefinition(Activities.PageFlow definition)
    {
        if (definition == null)
            throw new ArgumentNullException("definition");

        _definition = definition;
        _abortPage = new Page("PageFlow.AbortPage", (string.IsNullOrEmpty(definition.NotRunningUrl) ? this._definition.Name : definition.AbortUrl));
    }

    // ...
}

Changing the Page Flow Provider

The implementation of IPageFlowProvider.ProcessRequest(string url) will need to change, although keeping the signature.

The ProcessResult class will need to be changed. Besides redirecting or doing nothing, there will be a new action: Rewrite.

/// <summary>
/// An object describing the results of a ProcessRequest() call on 
/// <see cref="PageFlowHttpModule"/>.
/// </summary>
public class ProcessResult
{
    /// <summary>
    /// Possible actions to take as a result of the URL processing.
    /// </summary>
    public enum ProcessAction : int
    {
        /// <summary>
        /// No action to be taken.
        /// </summary>
        None = 0,

        /// <summary>
        /// Rerwrite the URL to execute the page.
        /// </summary>
        Rewrite = 1,

        /// <summary>
        /// Redirect the request.
        /// </summary>
        Redirect = 2
    }

    private string _url;
    private ProcessAction _action;

    /// <summary>
    /// Creates an instance of a ProcessResult.
    /// </summary>
    public ProcessResult()
    {
    }

    /// <summary>
    /// Creates an instance with the specified properties.
    /// </summary>
    /// <param name="action">The action to process.</param>
    /// <param name="url">The URL.</param>
    public ProcessResult(ProcessAction action, string url)
    {
        _action = action;
        _url = url;
    }

    /// <summary>
    /// Gets the URL to redirect to.
    /// </summary>
    public string Url
    {
        get { return _url; }
    }

    /// <summary>
    /// Gets the determiniation if the request should be redirected to the Url.
    /// </summary>
    public ProcessAction Action
    {
        get { return _action; }
    }
}

Rewrite will change the HTTP request in order to execute the page registered for the current state.

Changing the WorkflowFoundationPageFlowProvider

In order to comply to these changes, the provided implementation (WorkflowFoundationPageFlowProvider) will need to be changed.

If the navigation is to a page fllow, the provider will need to instruct the HTTP module to rewrite the path. So, every method creating an instance of ProcessResult wil need to be changed.

/// <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>
    /// 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)
            {
                result = ProcessWithNonExistingInstance(definition);
            }
            else
            {
                result = ProcessWithExistingInstance(definition, instance);
            }
        }
        else
        {
            // Do nothing.
            result = new ProcessResult(ProcessResult.ProcessAction.None, string.Empty); ;
        }

        return result;
    }

    /// <summary>
    /// Process the request with a running page flow instance.
    /// </summary>
    /// <param name="instance">The running page flow instance.</param>
    /// <param name="url">The request url.</param>
    /// <returns>The <see cref="ProcessResult" /> holding the redirect action to take.</returns>        
    private ProcessResult ProcessWithRunningInstance(IPageFlow instance, string url)
    {
        // The default action is to Rewrite to execute the current state's page.
        ProcessResult result = new ProcessResult(ProcessResult.ProcessAction.Rewrite, instance.CurrentPage.Url);

        if (!(instance.Definition.Name.Equals(url, StringComparison.OrdinalIgnoreCase)))
        {
            switch (instance.Definition.Abandonable)
            {
                case AbandonBehavior.AllowAndDiscardInstance:
                    instance.Abort(false);
                    result = ProcessRequest(url);
                    break;
                case AbandonBehavior.AllowAndSaveInstance:
                    instance.Suspend();
                    result = ProcessRequest(url);
                    break;
                case AbandonBehavior.Prevent:
                    // Redirect to the current page flow.
                    result = new ProcessResult(ProcessResult.ProcessAction.Redirect, instance.Definition.Name);
                    break;
            }
        }
        return result;
    }

    /// <summary>
    /// Process the request using the specified instance.
    /// </summary>
    /// <param name="definition">The page flow definition corresponding to the request url.</param>
    /// <param name="instance">The existing page flow instance.</param>
    /// <returns>The <see cref="ProcessResult" /> holding the redirect action to take.</returns>        
    private ProcessResult ProcessWithExistingInstance(IPageFlowDefinition definition, IPageFlow instance)
    {
        ProcessResult result;
        switch (definition.Abandonable)
        {
            case AbandonBehavior.AllowAndSaveInstance:
                result = ResumeInstance(instance, definition);
                break;
            default:
                // Redirect to the current page flow.
                result = new ProcessResult(ProcessResult.ProcessAction.Redirect, HttpContext.Current.Request.AppRelativeCurrentExecutionFilePath);
                break;
        }
        return result;
    }

    private ProcessResult ProcessWithNonExistingInstance(IPageFlowDefinition definition)
    {
        ProcessResult result;
        if (HttpContext.Current != null && HttpContext.Current.Request.HttpMethod.Equals("POST", StringComparison.OrdinalIgnoreCase))
        {
            // NOTE: this special case happens when there is no running instance,
            //       there are no suspended instances, and the url belongs to a page flow
            //       but we are on PostBack. In this case we let the request flow, if not we
            //       send the user to NotRunning url.
            // Redirect to the current page flow.
            result = new ProcessResult(ProcessResult.ProcessAction.Redirect, definition.Name);
        }
        else
        {
            // Rewrite to execute the current page flow's not running page.
            result = new ProcessResult(ProcessResult.ProcessAction.Rewrite, definition.NotRunningRedirect);
        }
        return result;
    }

    private ProcessResult ResumeInstance(IPageFlow instance, IPageFlowDefinition definition)
    {
        ProcessResult result;
        if (instance.Status == PageFlowStatus.Suspended)
        {
            // Rewrite to execute the current page flow's not running page.
            result = new ProcessResult(ProcessResult.ProcessAction.Rewrite, definition.NotRunningRedirect);
        }
        else
        {
            if (instance.CurrentPage != null)
            {
                // Rewrite to execute the current state's page.
                result = new ProcessResult(ProcessResult.ProcessAction.Rewrite, instance.CurrentPage.Url);
            }
            else
            {
                // Rewrite to execute the current page flow's not running page.
                result = new ProcessResult(ProcessResult.ProcessAction.Rewrite, definition.NotRunningRedirect);
            }
        }
        return result;
    }
}

Changing the Page Flow HTTP Module

With the Page Flow Application Block, every thing starts in its HTTP module. So, we also need to make a few changes here.

Instead of processing the request in the PostAcquireRequestState event, the request will be processed in two steps:

  1. In the PostAuthorizeRequest event, the page flow provider will be requested to process the request. Because the path to the executing pages is being rewritten, this needs to be done even if no session state is loaded.
  2. In the PostMapRequestHandler event, if the path was rewritten in the PostAcquireRequestState event, it's rewritten back to the original URL.
/// <summary>
/// An <see cref="IHttpModule"/> for working with PageFlows
/// </summary>
public class PageFlowHttpModule : IHttpModule
{
    private ProcessResult result;
    private string originalUrl;

    // ...

    /// <summary>
    /// Initializes the PageFlowHttpModule to work with the <see cref="HttpApplication"/>.
    /// </summary>
    /// <param name="context">The <see cref="HttpApplication"/>.</param>
    public void Init(HttpApplication context)
    {
        if (context == null)
            throw new ArgumentNullException("context");

        context.PostAuthorizeRequest += new EventHandler(OnPostAuthorizeRequest);
        context.PostMapRequestHandler += new EventHandler(OnPostMapRequestHandler);

        WebConfigurationManager.OpenWebConfiguration(null);
        HttpRequestHelper.LoadExtensionsHandledByPageHandlerFactoryFromConfig((HttpHandlersSection)WebConfigurationManager.GetSection("system.web/httpHandlers"));
    }

    /// <summary>
    /// Processes an <see cref="HttpRequest"/>.
    /// </summary>
    /// <param name="request">The request to process</param>
    /// <returns>A <see cref="ProcessResult"/>.</returns>
    public ProcessResult ProcessRequest(HttpRequest request)
    {
        Guard.ArgumentNotNull(request, "request");

        ProcessResult result = new ProcessResult();

        if (HttpRequestHelper.IsHandledByPageHandlerFactory(request.AppRelativeCurrentExecutionFilePath))
        {
            result = PageFlowDirectory.Provider.ProcessRequest(request.AppRelativeCurrentExecutionFilePath);
        }
        return result;
    }

    private void OnPostAuthorizeRequest(object sender, EventArgs e)
    {
        this.result = ProcessRequest(HttpContext.Current.Request);
        switch (result.Action)
        {
            case ProcessResult.ProcessAction.Redirect:
                HttpContext.Current.Response.Redirect(this.result.Url, true);
                break;
            case ProcessResult.ProcessAction.Rewrite:
                System.Diagnostics.Debug.WriteLine(this.result.Url);
                this.originalUrl = HttpContext.Current.Request.AppRelativeCurrentExecutionFilePath;
                HttpContext.Current.RewritePath(this.result.Url, true);
                break;
        }
    }

    void OnPostMapRequestHandler(object sender, EventArgs e)
    {
        if ((this.result != null) && (this.result.Action == ProcessResult.ProcessAction.Rewrite))
        {
            System.Diagnostics.Debug.WriteLine(this.result.Url);
            HttpContext.Current.RewritePath(this.originalUrl, true);
        }
        this.result = null;
    }
}

Conclusion

This implementation has several benefits:

  • Deployment - There is no need to deploy the URL of each page. The entire set of pages of a page flow can be changed without having to notify users or reconfigure menus.
  • Security - This adds two kinds of security:
    • Security by obscurity - The user will never now how many pages make a particular page flow.
    • URL security - The "real" pages can, and should, be blocked from acess to the users.
    • Page flow security - The user can never jump into a particular state.

Disclaimer

This implementation has not been fully tested and has known problems.

Downloads

Posted Sun, Jul 1 2007 1:18 by Paulo Morgado | 2 comment(s)