Adding ribbon items into existing tabs/groups (ribbon customization part 2)

This is the second article in my series on ribbon customization. Last time we looked in detail at creating custom tabs, and in the course of that also looked at how to create "groups" on a tab, and also how to add controls. We mentioned that the container hierarchy is ribbon > tab > group > controls, and showed how to create all of those in my example (which also used SP.UI.Status/SP.UI.Notify in the Client OM). The XML there was fairly extensive, but in this post we’ll see things are somewhat simpler if you don’t need to create an entire tab and instead only need to add something into an existing area of the ribbon. Here we cover the two main scenarios:

  • Adding a group to an existing tab
  • Adding controls to an existing group on an existing tab

Also, the last post covered a lot of ground on creating new tabs etc. but I didn’t get to cover something which I wanted to, so we’ll mop that up here:

  • Creating contextual tabs

Summary of approach – customizing existing ribbon areas

In order to slot your customizations into existing places, the following approach is used (I’ve broken it down, but in reality you’ll probably do some of this cross-referencing automatically once you understand the relationship between various chunks of ribbon XML and the result):

  1. Identify the location you wish to add your customization(s) toDefault Ribbon Customization Locations has a granular list, but I find it easier to first identify just the tab you’re shooting for from the list below (taken from CMDUI.XML):
    • Ribbon.Read
    • Ribbon.BDCAdmin
    • Ribbon.DocLibListFormEdit
    • Ribbon.ListForm.Display
    • Ribbon.ListForm.Edit
    • Ribbon.PostListForm.Edit
    • Ribbon.SvcApp
    • Ribbon.Solution
    • Ribbon.UsageReport
    • Ribbon.WikiPageTab
    • Ribbon.PublishTab
    • Ribbon.WebPartPage
    • Ribbon.WebApp
    • Ribbon.SiteCollections
    • Ribbon.CustomCommands      (note that these are just ‘standard’ tabs – read on for tabs in contextual groups)
  2. Find the declaration in CMDUI.XML for this tab, by searching on the string ID.
  3. If you haven’t already, find the ribbon location in the SharePoint UI – compare with the XML so you know what you’re working with.
  4. Stop, do not pass go without collecting the following information:
    1. Full ‘Location’ value of where you are targeting, i.e. the Location of the tab (if you’re adding a group) or group (if you’re directly adding controls)
    2. The ‘Sequence’ number you want to use – determined by looking at the Sequence numbers of the surrounding elements and working out where exactly you want to put your customization. As you’d expect, Sequence is a left-to-right representation of how things come out on the page.
    3. If adding a group, the ‘GroupTemplate’ used by an existing group with similar controls.
    4. The ‘TemplateAlias’ used by a control which is the same type (e.g. button) and size (e.g. large) as the control you are adding.
  5. Use these values when generating the XML for your customization.

So with that process in mind, let’s look at the XML needed for the scenarios I listed.

Adding a group to an existing tab:

In this example I’m provisioning a single button (lifted from last week’s example) into my new group, which is then being added to the wiki page editing tab (‘Ribbon.WikiPageTab’) in CMDUI.XML. Even if you only have a single control, groups are still very useful as they visually ‘categorize’ your button so it’s function is separated from it’s neighbors. Here I picked a Sequence of ‘15’ to nestle in between the ‘Ribbon.WikiPageTab.EditAndCheckout’ group (10) and ‘Ribbon.WikiPageTab.Manage’ (20):

Ribbon.WikiPageTab

Here’s the XML used to get this – you’ll notice there’s much less of it compared to last week’s ‘full ribbon’ example:

   1: <?xml version="1.0" encoding="utf-8"?>
   2: <Elements xmlns="http://schemas.microsoft.com/sharepoint/">
   3:   <CustomAction
   4:     Id="COB.SharePoint.Ribbon.NewGroupInExistingTab"
   5:     Location="CommandUI.Ribbon">
   6:     <CommandUIExtension>
   7:       <CommandUIDefinitions>
   8:         <CommandUIDefinition
   9:           Location="Ribbon.WikiPageTab.Scaling._children">
  10:           <MaxSize
  11:             Id="COB.SharePoint.Ribbon.NewGroupInExistingTab.NotificationGroup.MaxSize"
  12:             Sequence="15"
  13:             GroupId="COB.SharePoint.Ribbon.NewGroupInExistingTab.NotificationGroup"
  14:             Size="Large" />
  15:         </CommandUIDefinition>
  16:         <CommandUIDefinition
  17:           Location="Ribbon.WikiPageTab.Groups._children">
  18:           <Group
  19:             Id="COB.SharePoint.Ribbon.NewGroupInExistingTab.NotificationGroup"
  20:             Sequence="15"
  21:             Description="Used to demo adding a group"
  22:             Title="Chris's custom group"
  23:             Template="Ribbon.Templates.Flexible">
  24:             <Controls Id="COB.SharePoint.Ribbon.NewGroupInExistingTab.NotificationGroup.Controls">
  25:               <Button
  26:                 Id="COB.SharePoint.Ribbon.NewGroupInExistingTab.NotificationGroup.NotifyHello"
  27:                 Command="COB.NewGroupInExistingTab.Command.Notify"
  28:                 Sequence="15" Image16by16="/_layouts/images/NoteBoard_16x16.png" Image32by32="/_layouts/images/NoteBoard_32x32.png"
  29:                 Description="Uses the notification area to display a message."
  30:                 LabelText="Notify hello"
  31:                 TemplateAlias="o1" />
  32:             </Controls>
  33:           </Group>
  34:         </CommandUIDefinition>
  35:       </CommandUIDefinitions>
  36:       <CommandUIHandlers>
  37:         <CommandUIHandler
  38:           Command="COB.NewGroupInExistingTab.Command.Notify"
  39:           CommandAction="javascript:
  40:             SP.UI.Notify.addNotification('Hello from the notification area'); 
  41:           " />
  42:       </CommandUIHandlers>
  43:     </CommandUIExtension>
  44:   </CustomAction>
  45: </Elements>

Points of note:

  • The Location of ‘Ribbon.WikiPageTab.Groups._children’ on the main CommandUIDefinition tells the framework I am adding to the groups collection to the ‘Ribbon.WikiPageTab’ tab. This makes sense as I am adding a group.
  • When adding a group, you must decide whether to use an out-of-the-box GroupTemplate (defines how controls are laid out), or whether you will supply the definition. In this sample I’m using ‘Ribbon.Templates.Flexible’ which is suitable for simple layouts like this (one button!), whereas in the last article I showed creating a custom GroupTemplate. It’s worth spending some time in CMDUI.XML looking at the wide range of existing generic templates before creating your own, but by the same token sometimes it might be simpler just to create one, rather than find an existing one which matches the controls you’re trying to lay out. It soon becomes clear how the XML works here though – the analogy I used last week is using HTML to define a table.
    • Remember that the ‘TemplateAlias’ on your controls must match one defined somewhere in the GroupTemplate – in my example above, I’m using a TemplateAlias of ‘o1’ which I know is defined in ‘Ribbon.Templates.Flexible’
  • A ‘MaxSize’ definition must be supplied to the appropriate Scaling collection (i.e. ‘Ribbon.WikiPageTab.Scaling._children’ in this case), though it seems a ‘Scale’ definition is optional (e.g. if you want different controls to be used when less space is available)

Adding controls to an existing group:

If all we need to do is add a control or two to an existing group on an existing tab, things are simpler still. All we need to do is define the control/specify where it goes (CommandUIDefinition) and add the JavaScript behaviour (a CommandUIHandler element for simple cases – the approach for complex cases is discussed later in the series). For this example I’m adding to ‘Ribbon.ListForm.Display.Manage.Controls._children’ – this is DispForm.aspx (the list item display form), and is a good reminder that ribbons exist in application pages and can be customized there too:

Ribbon.ListForm.Display.Manage

..and here’s the relevant XML:

   1: <?xml version="1.0" encoding="utf-8"?>
   2: <Elements xmlns="http://schemas.microsoft.com/sharepoint/">
   3:   <CustomAction
   4:    Id="COB.SharePoint.Ribbon.NewControlInExistingGroup"
   5:    Location="CommandUI.Ribbon" Sequence="20">
   6:     <CommandUIExtension>
   7:       <CommandUIDefinitions>
   8:         <CommandUIDefinition Location="Ribbon.ListForm.Display.Manage.Controls._children">
   9:           <Button Id="COB.SharePoint.Ribbon.NewControlInExistingGroup.Notify"
  10:                   Command="COB.Command.NewControlInExistingGroup.Notify"
  11:                   Sequence="5" Image16by16="/_layouts/images/NoteBoard_16x16.png" Image32by32="/_layouts/images/NoteBoard_32x32.png"
  12:                   Description="Uses the notification area to display a message."

  13:                   LabelText="Notify hello"
  14:                   TemplateAlias="o1"/>
  15:         </CommandUIDefinition>
  16:       </CommandUIDefinitions>
  17:       <CommandUIHandlers>
  18:         <CommandUIHandler
  19:           Command="COB.Command.NewControlInExistingGroup.Notify"
  20:           CommandAction="javascript:
  21:           
  22:           SP.UI.Notify.addNotification('Hello from the notification area'); 
  23:           " />
  24:       </CommandUIHandlers>    
  25:     </CommandUIExtension>
  26:   </CustomAction>
  27: </Elements>

Points of note:

  • Scaling instructions such as ‘MaxSize’ are not required, nor is a ‘Group’ or ‘GroupTemplate’ – since all these apply to groups, which we are not creating
  • As mentioned above, ensure that the ‘TemplateAlias’ on your controls matches one defined somewhere in the GroupTemplate for the parent group. If you’re using one of the ‘Flexible’ templates, ‘o1’ gives a large button whilst ‘o2’ gives a medium one. Always check the XML for the target group though, there could be differences.

The ‘Custom Commands’ tab

Before rushing off to create your new tab, consider that SharePoint 2010 already provides a home which may be suitable for your customization, for list pages at least – the Custom Commands tab. It only appears on list pages and contains just one lonely button under normal circumstances. This tab is actually the post-upgrade home for any SharePoint 2007 CustomActions you had – assuming you don’t introduce changes during your upgrade process, any CustomActions which didn’t target the ECB will end up here. In any case, it could help avoid tab proliferation so don’t forget it when building your customizations:

CustomCommands 

Creating new contextual tabs

So this section doesn’t quite fit with the theme of this article (adding items to existing areas of the SharePoint ribbon), but I vote we conveniently gloss over that fact. Anyway, in addition to regular tabs, you’ll have noticed that the ribbon also displays many contextual tabs which only appear when relevant. In fact, these are really contextual groups which can contain any number of tabs – perhaps the most common are the ‘List Tools’ (blue, shown in the last screenshot) and ‘Library Tools’ which appear in lists and libraries respectively. If we have custom ribbon controls which we only want to present conditionally, a contextual group could be a good design choice since it would fit well with existing ribbon semantics. They also look rather sexy. As you might expect, we can amend existing contextual groups or create our own:

ContextualGroup

To do this, simply wrap your Tab element(s) in a ‘ContextualGroup’ element (notice the CustomAction element has no RegistrationId/RegistrationType attributes, meaning it is scoped globally) and write some code which runs when the group should be shown. First the declaration of the ContextualGroup and Tab:

   1: <?xml version="1.0" encoding="utf-8"?>
   2: <Elements xmlns="http://schemas.microsoft.com/sharepoint/">
   3:   <CustomAction
   4:    Id="COB.SharePoint.Ribbon.ContextualTab"
   5:    Location="CommandUI.Ribbon">
   6:     <CommandUIExtension>
   7:       <CommandUIDefinitions>
   8:         <CommandUIDefinition Location="Ribbon.ContextualTabs._children">
   9:           <ContextualGroup Id="COB.SharePoint.Ribbon.ContextualGroup" Sequence="50" Color="Orange" Command="COBContextualGroupCommand" ContextualGroupId="COB.Contextual" Title="Chris's Contextual Group">
  10:             <Tab Id="COB.SharePoint.Ribbon.ContextualTab" Title="Chris's custom tab" Description="Groups and controls will go in here" Sequence="501">
  11:               <!-- Add Scaling, Groups, GroupTemplates (optional), CommandUI Handlers etc. in here as per creating a normal tab -->
  12:             </Tab>
  13:           </ContextualGroup>
  14:         </CommandUIDefinition>
  15:       </CommandUIDefinitions>
  16:     </CommandUIExtension>
  17:   </CustomAction>
  18: </Elements>

Here I’m writing server-side code (e.g. from a web part, custom field control, or other custom control), and all you need to do is call SPRibbon.MakeTabAvailable() and SPRibbon.MakeContextualGroupInitiallyVisible(), the latter assuming you want your group to be visible immediately when the page loads:

   1: protected override void OnPreRender(EventArgs e)
   2: {
   3:     SPRibbon currentRibbon = SPRibbon.GetCurrent(this.Page);
   4:     currentRibbon.MakeTabAvailable("COB.SharePoint.Ribbon.ContextualTab");
   5:     currentRibbon.MakeContextualGroupInitiallyVisible("COB.SharePoint.Ribbon.ContextualGroup", string.Empty);
   6:     
   7:     base.OnPreRender(e);
   8: }

In the absence of documentation, equivalent client-side code isn’t immediately obvious but I’ll dig around and update this post when I find it.

In terms of updating an existing contextual group or child tab, the following are defined in CMDUI.XML (contextual groups at level 1, child tabs at level 2), and you can probably identify some of them with things you’ve seen on your SharePoint 2010 travels:

  • Ribbon.EditingTools
    • Ribbon.EditingTools.CPEditTab
    • Ribbon.EditingTools.CPInsert
  • Ribbon.Image
    • Ribbon.Image.Image
  • Ribbon.LibraryContextualGroup
    • Ribbon.Document
    • Ribbon.Library
  • Ribbon.ListContextualGroup
    • Ribbon.ListItem
    • Ribbon.List
  • Ribbon.Link
    • Ribbon.Link.Link
  • Ribbon.Table
    • Ribbon.Table.Layout
    • Ribbon.Table.Design
  • Ribbon.WebPartInsert
    • Ribbon.WebPartInsert.Tab
  • Ribbon.WebPartCtx
    • Ribbon.WebPartOption
  • Ribbon.Calendar
    • Ribbon.Calendar.Events
    • Ribbon.Calendar.Calendar
  • Ribbon.PermissionContextualGroup
    • Ribbon.Permission

Summary

The ribbon is a key building block for SharePoint 2010 solutions, and anywhere you see a ribbon tab it can be customized. This means list pages, the page editing experience, particular application pages, Central Administration and many other locations can be targeted in the same way – identify the ID of the location from CMDUI.XML, then use this in the XML which declares your ribbon customization. In addition to adding items to existing tabs and groups, we also looked at contextual groups, and showed how to conditionally display them with code.

Next time – going beyond simple button customizations with JavaScript page components

Customizing the ribbon (part 1) – creating tabs, groups and controls

Some good posts are starting to appear on SharePoint 2010 ribbon customization now, and over the next couple of articles I want to cover some key things you might want to do with the ribbon when developing your solutions. Broadly speaking, the plan for this series is:

  1. Creating tabs, groups and controls (this post)
  2. Adding items into existing ribbon tabs and groups
  3. JavaScript considerations when working with the ribbon
  4. Developing JavaScript page components for the ribbon
  5. Working with the ribbon programatically e.g. from web parts and field controls

Diving straight in then, some ribbon fundamentals:

  • Ribbon elements must be defined in declarative XML with the CustomAction tag – even if you will actually use code to manipulate them (e.g. make visible, enable/disable etc.)
  • The “control hierarchy” is ribbon > tab > group > controls – we’ll explore all these in this post
  • Individual buttons/controls do not appear and disappear on the ribbon. This is a key ribbon principle, to avoid the “I’m sure this button was here yesterday!” effect – instead, depending on the context:
    • Entire tabs can be shown/hidden
    • Individual controls can be enabled/disabled
  • It’s not possible to add custom controls to the ribbon e.g. a custom .ascx/server control. The list of controls defined by the ribbon can be found here in the MSDN docs, and includes things like Button, Checkbox, Color Picker, Combo Box, Dropdown, Textbox, Toggle Button etc, but also some funky ones like Spinner, Split Button and Flyout Anchor (definitions of these exotic varieties can be found in the documentation). Flyout Anchor is particularly interesting as it takes XML as a datasource and can be used to build interesting “pickers” e.g. with images – I’ll hopefully cover this in detail in a future post
  • The definitions for the out-of-the-box ribbon elements are split across several files in the SharePoint root, with TEMPLATE\GLOBAL\XML\CMDUI.XML being the main one. You will likely spend significant time in this file looking for examples similar to what you’re building.

It’s also worth giving special consideration to how JavaScript plays with the ribbon – it’s used frequently since much happens on the client. Depending on the scope you need for your JavaScript (e.g. every page vs. a couple) and the complexity of what you’re doing, JavaScript can be supplied in a few ways:

  • By embedding it into your declarative XML (via a separate CustomAction with a new ‘Location=”ScriptLink”’ attribute) – this post uses this approach, though later in the series I’ll show the next option
  • By deploying a custom .js file which contains some object-oriented JavaScript. This is the approach used for more complex customizations, where you need to create an object which is the client-side “page component” in addition to your XML. The page component supplies the implementation for how your custom ribbon elements should handle various events (“commands”) . This object needs to be derived from the existing CUI.Page.Component object defined in CUI.js. As with any JavaScript file, you then have a couple of options for referencing it on your page.

In this post we’ll show adding JavaScript the first way, though later in the series I’ll show the use of a page component.

Example – creating a new custom tab

This is a fairly in-depth example, since by necessity it also covers creating custom groups and controls too. Also, to kill two birds with one stone, I thought it would be good to look at the new ‘notifications’ and ‘status’ frameworks in the Client Object Model for passing messages back to the user in your SharePoint app. First we’ll walk through what my custom tab looks like, then what it actually does. I have a tab titled “Chris’s custom tab” with 3 groups (“Notification messages”, “Add status messages” and “Remove status messages”) each with some buttons of different sizes and images in them:

CustomRibbonTab

Clicking the ‘Notify hello’ button adds a transient message in the notifications area (fades in from right and stays for 5 seconds by default):

SP.UI.Notify

Clicking the ‘Info status’ button shows a status message this time, in the default color (this remains on screen until removed with another API call):

SP.UI.Status_Info

Clicking the ‘Warning status’ button shows a status message of a different color to indicate severity, I chose red: 

SP.UI.Status_Warning

You might also have noticed the ‘remove status’ buttons have become enabled when a status message is present – such client-side checks can be done by linking a ‘CommandUIHandler’ with an ‘EnabledScript’ attribute, as we’re about to see.

So what XML is required to get that? Well before you scroll through, note that I’ve taken the complex route with some of the declarations so that my example is as informative as possible – most samples I’ve seen so far simply add a button or two in a single group and don’t specify the “’group template” details which determines how the controls in the group get laid out. This is fine for a button or two as you can just reference an out-of-the-box group template, but if you want to do anything different you’re a bit stuck so hopefully this is good documentation:

   1: <?xml version="1.0" encoding="utf-8"?>
   2: <Elements xmlns="http://schemas.microsoft.com/sharepoint/">
   3:   <CustomAction
   4:    Id="COB.SharePoint.Ribbon.CustomTab"
   5:    Location="CommandUI.Ribbon" RegistrationType="List" RegistrationId="101">
   6:     <CommandUIExtension>
   7:       <CommandUIDefinitions>
   8:         <CommandUIDefinition Location="Ribbon.Tabs._children">
   9:           <Tab Id="COB.SharePoint.Ribbon.CustomTab" Title="Chris's custom tab" Description="Groups and controls will go in here" Sequence="501">
  10:             <Scaling Id="COB.SharePoint.Ribbon.CustomTab.Scaling">
  11:               <MaxSize Id="COB.SharePoint.Ribbon.CustomTab.NotificationGroup.MaxSize"
  12:                        GroupId="COB.SharePoint.Ribbon.CustomTab.NotificationGroup"
  13:                        Size="OneLarge"/>
  14:               <Scale Id="COB.SharePoint.Ribbon.CustomTab.NotificationGroup.Scaling.CustomTabScaling"
  15:                      GroupId="COB.SharePoint.Ribbon.CustomTab.NotificationGroup"
  16:                      Size="OneLarge" />
  17:               <MaxSize Id="COB.SharePoint.Ribbon.CustomTab.StatusGroup.MaxSize"
  18:                       GroupId="COB.SharePoint.Ribbon.CustomTab.StatusGroup"
  19:                       Size="TwoMedium"/>
  20:               <Scale Id="COB.SharePoint.Ribbon.CustomTab.StatusGroup.Scaling.CustomTabScaling"
  21:                      GroupId="COB.SharePoint.Ribbon.CustomTab.StatusGroup"
  22:                      Size="TwoMedium" />
  23:               <MaxSize Id="COB.SharePoint.Ribbon.CustomTab.RemoveStatusGroup.MaxSize"
  24:                       GroupId="COB.SharePoint.Ribbon.CustomTab.RemoveStatusGroup"
  25:                       Size="TwoLarge"/>
  26:               <Scale Id="COB.SharePoint.Ribbon.CustomTab.RemoveStatusGroup.Scaling.CustomTabScaling"
  27:                      GroupId="COB.SharePoint.Ribbon.CustomTab.RemoveStatusGroup"
  28:                      Size="TwoLarge" />
  29:             </Scaling>
  30:             <Groups Id="COB.SharePoint.Ribbon.CustomTab.Groups">
  31:               <Group
  32:                 Id="COB.SharePoint.Ribbon.CustomTab.NotificationGroup"
  33:                 Description="Contains notification items"
  34:                 Title="Notification messages"
  35:                 Sequence="52"
  36:                 Template="Ribbon.Templates.OneLargeExample">
  37:                 <Controls Id="COB.SharePoint.Ribbon.CustomTab.NotificationGroup.Controls">
  38:                   <Button
  39:                     Id="COB.SharePoint.Ribbon.CustomTab.NotificationGroup.Notify"
  40:                     Command="COB.Command.Notify"
  41:                     Sequence="15" Image16by16="/_layouts/images/NoteBoard_16x16.png" Image32by32="/_layouts/images/NoteBoard_32x32.png"
  42:                     Description="Uses the notification area to display a message."
  43:                     LabelText="Notify hello"
  44:                     TemplateAlias="cust1"/>
  45:                 </Controls>
  46:               </Group>
  47:               <Group
  48:                 Id="COB.SharePoint.Ribbon.CustomTab.StatusGroup"
  49:                 Description="Contains 'add status' items"
  50:                 Title="Add status messages"
  51:                 Sequence="49"
  52:                 Template="Ribbon.Templates.TwoMediumExample">
  53:                 <Controls Id="COB.SharePoint.Ribbon.CustomTab.StatusGroup.Controls">
  54:                   <Button
  55:                    Id="COB.SharePoint.Ribbon.CustomTab.StatusGroup.AddStatusInfo"
  56:                    Command="COB.Command.AddStatusInfo"
  57:                    Sequence="17" Image16by16="/_layouts/images/info16by16.gif" Image32by32="/_layouts/images/info16by16.gif"
  58:                    Description="Uses the status bar to display an info message."
  59:                    LabelText="Info status"
  60:                    TemplateAlias="cust2"/>
  61:                   <Button
  62:                     Id="COB.SharePoint.Ribbon.CustomTab.StatusGroup.AddStatusWarning"
  63:                     Command="COB.Command.AddStatusWarning"
  64:                     Sequence="17" Image16by16="/_layouts/images/warning16by16.gif" Image32by32="/_layouts/images/warning32by32.gif"
  65:                     Description="Uses the status bar to display a warning message."
  66:                     LabelText="Warning status"
  67:                     TemplateAlias="cust3"/>
  68:                 </Controls>
  69:               </Group>
  70:               <Group
  71:                 Id="COB.SharePoint.Ribbon.CustomTab.RemoveStatusGroup"
  72:                 Description="Contains 'remove status' items"
  73:                 Title="Remove status messages"
  74:                 Sequence="52"
  75:                 Template="Ribbon.Templates.TwoLargeExample">
  76:                 <Controls Id="COB.SharePoint.Ribbon.CustomTab.RemoveStatusGroup.Controls">
  77:                   <Button
  78:                     Id="COB.SharePoint.Ribbon.CustomTab.RemoveStatusGroup.RemoveLastStatusButton"
  79:                     Command="COB.Command.RemoveLastStatus"
  80:                     Sequence="15" Image16by16="/_layouts/images/warning16by16.gif" Image32by32="/_layouts/images/CRIT_32.GIF"
  81:                     Description="Removes the last message from the status bar."
  82:                     LabelText="Remove last status message" 
  83:                     TemplateAlias="cust4"/>
  84:                   <Button
  85:                     Id="COB.SharePoint.Ribbon.CustomTab.RemoveStatusGroup.RemoveAllStatusButton"
  86:                     Command="COB.Command.RemoveAllStatus"
  87:                     Sequence="15" Image16by16="/_layouts/images/warning16by16.gif" Image32by32="/_layouts/images/CRIT_32.GIF"
  88:                     Description="Removes all messages from the status bar."
  89:                     LabelText="Remove all status messages"
  90:                     TemplateAlias="cust5"/>
  91:                 </Controls>
  92:               </Group>
  93:             </Groups>
  94:           </Tab>
  95:         </CommandUIDefinition>
  96:         <CommandUIDefinition Location="Ribbon.Templates._children">
  97:           <GroupTemplate Id="Ribbon.Templates.OneLargeExample">
  98:             <Layout Title="OneLarge" LayoutTitle="OneLarge">
  99:               <Section Alignment="Top" Type="OneRow">
 100:                 <Row>
 101:                   <ControlRef DisplayMode="Large" TemplateAlias="cust1" />
 102:                 </Row>
 103:               </Section>
 104:             </Layout>
 105:           </GroupTemplate>
 106:         </CommandUIDefinition><CommandUIDefinition Location="Ribbon.Templates._children">
 107:           <GroupTemplate Id="Ribbon.Templates.TwoMediumExample">
 108:             <Layout Title="TwoMedium" LayoutTitle="TwoMedium">
 109:               <Section Alignment="Top" Type="TwoRow">
 110:                 <Row>
 111:                   <ControlRef DisplayMode="Medium" TemplateAlias="cust2" />
 112:                 </Row>
 113:                 <Row>
 114:                   <ControlRef DisplayMode="Medium" TemplateAlias="cust3" />
 115:                 </Row>
 116:               </Section>
 117:             </Layout>
 118:           </GroupTemplate>
 119:         </CommandUIDefinition>
 120:         <CommandUIDefinition Location="Ribbon.Templates._children">
 121:           <GroupTemplate Id="Ribbon.Templates.TwoLargeExample">
 122:             <Layout Title="TwoLarge" LayoutTitle="TwoLarge">
 123:               <Section Alignment="Top" Type="OneRow">
 124:                 <Row>
 125:                   <ControlRef DisplayMode="Large" TemplateAlias="cust4" />
 126:                   <ControlRef DisplayMode="Large" TemplateAlias="cust5" />
 127:                 </Row>
 128:               </Section>
 129:             </Layout>
 130:           </GroupTemplate>
 131:         </CommandUIDefinition>
 132:       </CommandUIDefinitions>
 133:       <CommandUIHandlers>
 134:         <CommandUIHandler
 135:           Command="COB.Command.Notify"
 136:           CommandAction="javascript:
 137:           
 138:           var notificationId = SP.UI.Notify.addNotification('Hello from the notification area'); 
 139:           " />
 140:         <CommandUIHandler
 141:           Command="COB.Command.AddStatusInfo"
 142:           CommandAction="javascript:
 143:           
 144:           var statusId = SP.UI.Status.addStatus('Quite important status message');
 145:           latestId = statusId;
 146:           enableRemoveStatusButton();
 147:           " />
 148:         <CommandUIHandler
 149:           Command="COB.Command.AddStatusWarning"
 150:           CommandAction="javascript:
 151:           
 152:           var statusId = SP.UI.Status.addStatus('Very important status message');
 153:           SP.UI.Status.setStatusPriColor(statusId, 'red');
 154:           latestId = statusId;
 155:           enableRemoveStatusButton();
 156:           " />
 157:         <CommandUIHandler
 158:           Command="COB.Command.RemoveLastStatus" EnabledScript="javascript:enableRemoveStatusButton();"
 159:           CommandAction="javascript:
 160:           
 161:           SP.UI.Status.removeStatus(latestId);
 162:           latestId = '';
 163:           enableRemoveStatusButton();" />
 164:         <CommandUIHandler
 165:           Command="COB.Command.RemoveAllStatus" EnabledScript="javascript:enableRemoveStatusButton();"
 166:           CommandAction="javascript:
 167:           
 168:           SP.UI.Status.removeAllStatus(true);
 169:           latestId = '';
 170:           enableRemoveStatusButton();" />
 171:       </CommandUIHandlers>
 172:     </CommandUIExtension>
 173:   </CustomAction>
 174:   <CustomAction Id="COB.Command.RemoveLastStatus.CheckEnable" Location="ScriptLink"
 175:              ScriptBlock="
 176:                 var latestId = '';
 177:                           
 178:                 function enableRemoveStatusButton() 
 179:                 { 
 180:                   if (latestId == '')
 181:                   {
 182:                     return false;
 183:                   }
 184:                   else
 185:                   {
 186:                     return true;
 187:                   }
 188:                 }"
 189:                 />
 190: </Elements>

Some key points, following the XML sequence:



  • CustomAction:

    • Notice I have two CustomAction elements – one for the ribbon elements, the other for some JavaScript I want to use with my custom elements. This is the approach mentioned earlier where the JavaScript is effectively embedded in your XML [sidenote: you don’t have  to be doing ribbon customization to leverage this approach – this use of CustomAction is a new way of providing JavaScript to the page, just be aware it will be added for every page in the Feature scope (e.g. site/web) and you have no control over where in the page it will be injected. It does give you the ability to take away your JavaScript via Feature deactivation though, which could be useful for many scenarios).

      • The Location attribute of CustomAction for ribbon elements should always be “CommandUI.Ribbon”
      • The Location attribute of CustomAction for script is a new value, “ScriptLink”

    • My ribbon tab is scoped to document libraries only – this is courtesy of the RegistrationType=”List” RegistrationId=”101″ attributes (which is exactly what you did when targeting a CustomAction to doc libs in SharePoint 2007, no change there)
    • When targeting a list in this way, RegistrationId refers to the list template ID (e.g. generic list = 100. document library = 101 etc. – here’s a full list of list template IDs for 2007, there could be a couple of new ones in 2010) – it is not possible to declaratively target a list by e.g. GUID or URL. So consider that this could drive you to create a list template when you otherwise might not have.
    • Other options for the RegistrationType continue to be “ContentType”, “ProgID” and “FileType”, but I’m pretty sure only “List” can be used for ribbon elements, but I’ve not tested that yet so I reserve the right to be wrong! If you want a different scope level, you would omit the RegistrationType and RegistrationId attributes and use code such as SPRibbon.MakeTabAvailable() to conditionally show the ribbon. More on this later in the series when I show how to add ribbon customizations for a web part or custom field control.

  • CommandUIDefinition:

    • Another element you might have multiple of – one for the main customization definition, one for each of the “GroupTemplate” elements being provisioned (more on this later).  For the main one, the “Location” attribute here is crucially important as this specifies where the customization should appear. My value of “Ribbon.Tabs._children” indicates I’m adding something into the “Ribbon.Tabs” collection defined by SharePoint (typically in CMDUI.XML) – “_children” is a convention used when adding to  many collections in the ribbon architecture. We’ll look at how to add new groups and controls into an existing group in the next article, but as a quick example, adding a group into “Ribbon.Library.Groups._children” would make your group appear somewhere in here (depending on the “Sequence” value assigned on the definition for the group):

      Ribbon.Library.Groups

  •  Tab:

    • The “Sequence” attribute decides where to place my tab amongst the existing ones. Out-of-the-box values are generally multiples of 10, sometimes of 5, so your sequence values should avoid such numbers to avoid conflict. Generally you’ll need to find the declaration of the surrounding elements near where you are targeting (in CMDUI.XML) to find the appropriate number.

  • Scaling (and children):

    • This section defines how your elements should behave when the window is resized and there’s not enough room. You need a “MaxSize” and “Scale” element for each Group you define. These define the size and layout the element(s) should be at when at “max”, and also what to change to when the window is smaller – effectively you can provide multiple layouts for your controls depending on the window size (e.g. prioritising the important buttons).

  • Group:

    • This is the “section on the ribbon tab” which is the container for your controls – in the small image above, an example of a Group is ‘View Format’ which contains the two leftmost buttons. Key things here are the “Sequence” (same deal as elsewhere) and the “Template” – this is a reference to a “GroupTemplate” element (which we’ll come onto shortly). In essence, this is the link which will tell the ribbon framework how to lay out the controls in this group.

  • Controls:

    • Fairly obvious, this is the parent node for any controls you want to add e.g. buttons, dropdowns etc etc. Note that each of your controls in here must have a “TemplateAlias” attribute – this tells the framework exactly where to place the individual control within the GroupTemplate which is referenced.
    • Controls expose various commands, via attributes – a Button simply has a “Command” attribute which fires when clicked, whereas a Dropdown has additional ones such as “PopulateQueryCommand” and “QueryCommand”. These link to “CommandUIHandler” elements or code defined in a JavaScript page component.

  • GroupTemplate:

    • Similar to defining say, a HTML table, this section provides the actual layout of the controls, alignments, control sizes etc. Each control which is being declared needs a corresponding “ControlRef” element which will be matched to the control on the “TemplateAlias” value.

  • CommandUIHandler:

    • This is the where you get to define the JavaScript which executes when the basic “Command” is fired for a control (e.g. a button is clicked). The command name must match that defined on the Control element, and the “CommandAction” attribute contains the script for the basic command. You can also use the “EnabledScript” attribute to add some script which decides whether the control should be enabled or not – this is how my ‘’remove status’ buttons are only enabled when there is a message to remove.
    • Since all the JavaScript gets added to the same page, as you’ll see in my sample it is possible to declare variables which get used by other JavaScript provisioned by a different CommandUIHandler – again though, whilst the sequence is deterministic you cannot control where your script gets added into the overall page (at the start of the <body> tag), so if you need your code to run when the DOM is complete you’d have to take steps to get your code called at the appropriate time – more on this later in the series.

Hope you found this useful. Next time we’ll take a quick look at adding items to existing ribbon locations, before moving onto working with JavaScript and page components etc.

Editing .cmp files to fix lookup field issues

I ran into an interesting (well, it’s all relative) content deployment issue the other day, which I’m pretty sure will apply to both SharePoint 2007 and SharePoint 2010. In preparation for some SharePoint training I was delivering at my current client, I wanted to move some real data from production into the training environment to make the training more realistic. To do this, I used my Content Deployment Wizard tool, which uses SharePoint’s Content Deployment API to export content into a .cmp file. (Quick background – the tool does exactly the same thing as ‘STSADM –o export’ and out-of-the-box content deployment, but allows more control. A .cmp file is actually just a renamed cab file i.e. a compressed collection of files, similar to a .wsp). However, when importing the .cmp file containing my sites/documents etc., the operation failed with the following error:

The element ‘FieldTemplate’ in namespace ‘urn:deployment-manifest-schema’ has invalid child element ‘Field’ in namespace ‘http://schemas.microsoft.com/sharepoint/’. List of possible elements expected: ‘Field’ in namespace ‘urn:deployment-manifest-schema’.

So clearly we have a problem with a field somewhere, and it’s an issue I was vaguely aware of – cross-web lookup fields deployed with a Feature break content deployment. Michael Nemtsev discusses the issue here, saying “There are several samples how to deploy lookup fields via feature (http://www.sharepointnutsandbolts.com/2007/04/feature-to-create-lookup-fields-on.html) but all of them are not suitable for the Content Deployment Jobs. Because you will get the exception…”

Oops that’s a link to an old article of mine. So effectively *my* code to create lookup fields doesn’t work with *my* content deployment tool – don’t you just love it when that happens?! However, I actually have a clear conscience because I know both utilities are doing valid things using only supported SharePoint APIs – this is simply one of those unfortunate SharePoint things. As Michael says, all of the cross-web lookup field samples would have this issue. So what can we do about it?


For fields yet to be created

In this scenario my recommendation would be to use the technique Michael suggests in his post, which is to strip out the extraneous namespace at the end of our code which creates the lookup.

For fields which are already in use (i.e. the problem I ran into)

If your lookup fields have already been deployed, then you have 2 options:

  • develop and test a script to retrospectively find and fix the issue across your web/site collection/farm/whatever scope you need
  • fix the issue in the .cmp file you were trying to import in the first place, so this particular import will succeed

Clearly your decision might depend on how much content deployment you want to do to the site/web/document library or list which has the problem. If you’re anticipating doing it all the time, you should fix the underlying issue. If, as in my scenario, you just need to get an ad-hoc import to succeed, here’s how..

Hacking the .cmp file

The process is effectively to fix-up the .cmp file, then rebuild it with the updated files. I noticed an unanswered question on Stack Overflow about this process, so clearly it’s something that can occasionally arise. Of course even in these WSPBuilder/SP2010 tools days, all SharePoint devs should know you can use makecab.exe with a .ddf file to build a cab file – but what happens when you have hundreds of files? That’s hundreds of lines you’ll need in your .ddf file? Certainly you could write some code to generate it for you, but chances are you’re looking for a quick solution. 

The first process I came up with was:

  1. Rename .cmp file to .cab, extract files to other directory.
  2. Fix-up files (more detail shortly).
  3. Use WSPBuilder to generate the .ddf file from files on filesystem – edit this to ensure paths are correct.
  4. Use makecab.exe + the generated .ddf file to build the .cab file.
  5. Rename extension to .cmp.

However, a slightly better way is to use Cab File Maker, like this:

  1. Rename .cmp file to .cab, extract files to other directory.
  2. Fix-up files – to do this, edit manifest.xml, remove all instances of following string – “xmlns=http://schemas.microsoft.com/sharepoint/
  3. Open Cab File Maker 2.0, drag files in and set options for where to generate ddf file and cmp file, like so:

    CabFileMaker
  4. Voila – your cmp file is now ready to go and should import successfully.

ECM platform enhancements – Content Organizer, List Throttling, Enterprise Content Types etc.

This is part 2 of my write-up on my ‘ECM Enhancements in SharePoint 2010’ talk which I did recently. You can find previous parts here:

Part 1: Managed Metadata in SharePoint 2010 – a key ECM enhancement
Part 1.5: Managed Metadata in SharePoint 2010 – some notes on the "why"

Having spent some time on the taxonomy/metadata piece in the last articles, this post will delve into other ECM enhancements. ECM is quite a broad topic (Document Management/Collaboration/Records Management/Web Content Management), but it’s amazing just how much of the new stuff you can’t cover in such a talk – Sandboxed Solutions, BCS, Service Applications, API improvements etc. are all only tenuously linked to ECM, so didn’t get coverage.

Here’s what I think of as some key generic ‘ECM platform’ enhancements:

  • Scalability
  • Enterprise Content Types
  • User experience
  • Taxonomy/metadata (as covered in earlier posts)
    • Navigation by metadata
  • Content Organizer
  • Document Sets
  • Tagging

No doubt you might think of others as being a big deal too, but that’s a good list for starters. In this article we’ll look at some of these items in detail, but for others I’ll point you to other articles which have good coverage on those topics.

Scalability/list throttling

Although us architects/developers think that we know a lot about scaling SharePoint these days, consider that in the 2007 release a common cause of Out Of Memory exceptions in large farms was use of the Content Query Web Part on site home pages, left to default settings. Of course this usage is perfectly natural – CQWP is about rolling-up content after all, but what compounded the problem is that some site templates (e.g. publishing portal) added this to the home page for you, thus triggering the problem without explicit configuration. In large sites this would result in a significant query which put significant load on the servers, and the built-in caching didn’t fully mitigate it.

In SharePoint 2010, the query-throttling feature is designed to prevent this problem – this will cut off large queries once a threshold has been passed and the full results will not be returned, thus safeguarding stability. The limits are set at the web application level, and it’s possible to specify a window when the governor will not kick in (aka ‘happy hour’):

ListThrottleSettings

If the query originated from custom code, the query will not complete and an SPQueryThrottledException will be raised:

SPQueryThrottledException

This of course, is a good thing – developers just need to ensure their 2010 code catches this exception type and presents a pretty message on the page, rather than show a Yellow Screen of Death. Interestingly, when a user encounters a similar scenario in a regular SharePoint list view, the experience is somewhat more sophisticated as some results are returned (up to the point where the threshold is crossed), and an equivalent ‘signal’ is passed to the user in the form of a message:

ThrottledListResults

As an aside, I notice at this point I’ve been redirected to a URL which contains all the parameters needed to display the “permitted” subset of data:

http://cob.collab.dev/Lists/LargeList/ShowEverything.aspx#ServerFilter=FilterField1=ID-FilterValue1=3752-FilterOp1=Geq-OverrideScope=RecursiveAll-FallbackLimit=3752-ProcessQStringToCAML=1 

I haven’t yet worked out if it’s possible to use this “show partial results” facility in custom code – certainly it would be nice in some scenarios. It sounds feasible as in the list view request, the server has worked out what parameters constitute an acceptable query (ID >= 3752) and (it seems) has redirected the user to a new request which contains the filter parameters. I’d be interested to hear if anyone has noticed how this can be done with code – I haven’t dug too deep, but the most obvious place to communicate this info back (the SPQueryThrottledException) doesn’t have anything.

Enterprise Content Types

In 2007, maintaining content types and site columns across an enterprise deployment effectively became a technical problem – the business could not take care of this without help. Changes they made would only be local to a site collection and code/scripts were required to roll out updates more globally. With Enterprise Content Types in SharePoint 2010, this common pain point is eased – effectively the model is a hub and spoke-type arrangement where a ‘master site’ is nominated, and then a service application/set of timer jobs takes care of synchronizing the changes to other sites which are hooked up to the same Managed Metadata service app as the hub. I decided not to demo this in my talk in the end, partly because it doesn’t really make the world’s most scintillating demo. However, that’s not to downplay the significance of this feature – I think this goes a long way to making SharePoint more manageable in the enterprise. Others have covered this well, a couple of good write-ups are:

User experience

Put simply, the new wiki-style page editing experience will have a huge impact on collaboration sites. The ability for users to place rich text, images and even web parts exactly where they like on a page (as opposed to having to deal with web part zones and the Content Editor web part) will make a lot of end users happy:

PageEditing

Interestingly if you don’t want the new page editing experience in team sites, you’ll probably need to do some customization work. It is possible to create web part pages in the new team site template (and even store them in the ‘Site Pages’ library alongside wiki pages), but the user experience isn’t obvious and the menu options aren’t right there in front of the user – you have to pass by the ‘New Page’ menu item and head to ‘More Options’ > ‘Web Part Page’ and specify which library to store it in. So if you did want the ‘old-style’ page editing, you’d probably either use a custom site definition or at least add something to the Site Actions menu or ribbon if sticking to the 2010 team site template. In any case, the wiki page editing functionality is defined by the actual field control used rather than the field type – it seems there’s a fair amount of behind-the-scenes jiggery-pokery which goes on with hidden fields, update panels and a new EmbeddedFormField control (in team sites at least, publishing sites continue to use the RichHtmlField control), but the upshot is that anywhere the new Rich Text Editor is used will display the new editing experience.

I know for a fact that the site owners on the collaboration roll-out I’m currently working on would welcome this with open arms. Consequently this is a significant for advance for SharePoint as an ECM platform, and I can see it being a key driver for some organizations to upgrade to 2010 for their collab sites.

Finally, as alluded to just now, publishing sites also get the new RTE – which as an aside, means things like the wiki-syntax for linking pages can be used there too.

Content Organizer

You may have already seen AC talk about this – in essence the Content Organizer allows you to add certain types of content (e.g. documents, pages, rich media files but NOT list items) to one bucket (known as the ‘Drop-Off Library’), and then let some rules define where it should actually end up within your site. A timer job handles the actual processing of the rules. Typically you’ll define rules based on metadata, so continuing to use my electrical goods example, here I’m creating a rule which moves documents with a ‘Screen type’ value of ‘Plasma’ to a library called ‘Television specifications’:

ContentOrganizerRules

I’m also specifying that subfolders should be created within the document library, such that I’ll end up with a set of folders for each of the ‘Screen type’ values encountered when documents are uploaded. In general, the way rule definition works is you first specify the content type the rule applies to, then the UI allows you to select columns on that content type to use as metadata filters. The metadata bit is actually optional though – your rule could simply be based on content type, with no further sub-filter based on metadata values.

If you’re like me, you may have wondered how the Content Organizer copes with certain scenarios – so here are a few findings:

  • What happens if users don’t go to the ‘Drop-Off Library’ to add documents there? Surely the rules won’t run otherwise? To ensure all uploads do indeed use the routing rules, you can specify that users should be redirected to the Drop-Off Library when they upload – this is pretty seamless to the user, they just see a small message in the dialog indicating their file will be routed:

    ContentOrganizerMessage
  • What happens if the user doesn’t have permissions to write to the library specified in the rule? The user will get an Access Denied, and the document gets cleared from the Drop-Off Library. I’d be interested to know if the ‘rule managers’ are notified in such a scenario (I didn’t have SMTP set up at the time of testing) – this is the group who can be configured to be notified when a document is uploaded which doesn’t match any rules, but I’m not yet sure if they get notified for other cases like this.
  • When specifying a rule to route items to another site’s Content Organizer, what happens if the rules there redirect back to this one? Somewhat tongue in cheek this one, but hey, it’s good to know what happens! The answer is the document stays in the Drop-Off Library, and as with any such routing failure, if configured the rule managers are notified after the specified wait period is over.
  • What does the user see if no rules are matched? As well as the notification to the rule managers, the user sees a message to inform them their document won’t be being routed anywhere just yet:

    RuleNotMatched

Steve Peschka has a great series of 3 in-depth articles on Content Organizer, and also you can quickly understand a lot about defining rules from the screenshots in AC’s article.

Document Sets

Briefly, Document Sets allow multiple items to be treated as one in terms of workflow, approval, versioning and so on. A classic use case might be a proposal which consists of several documents – however they are bundled up and given to a client all together, meaning they need to be treated almost as a ‘release’ in coding terms. There are some useful features in here, like the fact you can set default values on columns, which individual documents will inherit when added to the set. Also, each Document Set gets a “home page”, meaning content (including web parts) can be added to provide an entrance on to the information.

Liam has a good write-up here – “SharePoint 2010 User Experience – Document Sets

Tagging

Tagging in SharePoint 2010 is a deep area so I can’t do it justice here, and it reaches into the “social” arena as well as the Managed Metadata framework I’ve previously discussed. Some highlights are the fact that the social bookmarking feature (“Tags and notes”) allows any item inside or outside SharePoint to have your tags and notes added, and that your recent tagging activity is summarized in your activity feed on your MySite (à la Facebook tagging).

Christian Glessner has a write-up here – Managing Metadata in SharePoint 2010

Summary

SharePoint 2010 contains a raft of ECM enhancements, and any one could be a killer feature for your organization. The “ECM platform” features discussed here are relevant whether you are using SharePoint for collaboration/document management, WCM, Records Management or all three. Many of the criticisms of the 2007 release have been addressed, and right now it looks like SharePoint 2010 will be an even bigger hit for ECM than it’s predecessor.

Common compilation error for SharePoint 2010 VS projects

This post is a quick public service announcement for a beta 2 issue which I think a lot of SharePoint developers are about to hit. I can’t take any credit whatsoever for the fix, I’m simply playing the messenger here – purely because I notice that whilst the information is in the public domain now, search engine users will probably not find it. Since I’ve hit this a few times already (including when writing code for my Managed Metadata demo), I figure it’s worthy of more attention.

The extremely useful SharePoint 2010 Beta Release Known Issues post over on the SharePoint Developer Documentation Team Blog has the info in one of the ‘development issues’ points:

  • Some assemblies, such as Microsoft.SharePoint.Publishing, appear in some cases to have a dependency on an incorrect version of the System.Web.DataVisualization assembly. The incorrect reference causes build failures. If you see this problem, add a reference to the correct version of System.Web.DataVisualization on your system. If you installation is on the C drive, that assembly will be located here:

C:\Program Files (x86)\Microsoft  Chart Controls\Assemblies\System.Web.DataVisualization.dll

To expand on this, this is a known bug which causes a design-time only error, since the correct version will be loaded by .Net at runtime – so it will stop you compiling, but once you’ve got past that you’re good. When I hit the issue the fix above didn’t seem to work for me, but an alternative fix of adding this path into a specific registry key did – however, when I went back to test again adding the reference did solve my problem, so I’m assuming I did something wrong first time. Certainly this approach is far preferable to editing the registry on every dev VM you have, so I won’t publish those details.

At least the following assemblies seem to be affected:

  • Microsoft.Office.Server.Search
  • Microsoft.SharePoint.Taxonomy
  • Microsoft.SharePoint.Publishing

I tried using NDepend to generate a full list, but for some reason the trial version isn’t finding dependencies which I think exist. In any case, if you discover additional assemblies with this issue, please leave a comment.

Finally, to help folks searching for this on the interweb here are some of the errors you’re probably searching on:

The primary reference "Microsoft.Office.Server.Search, Version=14.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c, processorArchitecture=MSIL" could not be resolved because it has an indirect dependency on the framework assembly "System.Web.DataVisualization, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" which could not be resolved in the currently targeted framework. ".NETFramework,Version=v3.5". To resolve this problem, either remove the reference "Microsoft.Office.Server.Search, Version=14.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c, processorArchitecture=MSIL" or retarget your application to a framework version which contains "System.Web.DataVisualization, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"

The primary reference "Microsoft.SharePoint.Taxonomy, Version=14.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c, processorArchitecture=MSIL" could not be resolved because it has an indirect dependency on the framework assembly "System.Web.DataVisualization, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" which could not be resolved in the currently targeted framework. ".NETFramework,Version=v3.5". To resolve this problem, either remove the reference "Microsoft.SharePoint.Taxonomy, Version=14.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c, processorArchitecture=MSIL" or retarget your application to a framework version which contains "System.Web.DataVisualization, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"

The primary reference "Microsoft.SharePoint.Publishing, Version=14.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c, processorArchitecture=MSIL" could not be resolved because it has an indirect dependency on the framework assembly "System.Web.DataVisualization, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" which could not be resolved in the currently targeted framework. ".NETFramework,Version=v3.5". To resolve this problem, either remove the reference "Microsoft.SharePoint.Publishing, Version=14.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c, processorArchitecture=MSIL" or retarget your application to a framework version which contains "System.Web.DataVisualization, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"

Again, thank the guys behind the SharePoint Developer Documentation Team Blog and the Product Group folks for this info, not me.

Managed Metadata addendum – why metadata?

Having looked at the “what” of SharePoint 2010 Managed Metadata framework in the previous article, before moving on to discuss other ECM enhancements in the next article I want to quickly look at the “why”. One of the reasons I want to do this is because I notice some SharePoint folks, developers in particular, seem to be somewhat “meh” about this 2010 feature in comparison to “sexier” topics.

Metadata is the gateway to so much more than just good search results these days though, and I think developers sometimes overlook this. For a slide in my talk, I came up with 4 categories of functionality and gave some examples of each – I encouraged the audience to keep these examples in the back of their mind as I demonstrated how to use and extend the Managed Metadata framework:

MetadataDrivenFunctionality 

I’m sure there are lots of other examples too. Some are more relevant for collaboration sites e.g. navigating by metadata is huge for large lists/libraries, whereas others, such as the examples I badged as “Alternative navigation”, are becoming more common in publishing sites (e.g. public-facing). I also see more and more WCM sites where landing pages have content generated by complex rules – going beyond say, the Content Query Web Part and more towards generating entire pages with a high-end search engine such as FAST, Endeca or Autonomy. Behind all this is always well-defined metadata, and you need to give authors powerful tools to tag content properly for this to work well.

The “faceted” angle comes in two different flavours too – faceted search is where an actual search query is being performed (i.e. relevance is considered) but facets are used to further refine or filter the resultset, whereas faceted navigation is more analogous to a SQL query with multiple filters in a WHERE clause. Since we’re on the subject and there wasn’t space to cover it in the last article, let’s take a look at what SharePoint Server 2010 now provides in terms of faceted navigation, or in more appropriate SharePoint terminology, navigation by metadata.

Navigation by metadata in a SharePoint Server 2010 list (faceted navigation)

Although the idea of navigating by metadata has been around for a while, the SharePoint 2010 release is the first which has had something in the box for this – this is currently a SharePoint Server deal only though, so SharePoint Foundation users are out of luck (this is the situation in the beta packaging anyway).  There are two navigation concepts which can be used in lists and libraries – “navigation hierarchies” and “key filters”. Navigation hierarchies provide an expandable treeview which effectively allow the user to “drill down” the taxonomy and then select a node in order to filter items in the current view. Key filters offer no such expand/collapse functionality, but do support the same type-ahead or “browse in a picker” experience for a specific field that authors get when tagging items – additionally, more field types can be used as navigators in key filters compared to in a navigation hierarchy.

Both appear under the quick launch navigation:

NavigationControlsInContext_Wide

..and can be used to filter the current view to find the items the user is interested in:

MetadataNavigation

Effectively you’re getting a choice of user experience, although both can be provided only the same list if preferred. Notably, you can filter the list view by combining filters from both navigation types, it isn’t a “one or the other” situation.

When configuring in the list settings, for each you can specify which list fields you wish to use as navigators: 

MetadataNavigationSettingsWide

I think this is a huge win for any SharePoint collaboration environment with reasonably-sized lists/libraries. Although both navigation types are off by default, I can’t think of many reasons why you wouldn’t want to enable this in many places. A cursory look through the SDK didn’t tell me how I might enable the functionality for all lists created from a given template (the list schema XML or SPList class don’t seem to have been extended to allow setting this), but I’m sure it’s possible – some relevant classes can be found the Microsoft.Office.DocumentManagement.MetadataNavigation namespace.

Summary

Although metadata isn’t the most glamorous topic for some folks, it’s the foundation for a huge range of functionality across collaboration and publishing sites, and everything in between. The end goal is never simply to have content tagged, but rather what you’re in a position to do once that’s the case. Consider how the managed metadata framework can help you implement end-user functionality in your site. One example which is now standard functionality in SharePoint Server 2o10 is navigation by metadata within a list or library – "navigation hierarchies” and “key filters” provide highly useful options for finding items in large lists, but a wide range of other possibilities exists too – you don’t need FAST to provide innovative ways to navigate a website.

Managed Metadata in SharePoint 2010 – a key ECM enhancement

Last week I did a talk on ‘Enterprise Content Management enhancements in SharePoint 2010’ at the UK SharePoint User Group. Since the talk was 70% demos simply posting the slide deck doesn’t really convey the discussion, so over the next 2 posts I’ll cover the same ground in written form. So this ‘ECM enhancements mini-series’ consists of:

Part 1 – Managed Metadata in SharePoint 2010 – a key ECM enhancement (this post)
Part 2 – ECM platform enhancements – Enterprise Content Types, Content Organizer, Scalability etc. (link to be added)

I want to focus on Managed Metadata first as it will be such a key ECM building block in SharePoint 2010.

Background

In SharePoint 2007, metadata was a huge blind spot – many organizations have a fundamental requirement to only allow certain ‘approved’ terms from a central list to be used as metadata. Broadly, the options were:

  • Use a choice or lookup field (scoped to web, or possibly deployed as Feature which can give broader reach but more maintenance problems)
  • Build a custom field type
  • Buy a vendor’s solution (which will involve a custom field type somewhere)
  • Attempt to simply guide authors to use the correct terms in a plain old textbox

Frequently, metadata terms are in a hierarchy which counts some of those options out. Otherwise the first and last options were lame/unsuitable across large deployments, and I can practically guarantee that any vendor or custom solution out there wouldn’t be as rich as a proper baked-into-SharePoint implementation. And this is what we’ve now got in SharePoint 2010 with the “Managed Metadata” capability – I wouldn’t say it covers all of the bases, but it can be extended easily. In my talk I joked that I couldn’t bear to do a talk without any code, and so showed how a notable hole in the metadata framework in can be plugged in 10 minutes flat by using the Microsoft.SharePoint.Taxonomy namespace. More on this later.

A key thing to note is that the new Managed Metadata field now exists by default on many core content types such as ‘Document’ – so it’s right there without having to explicitly add it to your content.

SharePoint 2010 – Creating the central taxonomy

An organisation’s taxonomy is defined in the Term Store Management Tool – this is part of the Managed Metadata service application, and can be accessed either from Central Administration or from within Site Settings. Permissions are defined within the Term Store itself. For my demo I “borrowed” the taxonomy from a popular UK electrical retailer, and added the terms manually (but note you can also import from CSV). The following image shows the different types of node used to structure and manage a SharePoint 2010 taxonomy, and also the options available to manage a particular term:

TermStore  

Adding site columns – making terms available for use 

In order for authors to be able to use the terms on a document library, a column needs to be created (most likely on the appropriate content types) of type ‘Managed Metadata’. There are 2 key steps here:

  1. Mapping the column to the area of the taxonomy which contains the terms we wish to use for this field:

    ManagedMetadataSiteColumn

    Some notes on this:
    • The node selected is used as the top-level node – if it has children, these values can also be used in this field.
    • Site collections can optionally define their own terms sets at the column level (i.e. leverage the authoring experience you’re about to see, but not just for organization-wide terms sets) rather than use the central one. This is labelled as ‘Customize your term set’ in the image above, and allows terms to be added when this radio button is selected.
  2. Specifying whether ‘Fill-in’ choices are allowed (shown on lower part of above image):
    • First thing to note is that ‘fill-in’ choices are only possible when the ‘Submission Policy’ of the linked parent term set is set to ‘Open’. This provides centralized master control to override the local setting on the column.
    • When the “Allow ‘Fill-in’ choices” option on the column is set to ‘Yes’, we specify that authors can add terms into the taxonomy as they are tagging items – in taxonomic terms, this model is known as a folksonomy, meaning it is controlled by end users/community rather than centrally defined. Although the setting is quite innocuous, but this is hugely different in Information Architecture terms – typically it is often beneficial when content authors are trusted and capable and there is a desire to grow the taxonomy ‘organically’, perhaps because a mature one doesn’t exist yet.
    • I can imagine some document libraries may use both types (traditional taxonomy and folksonomy). One column is understood to be more controlled, the other free and easy. With some custom dev work on the search side, it would probably be possible (definitely if you have FAST) to weight the more controlled field higher than the folksonomy field in search queries – thus providing the best combination of tagging and “searchability”.

The end user experience – web browser

Now that we have a managed metadata site column, when a user is tagging a document in an appropriate library they can either get a ‘type-ahead’ experience where suggestions will be derived from the allowed terms:

ManagedMetadataTypeAhead  

..or they can click the icon to the right and use a picker to select (e.g. if they don’t know the first letters to type):

ManagedMetadataPicker

The document is now tagged with an approved term from the taxonomy. Note that if the field allows fill-in choices (i.e. it’s a folksonomy field), this dialog has an extra ‘Add new item’ link for this purpose:

ManagedMetadataAddNewItem

The end user experience – Office 2010 client

Alternatively, content authors can tag metadata fields natively from within Office 2010 applications if they prefer. This can be done within the Document Information Panel, but also in the new Office Backstage view which I’m liking more and more. They get exactly the same rich experience – both type-ahead and the picker can be used just as in the browser:

OfficeBackstage

And it’s things like this which other implementations (e.g. vendor/custom) just typically do not provide.

So that’s the basics, onto some other aspects I discussed or demo’d.

Managed Metadata framework features

  • Synonyms – a term can have any number of synonyms. So if you want your authors to say, tag items with ‘SharePoint Foundation’ instead of ‘WSS’, you’d define the latter as a synonym of the former. In my television specifications demo, I added some phoney terms ‘Plasma Super’ and ‘Plasma Ultra’ to my preferred term of ‘Plasma’, and showed that in the user experience the synonyms show up (indented) in the type-ahead, but cannot actually be selected – the preferred term of ‘Plasma’ will always end up in the textbox:

    ManagedMetadataSynonymTypeAhead

    In case you’re curious as to equivalent picker experience, this shows synonyms in a ‘tooltip’ kind of way when you hover over the term.

  • Multi-lingual – for deployments in more than one language, the metadata framework fully supports the SharePoint 2010 MUI (Multi-lingual User Interface), meaning that if the translations have been defined, users can tag items in the language tied to the locale of the current web. The underlying association is the same as the value actually stored in the SharePoint field is partly made up of the ID.
  • Taxonomy management – as shown in the term store screenshot way above, terms can be copied, reused (so a term can exist in multiple locations in the taxonomy tree without being a duplicate i.e. in a ‘polyhierarchy’ fashion – a common requirement for some clients), deprecated (so no new assignments of the term can occur), merged and moved etc. In short, the types of operation you’d expect to need at various times.
    • I’d add a note that these are possible against terms in the taxonomy – the parent node types of term set and group (in ascending order) logically don’t have the same options, so if you make the beginner’s mistake of creating a term set when you really wanted a term with a hierarchy of child terms underneath, you have some retyping to do as you can’t restructure by demoting a term set to a term.  The key is simply understanding the different node types and ideally having more brain cells than I do.
  • Descriptions – minor point, but big deal. Add a description to a term to provide a message to users (in a tooltip) about when and how to use a term. This can be used to disambiguate terms  or otherwise guide the user e.g. “This tag should only be used for Sony, not Sony Bravia models”.
  • Delegation/security – permissions to manage the taxonomy are defined at the group level (top-level node), so if you wish to have different departments managing different areas of the tree, you can do this if you create separate groups.  Related to this, each term set can be allocated a different owner and set of stakeholders – this isn’t security partitioning, but does provide a place to specify who is responsible and who should be informed of changes at this level (in a RACI kind of way).
  • User feedback – if the term set has a contact e-mail address defined, a ‘Send feedback’ mailto link appears in the term picker, thus providing a low-tech but potentially effective way of users suggesting terms or providing feedback on existing terms.
  • Social – a user’s tagging activity will be shown in their activity feed

No doubt I’ve missed some – add a comment if any spring to mind please!
 

Extending the metadata framework – adding approval

So there are some great features in the framework, but one thing that seems to be ‘missing’ is the idea of being able to approve terms before they make it into the central taxonomy. So perhaps we want to allow regular users to add terms into the taxonomy quite easily, but only if they are approved by a certain user/group – this would give a nice balance between a centrally-controlled taxonomy and a true folksonomy. I put the word ‘missing’ in quotes just now because quite frankly, it’s pretty trivial to build such a thing based on a SharePoint list and that’s just what I did in my talk. I’m sure more thought would need to go into it for production, but probably not much more.

All we really need is to set up a list somewhere, add some columns, and add an event receiver. Adding an item to my list looked like this – I need to specify the term to add and also the parent term to add it under (using a managed keywords column mapped to the base of my taxonomy, meaning terms can be added anywhere):

TermToBeApproved

Then I just need some event receiver code to detect when an item is approved, and then add it to the term store:

   1: public class TaxonomyItemReceiver : SPItemEventReceiver
   2: {
   3:    public override void ItemUpdated(SPItemEventProperties properties)
   4:    {
   5:        if (properties.ListItem["Approval Status"].ToString() == "0")
   6:        {
   7:            string newTerm = properties.ListItem.Title;
   8:            TaxonomyFieldValue parentTerm = properties.ListItem["Parent term"] as TaxonomyFieldValue;
   9:  
  10:            TaxonomySession session = new TaxonomySession(properties.Web.Site);
  11:            TermStore mainTermStore = session.TermStores[0];
  12:            Term foundTerm = session.GetTerm(new Guid(parentTerm.TermGuid));
  13:            Term addedTerm = foundTerm.CreateTerm(newTerm, session.TermStores[0].DefaultLanguage);
  14:            mainTermStore.CommitAll();
  15:        }
  16:     
  17:        base.ItemUpdated(properties);
  18:    }
  19: }


My code simply finds the term specified in the ‘Parent term’ column, then adds the new term using Term.CreateTerm() in Microsoft.SharePoint.Taxonomy. Note the use of the TaxonomyFieldValue wrapper class – this is just like the SPFieldLookupValue class you may have used for lookup fields, as terms are stored in the same format with both an ID and label so this class wraps and provides properties.

Once this code has run, the term has been added to the store and is available for use throughout the organization – perhaps the best of both worlds. Amusingly, when we got to the “soooo, did it work?” bit in my talk the demo gods mocked me and the type-ahead on the term picker waited a full 10 seconds before the term came in, leading to a big “ooof……[pause]…..woohoo!” from the audience which capped off a hugely fun talk (for me at least).


Next time: other ECM enhancements such as Enterprise Content Types, Content Organizer, Scalability etc.

Speaking on SP 2010 at UK user group – ECM Enhancements

I’m excited to be giving the London/SE England SharePoint user group’s first SharePoint 2010 presentation in a couple of weeks, woohoo! It’s great to be able to talk/demo publicly on SP 2010 at last. The topic is a fairly general one – ‘Enterprise Content Management Enhancements in SharePoint 2010’, so I’m hoping there’s something in there for devs, admins, PMs, end-users alike. Here’s the abstract:

Session 2 – ECM Enhancements in SharePoint 2010 – Chris O’Brien

Enterprise Content Management is SharePoint’s bedrock. The 2010 release adds significant new capabilities in all ECM pillars (Document Management, Web Content Management and Records Management), and Microsoft have addressed some of the top ECM pain points reported by clients in SharePoint 2007. This session looks at the improvements in user experience, scalability and taxonomy, with demos showing how to leverage new features such as Enterprise Content Types, the Managed Metadata field and Content Organizer to better manage your documents and/or pages. Specific enhancements in WCM and RM will also be discussed. This session is a great way to kick-start your awareness of SharePoint 2010, and questions on other aspects of the new version are welcome in the Q & A wrap-up at the end (or the pub!)

If there’s time I’ll also show a simple example of extending the new metadata capability with code – it’s a good excuse to show the new SharePoint/Visual Studio 2010 integration and deployment process if nothing else!

Also, if this session isn’t enough there’s another thought-provoking session if you’re interested in accessibility:

Session 1 – Developing an Accessible SharePoint System – Martin Hatch

  • What is accessibility?
  • Presenting “SAS”: our unique SharePoint Accessibility Solution presenting the functionality that is delivered by the platform.
  • Challenges and major hurdles
  • Technical approach
    • AAA XHTML rendering
    • Accessible Content Management
    • Web Part Editing
    • List views, toolbars and menus
    • Back-end administration
  • Roadmap
  • Questions

There are currently already 88 people registered, and we have room for many more. The event is being held on Wednesday 25th November at Microsoft’s Victoria office in London:

Arrive 18:00 – 18:30 as the first session will start at 1830 sharp.

Microsoft London (Cardinal Place)
100 Victoria Street
London SW1E 5JL
Tel: 0844 800 2400

To register, simply leave your name at this thread over on the SUGUK website – http://suguk.org/forums/1/21214/ShowThread.aspx 

P.S. For user group newbies, all our events are always free.

SP2010 – developer skills preparation

So either you went to the SharePoint Conference last week, or you didn’t go but have seen the blog articles/tweets about various pieces of new functionality in SharePoint 2010. But ya can’t get your hands on the bits until late November when the public beta is released – so what’s a poor developer supposed to do? Well, lots it turns out. Most SharePoint developers are going to need to learn some new skills if you want to hit the ground running on your first SharePoint 2010 project – this post highlights some “generic” (by which I mean non-SharePoint!) topics and techniques which will be useful. This list is primarily a list I made for myself so your priorities might be different, but it’s really what I’d want my first SP2010 project team-mates (whoever they may be!) to be looking at now.

Note that technically, all of these skills and techniques listed here are optional – if you prefer to do things the 2007 way, generally all those approaches will still work. However, if you’re interested in taking advantage of new platform features (and you should be), this list is for you.

LINQ

Call me a code monkey, but possibly *the* most exciting developer feature for me in SharePoint 2010 is LINQ. If you haven’t used LINQ before with other data sources (e.g. SQL, XML, objects), it basically provides a consistent, strongly-typed way of dealing with entities in your data. In SharePoint’s case, this means webs, lists and list items, so we have the option of moving away from code like this:

   1: using (SPSite site = new SPSite("http://cob.spfoundation.dev")
   2: {
   3:     using (SPWeb financeWeb = site.OpenWeb("/finance"))
   4:     {
   5:         SPList announcementsList = financeWeb.Lists["Announcements"];
   6:         foreach (SPListItem announcementItem in announcementsList.Items)
   7:         {
   8:             DateTime expires = DateTime.MinValue;
   9:             if (DateTime.TryParse(announcement["Expires"].ToString(), out expires))
  10:             {
  11:                 // we finally got the value..
  12:             }
  13:         }
  14:     }
  15: }


To something like this:

   1: using (FinanceWebDataContext financeWeb = new FinanceWebDataContext("http://cob.spfoundation.dev/finance"))
   2: {
   3:     EntityList<Announcement> announcements = financeWeb.Announcements;
   4:     foreach (Announcement announcement in announcements)
   5:     {
   6:         DateTime expires = announcement.Expires;
   7:     }
   8: }

In effect, LINQ provides your data access abstraction for free. In addition, other data access scenarios such as inserting/updating list items and querying lists (think SPSiteDataQuery or SPQuery) will also be simplified. If you don’t already, my advice would be to start to understand fundamental aspects of how LINQ works (e.g. deferred execution) now.

Recommended reading:


jQuery

Another innovation in SharePoint 2010 is the client object model, which allows us to work with SharePoint data on the client (e.g. in JavaScript or Silverlight) in much the same way as we’re used to in .Net code on the server. In the case of JavaScript, jQuery will be useful here (though certainly not mandatory) because of the script which will frequently surround your use of the client OM. Put simply, whatever kind of objects you’re using in JavaScript, if you’re interacting with page elements jQuery is likely to reduce and simplify the code required.

Recommended reading (a book I read in this case, though I’m sure great online guides exist too):


PowerShell 

I’d probably been avoided learning PowerShell to date, partly because there always seemed to be another way to do things which wasn’t too bad. However, it’s clear PowerShell will play a greater role in SharePoint than previously courtesy of the many cmdlets included in the product – certainly admins who are script-inclined are likely to be very happy. The draw here is the sheer power – PowerShell is known for being able call into the .Net framework (and your own custom code), but is also capable of dealing with the filesystem, registry etc. This means that PowerShell can be used for a variety of SharePoint related tasks – scripted installations, configuration scripts, site provisioning/updates and more.

Recommended reading:


Silverlight

Whilst it wouldn’t necessarily have been difficult for skilled developers to build a custom web part to render Silverlight movies, it’s an indication of Microsoft’s desire to make it easy to build rich sites that Silverlight web parts are included in the SharePoint 2010 box. Having struggled with Flash movies which read from XML from other teams in the past, having this kind of technology on the “Microsoft developer” side of the fence is pretty appealing. If you have the skills you’ll be able to build web parts with killer user interfaces, and assuming Silverlight is available to your audience the whole SharePoint world will be able to use them.

Recommended reading:


FAST search

You’ve probably heard that SharePoint Server 2010 has native integration with FAST search technology. If you’ve seen the capabilities of FAST and/or had conversations with clients about going beyond a ‘standard’ implementation of search, you’ll know how exciting this is. On my last project I worked with some guys from FAST on a proof-of-concept with extremely custom integration between SharePoint/FAST, so it’s great to see the barrier to entry being lowered here. For many, seeing the art of the possible in this space is a real eye-opener – often you don’t realise what you’ve been missing until you see it. On this one, my main recommendation at this stage is solely to introduce yourself to the concepts used by FAST such as the document processing and query pipelines, dictionaries, entity extraction and so on.

Recommended reading:


WCF

This one makes my list for two reasons – partly because many of SharePoint’s own web services are now WCF services, but also because if you ever want to build a service application in SharePoint 2010, you’ll need to do WCF work. In general terms, it’s good advice that all new .Net web services should be built using WCF (.svc) rather than as .asmx web services anyway.

Recommended reading:


Summary

Although there will clearly be many new things to learn, the good news is many of the techniques you became comfortable with in SharePoint 2007 will still be applicable. The list above focuses on technologies which can play a part with SharePoint 2010, and if you’re anything like me you may want to spend some time with them before you get your hands on SharePoint 2010.

My favorite SharePoint 2007 development techniques (with an eye on SP2010)

As we home in on the release of SharePoint 2010, I wanted to write down a couple of thoughts for posterity on SharePoint 2007 development, mainly for my benefit. One of the reasons for doing this is because I’ve been working with the SP2010/VS2010 Tech Previews recently, and whilst I’ve not done a full “compare and contrast” exercise, I can certainly see that in the future I will want to reference back to how I liked to handle something in the SharePoint 2007 world, and more importantly, why. My experience is that transitioning to a new platform brings on a certain amnesia, where for some reason it’s difficult to remember just how the equivalent thing worked in the last version (CMS2002 anyone?) – undoubtedly we need to avoid restricting our thinking with irrelevant practices and constraints, but sometimes the old way is definitely useful as a reference point.

This isn’t a comprehensive list – many of my points below came out of a “developer sunset review” of my last project (special thanks to ex team-mate Jaap Vossers for some of the ideas/discussions we had around this stuff). Some techniques are in there because we used them and they worked great, others because we didn’t and I thought we suffered from not having them. A couple of others are just “things I’ve still not implemented but think would be the best option” – some of which could still be appropriate under SP2010. Many are what I believe to be the established “baseline approach” by many teams implementing SharePoint 2007 – it’s perhaps stretching it to say “best practice” for some, so I won’t. Even so, I’m *sure* so folks will have different views/techniques, these are the ones I wanted to capture – by all means leave a comment or point me to something else if you have better thoughts:


Visual Studio solution/project structure

  • Every VS project which deploys to the 12 folder contains a 12 folder structure 
  • Use the “class library” project template to start with a clean base – edit the .csproj file to add back menu options for ‘Add user control’ 
  • WSPBuilder as WSP-generation tool of choice
  • One main ‘consolidation’ project which is used to generate a single .wsp (where appropriate) – usually this is my [Company].[Client].Project.Web project
  • Use post-build command on each project to XCOPY 12 hive files into the consolidation project, so that avoid having one .wsp for each VS project – fewer .wsps are preferred to reduce versioning/dependency issues
  • User controls – for publishing sites, consider implementing HTML markup in user controls, not page layouts (as discussed in my Top 5 WCM tips post and by Waldek in Leveraging ASP.NET User Controls in SharePoint development)
  • User controls (if not using the above technique) – to get design surface, consider using a separate web project for initial development of user controls, then either using post-build events to copy the .ascx to your main project or using the ‘Add as link’ technique. (As far as I remember this is the only way to have a functioning design surface for user controls?)
    • Remember that many .ascx artifacts cannot exist in a subfolder of CONTROLTEMPLATES (e.g. custom field controls), they must be at the root for SharePoint to load them
  • Use Visual Studio post-build events to re-GAC assemblies and copy 12 folder – so that default action on compile is the “quick deploy” option. This happens so often in dev that I’d rather have any other option require an explicit action on my part, we since rarely want to compile but NOT load new assemblies
  • Consider creating custom VS build types e.g. “DebugNoDeploy”, “ReleaseNoDeploy”
    • Additionally, create a build type to re-provision your site in dev (if this step happens frequently in development of your implementation)
  • Leverage custom VS tool options where appropriate (e.g. “Tools > MyScript.bat”)
  • Re-bin is much faster than re-GAC (for code which can be tested outside of the GAC) – custom tool script or custom build type. This is useful for the highly-iterative part of development.

SharePoint coding tidbits

A selection of random thoughts I want to hold on to, as I think they’ll likely be relevant in the 2010 world:

  • The impact of app pool resets in dev should always be minimized by disabling the Certificate Revocation List check – I notice significant gains when doing this
  • Think carefully before storing data in SPWeb property bags – data stored here is not accessible to a query!
  • Use constants for SharePoint names, in particular field names
    • This is critical for consistency across project teams, and for providing name changes via Visual Studio refactoring
    • On balance, better to have separate DisplayName & InternalName constants
  • Logging – My preferred logging framework is the excellent log4net
    • If you have a requirement to log to a SharePoint list, creation of a custom log4net appender is the way to go. I haven’t done this yet, and bizarrely it seems no-one else has (publicly). Would be fairly trivial to do though
    • Fellow MVP Dave Mann pointed out to me that log4net causes problems when used in SharePoint workflows, as the logger class cannot be serialized when the workflow sleeps. It might be possible to mitigate this by not storing the logger as a private variable, but instantiating every time used (likely log4net returns same object, but performance unlikely to be critical in a workflow anyway)
  • Managing web.config modifications in dev (when you just don’t have time to SPWebConfigModification yet):
    • I don’t have a good story here – the best I’ve come up with is to have a ‘reference’ web.config stored in source control which can be used to sync changes between devs. As a sidenote, perhaps this issue can be avoided if the first coding week on the project lays down the plumbing code for SPWebConfigModification Feature Receivers as a “mandatory setup task”, so that it’s minimal friction when a new web.config change is required – otherwise I think it’s common to skip this and go into “technical debt” until such a time when the team can catch up on such things. And we all know what can happen there..

So whether you start to look at SharePoint 2010 immediately or your day job remains focused on 2007 for another year, I hope this list has been useful. Speaking personally, I know that in the interests of Kaizen (continual improvement), it will be illuminating to look back and see what’s still relevant and what isn’t. Looking forward, like many other MVPs this blog will now focus more on SP2010 going forward – I’ll most likely revisit some of this in view of my experiences with the VS2o1o Tools for SharePoint in the next couple of weeks (after the NDA is lifted). Stay tuned!