<CharlieDigital /> Programming, Politics, and uhh…pineapples

25Jul/11Off

jQuery parsererror and WCF Data Services

Posted by Charles Chen

If you're using WCF Data Services (REST APIs for SharePoint, for example) and you're getting the mysterious "parsererror" error message from jQuery, chances are you'll need to modify your scripts according to this bug report.

The root of the error is the occurrence of single quotes within your JSON response.  This can be fixed by adding the following snippet of code before you make your AJAX calls:

$.ajaxSetup({
    // use custom converter to handle invalid json data
    // returned by WCF Data Service
    // fixes invalid escaped single quotes (\') in json data
    // e.g. { "foo": "bar\'tender" } --> { "foo": "bar'tender" }
    converters: {
        "text json": function( textValue ) {
            return jQuery.parseJSON( textValue.replace(/(^|[^\\])\\'/g, "$1'") );
        }
    }
});

Oddly, this error seemingly came out of nowhere for me; script was working fine one day and broken the next...

Luckily, the patch in this ticket seems to have fixed it for me.

Filed under: jQuery, SharePoint No Comments
18Jul/11Off

Powershell, SharePoint Solutions, and Waiting for Deployment

Posted by Charles Chen

When installing SharePoint solution packages using Powershell, you'll need to wait for the solution to be installed after calling install-spsolution before you can enable features using enable-spfeature.  As a part of an automated script, you'd like for the script to know when the install is finished before continuing.

It's surprisingly easy and can be accomplished with only a tiny bit of code:

$sln = add-spsolution [YOUR_SOLUTION_PATH_HERE]

install-spsolution -identity [YOUR_SOLUTION_NAME_HERE] -gacdeployment

while($sln.JobExists)
{
    echo "Deployment in progress..."

    start-sleep -s 5
}

That's it!

On uninstall, the process is a little bit different as uninstall-spsolution does not return an instance of the solution.  You will want to grab the solution before you start the retraction:

$sln = get-spsolution -identity [YOUR_SOLUTION_IDENTITY]

uninstall-spsolution -identity [YOUR_SOLUTION_IDENTITY] -confirm:$false -webapplication:$webAppName

echo "Started solution retraction..."

while($sln.JobExists)
{
	echo " > Uninstall in progress..."
	start-sleep -s 5
}

remove-spsolution -identity [YOUR_SOLUTION_IDENTITY] -confirm:$false

This helps make for a much smoother install and uninstall process as you can be more certain that your solution is installed before executing code dependent on the solution (feature activation, solution removal, etc).

Filed under: SharePoint No Comments
14Jul/11Off

WCF Data Services Cheat Sheet

Posted by Charles Chen

I was working with some SharePoint REST queries and couldn't find a good list of the operations supported by the REST API.  Namely, I was trying to figure out if it supported a "contains" operation (it does using indexof).

I found a very nicely put together set of tables which shows the LINQ queries, the corresponding URL, and the SQL produced by the query.

This is the result of my tiny research about translating LINQ to REST into oData URI Conventions and finally into T-SQL. Event though I knew what I needed on the T-SQL side, I was not sure which LINQ statement would create the respective SQL query. So, as an experimental test bed I created a little console application and used Netflix oData Service to see how LINQ to REST gets translated to URIs

Very useful indeed.

On a related note, for those that are working with the REST APIs for SharePoint, I've found that you can actually reduce your network traffic when using $expand by taking only the fields that you're interested in for the expanded item as well. For example:

http://[site]/_vti_bin/listadata.svc/Tasks?$expand=AssignedTo&$select=AssignedTo/Name,AssignedTo/Account,Id,Title

The downside is that this will barf out invalid JSON if you have a null value for the expanded field (in this case, if the task is not assigned to any user).  Here's a sample output:

{
   "d":{
      "results":[
         {
            "__metadata":{
               "uri":"http://project.dev.com/PWA/ts1/_vti_bin/listdata.svc/Tasks(2)",
               "etag":"W/\"3\"",
               "type":"Microsoft.SharePoint.DataService.TasksItem"
            },
            "Id":2,
            "ContentType":"Task",
            "Title":"Action 1",
            "Path":"/PWA/ts1/Lists/Tasks/Milestone 1",
            "PriorityValue":"(2) Normal",
            "StatusValue":"Not Started",
            "AssignedTo":{
               "__metadata":{
                  "uri":"http://project.dev.com/PWA/ts1/_vti_bin/listdata.svc/UserInformationList(18)",
                  "etag":"W/\"7\"",
                  "type":"Microsoft.SharePoint.DataService.UserInformationListItem"
               },
               "Name":"Charles Chen",
               "Account":"DEV\\charles"
            },
            "StartDate":"\/Date(1310428800000)\/",
            "DueDate":"\/Date(1310601600000)\/"
         },
         {
            "error":{
               "code":"",
               "message":{
                  "lang":"en-US",
                  "value":"An error occurred while processing this request."
               }
            }
         }

You can see that the second item in the "results" array is where the task was not assigned to a user.  The response is abruptly cut off with the JSON being completely broken at that point and in an invalid state.  I would have expected that the result would just set "AssignedTo":null

A bummer, but it doesn't detract from my love for the REST APIs :-D

Filed under: Awesome, SharePoint, WCF 1 Comment
18May/11Off

Introducing GameTime – Real Time Collaboration for SharePoint

Posted by Charles Chen

Background

Since 2005, I've been thinking about building a web-based, real-time collaboration solution.  Back in those days, I had just discovered AJAX.NET (before ASP.NET provided an implementation of AJAX) and I had drawn up a design for a chat-centric collaboration platform.  My friend and co-worker Dan Chawner would sit in adjacent cubicles and exchange IMs over MSN Messenger as we worked on projects.  I thought: "wouldn't it be great if I could actually do things with these IMs instead of copying/pasting them?"

Over the years, that design languished as I moved on to other interests.  When I first saw Groove (before Microsoft purchased it) and Wave, it brought back memories of those designs.  Wave, in particular, was very close to what I had imagined building (albeit without all of the crazy in-line edits and what not).  I had kind of given up the idea after not being able to find any direction myself on how to make such a tool useful.

Experience

It turns out what I needed was more experience -- both technically and professionally -- to finally put it all together.  One thing that I've learned in the last few years of working with SharePoint is that it's generally a really cumbersome platform for collaboration when left to it's own devices.  It's great for:

  1. Storing documents
  2. Finding documents you've stored
  3. Storing lists of things
  4. ???

Everything else?  I guess it's kind of mediocre.

And yet, organizations -- multi-billion dollar organizations -- depend on SharePoint as a platform for collaboration, communication, sharing information, and in general, getting things done.

This is what experience has taught me as I sat through scrums watching folks update list items, as I dealt with the deluge of emails sent "Reply All" trying to figure out the status of tasks, and as I dealt with communicating effectively as a part of a team of remote consultants.

There are real inefficiencies when you try to use out-of-the-box SharePoint for scenarios which it was not designed and it's not a terribly useful platform for collaboration so much as it is for storage and retrieval of information (and even some would debate how well it's designed for those purposes....).

The question we set out to answer is how can we make the SharePoint platform more efficient for collaboration?  How can we help teams that work with remote members collaborate and communicate effectively?  How can we make SharePoint more than just a document and information repository?  How can we enable SharePoint to deliver notifications and updates in real-time?

Opportunity

Right before Christmas, my wife was put on strict bed rest at home carrying our daughter, Charlotte (she was deemed a high risk pregnancy as we've lost three other fetuses in two prior pregnancies).  At first, I considered taking the 6 week unpaid family leave.  But our due date was at the end of April; that would hardly get me through February with my vacation days.  I knew I had to quit and tough it out for at least these 4 months to make sure that we carried this baby to term.

This is when I finally put two-and-two together: I had to use this one opportunity to take a risk, go all-in and try to manifest this idea that I've been carrying around with me for years.

The Result

We've been "dogfooding" it for over a month now!

What came out of this process is GameTime, a real-time collaboration solution built on SharePoint and the same underlying technology in Google Wave, XMPP.  In one sentence?  It's Campfire for SharePoint.

At the core of GameTime is the concept of a "Huddle" where team members come together around a web-based chat interface.  But it's more than that; we've integrated it with SharePoint document libraries and lists to create a context for real-time collaborative efforts right in SharePoint.  Each Huddle is composed of collaborators, documents, milestones, and tasks -- the essentials of any collaborative effort and it's all wired up to react in real-time.

When a document is checked out in SharePoint, a real-time notification shows up in the chat stream and the document is updated in the Huddle.  When a new task is created and assigned, a real-time notification shows up in the chat stream and the task is added to the Huddle.  When a user comes into the Huddle, a real-time presence notification is sent and the user's status is updated immediately in the Huddle.

GameTime finally gives SharePoint users an actual reason to be in the SharePoint environment outside of point interactions (for example: trying to find a document); it gives SharePoint a central role in day-to-day collaboration instead of being just a storage repository that is called upon once in a while.  But even more importantly, perhaps, is that it adds a real-time element to SharePoint.  No more waiting for email notifications.  No more playing email-tag to get the status of tasks.  No more waiting for someone to check documents in/out.  You can see SharePoint activity in real-time right from your Huddle.

This short demo video should give you an idea of the functionality and capabilities of the product (this video represents about 60% of the current functionality):

Now the Hard Part

It's taken the small team of John Peterson (and his alter ego "Tyrone Engels") and myself nearly 4 months of work to get GameTime to this point and just this week, we've started our first AdWords campaign -- a great milestone.  The challenge of spreading the word and getting our first sale is now before us so indulge me with this shameless plug!

If your organization runs SharePoint 2010 (Server or Foundation) and you're interested in trying out that real-timey goodness of GameTime, fill out our contact form and get your first 10 licenses, free.  You can also use the form to schedule a live demo in our hosted environment.  I truly believe that you'll be sold once you experience it, live.

The Future

While we're focused on getting our first sale, we've started to plan for upcoming tradeshows and we've started to develop our next set of features.  These include:

  • Higher level, real-time dashboards built off of the same platform
  • Mobile integration for Android, Blackberries, iPhones, and Windows Phone
  • Chat and real-time notifications everywhere in the SharePoint environment -- get immediate notification of changes anywhere you are SharePoint.

So head over to our web site: http://thinktastic.com and contact us to get a fully featured trial license!

Filed under: News, SharePoint, XMPP No Comments
3May/11Off

SharePoint Content Type Lifecycle Management

Posted by Charles Chen

That's just a fancy term for updating content types via feature upgrades.

In SharePoint 2010, there's a new UpgradeActions element and a handy new AddContentTypeField element as well.

There are a couple of good blog posts that discuss this topic and an unusually useful MSDN article on the topic of application lifecycle management in SharePoint 2010.  These more than cover the topic of adding new fields to existing content types via upgrades.

One key point from this article is the following section of text:

This section describes at a high level how you can put these feature-versioning and upgrading capabilities to work. When you create a new version of a feature that is already deployed on a large SharePoint 2010 farm, you must consider two different scenarios: what happens when the feature is activated on a new site and what happens on sites where the feature already exists. When you add new content to the feature, you must first update all of the existing definitions and include instructions for upgrading the feature where it is already deployed.

For example, perhaps you have developed a content type to which you must add a custom site column named City. You do this in the following way:

  1. Add a new element file to the feature. This element file defines the new site column and modifies the Feature.xml file to include the element file.
  2. Update the existing definition of the content type in the existing feature element file. This update will apply to all sites where the feature is newly deployed and activated.
  3. Define the required upgrade actions for the existing sites. In this case, you must ensure that the newly added element file for the additional site column is deployed and that the new site column is associated with the existing content types. To achieve these two objectives, you add the ApplyFeatureManifests and the AddContentTypeField upgrade actions to your Feature.xml file.

It would seem that this would be a great mechanism for managing your metadata upgrades.  As bullet number 2 implies, one simply adds the new field to your existing element manifest file for new installs and add a new element manifest file plus a AddContentTypeField element to your feature XML for upgrades.

After trying this out for almost two days now, I can say with some certainty that this does not work as documented or designed.  You cannot add the field to both your original element manifest XML (for new installs) and to a separate element manifest file for upgrades.  It does not work.  After performing an upgrade, the symptom is that the field does not get pushed down to the list level content types that inherit from the site level content type.  If you go to add the field manually, you will see the field appear twice:

You can see the result of two passes of testing here.  At the site level, the content type is fine and dandy; it's only at the list level where things go wrong.  Obviously, this is less than ideal.  I tried numerous variations of feature setup to see if I could get it to work without code and none of them worked.

Ultimately, the solution that worked for me was to add a CustomUpgradeAction as well in addition to the AddContentTypeField.

The upgrade action has the following code in it:

// Delete the field link from the content type at the site level.
contentType.FieldLinks.Delete(fieldId);

contentType.Update(true);

// Add it again and force it to push down.
contentType.FieldLinks.Add(fieldLink);

contentType.Update(true);

It's a lot of redundancy, but ultimately, the only way I could get the upgrade to work with the field in both the original element manifest (desired for new installs) and in a new element manifest for upgrades.  I should note that if you do NOT add the field to the original element manifest, everything works as documented.  But this is a less desirable scenario as it means that every clean install would also require all of the updates to be run afterwards.

Here is the full listing

My feature.xml file:

<?xml version="1.0" encoding="utf-8"?>

<Feature Id="0C4C1210-B056-4AFC-B556-A8BB30E1A9F1"
         Title="Zaang Labs GameTime Content Types"
         Description="Installs the content types used by Zaang Labs GameTime."
         Hidden="FALSE"
         Scope="Site"
         DefaultResourceFile="core"
         ReceiverAssembly="ZaangLabs.Server.Framework, Version=1.0.0.0, Culture=neutral, PublicKeyToken=4df39f66bb4e1d17"
         ReceiverClass="ZaangLabs.Server.Framework.Components.FeatureReceivers.MetadataPrimer"
         Version="1.0.0.1"
         xmlns="http://schemas.microsoft.com/sharepoint/">
    <ElementManifests>
        <ElementManifest Location="zl_gametime_contenttypes.xml" />
    </ElementManifests>
    <Properties>
        <Property Key="zaanglabs.prime.if.contains" Value="ZaangLabs,Zaang Labs"/>
        <Property Key="zaanglabs.mark.for.removal" Value="GameTime Configuration,GameTime Huddles"/>
        <Property Key="zaanglabs.config.assembly" Value="ZaangLabs.GameTime.Core, Version=1.0.0.0, Culture=neutral, PublicKeyToken=4df39f66bb4e1d17"/>
        <Property Key="zaanglabs.config.resource.path" Value="ZaangLabs.GameTime.Core.zaanglabs.custom.web.config.xml"/>
    </Properties>
    <UpgradeActions
        ReceiverAssembly="ZaangLabs.Server.Framework, Version=1.0.0.0, Culture=neutral, PublicKeyToken=4df39f66bb4e1d17"
        ReceiverClass="ZaangLabs.Server.Framework.Components.FeatureReceivers.ContentTypeUpdater">
        <VersionRange BeginVersion="1.0.0.0" EndVersion="1.0.0.1">
            <ApplyElementManifests>
                <ElementManifest Location="zl_upgrade_1_0_0_1.xml"/>
            </ApplyElementManifests>
            <AddContentTypeField ContentTypeId="0x01002167C000000000000000000000000002" FieldId="{0bc82141-5fa4-4948-b7b9-888f10010020}" PushDown="TRUE" />
            <CustomUpgradeAction Name="AddFields">
                <Parameters>
                    <Parameter Name="add.field.1">0x01002167C000000000000000000000000002,{0bc82141-5fa4-4948-b7b9-888f10010020}</Parameter>
                </Parameters>
            </CustomUpgradeAction>
        </VersionRange>
    </UpgradeActions>
</Feature>

A snippet of the original element manifest with the new field added to the content type for new deployments:

<!-- Other fields omitted -->
<Field DisplayName="Share Milestones With"
    Name="Share_Milestones_With"
    StaticName="Share_Milestones_With"
    ID="{0bc82141-5fa4-4948-b7b9-888f10010020}"
    Description="Leave this blank to use a new set of milestones for this Huddle.  Otherwise, select an existing Huddle to share Milestones with."
    Type="Lookup"
    ShowField="Title"
    List="Self"
    SourceID="schema.zaanglabs"
    Group="Zaang Labs" />
<ContentType Name="Huddle"
    ID="0x01002167C000000000000000000000000002"
    Description="Zaang Labs Huddle."
    Group="Zaang Labs">
    <FieldRefs>
        <FieldRef ID="{0BC82141-5FA4-4948-B7B9-888F00000010}" Name="Huddle_Description" Required="TRUE" />
        <FieldRef ID="{0BC82141-5FA4-4948-B7B9-888F00000011}" Name="Huddle_Collaborators" Required="TRUE" />
        <FieldRef ID="{0BC82141-5FA4-4948-B7B9-888F00000012}" Name="Huddle_Documents" Required="FALSE" ShowInNewForm="FALSE" ShowInEditForm="FALSE"/>
        <FieldRef ID="{0BC82141-5FA4-4948-B7B9-888F00000013}" Name="Huddle_Milestones" Required="FALSE" ShowInNewForm="FALSE" ShowInEditForm="FALSE" />
        <FieldRef ID="{0BC82141-5FA4-4948-B7B9-888F00000014}" Name="Huddle_Tasks" Required="FALSE" ShowInNewForm="FALSE" ShowInEditForm="FALSE" />
        <FieldRef ID="{0BC82141-5FA4-4948-B7B9-888F00000015}" Name="Remove_Artifacts" />
        <FieldRef ID="{0BC82141-5FA4-4948-B7B9-888F00000016}" Name="Huddle_Documents_List_ID" />
        <FieldRef ID="{0BC82141-5FA4-4948-B7B9-888F00000017}" Name="Huddle_Tasks_List_ID" />
        <FieldRef ID="{0BC82141-5FA4-4948-B7B9-888F00000018}" Name="Huddle_Milestones_List_ID" />
        <FieldRef ID="{0BC82141-5FA4-4948-B7B9-888F00000019}" Name="Huddle_Owner" ShowInNewForm="FALSE" ShowInEditForm="FALSE"/>
        <FieldRef ID="{0bc82141-5fa4-4948-b7b9-888f10010020}" Name="Share_Milestones_With"/>
    </FieldRefs>
    <!-- Omitted -->
</ContentType>

The new upgrade element manifest:

<?xml version="1.0" encoding="utf-8" ?>
<Elements xmlns="http://schemas.microsoft.com/sharepoint/">
    <Field DisplayName="Share Milestones With"
        Name="Share_Milestones_With"
        StaticName="Share_Milestones_With"
        ID="{0bc82141-5fa4-4948-b7b9-888f10010020}"
        Description="Leave this blank to use a new set of milestones for this Huddle.  Otherwise, select an existing Huddle to share Milestones with."
        Type="Lookup"
        ShowField="Title"
        List="Self"
        SourceID="schema.zaanglabs"
        Group="Zaang Labs" />
</Elements>

And the custom feature receiver (with portions omitted):

using System;
using System.Collections.Generic;
using Microsoft.SharePoint;
using ZaangLabs.Server.Framework.Components.Deployment;

namespace ZaangLabs.Server.Framework.Components.FeatureReceivers
{
    /// <summary>
    ///     Updates
    /// </summary>
    public class ContentTypeUpdater : FeatureReceiverBase
    {
        /// <summary>
        ///     Triggered when a feature is updated.
        /// </summary>
        /// <param name = "properties">The properties.</param>
        /// <param name = "upgradeActionName">Name of the upgrade action.</param>
        /// <param name = "parameters">The parameters.</param>
        public override void FeatureUpgrading(SPFeatureReceiverProperties properties, string upgradeActionName, IDictionary<string, string> parameters)
        {
            if(upgradeActionName.ToUpperInvariant() != "ADDFIELDS")
            {
                base.FeatureUpgrading(properties, upgradeActionName, parameters);
                return;
            }

            try
            {
                SPSite site = GetSite(properties);

                using (SPWeb web = site.RootWeb)
                {
                    web.AllowUnsafeUpdates = true;

                    foreach (string key in parameters.Keys)
                    {
                        // Iterate and split each content type, field value.
                        string value = parameters[key];

                        string[] parts = value.Split(',');

                        SPContentTypeId contentTypeId = new SPContentTypeId(parts[0]);
                        Guid fieldId = new Guid(parts[1]);

                        SPField field = web.Fields[fieldId];
                        SPFieldLink fieldLink = new SPFieldLink(field);
                        SPContentType contentType = web.ContentTypes[contentTypeId];

                        Log("Adding field \"{0}\" to content type \"{1}\".", field.Title, contentType.Name);

                        contentType.FieldLinks.Delete(fieldId);

                        contentType.Update(true);

                        contentType.FieldLinks.Add(fieldLink);

                        contentType.Update(true);
                    }

                    web.AllowUnsafeUpdates = false;
                }
            }
            catch(Exception exception)
            {
                Log("Failed to execute the custom upgrade action: {0}", exception);
            }

            base.FeatureUpgrading(properties, upgradeActionName, parameters);
        }
    }
}
Filed under: SharePoint No Comments
23Apr/11Off

Updating User Fields via ListData.svc

Posted by Charles Chen

Just putting this out there since I didn't find any info out there.  If you're using the ADO.NET REST data services to update user fields, you can't just set it like so:

{"AssignedTo" : "3;#Charles Chen"} // No good!
{"AssignedTo" : "DEV\\cchen"} // Nope!
{"AssignedTo" : {"Account" : "DEV\\cchen"}} // Nope!

You have to do it using a reference to the users list:

// Put the integer ID of the user in the ( )
{"AssignedTo" : {"__metadata" : {"uri" : "UserInformationList(2)"}}}

Not very developer friendly, it seems.

On the topic of the SharePoint REST services, Justin Lee has a great post that gives a quick overview of the core functionality.

Filed under: SharePoint No Comments
7Mar/11Off

Deep Insight on SharePoint Event Receivers

Posted by Charles Chen

Not mine, but came across an excellent post by Hristo Pavlov (who unfortunately, no longer works in the SharePoint space).

It goes deep into the bowels of the threading model and the considerations one should have when designing custom code in event receivers.  Just what I was looking for and handy to file in your mental file cabinet for future reference.

On a side note, I love his last post:

I am moving away from SharePoint and consulting into serious custom software development and this is my last blog post here. I wish all the best to all of you that remain in the SharePoint world.

Filed under: SharePoint No Comments
4Mar/11Off

Getting Version Changes From SharePoint

Posted by Charles Chen

SharePoint doesn't provide a straightforward way to do this it seems.  My first crack was to try to leverage the BeforeProperties and AfterProperties in an event receiver, but it turns out that this doesn't work quite as you would expect based on the naming of these two properties (and the same goes for ChangedProperties).

However, clearly we can see that the version history contains the changes in the fields from one version to the next.

Here's some basic code to identify the fields that changed from one version to the next:

using (SPSite site = new SPSite("http://my.domain.com/"))
using (SPWeb web = site.OpenWeb())
{
    SPList list = web.GetList("http://my.domain.com/Lists/Stuff");
    SPListItem item = list.Items[1];

    Console.Out.WriteLine(item.Title);

    SPListItemVersion currentVersion = item.Versions[0];
    SPListItemVersion previousVersion = item.Versions.Count > 1 ? item.Versions[1] : null;

    Console.Out.WriteLine("Version is current: {0}", currentVersion.IsCurrentVersion);

    foreach (SPField field in currentVersion.Fields)
    {
        if (field.ShowInVersionHistory == false)
        {
            continue;
        }

        if (previousVersion == null)
        {
            Console.Out.WriteLine("  > {0} changed to \"{1}\"",
                field.StaticName, currentVersion[field.StaticName]);
            continue;
        }

        if (currentVersion[field.StaticName].Equals(previousVersion[field.StaticName]))
        {
            continue;
        }

        Console.Out.WriteLine("  > {0} changed from \"{1}\" to \"{2}\"",
            field.StaticName, previousVersion[field.StaticName], currentVersion[field.StaticName]);
    }
}

This will get you the changes between the current version and the previous version.  If it's a new object, all of the changes will be returned.

Filed under: .Net, SharePoint No Comments
18Feb/11Off

Most Annoying Thing About SharePoint 2010?

Posted by Charles Chen

I've been bashing my head against a SharePoint 2010 solution package for almost 10 hours now across two days.  I mean god forbid Microsoft provides us poor developers with some useful error messages or even a hint of what's going on inside that crazy contraption.

It seems that other's have also encountered this problem, but I'll summarize here: in SharePoint 2007, when you create a custom list template and list definition, you could associated a custom content type with the list quite easily (or so I thought...more on this in a sec).  Here's an example from my schema.xml file:

<?xml version="1.0" encoding="utf-8"?>
<List xmlns:ows="Microsoft SharePoint"
	Title="Basic List" FolderCreation="FALSE" Direction="$Resources:Direction;"
	Url="Lists/Basic List" BaseType="0" EnableContentTypes="TRUE">
	<MetaData>
		<ContentTypes>
			<ContentTypeRef ID="0x0100C171C000000000000000000000000003"/>
			<ContentTypeRef ID="0x0120" />
		</ContentTypes>

<!-- additional markup omitted -->

I can then use the content type fields in the views without having add the fields or field references separately:

<ViewFields>
	<FieldRef Name="LinkTitleNoMenu"></FieldRef>
	<FieldRef Name="Request_Name" ></FieldRef>
	<FieldRef Name="Request_Status" ></FieldRef>
	<FieldRef Name="Requestor" ></FieldRef>
	<FieldRef Name="Ticket_Project_Number" ></FieldRef>
	<FieldRef Name="Ticket_Group" ></FieldRef>
	<FieldRef Name="Support_Ticket_Number" ></FieldRef>
	<FieldRef Name="Support_Ticket_Name" ></FieldRef>
	<FieldRef Name="Support_Ticket_Status" ></FieldRef>
	<FieldRef Name="Support_Ticket_Res_Date" ></FieldRef>
	<FieldRef Name="Technical_Manager" ></FieldRef>
</ViewFields>

In SharePoint 2010, this doesn't work. Let me rephrase this: it doesn't work 100% correctly.  The default behavior seems to be that the content type will be copied over, but the fields aren't copied over...

Well in fact, Microsoft's documentation states that this is as designed:

When SharePoint Foundation creates a list instance, it includes only those columns that are declared in the base type schema of the list or in the list schema. If you reference a site content type in the list schema, and that content type references site columns that are not included in the base type schema of the list or in the list schema, those columns are not included. You must declare those columns in the list schema for SharePoint Foundation to include them on the list.

What you'll get is the content type will indeed be on the list, but the fields won't be copied over and any views that use those fields will be borked.  This is a really annoying problem.  Really, it is.

The solution is to either build some tool that does an XSL transform on my content types and produces the views (thereby avoiding the necessity of copying the fields over manually) or write some code to fix it after the list has deployed.  But it's problematic because I want to deploy data with my list instance using markup like so:

<?xml version="1.0" encoding="utf-8" ?>
<Elements xmlns="http://schemas.microsoft.com/sharepoint/">
    <ListInstance
		Title="My Custom Configuration"
		Id="18000"
		FeatureId="ACA62A34-7B27-442D-97C6-55FC3F55A2BF"
		TemplateType="18000"
		Url="Lists/MyCustomConfiguration"
        OnQuickLaunch="TRUE">
      <Data>
          <Rows>
              <Row>
                  <Field Name="ID">1</Field>
                  <Field Name="Title">some.setting.name</Field>
                  <Field Name="Configuration_Value">http://myserver.mydomain.com</Field>
                  <Field Name="Default_Value">http://myserver.mydomain.com</Field>
                  <Field Name="Usage_Description">The URL of the something something server</Field>
                  <Field Name="Property_Name">SomethingSomethingServer</Field>
                  <Field Name="Property_Type">string</Field>
              </Row>
          </Rows>
      </Data>
    </ListInstance>
</Elements>

I need to be able to deploy the content type to the list automatically.  (If you're not deploying default data with your list, the ContentTypeBinding element may do the trick.)

So I fumbled around with this for a day and a half while bitching and moaning about it to anyone who would listen to me.  Did I screw up the content type somehow?  Did I screw up the list template somehow?  Did I mess up an ID somewhere?  I mean, I've done this a million times in the past in a million different solution packages and even in SharePoint 2010...what the heck was different about this package?

Finally (in fact, just moments ago!) it dawned on me that I've always "primed" my content types when deploying them in my previous solutions based on some findings from Martin Hatch!

You do not have to declare custom fields in the schema.xml, as long as you manually update your content type before creating any libraries.

Bingo!

Martin links to a code sample, but the gist of it is pretty simple.

  1. Wire up a feature receiver for your content type feature.
  2. On feature activation, "prime" the content types that have a specific group name
  3. ???
  4. Profit!

Okay, the profiting part is questionable, the the rest of it isn't!

The first part is to wire up the feature for a feature receiver.

<Feature Id="0C4C1210-B056-4AFC-B556-A8BB30E1A9F1"
         Title="My Custom Content Types"
         Description="Installs the content types."
         Hidden="FALSE"
         Scope="Site"
         DefaultResourceFile="core"
         ReceiverAssembly="MyAssembly, Version=1.0.0.0, Culture=neutral, PublicKeyToken=56f39f66bb4e1d17"
         ReceiverClass="MyAssembly.Components.FeatureReceivers.MetadataPrimer"
         xmlns="http://schemas.microsoft.com/sharepoint/">
    <ElementManifests>
        <ElementManifest Location="my_contenttypes.xml" />
    </ElementManifests>
    <Properties>
        <Property Key="prime.if.contains" Value="My Custom Content Type"/>
    </Properties>
</Feature>

Note that I've added a feature property here: a key called "prime.if.contains".  This allows me to specify the group names of the content types that I want to prime.  That way, I don't have to prime all of the content types.

I create a base class for my feature receivers:

/// <summary>
///     Abstract base class for custom feature receivers.
/// </summary>
/// <remarks>
///     All custom feature receivers should be updated to inherit from this class for common functionality.
/// </remarks>
public abstract class FeatureReceiverBase : SPFeatureReceiver
{
	/// <summary>
	///     Gets the <c>SPWeb</c>.
	/// </summary>
	/// <remarks>
	///     This method allows the feature receiver to obtain a reference to the current <c>SPWeb</c>
	///     regardless of whether there is an <c>HttpContext</c> (i.e. in the case that the feature
	///     is activated from <c>STSADM</c>).
	/// </remarks>
	/// <param name = "properties">The properties.</param>
	/// <returns>The <c>SPWeb</c> instance from the properties.</returns>
	protected static SPWeb GetWeb(SPFeatureReceiverProperties properties)
	{
		SPWeb web;

		if (SPContext.Current != null && SPContext.Current.Web != null)
		{
			web = SPContext.Current.Web;
		}
		else if (properties.Feature.Parent is SPWeb)
		{
			web = (SPWeb) properties.Feature.Parent;
		}
		else if (properties.Feature.Parent is SPSite)
		{
			web = ((SPSite) properties.Feature.Parent).RootWeb;
		}
		else
		{
			throw new Exception("Unable to retrieve SPWeb - this feature is not site or web-scoped.");
		}

		return web;
	}
}

And then I add the primer class:

/// <summary>
///     <para>Receiver which "primes" the content types by updating each of the custom content types to push the changes down.</para>
///     <para><see cref = "http://mkeeper.spaces.live.com/blog/cns!60F12A60288E5607!278.entry" /></para>
/// </summary>
public class MetadataPrimer : FeatureReceiverBase
{
	private const string _primerKey = "prime.if.contains";

	/// <summary>
	///     Handles the feature activated event by priming content types specified by the primer key property.
	/// </summary>
	/// <param name = "properties">The properties.</param>
	public override void FeatureActivated(SPFeatureReceiverProperties properties)
	{
		Console.Out.WriteLine("Activating Metadata Primer");

		// Prime the content types.
		SPWeb web = GetWeb(properties);

		SPContentTypeCollection contentTypes = web.ContentTypes;

		List<string> contentTypeIds = new List<string>();

		// Capture the feature properties.
		Dictionary<string, string> featureProperties = new Dictionary<string, string>();

		foreach (SPFeatureProperty property in properties.Feature.Properties)
		{
			featureProperties[property.Name] = property.Value;
		}

		// Must find the key "prime.if.contains"
		if (!featureProperties.ContainsKey(_primerKey))
		{
			ExceptionFactory.Throw<ArgumentException>(
				"The property prime.if.contains is not defined in the feature.");
		}

		string[] tokens = featureProperties[_primerKey].Split(new[] {','}, StringSplitOptions.RemoveEmptyEntries);

		Log("Priming content types containing: {0}", featureProperties[_primerKey]);

		foreach (SPContentType contentType in contentTypes)
		{
			bool prime = tokens.Any(token => contentType.Group.ToUpperInvariant().Contains(token.ToUpperInvariant()));

			if (!prime)
			{
				continue; // EXIT: Next iteration.
			}

			contentTypeIds.Add(contentType.Id.ToString());

			Log("Adding content type \"{0}\" ({1}) for priming", contentType.Name, contentType.Id);
		}

		// Do it in two loops since doing it one causes issues if a parent content type is primed (child content types
		// fail to prime because the parent changes).
		foreach (string id in contentTypeIds)
		{
			try
			{
				SPContentTypeId contentTypeId = new SPContentTypeId(id);

				SPContentType contentType = web.ContentTypes[contentTypeId];

				Log("Priming content type: {0}", contentType.Name);

				contentType.Update(true, false);
			}
			catch (Exception)
			{
				Log("Failed to prime content type with ID: {0}", id);
			}
		}
	}

	/// <summary>
	///     Logs the specified message to both log4net and the console.
	/// </summary>
	/// <param name = "message">The message.</param>
	/// <param name = "args">The args.</param>
	private static void Log(string message, params object[] args)
	{
		Console.Out.WriteLine(message, args);
	}

	/// <summary>
	///     Features the deactivating.
	/// </summary>
	/// <param name = "properties">The properties.</param>
	public override void FeatureDeactivating(SPFeatureReceiverProperties properties) {}

	/// <summary>
	///     Features the installed.
	/// </summary>
	/// <param name = "properties">The properties.</param>
	public override void FeatureInstalled(SPFeatureReceiverProperties properties) {}

	/// <summary>
	///     Features the uninstalling.
	/// </summary>
	/// <param name = "properties">The properties.</param>
	public override void FeatureUninstalling(SPFeatureReceiverProperties properties) {}
}

(Note: remove or replace the usages of ExceptionFactory -- this is just a custom exception helper that I use)

You can see, the gist of it is that the code will iterate all of the content types at the site, find ones that belong in the given groups, and them prime the content type by calling SPContentType.Update(bool, bool).

So there you have it; it didn't work in 2007 either, but I had completely forgotten that I had written the primer for this express purpose.  With this simple bit of code, you can save yourself the hassle of having to copy fields from your content type to your schema.xml definition file when creating custom list definitions for SharePoint and all of the fields are nicely copied over from your site level content type to your list instance without having to manually modify the schema.xml file for your list.

Filed under: .Net, SharePoint Comments Off
29Oct/10Off

Cancelling an Event In SharePoint 2010

Posted by Charles Chen

Small note on the API change: the Cancel property has been deprecated in favor of the new Status property.

Hope this saves someone some time.

Filed under: .Net, SharePoint No Comments