Monday, May 16, 2011

Implementing read-only mode in ASP.NET Using Inversion of Control, Reactive Programming, & the Visitor Pattern

In this post I wanted to focus primarily on Inversion of Control and a little on Reactive Programming (Rx) since I think the two go hand in hand. I really like these functional style approaches to programming. IoC is very reactive in nature, because the relying party ultimately ends up reacting to the input you created for it (details to follow). These 2 concepts are very mundane in nature, but serve as the core building blocks of some of our most widely used functions and libraries. If my suggestions leave you with any doubt or skepticism, feel free to pay one of my favorite guys a visit.

You're sure to have come across IoC if you're into Domain Driven Design (DDD) like myself. I'd suggest picking up a copy of the book with the bridge. Don't worry. You won't have any problem finding it...trust me. It's been around for 5 years by now. Yea...it's that good.

Inversion of Control/Rx examples

  • how NUnit runs your tests for you
  • how ASP.NET MVC invokes your controller for you, conveniently populating the arguments you requested like route values, models, etc.
  • how jQuery abstracts away AJAX for you and allows you to be ignorant as to how the response was generated and where it came from
  • how WCF invokes your service/host for you
  • how a DI Container creates concrete instance for you as opposed to you doing it yourself
  • how Windows Forms and ASP.NET listen for events for you and allow you to react to them
  • how the Windows and the CLR unite to provide your program with command line arguments via the string[] args parameter to your command line program's Main method. You react to the arguments, but not  once have you ever had to worry about where they came from or how they got there. It's all handled by the runtime.


Let's get into some code shall we?

Recursion....Important but BOOORRRIIIINNNGGGG!!

public static class AdubbExtensions {
    public static void ToReadOnly(this Page page) {
        page.ToReadOnly(new DefaultReadOnlyModeControlVisitor());
    }

    public static void ToReadOnly(this Page page, IReadOnlyControlVisitor visitor) {
        Action<IEnumerable<Control>> recursor = null;

        recursor = controls => {
            foreach (var control in controls) {
                if (control is BulletedList) continue;

                control.IfIs<ListControl>(l => {
                    var placeHolder = new PlaceHolder();

                    l.Items
                    .Cast<ListItem>()
                    .ToList()
                    .Select(i => new ListItemControl(placeHolder, i))
                    .ForEach(visitor.Visit);

                    /* swap the ListControl with a placeholder control so it's children will be rendered. otherwise the ListControl would ignore our child controls that 
                     * we added to the Controls collection. it doesn't read that property when rendering. it reads the Items property which we're not even dealing with. */
                    l.SwapWith(placeHolder);
                });

                control.IfIs<TextBox>(visitor.Visit);
                control.IfIs<CheckBox>(visitor.Visit);

                // for ListControl, the Controls collection will be empty. the actual controls are contained within the Items property.
                recursor(control.Controls.Cast<Control>().ToList());
            }
        };

        recursor(page.Form.Controls.Cast<Control>().ToList());
    }

    public static int Index(this Control control) {
        var index = -1;
        if (control.Parent == null) return index;

        foreach (var c in control.Parent.Controls) {
            index++;

            if (ReferenceEquals(c, control))
                return index;
        }

        throw new InvalidOperationException("Could not find control in parent's control tree.");
    }

    public static void SwapWith(this Control old, Control @new) {
        var index = old.Index();
        var parent = old.Parent;

        parent.Controls.Remove(old);
        parent.Controls.AddAt(index, @new);
    }

    public static void IfIs<T>(this object target, Action<T> action) where T : class {
        var wannaBe = target as T;

        if (wannaBe != null) action(wannaBe);
    }

    public static void ForEach<TType>(this IEnumerable<TType> target, Action<TType> action) {
        foreach (var element in target)
            action(element);
    }
}

internal class DefaultReadOnlyModeControlVisitor : IReadOnlyControlVisitor {
    const string LineBreak = "<br />";
    const string SelectableItemReadOnlyFormat = "<b>{0}{1}{2}</b> {3}{4}";
    const char OpenCurly = '{';
    const char ClosedCurly = '}';
    const char X = 'X';
    const char Underscore = '_';

    public void Visit(TextBox textBox) {
        textBox.SwapWith(new Literal { Text = string.Format("{0}{1}", textBox.Text, LineBreak) });
    }

    public void Visit(CheckBox checkBox) {
        checkBox.SwapWith(new Literal { Text = string.Format(SelectableItemReadOnlyFormat, OpenCurly, checkBox.Checked ? X : Underscore, ClosedCurly, checkBox.Text, LineBreak) });
    }

    public void Visit(ListItemControl listItem) {
        listItem.SwapWith(new Literal { Text = string.Format(SelectableItemReadOnlyFormat, OpenCurly, listItem.Selected ? X : Underscore, ClosedCurly, listItem.Text, LineBreak) });
    }
}

public interface IReadOnlyControlVisitor {
    /// <summary>
    /// Converts a <see cref="TextBox"/> to read-only mode.
    /// </summary>
    /// <param name="textBox">The text box to convert to read-only mode.</param>
    void Visit(TextBox textBox);

    /// <summary>
    /// Converts a <see cref="CheckBox"/> to read-only mode.
    /// </summary>
    /// <param name="checkBox">The check box to convert to read-only mode.</param>
    void Visit(CheckBox checkBox);

    /// <summary>
    /// Converts a <see cref="ListItemControl"/> to read-only mode.
    /// </summary>
    /// <param name="listItem">The list item to convert to read-only mode.</param>
    void Visit(ListItemControl listItem);
}

/// <summary>
/// A wrapper for ListItem since it's not a control.
/// </summary>
public class ListItemControl : Control {
    public ListItemControl(Control parent, ListItem listItem) {
        Selected = listItem.Selected;
        Text = listItem.Text;

        parent.Controls.Add(this);
    }

    public bool Selected { get; private set; }
    public string Text { get; private set; }
}

The beauty of the design is that we handled the recursive part of the code. That's the part that no one wants or even cares to deal with and rightfully so. I picked up this concept while reading Real World Functional Programming. Thomas P talks about how to implement routines like Sum, Max, and Min by encapsulating recursively iterating a list and accepting a function that knows how to do the rest. The client never has to worry about writing the loop. They just provide a function that abstractly accepts 2 values, does something with them, and returns the result. In the case of Sum, you'd provide the + operator as a function by wrapping it in parenthesis. Then you'd have a function like aggregate/reduce (it's really called fold in F#) that accepts the + operator. So a client could call

let seed = 0
let ten = Seq.aggregate seed [1; 2; 3; 4;] (+)

let seed' = 1
let twentyFour = Seq.aggregate seed' [1; 2; 3; 4;] (*)

This approach is backed by this blog post and a must have in any functional style language. It's a lot more verbose to define in C#, but definitely works. You're probably thinking what I'm thinking. Isn't the Aggregate function available in LINQ? Yup. Sure is. And I use it all the time.

In my case, you should only have to worry about reacting to a particular type of control. You inverted control over to me so that you can declaratively tap into the processing of the control tree. That's kind of funny when you think about it. You mean I'm going to give this person a reference to myself and I can't even control when or how many times I'm invoked? That's the beauty of it my friends. It's what IoC is all about. The same thing happens in ASP.NET MVC. When have you ever been in control of when your controller was invoked? You're not. That's the job of the action invoker. You just have to write code. A simple yet powerful concept.

I simply pluck controls from the tree, and if they match, I tell you about it. You're kind of the subject in this case. This is similar to how IQueryable and LINQ Providers work. You write code that knows how to handle each type of expression. Then .NET notifies your expression tree visitor when that particular type of expression shows up. Also when you implement a query provider, you have no control over when your code is executed. .NET will invoke your provider accordingly once the client makes calls to Where, Select, OrderBy, etc. Lastly there's the aforementioned Reactive Framework (Rx) in .NET. It's pretty cool as well with IObserver and IObervable. Their counterparts over in LINQ are IQueryable and IQueryProvider. In both cases .NET has conveniently implemented extesion methods that make use of these 2 heavy weight abstractions. You write code, and the extension methods provided by .NET determine when it will be executed.

I can imagine the next time those Microsoft guys find a standard and generic way of executing code. They'll be some other IX and IXable interface tandem. I have to them credit. They always find ways to formulate the perfect marriage between husband and wife. I wonder if IQueryable and IQueryProvider will be producing offspring in the near future. The world may never know.

Honey, we Have a Visitor...

I'm sure that some of you were attracted to this post to find out how I made usage of the visitor pattern. I used the visitor pattern to handle each type of control. That's exactly what that pattern was created for. You make some high level class that knows how to handle concrete instance of an inheritance hierarchy (2 points for polymorphism). I find it rare that I have a legitimate purpose for using it here, but it worked to perfection in this particular scenario. I created a default implementation of my IReadOnlyControlVisitor interface, but clients are allowed to swap that out if need be. For the record, RadioButton is a CheckBox via inheritance. So I kind of killed two birds with one stone on that regard (lucky me).

Why Make a Separate Control for ListItem?

I had to treat the ListItem object with a special case. Firstly, anytime I come into contact with a ListControl, I immediately drill down into its children. Why should clients have to write the same old loop over and over. All it cares about is the individual items. Secondly, ListItem is not a control, so I needed to make a wrapper for it that encapsulates whether its selected or not. At that point, I can treat it just like any other control and swap it, find its index, etc.

Adding flavor with SwapWith, IfIs<T> and Index

The 2 extension methods, SwapWith, and Index, are 2 pretty clever utilities I implemented. They're both pretty simple and straight forward. SwapWith is just a function if Index. IfIs<T> is an idea I got from a pal and decided to implement myself. I'm sure my implementation matches his line for line. We can both agree that we were fed up writing that POTC (Plain Old Type Cast). So we implemented a more declarative and functional implementation that's not as noisy as the de facto imperative check.

Two can Play that Game

Just to flex the design a little, I came up with a separate visitor that expresses how simple it is for us to customize our implementation. We're wiping out the DefaultReadOnlyModeControlVisitor with a more naive one.

internal class RainbowControlVisitor : IReadOnlyControlVisitor {
    public void Visit(TextBox textBox) {
        textBox.SwapWith(GetDiv("red", "Wacky Red"));
    }

    public void Visit(CheckBox checkBox) {
        checkBox.SwapWith(GetDiv("blue", "Wacky Blue"));
    }

    public void Visit(ListItemControl listItem) {
        listItem.SwapWith(GetDiv("green", "Wacky Green"));
    }

    static Control GetDiv(string color, string text) {
        var red = new WebControl(HtmlTextWriterTag.Div);

        red.Style.Add(HtmlTextWriterStyle.Color, color);
        red.Controls.Add(new Literal { Text = text });

        return red;
    }
}

Getting in Trouble

There are two ways to make this implementation blow up. The first is due to LINQ and that lazy bastard IEnumerable<T> (I love you). Since I'm modifying the incoming control collection, I have to be done enumerating it by the time it arrives. Put simply, the code will fail without a call to ToList which forces eager evaluation. Secondly code nuggets (<%...%>) will make her blow. You can remedy that situation by following this stackoverflow post. There was another good post out there by Rich Strahl, but I can't seem to locate it. The simplest solution is to wrap any code using code nuggets in a PlaceHolder control. All better now?

Samples Anyone?

<%@ Page Title="Home Page" Language="C#" MasterPageFile="~/Site.master" AutoEventWireup="true"
    CodeFile="Default.aspx.cs" Inherits="Default" %>

<asp:Content ID="HeaderContent" runat="server" ContentPlaceHolderID="HeadContent">
</asp:Content>
<asp:Content ID="BodyContent" runat="server" ContentPlaceHolderID="MainContent">
    <h2>
        Welcome to ASP.NET!
    </h2>
    <p>
        To learn more about ASP.NET visit <a href="http://www.asp.net" title="ASP.NET Website">www.asp.net</a>.
    </p>
    <p>
        You can also find <a href="http://go.microsoft.com/fwlink/?LinkID=152368&clcid=0x409"
            title="MSDN ASP.NET Docs">documentation on ASP.NET at MSDN</a>.
    </p>

    <asp:TextBox Text="Antwan As A Literal" runat="server" />
    <asp:RadioButtonList ID="buttonList" runat="server" />
    <asp:CheckBoxList ID="checkBoxList" runat="server" />
    <asp:RadioButton runat="server" Checked="true" Text="Antwan checked me homie!!"  />
    <asp:CheckBox runat="server" Text="R.I.P. to Bone of Cali Swagg" />
</asp:Content>

public partial class Default : Page {
    protected void Page_Load() {
        if (IsPostBack) {
            Response.Write("Why would you bind twice with view state enabled? Don't be silly.");
            return;
        }

        var foods = new List<string> { "pizza", "pineapples", "macaroni" };
        var dances = new List<string> { "cali-duggie", "detroit-jit", "atlanta-shoulder lean" };

        buttonList.DataSource = foods;
        checkBoxList.DataSource = dances;

        buttonList.DataBind();
        checkBoxList.DataBind();

        this.ToReadOnly();

        // skittles
        // this.ToReadOnly(new RainbowControlVisitor());
    }
}

Before


After


Skittles



Conclusion

And that's it. We began by handling the mundane recursive part of our implementation to alleviate the burden on our clients. No one should have to repeatedly implement that code. This set the stage for IoC. It gave us the ability to serve controls to the client in a reactive and convenient fashion. We made use of the visitor pattern to handle each type of control we wanted to convert to read-only mode. We provided clients with a default implementation but gave them the ability to override that implementation by providing their own version of IReadOnlyControlVisitor. Lastly we handled the special case for ListItems since they do not inherit from the base Control class provided by ASP.NET.

No comments:

Post a Comment