<CharlieDigital/> Programming, Politics, and uhh…pineapples

29Aug/09Off

SharePoint Design Patterns: Entry 2

In the previous entry, we explored how to clean up our interaction with instances of SPSite, SPWeb, and SPList objects. Logically, the next scenario that we'd want to cover is working with the SPListItem that we get back from the list.

What does this look like? Usually something like this (we'll use some sample code from our last entry):

public string GetLastModifiedBy(int id) {
    string lastModifiedBy;
 
    using(ProposalsLibrary library = new ProposalsLibrary()) {
        SPListItem item = ...; // Get the instance
        lastModifiedBy = Convert.ToString(item["Modified_x0020_By"]);
    }
    return lastModifiedBy;
}

By itself, this seems innocent enough, but the problem lies in the fact that it's rarely so easy as returning simply one field value. Even if that were the case, the issue remains that these field name strings leak out across the codebase. Again, this is typically mitigated through the use of Constants or configuration settings, but it's still messy code, IMO. Aside from that, it can be difficult to figure out which fields are valid for which lists. In general, it makes it hard for developers to figure out how to work with the existing code without good documentation.

Enter the Decorator pattern. The intent of Decorator, as described in Design Patterns, is as follows:

Attach additional responsibilities to an object dynamically. Decorators provide a flexible alternative to sub-classing for extending functionality.

Basically, what we'd like to do is to make working with SPListItems more intuitive and more domain specific. Since we can't subclass SPListItem (and even if we could I don't think it would make sense to do so since it would also expose all of the properties and operations on the SPListItem), we'll have to leverage the simplified version of the Decorator pattern to help us instead. Here is an example:

using System;
using Microsoft.SharePoint;
 

namespace SharePointExample
{
    /// <summary>
    /// Models a sales proposal.
    /// </summary>
    public class Proposal
    {
        private SPListItem _managedItem;
        private string _title;
        private string _lastModifiedBy;
        private bool _checkedOut;
        private string _checkedOutUserLoginName;
        private string _checkedOutUserDisplayName;
        private string _status;
        private string _customerName;

        /// <summary>
        /// Gets the title.
        /// </summary>
        /// <value>The title.</value>
        public string Title
        {
            get { return _title; }
        }

        /// <summary>
        /// Gets the login name of the check out user.
        /// </summary>
        /// <remarks>
        /// Null or empty if not checked out.
        /// </remarks>
        /// <value>The login name of the checkout user.</value>
        public string CheckedOutUserLoginName
        {
            get { return _checkedOutUserLoginName; }
        }

        // Other properties omitted...

        /// <summary>
        /// Private constructor; use the <see cref="FromSPListItem"/>
        /// call to create an instance.
        /// </summary>
        private Proposal() { }

        /// <summary>
        /// Creates an instance from a SharePoint list item.
        /// </summary>
        /// <param name="item">The item.</param>
        /// <returns>An instance of <c>Proposal</c>.</returns>
        public static Proposal FromSPListItem(SPListItem item)
        {
            Proposal proposal = new Proposal();

            proposal._title = Convert.ToString(item["Title"]);
            proposal._checkedOut = (item["CheckoutUser"] != null
                && item.File.CheckOutStatus != SPFile.SPCheckOutStatus.None);

            if (proposal._checkedOut) {
                string fieldValue = Convert.ToString(item["CheckoutUser"]);

                // NOTE: item.Fields requires the display name.
                SPFieldUserValue checkoutUser = (SPFieldUserValue)
                    item.Fields["Checked Out To"].GetFieldValue(fieldValue);

                proposal._checkedOutUserLoginName = checkoutUser.User.LoginName;
                proposal._checkedOutUserDisplayName = checkoutUser.User.Name;
            }

            // Set other fields here...

            proposal._managedItem = item;

            return proposal;
        }
    }
}

If nothing else, we now have a clean, domain specific way to access the SharePoint list item. Team members and new developers don't have to guess which fields are valid for this items retrieved from this list; it would be hidden from them by the properties on the Proposal class instead. If you look at the code to determine the check-out user, you can see that we now also have a single location to encapsulate this parsing logic and any associated error handling we may want to add; other developers don't need to duplicate this code when they use the list item.

It's debatable whether you should use a constructor to initialize the instance, some sort of implicit conversion operation, or a static method like I've used here. I would rule out an implicit conversion operator since it may be hard for users to understand at first blush (i.e. XName and string). As for a "natural" constructor that takes the SPListItem instance? While it's slightly more discoverable (XDocument.Parse() is pretty hard to find for new users), I feel like that's a bit misleading and the intent isn't clear, however, YMMV. Another possible improvement is to pull the abstraction up yet another level since many of the fields are common (like title and last modified by); this exercise is left to the reader ;-).

If we go back to our first example, we can now write this as:

public string GetLastModifiedBy(int id) {
    string lastModifiedBy;
 

    using(ProposalsLibrary library = new ProposalsLibrary()) {
        SPListItem item = ...; // Get the instance

        lastModifiedBy = Proposal.FromSPListItem(item).LastModifiedBy;
    }

    return lastModifiedBy;
}

That's pretty cool. While this example is intentionally simple, you now have a nice object oriented way of working the the fields. In addition, you have a very logical place to put all of your domain specific operations on specific list item types (or should that be content types?). For example, it may make perfect sense to add a method here called CompressAndEmail(string recipientEmailAddress) which would compress the file contents of the proposal and email it to the specified recipient.

Admittedly, this still isn't ideal since 1) we still have to deal with SPListItem on some level ("leakage", if you will) and 2) what about the other side of this equation: updating the item? With regards to (1), we can simply hide this by adding a method to the ProposalsLibrary class as discussed in the previous entry:

using Microsoft.SharePoint;
 

namespace SharePointExample
{
    /// <summary>
    /// Concrete implementation of <see cref="Library"/>
    /// </summary>
    public class ProposalsLibrary : Library
    {
        protected override string ListName
        {
            get { return "<listNameHere>"; }
        }

        protected override string WebName
        {
            get { return "<webNameHere>"; }
        }

        /// <summary>
        /// Finds and instance of <see cref="Proposal"/> the by ID.
        /// </summary>
        /// <param name="id">The ID.</param>
        /// <returns>An instance of <see cref="Proposal"/>.</returns>
        public Proposal FindById(int id)
        {
            SPListItem item = ...; // Find the list item

            return Proposal.FromSPListItem(item);
        }
    }
}

Now our method looks like this instead:

public string GetLastModifiedBy(int id) {
    string lastModifiedBy;
 

    using(ProposalsLibrary library = new ProposalsLibrary()) {
        Proposal proposal = library.FindById(id);

       lastModifiedBy = proposal.LastModifiedBy;
    }

    return lastModifiedBy;
}

That's pretty awesome since it means that we can now remove all references to and knowledge of SharePoint from a whole layer of our application code.

Now onto point (2). For a moment, let's say our proposals have properties called "Status" and "CustomerName". From time to time, we may want to update these (from a source other than the web form, like through a custom web service call or if we have a batch process that runs on our SharePoint environment). One way we can handle this is by implementing the set operation on the properties which can be updated and a Save() method:

using System;
using Microsoft.SharePoint;
 

namespace SharePointExample
{
    /// <summary>
    /// Models a sales proposal.
    /// </summary>
    public class Proposal
    {
        private SPListItem _managedItem;
        private string _title;
        private string _lastModifiedBy;
        private bool _checkedOut;
        private string _checkedOutUserLoginName;
        private string _checkedOutUserDisplayName;
        private string _status;
        private string _customerName;

        /// <summary>
        /// Gets or sets the status.
        /// </summary>
        /// <value>The status.</value>
        public string Status
        {
            get { return _status; }
            set { _status = value; }
        }

        /// <summary>
        /// Gets or sets the name of the customer.
        /// </summary>
        /// <value>The name of the customer.</value>
        public string CustomerName
        {
            get { return _customerName; }
            set { _customerName = value; }
        }

        // Other properties omitted...

        /// <summary>
        /// Private constructor; use the <see cref="FromSPListItem"/>
        /// call to create an instance.
        /// </summary>
        private Proposal() { }

        /// <summary>
        /// Saves this instance back to SharePoint.
        /// </summary>
        public void Save()
        {
            // Error checking first; i.e check if user has item checked out

            // Save
            _managedItem["Customer_x0020_Name"] = CustomerName;
            _managedItem["Status"] = Status;
            _managedItem.SystemUpdate(false);
        }

        /// <summary>
        /// Creates an instance from a SharePoint list item.
        /// </summary>
        /// <param name="item">The item.</param>
        /// <returns>An instance of <c>Proposal</c>.</returns>
        public static Proposal FromSPListItem(SPListItem item)
        {
            // Same as before; omitted
        }
    }
}

Nice. In doing this, we've basically hidden most knowledge of the SharePoint list item from the users of our object and framework. Downstream developers only need to know about the domain specific objects. It's now far easier for a member of our team or a new developer to reuse this code and it cuts down on error prone duplication and leakage of field names across our codebase. We also now have a nice place to put field validation logic (for example, in the Save() method). If we wanted to, we can also add logic here (through some references to services or DAOs or something) to load additional metadata from external systems like databases.

If you want to get fancier, you could also add a dictionary keyed by string (internal field name) and with value type object and use that to hold your "set" properties until Save() is called. In the save, you'd simply iterate the keys and set the values then update, only changing the values that were actually set by the caller.

Updating the status of a proposal would go from looking like this:

public void UpdateStatus(int id, string status) {
    using(SPSite site = new SPSite(_siteUrl)) {
        using(SPWeb web = site.OpenWeb(_web)) {
            SPList list = web.Lists[_list];
 

            SPListItem item = ...; // Get the list item

            // Perhaps add error checking.

            item["Status"] = status;

            item.SystemUpdate();
        }
    }
}

To this instead:

public void UpdateStatus(int id, string status) {
    using(ProposalsLibrary library = new ProposalsLibrary()) {
        Proposal proposal = library.FindById(id);
 

        proposal.Status = status;
        proposal.Save();
    }
}

It becomes even more compelling once we consider that in a real world implementation of an UpdateStatus() operation, it would probably involve checking to see if a user has the object checked out in the first place! This would make our first example explode into a giant mess of code while in our second one, it would be one line (or integrated into the Save() method which could throw a custom NotCheckedOutException or framework InvalidOperationException or try to check it out automatically).

All this with relatively little work involved. It's better in every way: less nesting of code, much more readable and natural, less error prone, better discoverability, and more domain specific (and less SharePoint-centric). In the end, I think it dramatically improves usability and reuse of your SharePoint application code. It also leads to a nice, logical place to encapsulate much of the common, repetitive, and error prone code that would otherwise be littered among your application (or worse, UI) code.

In future installments, we'll examine how flesh out the FindById() method on the ProposalsLibrary. We'll also examine a GUI pattern to help promote reuse, improve tesatability, and cut down on duplication of business logic.

Posted by Charles Chen

Filed under: Dev, SharePoint Comments Off
Comments (4) Trackbacks (1)
  1. You may be interested to look at a similar open source project to abstract away SharePoint to allow better concentration of domain-specific coding: code.google.com/p/domaindrivensharepoint/ – not complete I think, but it’s going along the same path as you.

  2. Peter,

    Sounds awesome. The more that developers approach the SharePoint Issue in a quasi-domain driven way, the better!

    — Chuck

  3. item["Title"] – bad practice for built-in fields, see at this dict: http://msdn.microsoft.com/en-us/library/microsoft.sharepoint.spbuiltinfieldid_fields.aspx
    And more SharePoint practices you will read in http://spg.codeplex.com/

  4. Vitaly,

    That’s a good catch. Although in the case of "Title", it seems like it would be pretty consistent.

    The built in fields are definitely handy to mitigate discovery of some of the more obscure fields in SharePoint.

    Also, another good link 🙂 I think the value is in the documentation and code samples. We need more of these to balance out the tons of quick-and-dirty examples on the web.