26 April 2009

SPSiteColumnUsage, Find all references to a site column

When I try to delete a site column from a SharePoint site I often get the message "Site columns which are included in content types cannot be deleted. Remove all references to this site column prior to deleting it."

Well with only a few content types this isn't so hard to click through all content types and checking if the site column is used, but with a lot of them it's no fun and can be a hell of a job.

To make my life a bit easier I decided to create a piece of code that could delete site columns programmatically. I knew that SharePoint object model has a class that can return the usage of content types, namely SPContentTypeUsage. With this class you can tell if a content type is used in one or more lists somewhere inside the complete site collection and what the urls to those lists are.

My guess was that there should also be something like a site column usage, but I was wrong!

After some investigation on MSDN, several SharePoint blogs and good old Reflector, I created a SPSiteColumnUsage class. This class uses the LINQ and SPContentTypeUsage and can find all references to content types and lists where a site columns is in use.

SPSiteColumnUsage
Methods
  • GetUsages, this method takes a SPField instance as input argument and returns an array of SPSiteColumnUsage objects.
Properties
  • Id, the Id of the site column.
  • Scope, the scope of the site column.
  • ContentTypeId, the id of a content type where the site column is in use.
  • IsUrlToList, indicates if the information is about a list or a content type.
  • Url, the server relative to a list where the site column is in use.


Usage example to get info about the site column: Title
using (SPSite site = new SPSite("http://demosite"))
{
    using (SPWeb web = site.OpenWeb())
    {
        SPField field = web.Fields.GetFieldByInternalName("Title);

        Console.WriteLine("Field: {0}", fieldName);
        Console.WriteLine("==============================");

        var usage = SPSiteColumnUsage.GetUsages(field);
        foreach (var u in usage)
        {
            Console.WriteLine("Id: {0}", u.Id);
            Console.WriteLine("Scope: {0}", u.Scope);
            Console.WriteLine("ContentTypeId: {0}", u.ContentTypeId);
            Console.WriteLine("IsUrlToList: {0}", u.IsUrlToList);
            Console.WriteLine("Url: {0}", u.Url);
            Console.WriteLine();
        }
    }
}
See a working example on http://SPSiteColumnUsage.codeplex.com

The class also makes use of the extension method CoppyToArray I created.
For this method see a previous post: Using LINQ to query SharePoint collections

/// 
/// Class with site column usage information.
/// 
public class SPSiteColumnUsage
{
   public static IList< SPSiteColumnUsage> GetUsages(SPField field)
   {
       List< SPSiteColumnUsage> list = new List< SPSiteColumnUsage>();

       if(field != null && field.UsedInWebContentTypes)
       {
           // Use reflection to get the fields collection for the specified field
           SPFieldCollection fieldCollection = field.GetType().GetProperty("Fields", BindingFlags.Instance | BindingFlags.NonPublic).GetValue(field, null) as SPFieldCollection;
           if(fieldCollection != null)
           {
               // Get the web context from the collection
               SPWeb web = fieldCollection.Web;

               // First collect all contenttypes to an array, so we can use Linq
               var contentTypes = web.ContentTypes.CopyToArray< SPContentType>();

               // Filter contenttypes where field is used
               var contentTypesWithField = (from contentType in contentTypes
                                            where contentType.Fields.ContainsField(field.InternalName)
                                            select contentType).ToArray();

               // Create usages for fields in contenttypes
               foreach(SPContentType contentType in contentTypesWithField)
               {
                   SPSiteColumnUsage siteColumnUsage = new SPSiteColumnUsage(field.Id, field.Scope, contentType.Id);
                   list.Add(siteColumnUsage);
               }

               // Get unique lists-urls where contentypes are beeing used
               var listUrlsContentTypeUsage = (from contentType in contentTypesWithField
                                               let contentTypeUsages = SPContentTypeUsage.GetUsages(contentType)
                                               from contentTypeUsage in contentTypeUsages
                                               where contentTypeUsage.IsUrlToList
                                               select contentTypeUsage.Url).Distinct().ToArray();

               // Create usages for fields in list
               foreach (string listUrl in listUrlsContentTypeUsage)
               {
                   SPSiteColumnUsage siteColumnUsage = new SPSiteColumnUsage(field.Id, field.Scope, listUrl);
                   list.Add(siteColumnUsage);
               }
           }
       }

       return list;
   }

   private readonly Guid _Id;
   private readonly string _Scope;
   private readonly SPContentTypeId _ContentTypeId;
   private readonly string _Url;

   private SPSiteColumnUsage(Guid id, string scope, SPContentTypeId contentTypeId)
   {
       _Id = id;
       _Scope = scope;
       _ContentTypeId = contentTypeId;
       _Url = null;
   }

   private SPSiteColumnUsage(Guid id, string scope, string url)
   {
       _Id = id;
       _Scope = scope;
       _ContentTypeId = SPContentTypeId.Empty;
       _Url = url;
   }

   public Guid Id
   {
       [DebuggerStepThrough]
       get { return _Id; }
   }

   public string Scope
   {
       [DebuggerStepThrough]
       get { return _Scope; }
   }

   public SPContentTypeId ContentTypeId
   {
       [DebuggerStepThrough]
       get { return _ContentTypeId; }
   }

   public string Url
   {
       [DebuggerStepThrough]
       get { return _Url; }
   }

   public bool IsUrlToList
   {
       [DebuggerStepThrough]
       get { return _ContentTypeId.Equals(SPContentTypeId.Empty) && !string.IsNullOrEmpty(_Url); }
   }
}

02 April 2009

Microsoft SharePoint Designer 2007 is now free

When I first heared the news that Microsoft was going to give away SharePoint Designer 2007 for free, I thought it was a April Fool's joke. But now on April 2nd, I actual believe. :-)

Download it from Microsoft Download Center.
http://www.microsoft.com/downloads/details.aspx?displaylang=en&FamilyID=baa3ad86-bfc1-4bd4-9812-d9e710d44f42