From the outside in

The blog of Iain Angus

Declaratively Provision Managed Metadata Column in SharePoint 2010

As part of a project I have been working on we wanted to assign categories to items in SharePoint 2010 and decided to use Managed Metadata. Wictor Wilén has a good post explaining what Managed Metadata is and how to set it up. Like Wictor, I also prefer to provision site columns and content types using a combination of declarative CAML and feature receivers. I followed the Wictors post but found he didn’t add the extra steps required to make the solution work.

I’ve outlined the steps we took to provision a managed metadata column to a site content type.

Step 1 - Create a TaxonomyField and a Note field

<?xml version="1.0" encoding="utf-8" ?>
<Elements xmlns="http://schemas.microsoft.com/sharepoint/">
  <Field ID="{087C759A-7BD2-4B66-9CF5-277A3399636D}"
    Type="TaxonomyFieldType"
    DisplayName="MMSCategories"
    ShowField="Term1033"
    Required="TRUE"
    EnforceUniqueValues="FALSE"
    Group="_Custom"
    StaticName="MMSCategories"
    Name="MMSCategories"
     />
  <Field ID="{437B0ED2-A31B-47F7-8C69-6B9DE2C4A4F6}"
    Type="Note"
    DisplayName="MMSCategories_0"
    StaticName="MMSCategoriesTaxHTField0"
    Name="MMSCategoriesTaxHTField0"
    ShowInViewForms="FALSE"
    Required="FALSE"
    Hidden="TRUE"
    CanToggleHidden="TRUE"
    />
</Elements>
Step 2 - Add the TaxonomyField and Note Field to a content type
<?xml version="1.0" encoding="utf-8"?>
<Elements xmlns="http://schemas.microsoft.com/sharepoint/">
  <!-- Parent ContentType: Item (0x01) -->
  <ContentType ID="0x0100aa33de693811427c886a5d27f17ed23d"
               Name="Taxonomy Spike - MMSContentType"
               Group="Custom Content Types"
               Description="My Content Type"
               Inherits="TRUE"
               Version="0">
    <FieldRefs>
      <FieldRef ID="{437B0ED2-A31B-47F7-8C69-6B9DE2C4A4F6}" Name="MMSCategoriesTaxHTField0"/>
      <FieldRef ID="{087C759A-7BD2-4B66-9CF5-277A3399636D}" Name="MMSCategories"/>
    </FieldRefs>
  </ContentType>
</Elements>

Step 3 - Wire up the Note field to the TaxonomyField using a feature receiver

The TaxonomyField has a member called TextField, with the following remark on the MSDN page…

“Every TaxonomyField object contains a related hidden text field that contains a string representation of the taxonomy field value. The hidden text field is identified by the GUID returned by this property.”

…so as well as the defining the Taxonomy Field we also need to define something to store the string representation.

   1:  public override void FeatureActivated(SPFeatureReceiverProperties properties)
   2:  {
   3:      SPSite site = properties.Feature.Parent as SPSite;
   4:      Guid fieldId = new Guid("{087C759A-7BD2-4B66-9CF5-277A3399636D}");
   5:      if (site.RootWeb.Fields.Contains(fieldId))
   6:      {
   7:          TaxonomySession session = new TaxonomySession(site);
   8:   
   9:          if (session.TermStores.Count != 0)
  10:          {
  11:              var termStore = session.TermStores["Managed Metadata Service"];
  12:              var group = termStore.Groups["Test Store"];
  13:              var termSet = group.TermSets["Categories"];
  14:              TaxonomyField field = site.RootWeb.Fields[fieldId] as TaxonomyField;
  15:              // Connect to MMS 
  16:              field.SspId = termSet.TermStore.Id;
  17:              field.TermSetId = termSet.Id;
  18:              field.TargetTemplate = string.Empty;
  19:              field.AnchorId = Guid.Empty;
  20:              field.TextField = new Guid("{437B0ED2-A31B-47F7-8C69-6B9DE2C4A4F6}");
  21:              field.Update(true);
  22:          }
  23:      }
  24:  }

Line 20 is the important one, this is the code that wires the Note field created declaratively to the TaxonomyField.

comexception (0x80004005): cannot complete this action when deploying a SharePoint MasterPage as a feature

I've been working on deploying SharePoint MasterPages and PageLayouts as a feature. There are a lot of good posts out there on how to do this. Everything went ok, the feature deployed and activated fine, the page was approved and I could specify the masterpage to be used. I checked to see how the site looked and all was not well. The page blew and I had a nice comexception error:

    comexception (0x80004005): cannot complete this action

The strange thing is that the masterpage worked fine when manually uploaded to the masterpage gallery and only blew up when deployed as a feature. When searching the internet for comexception (0x80004005): cannot complete this action (what else do you do with COM exceptions and SharePoint?) I found information relating to identity impersonate, none of which was relevant the issue I was having.

I checked the application event logs and could see an ASP.NET 2.0.50727.0 warning message, with the event message "A parser error has occurred". Reading a bit further on into the event log entry I noticed "The type or namespace 'portal' does not exist in the namespace 'Microsoft.SharePoint' (are you missing an assembly reference?)". I checked the masterpage markup and noticed that a namespace was "Microsoft.SharePoint.portal.WebControls" when it should have been "Microsoft.SharePoint.Portal.WebControls".

In the end it was correcting a typo and paying good attention to what the event logs told me that saved the day.

Newcastle SharePoint User Group

I'm heading over to Newcastle for the SharePoint User Group tonight.

It's a whiteboard session on MOSS Architecture.

Newcastle SharePoint User Group

Hope to see you there if you are going.

 UPDATE....

Big thank you to John Timney (Cap Gemini), Brian English (Cap Gemini) and Phil Hunter (BT) for an excellent user group meeting. There was a mix of people from different industries, lots of round table discussion and good information shared.

I'm currently implementing an intranet/extranet MOSS architecture for 4500 users and found the whiteboard session invaluable.

Once again, many thanks.

SUBST, GAC and SharePoint

Working with SharePoint, sometimes you just have to install assemblies to the GAC. An example of this I have had to deal with recently was writing a webpart to access methods in the UserProfileManager class.

Debugging GAC installed assemblies can be a bit frustrating, hopefully the following steps will make it alot easier and obvious....

1. Install the assembly into the GAC

2. Map a drive using SUBST to %SYSTEMROOT%\assembly

SUBST maps a drive to a path - http://www.microsoft.com/technet/archive/msdos/07_refer.mspx?mfr=true 

A mapped drive does not use the Assembly Cache Viewer and will show you the true folder structure.

3. Locate and open the GAC_MSIL folder in the mapped drive

4. Locate and open the folder containing your assembly. The name of the folder is the same as the assembly name.

5. Copy the pdb (program database) file from your project build folder to your assembly folder in the GAC. Because you have mapped a drive to %SYSTEMROOT%\assembly you should be able to do this easily with Windows Explorer.

6. Do an iisreset /noforce (this is not really required but usually helps - general rule of thumb for working with SharePoint).

7. Go to Visual Studio and attach the debugger to the w3wp.exe (remember there may be more than one depending on how many app pools you have).

You should now be able to hit the breakpoints you have set.

Accessing User Profile Information in WSS 3.0

MOSS and WSS 3.0 handle user profiles differently. In MOSS, user profile information is stored centrally and can be shared across site collections. Profiles can be manipulated using the UserProfileManager class found in Microsoft.Office.Server.dll.

Life just isn't the same when working with WSS 3.0. When developing a workflow, for example, it may be useful to access a property of the user profile. You can't use UserProfileManager as it doesn't exist and SPUser doesn't expose most of the profile information. In WSS 3.0 the user profile information is stored in a hidden list specific to an individual site collection. If you have multiple site collections, a different hidden list is used for each site collection. The User Profile Information is just another list and can be treated as such -

SPList userProfileList = web.Lists["User Information List"];

foreach (SPListItem user in userProfileList.Items)
{
    Console.WriteLine(user["Name"] + " " + user["Department"];
}

The cut down C# above lists all the user names and departments.

Accessing the user profile information in WSS 3.0 is straightforward, once you know how ;-)

SharePoint User Group - Newcastle

Big thank you to Penny Coventry and Steve Smith for presenting at the SharePoint User Group UK Newcastle meeting on Wednesday night. Both were excellent sessions.

Penny gave an overview of SharePoint Designer, which if not already, will probably become your default tool for design and basic development of SharePoint sites.

Steve covered Forefront Security for SharePoint. There are not too many choices when it comes to anti-virus solutions for SharePoint. Some vendors have had a less than successful record in this area and Forefront looks like a very interesting solution. Forefront offers real time and manual, multi engine scanning, as well as keyword content filtering.

SharePoint 2007 &quot;Friendly&quot; Error Messages

SharePoint 2007 displays a nice "friendly" error message when anything goes wrong. This "friendly" error message usually provides no useful information about why the error has occurred (sometimes you get an HRESULT). It may be friendly but isn't usually very helpful, not for a developer.

You can alter this default behaviour by making two changes to the web.config for the site you are debugging.

CallStack is set to false. Change the CallStack setting to true, as shown below -

<SafeMode MaxControls="200" CallStack="true" DirectFileDependencies="10" TotalFileDependencies="50" AllowPageLevelTrace="false">

customErrors mode is set to On. Change the mode to Off, as shown below -

<customErrors mode="Off" />

Hey presto, helpful error messages. Now if I could only fix the error... ;-)