It's great to see how flexible SharePoint 2007 actually is. You can create lists that can contain all sorts of data. Add columns, create data types, create content types etc. But great flexibility has a price. All to often I lay my eyes on code that's not readeable, manageable or strongly typed. Which leaves a lot of room for errors. In this post I'll drop some of my ideas on how to create and access lists in a SharePoint 2007 solution. Which will leave you with strongly typed access to your content types.
I don't know about you but I really don't like all the different way's of retrieving SPItem values. The one field needs a cast, the other field needs an object instantiation. Another problem is that I need to check if a field exists before trying to retrieve it's value. This leaves a lot of room for error. So I'm realy not happy with the SPItem class. Fortunately there's a great new feature called Extension Methods. Extension Methods allow us to extend the SPItem class so let's start with that. I created a simple Utility class that allows me some easier access to fields. Oh and it's strongly typed right away. Have a look at this.
using System;
using System.Globalization;
using Microsoft.SharePoint;
namespace SharePoint2007Base {
/// <summary>
/// The ItemUtility class contains some extension methods which help to retrieve values from fields
/// </summary>
public static class ItemUtility {
private static object GetFieldValue(this SPItem item, string fieldName) {
if (item == null) {
throw new ArgumentNullException("item");
}
if (string.IsNullOrEmpty(fieldName)) {
throw new ArgumentNullException("fieldName");
}
object retVal = null;
if (item.Fields.ContainsField(fieldName)) {
retVal = item[fieldName];
}
return retVal;
}
/// <summary>
/// Gets the value.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="item">The item.</param>
/// <param name="fieldName">Name of the field.</param>
/// <param name="defaultValue">The default value.</param>
/// <returns></returns>
public static T GetValue<T>(this SPItem item, string fieldName, T defaultValue) {
T retVal = defaultValue;
object fieldValue = item.GetFieldValue(fieldName);
if (fieldValue != null) {
retVal = (T)Convert.ChangeType(fieldValue, typeof(T), CultureInfo.InvariantCulture);
}
return retVal;
}
/// <summary>
/// Gets the lookup field value.
/// </summary>
/// <param name="item">The item.</param>
/// <param name="fieldName">Name of the field.</param>
/// <returns></returns>
public static SPFieldLookupValue GetLookupFieldValue(this SPItem item, string fieldName) {
SPFieldLookupValue retVal = null;
object fieldValue = item.GetFieldValue(fieldName);
if (fieldValue != null) {
retVal = fieldValue as SPFieldLookupValue;
}
return retVal;
}
/// <summary>
/// Gets the lookup field value collection.
/// </summary>
/// <param name="item">The item.</param>
/// <param name="fieldName">Name of the field.</param>
/// <returns></returns>
public static SPFieldLookupValueCollection GetLookupFieldValueCollection(this SPItem item, string fieldName) {
SPFieldLookupValueCollection retVal = new SPFieldLookupValueCollection();
object fieldValue = item.GetFieldValue(fieldName);
if (fieldValue != null) {
retVal = fieldValue as SPFieldLookupValueCollection;
}
return retVal;
}
/// <summary>
/// Gets the multiple choice field value.
/// </summary>
/// <param name="item">The item.</param>
/// <param name="fieldName">Name of the field.</param>
/// <returns></returns>
public static SPFieldMultiChoiceValue GetMultipleChoiceFieldValue(this SPItem item, string fieldName) {
SPFieldMultiChoiceValue retVal = new SPFieldMultiChoiceValue();
string fieldValue = item.GetValue<string>(fieldName, null);
if (!string.IsNullOrEmpty(fieldValue)) {
retVal = new SPFieldMultiChoiceValue(fieldValue);
}
return retVal;
}
/// <summary>
/// Gets the URL field value.
/// </summary>
/// <param name="item">The item.</param>
/// <param name="fieldName">Name of the field.</param>
/// <returns></returns>
public static SPFieldUrlValue GetUrlFieldValue(this SPItem item, string fieldName) {
SPFieldUrlValue retVal = null;
string fieldValue = item.GetValue<string>(fieldName, null);
if (!string.IsNullOrEmpty(fieldValue)) {
retVal = new SPFieldUrlValue(fieldValue);
}
return retVal;
}
/// <summary>
/// Gets the user field value.
/// </summary>
/// <param name="item">The item.</param>
/// <param name="fieldName">Name of the field.</param>
/// <returns></returns>
public static SPFieldUserValue GetUserFieldValue(this SPItem item, string fieldName) {
SPFieldUserValue retVal = null;
string fieldValue = item.GetValue<string>(fieldName, null);
if (!string.IsNullOrEmpty(fieldValue)) {
SPFieldUser userField = item.Fields.GetField(fieldName) as SPFieldUser;
retVal = userField.GetFieldValue(fieldValue) as SPFieldUserValue;
}
return retVal;
}
/// <summary>
/// Gets the user field value collection.
/// </summary>
/// <param name="item">The item.</param>
/// <param name="fieldName">Name of the field.</param>
/// <returns></returns>
public static SPFieldUserValueCollection GetUserFieldValueCollection(this SPItem item, string fieldName) {
SPFieldUserValueCollection retVal = new SPFieldUserValueCollection();
object fieldValue = item.GetFieldValue(fieldName);
if (fieldValue != null) {
retVal = fieldValue as SPFieldUserValueCollection;
}
return retVal;
}
}
}
This utility class makes retrieval of field values pretty easy already. We can now get a string value like this string fieldValue = item.GetValue<string>(fieldName, null); which is a great improvement tmho.
No, this is not the end of my post. In this part we wil create a simple wrapper around a SPListItem to decorate it with some properties. The decorator pattern is a widely used pattern. My implementation is a lazy one however. I do not pass on every single property and method of a SPListItem. I simple allow access to the underlying list item by a property called ListItem and add some extra strongly typed properties.
I do have a very good reason for doing this beyond making it even easier to retrieve values. I do not want to have my field names scattered all over my code files. First of all I do not want to know the field names of all my different content types by hart. But imagine that my content type changes and the field name with it. I would have to run through my code and find all refrences to that field. Just have a look at this code.
using Microsoft.SharePoint;
using SharePoint2007Base;
namespace SharePoint2007BaseConsoleTest {
/// <summary>
/// SampleListItemWrapper is a wrapper for a list item which enables us to acces it's fields strongly typed.
/// </summary>
public class SampleListItemWrapper {
private readonly SPListItem listItem;
/// <summary>
/// Initializes a new instance of the <see cref="SampleListItemWrapper"/> class.
/// </summary>
/// <param name="listItem">The list item to wrap.</param>
public SampleListItemWrapper(SPListItem listItem) {
this.listItem = listItem;
}
/// <summary>
/// Gets the list item.
/// </summary>
/// <value>The list item.</value>
public SPListItem ListItem {
get {
return listItem;
}
}
/// <summary>
/// Gets the custom string field.
/// </summary>
/// <value>The custom string field.</value>
public string CustomStringField {
get {
return ListItem.GetValue<string>("CustomStringField", string.Empty);
}
}
/// <summary>
/// Gets the custom int field.
/// </summary>
/// <value>The custom int field.</value>
public int CustomIntField {
get {
return ListItem.GetValue<int>("CustomIntField", 0);
}
}
/// <summary>
/// Gets the custom URL field.
/// </summary>
/// <value>The custom URL field.</value>
public SPFieldUrlValue CustomUrlField {
get {
return ListItem.GetUrlFieldValue("CustomUrlField");
}
}
}
}
This is way cool. We do have realy strongly typed access to our list item values with the field names defined in only one place! So if the underlying content type changes all we need to do is change one class only! Allthough this is way cool. This does create another problem called code duplication. Every wrapper class hass the same contructor and the same ListItem property. So let's add a little abstraction to our mix. How about this.
/// <summary>
/// The ListItemWrapper class form the base for ListItemWrapper classes
/// </summary>
public abstract class ListItemWrapper {
private readonly SPListItem listItem;
/// <summary>
/// Initializes a new instance of the <see cref="ListItemWrapper"/> class.
/// </summary>
/// <param name="listItem">The list item.</param>
protected ListItemWrapper(SPListItem listItem) {
if (listItem == null) {
throw new ArgumentNullException("listItem");
}
this.listItem = listItem;
}
/// <summary>
/// Gets the list item.
/// </summary>
/// <value>The list item.</value>
public SPListItem ListItem {
get {
return listItem;
}
}
}
Our sample wrapper and any other wrapper we create can now inherit from this base class.
This is just the first part of a two part blog post. In the next post I'll show you how you can add some generics and retrieve your list items in the following fashion:
foreach (SampleWrapper wrappedItem in SampleWrapper.GetDefaultViewItems("http://mysite/lists/mylist")) {
Console.WriteLine(wrappedItem.CustomStringField);
}
Happy coding!
Cheers,
Wes