I've moved my blog to!. Please update your links. This blog is no longer in use--you can find all posts and comments at my new blog; I will no longer be posting to this site and comments have been disabled.

Thursday, July 10, 2008

Creating List/Library Folders Programmatically

I've got this method that I keep copying and pasting into different solutions so I figured I'd post it here in case someone else could benefit from it.  You would think that it would be really easy to create folders in a list or library but oddly enough (or, if you've done SharePoint development long enough you would say typically enough), it's not.  So, here's a simple little method to help you get a folder and have it created if it doesn't already exist:

public static SPFolder GetFolder(this SPList targetList, string folderUrl)
    if (string.IsNullOrEmpty(folderUrl))
        return targetList.RootFolder;
    SPFolder folder = targetList.ParentWeb.GetFolder(targetList.RootFolder.Url + "/" + folderUrl);
    if (!folder.Exists)
        if (!targetList.EnableFolderCreation)
            targetList.EnableFolderCreation = true;
        // We couldn't find the folder so create it
        string[] folders = folderUrl.Trim('/').Split('/');
        string folderPath = string.Empty;
        for (int i = 0; i < folders.Length; i++)
            folderPath += "/" + folders[i];
            folder = targetList.ParentWeb.GetFolder(targetList.RootFolder.Url + folderPath);
            if (!folder.Exists)
                SPListItem newFolder = targetList.Items.Add("", SPFileSystemObjectType.Folder, folderPath.Trim('/'));
                folder = newFolder.Folder;
    // Still no folder so error out
    if (folder == null)
        throw new SPException(string.Format("The folder '{0}' could not be found.", folderUrl));
    return folder;

The code is actually not that bad.  It takes in a web and a list (although I could have/should have modified it to just take in the list and get the web via the ParentWeb property, but I digress) and a folder.  After checking to see if it already exists it then splits the folder path on "/" and then loops through each creating the folders as it goes (if it doesn't already exist).  So you'd call the method like this:

SPFolder folder = list.GetFolder("/subfolder1/subfolder1a");


Update 7/11/2008: Per the suggestion in the comments I changed the code so that the method is an extension method instead.

Tuesday, July 8, 2008

Creating a List via STSADM

I'm working on a project which involves scripting, via batch files, an intranet portal.  We wanted to script the creation of the various division and department sites along with several lists that would be needed in each site.  Eventually these site will be turned into custom site definitions using Features and Feature Stapling to provision the lists and various files but we needed something to enable us to quickly mock up a site taxonomy and present that to stakeholders.  I had lots of commands to accomplish many of the required tasks but I didn't have anything to create lists and do some basic configurations of those lists.  The first command I needed is detailed here and is called gl-addlist.  I'll discuss some of the other commands I created in follow-up posts.

Fortunately, creating a list via code is super simple - there's  single Add method you call via the SPListCollection object which you can get from the SPWeb's List property.  So turning this into a new STSADM command is as simple as getting the parameters needed and passing them into the Add method:

   1: public class AddList : SPOperation
   2: {
   3:      /// <summary>
   4:     /// Initializes a new instance of the <see cref="AddList"/> class.
   5:     /// </summary>
   6:     public AddList()
   7:     {
   8:         SPParamCollection parameters = new SPParamCollection();
   9:         parameters.Add(new SPParam("url", "url", true, null, new SPUrlValidator(), "Please specify the web url to add the list to."));
  10:         parameters.Add(new SPParam("urlname", "name", true, null, new SPNonEmptyValidator()));
  11:         parameters.Add(new SPParam("title", "title", true, null, new SPNonEmptyValidator()));
  12:         parameters.Add(new SPParam("featureid", "fid", true, null, new SPGuidValidator()));
  13:         parameters.Add(new SPParam("templatetype", "type", true, null, new SPIntRangeValidator(0, int.MaxValue)));
  14:         parameters.Add(new SPParam("description", "desc", false, null, new SPNonEmptyValidator()));
  15:         parameters.Add(new SPParam("doctemplatetype", "doc", false, null, new SPIntRangeValidator(0, int.MaxValue)));
  17:         StringBuilder sb = new StringBuilder();
  18:         sb.Append("\r\n\r\nAdds a list to a web.\r\n\r\nParameters:");
  19:         sb.Append("\r\n\t-url <web url to add the list to>");
  20:         sb.Append("\r\n\t-urlname <the name that will appear in the URL>");
  21:         sb.Append("\r\n\t-title <list title>");
  22:         sb.Append("\r\n\t-featureid <the feature ID to which the list definition belongs>");
  23:         sb.Append("\r\n\t-templatetype <an integer corresponding to the list definition type>");
  24:         sb.Append("\r\n\t[-description <list description>]");
  25:         sb.Append("\r\n\t[-doctemplatetype <the ID for the document template type>]");
  26:         Init(parameters, sb.ToString());
  27:     }
  30:     #region ISPStsadmCommand Members
  32:     /// <summary>
  33:     /// Gets the help message.
  34:     /// </summary>
  35:     /// <param name="command">The command.</param>
  36:     /// <returns></returns>
  37:     public override string GetHelpMessage(string command)
  38:     {
  39:         return HelpMessage;
  40:     }
  42:     /// <summary>
  43:     /// Runs the specified command.
  44:     /// </summary>
  45:     /// <param name="command">The command.</param>
  46:     /// <param name="keyValues">The key values.</param>
  47:     /// <param name="output">The output.</param>
  48:     /// <returns></returns>
  49:     public override int Execute(string command, StringDictionary keyValues, out string output)
  50:     {
  51:         output = string.Empty;
  53:         try
  54:         {
  55:             string url = Params["url"].Value;
  57:             using (SPSite site = new SPSite(url))
  58:             using (SPWeb web = site.OpenWeb())
  59:             {
  60:                 AddListHelper(web.Lists, Params["urlname"].Value, Params["title"].Value, Params["description"].Value,
  61:                               new Guid(Params["featureid"].Value),
  62:                               int.Parse(Params["templatetype"].Value),
  63:                               Params["doctemplatetype"].Value);
  64:             }
  65:         }
  66:         catch (Exception ex)
  67:         {
  68:             Console.WriteLine("An error occured executing the command: {0}\r\n{1}", ex.Message, ex.StackTrace);
  69:             return (int)ErrorCodes.GeneralError;
  70:         }
  71:         return OUTPUT_SUCCESS;
  72:     }
  74:     #endregion
  77:     /// <summary>
  78:     /// Adds a list to the specified list collection.
  79:     /// </summary>
  80:     /// <param name="lists">The lists collection to add the list to.</param>
  81:     /// <param name="urlName">Name of the list to use in the URL.</param>
  82:     /// <param name="title">The title of the list.</param>
  83:     /// <param name="description">The description.</param>
  84:     /// <param name="featureId">The feature id that the list template is associated with.</param>
  85:     /// <param name="templateType">Type of the template.</param>
  86:     /// <param name="docTemplateType">Type of the doc template.</param>
  87:     /// <returns></returns>
  88:     internal static SPList AddListHelper(
  89:         SPListCollection lists, 
  90:         string urlName, 
  91:         string title, 
  92:         string description, 
  93:         Guid featureId, 
  94:         int templateType, 
  95:         string docTemplateType)
  96:     {
  97:         if (docTemplateType == "")
  98:             docTemplateType = null;
 100:         Guid guid = lists.Add(title, description, urlName, featureId.ToString("D"), templateType, docTemplateType);
 101:         return lists[guid];
 102:     }
 104: }

The help for the command is shown below:

C:\>stsadm -help gl-addlist

stsadm -o gl-addlist

Adds a list to a web.

        -url <web url to add the list to>
        -urlname <the name that will appear in the URL>
        -title <list title>
        -featureid <the feature ID to which the list definition belongs>
        -templatetype <an integer corresponding to the list definition type>
        [-description <list description>]
        [-doctemplatetype <the ID for the document template type>]

The following table summarizes the command and its various parameters:

Command Name Availability Build Date
gl-addlist WSS v3, MOSS 2007 8/6/2008

Parameter Name Short Form Required Description Example Usage
url   Yes The URL of the web to add the list to. -url http://portal/
urlname name Yes The text that will appear in the URL for the list.  So if the web URL is http://portal and you want the list to be reached via http://portal/Lists/ListName/AllItems.aspx you would pass in "Lists/ListName" as the value. -urlname Lists/ListName

-name Lists/ListName
title   Yes The title of the list. -title "A New List"
featureid fid Yes The ID of the Feature that the list is defined in.  You can find this information in the 12/Template/Features/... folders.  For example, the Announcements list is defined in 12/Template/Features/AnnouncementsList. -featureid "00BFEA71-D1CE-42de-9C63-A44004CE0104"

-fid "00BFEA71-D1CE-42de-9C63-A44004CE0104"
templatetype type Yes The template type ID of the list.  Like the Feature ID this information can be obtained from the 12/Template/Features/... folder.  For example, the Announcements list template type ID is 104 and can be found in 12/Template/Features/AnnouncementsList/ListTemplates/Announcements.xml -templatetype 104

-type 104
description desc No A brief description of the list. -description "This is a new list."

-desc "This is a new list."
doctemplatetype doc No A string containing the integer ID for the document template type.  This value corresponds to the Type attribute value specified for a document template in the Onet.xml file of the specified site template.  The following list shows the default possible values: 100 - None, 101 - Word document, 102 - SharePoint Designer Web page, 103 - Excel spreadsheet, 104 - PowerPoint presentation, 105 - Basic page, 106 - Web Part page, 130 - Data connection, 1000 - Blank form.  This parameter only applies to document libraries. -doctemplatetype 100

-doc 100

The following is an example of how to create an announcements list intended specifically for new promotions:

stsadm -o gl-addlist -url http://portal -urlname Lists/Promotions -title Promotions -featureid "00BFEA71-D1CE-42de-9C63-A44004CE0104" -templatetype 104 -description "This list contains all announcements related to employee promotions."

Thursday, July 3, 2008

Web Part Page History CodePlex Project

Have you ever been working with a web part page (either a standard web part page or a publishing page) and made a bunch of changes to your web part configurations and content over a period of time only to realize sometime down the road that you need to revert to a previous version?  You then go to the version history for the page and find the one you want, do the revert and then return to the page only to find that none of your web parts were affected by the revert?  If you haven't already encountered this "feature", don't worry, you eventually will :)

The problem is that SharePoint stores its web part configurations differently than item level (meta data, page fields, whatever) data.  So there is no ability to roll back to a previous version of your web part changes.

In order to address this deficiency I decided to put together my first "real" CodePlex project.  Of course I have my STSADM extensions that have been quite popular but for the time being I'm not ready to open that project up for collaboration.  For this new project, however, I'm much more open to the idea - mainly because I simply can't test it and there may be lots of ways in which people way smarter than me may be able to improve upon what I started (note that initially I'll be very selective as to who can directly contribute but please don't hesitate to share any improvement ideas).

You can find the project here:  I've also created a reasonably details document that explains how to use the project and how it works.  The initial release is very much an alpha release - I've done enough testing to know that it works for simple cases.  My goal was to get something, even if it's rough, out to the public so that I can get a sense of what people think of the approach and if there's even a need for it (if you think there is please share your feedback so I know that my time on this was worth it).