Functional Construction for ASP.NET Web Forms

System.Xml.Linq (a.k.a. LINQ to XML) introduces a nifty approach to creating XML elements called functional construction. I’m not entirely sure why they call it functional given that constructing an object graph is a decidedly non-functional task in the traditional sense of the word, but I digress.

Functional construction has three key features:

  1. Constructors accept arguments of various types, handling them appropriately.
  2. Constructors accept a params array of type Object to enable creation of complex objects.
  3. If an argument implements IEnumerable, the objects within the sequence are added.

If you haven’t seen it in action, I encourage you to take a look at the examples on MSDN and elsewhere—it really is pretty slick. This post will show how a similar technique can be used to build control trees in ASP.NET web forms (and probably WinForms with minimal adjustment).

Basic functional construction can be implemented using two relatively simple extension methods:

public static void Add(this ControlCollection @this, object content)
{
    if (content is Control)
        @this.Add((Control)content);
    else if (content is IEnumerable)
        foreach (object c in (IEnumerable)content)
            @this.Add(c);
    else if (content != null)
        @this.Add(new LiteralControl(content.ToString()));
}

public static void Add(this ControlCollection @this, params object[] args)
{
    @this.Add((IEnumerable)args);
}

We handle four cases:

  1. Control? Add it.
  2. Sequence? Add each.
  3. Other value? Add literal.
  4. Null? Ignore.

And our params overload just calls its arguments a sequence and defers to the other.

In the time-honored tradition of contrived examples:

Controls.Add(
    new Label() { Text = "Nums:" },
    " ",
    from i in Enumerable.Range(1, 6)
    group i by i % 2
);

This would render “Nums: 135246”. Note that the result of that LINQ expression is a sequence of sequences, which is flattened automatically and converted into literals. For comparison, here’s an equivalent set of statements:

Controls.Add(new Label() { Text = "Nums:" });
Controls.Add(new LiteralControl(" "));
foreach (var g in from i in Enumerable.Range(1, 6)
                  group i by i % 2)
    foreach (var i in g)
        Controls.Add(new LiteralControl(i.ToString()));

Hopefully seeing them side by side makes it clear why this new method of construction might have merit. But we’re not done yet.

Expressions, Expressions, Expressions

Many language features introduced in C# 3.0 and Visual Basic 9 make expressions increasingly important. By expressions I mean a single “line” of code that returns a value. For example, an object initializer is a single expression…

var tb = new TextBox()
{
    ID = "textBox1",
    Text = "Text"
};

… that represents several statements …

var tb = new TextBox()
tb.ID = "textBox1";
tb.Text = "Text";

That single TextBox expression can then be used in a number of places that its statement equivalent can’t: in another object initializer, in a collection initializer, as a parameter to a method, in a .NET 3.5 expression tree, the list goes on. Unfortunately, many older APIs simply aren’t built to work in an expression-based world. In particular, initializing subcollections is a considerable pain. However, we can extend the API to handle this nicely:

public static T WithControls<T>(this T @this, params object[] content) where T : Control
{
    if(@this != null)
        @this.Controls.Add(content);
    return @this;
}

The key is the return value: Control in, Control out. We can now construct and populate a container control with a single expression. For example, we could build a dictionary list (remember those?) from our groups:

Controls.Add(
    new HtmlGenericControl("dl")
    .WithControls(
        from i in Enumerable.Range(1, 6)
        group i by i % 2 into g
        select new [] {
            new HtmlGenericControl("dt")
            { InnerText = g.Key == 0 ? "Even" : "Odd" },
            new HtmlGenericControl("dd")
            .WithControls(g)
        }
    )
);

Which would render this:

Odd
135
Even
246

Without the ability to add controls within an expression, this result would require nested loops with local variables to store references to the containers. The actual code produced by the compiler would be nearly identical, but I find the expressions much easier to work with. Similarly, we can easily populate tables. Let’s build a cell per number:

Controls.Add(
    new Table().WithControls(
        from i in Enumerable.Range(1, 6)
        group i by i % 2 into g
        select new TableRow().WithControls(
            new TableCell()
            { Text = g.Key == 0 ? "Even" : "Odd" },
            g.Select(n => new TableCell().WithControls(n))
        )
    )
);

In a future post I’ll look at some other extensions we can use to streamline the construction and initialization of control hierarchies.

Advertisement

Refactoring with LINQ & Iterators: FindDescendantControl and GetDescendantControls

A while back I put together a quick and dirty implementation of a FindControl extension method:

public static T FindControl<T>(this Control root, string id) where T : Control
{
    Control c = root;
    Queue<Control> q = new Queue<Control>();

    if (c == null || c.ID == id)
        return c as T;
    do
    {
        foreach (Control child in c.Controls)
        {
            if (child.ID == id)
                return child as T;
            if (child.HasControls())
                q.Enqueue(child);
        }
        c = q.Dequeue();
    } while (c != null);
    return null;
}

It got the job done (if the control exists!), but I think we can do better.

Refactoring with Iterators

My first concern is that the method is doing too much. Rather than searching for the provided ID, the majority of the code is devoted to navigating the control’s descendents. Let’s factor out that logic into its own method:

public static IEnumerable<Control> GetDescendantControls(this Control root)
{
    var q = new Queue<Control>();

    var current = root;
    while (true)
    {
        if (current != null && current.HasControls())
            foreach (Control child in current.Controls)
                q.Enqueue(child);

        if (q.Count == 0)
            yield break;

        current = q.Dequeue();
        yield return current;
    }
}

The new method is almost as long as the old one, but now satisfies the Single Responsibility Principle. I also added a check to prevent calling Dequeue() on an empty queue. For those that have studied algorithms, note that this is a breadth-first tree traversal.

Now we can update FindControl:

public static T FindControl<T>(this Control root, string id) where T : Control
{
    Control c = root;

    if (c == null || c.ID == id)
        return c as T;

    foreach (Control child in c.GetDescendantControls())
    {
        if (child.ID == id)
            return child as T;
    }
    return null;
}

With the control tree traversal logic extracted, this updated version is already starting to smell better. But we’re not done yet.

DRY? Don’t Repeat Someone Else, Either

My second concern is how we’re checking for the ID in question. It’s not that the equality operator is a bad choice, as it will work in many scenarios, but rather that it’s not consistent with the existing FindControl method. In particular, the existing FindControl understands naming containers (IDs that contain ‘$’ or ‘:’). Rather than implement our own comparison logic, we should just leverage the framework’s existing implementation:

public static T FindControl<T>(this Control root, string id) where T : Control
{
    if (id == null)
        throw new ArgumentNullException("id");

    if (root == null)
        return null;

    Control c = root.FindControl(id);
    if (c != null)
        return c as T;

    foreach (Control child in c.GetDescendantControls())
    {
        c = child.FindControl(id);
        if (c != null)
            return child as T;
    }
    return null;
}

Fun fact: FindControl will throw a NullReferenceException if id is null.

Refactoring with LINQ

So we have extracted the descendant logic and leaned on the framework for finding the controls, but I’m still not quite satisfied. The method just feels too…procedural. Let’s break down what we’re really trying to do:

  1. Look at the current control and all its descendants.
  2. Use FindControl on each with the specified ID.
  3. When we find the control, return it as type T.

As the subheading might suggest, we can express these steps quite nicely with LINQ:

  1. var controls = root.AsSingleton().Concat(root.GetDescendantControls());
  2. var foundControls = from c in controls
                        let found = c.FindControl(id)
                        where found != null
                        select found;
  3. return foundControls.FirstOrDefault() as T;

Behind the scenes, this is how I might have thought through this code:

  1. We use AsSingleton() (my new preferred name, to align with F#’s Seq.singleton, for AsEnumerable(), which I introduced here) and Concat() to prepend root to the list of its descendants, returned as a lazy enumeration.
  2. We use a query over those controls to retrieve matches from FindControl(), again returned as a lazy enumeration.
  3. We grab the first control found, or null if none match, and return it as T.

Because all our enumerations are lazy, we put off traversal of the entire control tree until we know we need to. In fact, if our ID is found in the root control, GetDescendantControls() won’t even be called! Through just a bit of refactoring, we have both an efficient and readable solution.

For completeness, here’s the final version with a more descriptive name to contrast with the existing FindControl():

public static T FindDescendantControl<T>(this Control root, string id) where T : Control
{
    if (id == null)
        throw new ArgumentNullException("id");

    if (root == null)
        return null;

    var controls = root.AsSingleton().Concat(root.GetDescendantControls());

    var foundControls = from c in controls
                        let found = c.FindControl(id)
                        where found != null
                        select found;

    return foundControls.FirstOrDefault() as T;
}

I have added these methods, along with AsSingleton() and a host of others, to the SharePoint Extensions Lib project. Check it out!

Avoiding UserProfile.PersonalSite Leaks

Roger Lamb has posted another edge case in the pursuit of leak-free object model code: UserProfile.PersonalSite. To help follow his best practices, I offer another round of extension methods. First, for UserProfile:

public static void ProcessPersonalSite(this UserProfile profile, Action<SPSite> action)
{
    using (SPSite site = profile.PersonalSite)
    {
        action(site);
    }
}

public static T SelectFromPersonalSite<T>(this UserProfile profile, Func<SPSite, T> selector)
{
    using (SPSite site = profile.PersonalSite)
    {
        return selector(site);
    }
}

Usage:

void PersonalSiteNoLeak()
{
    // open a site collection
    using (SPSite siteCollection = new SPSite("http://moss"))
    {
        UserProfileManager profileManager = new UserProfileManager(ServerContext.GetContext(siteCollection));
        UserProfile profile = profileManager.GetUserProfile("domain\\username");
        profile.ProcessPersonalSite(personalSite =>
        {
            // Process personalSite
        });
    }
}

int CountContextPersonalSiteWebs()
{
    UserProfile myProfile = ProfileLoader.GetProfileLoader().GetUserProfile();
    return myProfile.SelectFromPersonalSite(personalSite =>
    {
        return personalSite.AllWebs.Count;
    });
}

I’ve also encapsulated the verification logic for accessing a MySite web part’s PersonalSite, with variations for projection and with/without exceptions:

public static void ProcessPersonalSite(this WebPart webpart, Action<SPSite> action)
{
    IPersonalPage personalPage = webpart.Page as IPersonalPage;
    if (personalPage == null)
        throw new SPException("Unable to access personal site. Invalid page.");
    if (personalPage.IsProfileError)
        throw new SPException("Unable to access personal site because of a profile error.");

    action(personalPage.PersonalSite);
}

public static T SelectFromPersonalSite<T>(this WebPart webpart, Func<SPSite, T> selector)
{
    IPersonalPage personalPage = webpart.Page as IPersonalPage;
    if (personalPage == null)
        throw new SPException("Unable to access personal site. Invalid page.");
    if (personalPage.IsProfileError)
        throw new SPException("Unable to access personal site because of a profile error.");

    return selector(personalPage.PersonalSite);
}

public static bool TryProcessPersonalSite(this WebPart webpart, Action<SPSite> action)
{
    IPersonalPage personalPage = webpart.Page as IPersonalPage;
    if (personalPage == null || personalPage.IsProfileError)
        return false;

    action(personalPage.PersonalSite);
    return true;
}

public static bool TrySelectFromPersonalSite<T>(this WebPart webpart, Func<SPSite, T> selector, out T value)
{
    value = default(T);
    IPersonalPage personalPage = webpart.Page as IPersonalPage;
    if (personalPage == null || personalPage.IsProfileError)
        return false;

    value = selector(personalPage.PersonalSite);
    return true;
}

Usage:

protected override void CreateChildControls()
{
    base.CreateChildControls();

    this.ProcessPersonalSite(personalSite =>
    {
        // Process personalSite
    });
}

As always, feedback is encouraged.

Site Collection-Safe CQWP XSL Links

A common complaint about styling the ContentByQueryWebPart is that the XslLink properties, used to reference custom XSL files that can be deployed via features (the best practice), don’t support site collection-relative links. This means that one would need to provide a separate .webpart file for each site collection that doesn’t live at the server root. Other common workarounds are to “get creative” with relative URLs or add the Web Part with code and assign a server-relative URL. Fortunately, it’s relatively easy to extend CQWP to adjust any server-relative URL to accommodate non-root site collections:

public class SiteSafeContentByQueryWebPart : ContentByQueryWebPart
{
    protected override void OnLoad(EventArgs e)
    {
        MainXslLink = MakeServerRelativeUrl(MainXslLink);
        HeaderXslLink = MakeServerRelativeUrl(HeaderXslLink);
        ItemXslLink = MakeServerRelativeUrl(ItemXslLink);
    }

    private string MakeServerRelativeUrl(string url)
    {
        if (string.IsNullOrEmpty(url) || url[0] != '/')
            return url;

        var u = SPUtility.SiteRelativeUrlPrefix + url.TrimStart('/');
        return SPUtility.GetServerRelativeUrlFromPrefixedUrl(u);
    }
}

There are already several examples online of how to deploy and use an extended CQWP, so I won’t bother to repeat the process here. As long as the XslLink starts with “/”, it will be translated for the current site collection:

<properties>
  <property name="Title" type="string">SiteSafeContentByQueryWebPart</property>
  <property name="MainXslLink" type="string">/Style Library/CustomContentQueryMain.xsl</property>
  <property name="HeaderXslLink" type="string">/Style Library/CustomHeader.xsl</property>
  <property name="ItemXslLink" type="string">/Style Library/CustomItemStyle.xsl</property>
</properties>

If there’s demand for a barebones SCSCQWP, I can post one, but I would rather see some of the more advanced versions just integrate this code.

SPDataSource Mystery Modes: Webs & ListOfLists

Chris O’Brien is right: SPDataSource is quite handy. However, its documentation leaves something to be desired. In particular, there aren’t any examples (that I can find) of how to use the Webs and ListOfLists SPDataSourceModes. It turns out that the result sets map to a subset of properties (both internal and public) of SPWeb and SPList, respectively—but prepended with “__sp”. An easy way to see all available fields is to bind the data source to an asp:GridView with auto-generated columns:

<WSS:SPDataSource runat="server" ID="dsWebs" DataSourceMode="Webs" />
<asp:GridView runat="server" ID="grdWebs" DataSourceID="dsWebs" AutoGenerateColumns="True">
    <RowStyle VerticalAlign="Top" />
</asp:GridView>
<WSS:SPDataSource runat="server" ID="dsLists" DataSourceMode="ListOfLists" />
<asp:GridView runat="server" ID="grdLists" DataSourceID="dsLists" AutoGenerateColumns="True">
    <RowStyle VerticalAlign="Top" />
</asp:GridView>

For easy reference, I’ve put together a complete list of fields here:
SPDataSource Fields for Webs & ListsOfLists

Note that SharePoint Designer’s live preview of the grid shows extra columns (for Webs: __spAlerts, __spAllProperties, __spAllUsers, etc) that aren’t included in the rendering outside of Designer.

SPDataSourceMode.Webs Example

A list of fields is all well and good, but what can we do with it? Suppose we want an easy way to access our favorite settings pages for our subwebs. A contrived example, perhaps, but it will serve its purpose. We start with our data source:

<WSS:SPDataSource runat="server" ID="dsWebs"
  DataSourceMode="Webs" IncludeHidden="true"/>

Next, we’ll define a MenuTemplate of the site settings shortcuts we want available. %URL% is a token we’ll define in our SPMenuField.

<WSS:MenuTemplate runat="server" ID="WebMenu" CompactMode="true">
  <WSS:MenuItemTemplate runat="server" Text="Create"
    ClientOnClickNavigateUrl="%URL%/_layouts/create.aspx" />
  <WSS:MenuItemTemplate runat="server" Text="Site Settings"
    ClientOnClickNavigateUrl="%URL%/_layouts/settings.aspx" />
  <WSS:MenuSeparatorTemplate runat="server" />
  <WSS:MenuItemTemplate runat="server" Text="People and groups"
    ClientOnClickNavigateUrl="%URL%/_layouts/people.aspx" />
  <WSS:MenuItemTemplate runat="server" Text="Advanced permissions"
    ClientOnClickNavigateUrl="%URL%/_layouts/user.aspx" />
  <WSS:MenuSeparatorTemplate runat="server" />
  <WSS:MenuItemTemplate runat="server" Text="Site libraries and lists"
    ClientOnClickNavigateUrl="%URL%/_layouts/mcontent.aspx" />
  <WSS:MenuItemTemplate runat="server" Text="Sites and workspaces"
    ClientOnClickNavigateUrl="%URL%/_layouts/mngsubwebs.aspx" />
  <WSS:MenuItemTemplate runat="server" Text="Site features"
    ClientOnClickNavigateUrl="%URL%/_layouts/ManageFeatures.aspx" />
 <WSS:MenuItemTemplate runat="server" Text="Delete this site"
    ClientOnClickNavigateUrl="%URL%/_layouts/deleteweb.aspx" />
</WSS:MenuTemplate>

And finally, a simple SPGridView with a link and menu for our site, with an extra column just for good measure:

<WSS:SPGridView runat="server" ID="spGrdWebs"
  DataSourceID="dsWebs" AutoGenerateColumns="false">
  <Columns>
    <WSS:SPMenuField HeaderText="Site Title"
      NavigateUrlFields="__spDefaultUrl"
      NavigateUrlFormat="{0}"
      MenuTemplateId="WebMenu"
      TokenNameAndValueFields="URL=__spUrl,ID=__spID"
      TextFields="__spTitle" />
    <WSS:SPBoundField HeaderText="Site Created"
      DataField="__spCreated" />
  </Columns>
</WSS:SPGridView>

And our final result will look something like this:

So we’ve seen how to use SPDataSource in Webs mode, plus we have a code-free example of the often-overlooked SPMenuField. For what else could the Webs and ListOfLists modes be used?