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

5Jul/11Off

The Beauty of Mongo

Posted by Charles Chen

MongoDB is sexy.  Sexy as hell.

This isn't the first or the best intro to Mongo+.NET, but I hope this one can show you how it finally solves the object-relational disconnect with an easy to follow example.

Let's start with the model:

#region prologue

// Charles Chen

#endregion

using System;
using System.Collections.Generic;
using MongoDB.Bson;
using MongoDB.Bson.Serialization.Attributes;

namespace MongoAssessmentsTest
{
    public class Assessment
    {
        private DateTime _created;
        private string _description;
        [BsonId]
        private ObjectId _id;
        private List<ContentItem> _items;
        private DateTime _lastUpdated;
        private string _ownerId;
        private List<string> _tags;
        private string _title;

        public ObjectId Id
        {
            get { return _id; }
            set { _id = value; }
        }

        public string OwnerId
        {
            get { return _ownerId; }
            set { _ownerId = value; }
        }

        public string Title
        {
            get { return _title; }
            set { _title = value; }
        }

        public string Description
        {
            get { return _description; }
            set { _description = value; }
        }

        public List<string> Tags
        {
            get { return _tags; }
            set { _tags = value; }
        }

        public DateTime Created
        {
            get { return _created; }
            set { _created = value; }
        }

        public DateTime LastUpdated
        {
            get { return _lastUpdated; }
            set { _lastUpdated = value; }
        }

        public List<ContentItem> Items
        {
            get { return _items; }
            set { _items = value; }
        }
    }

    public class ContentItem
    {
        private List<Question> _questions;
        private string _text;

        public string Text
        {
            get { return _text; }
            set { _text = value; }
        }

        public List<Question> Questions
        {
            get { return _questions; }
            set { _questions = value; }
        }
    }

    [BsonKnownTypes(typeof(CheckboxQuestion), typeof(RadioQuestion), typeof(SelectQuestion), typeof(FreeTextQuestion))]
    public class Question
    {
        private int _order;
        private int _points;
        private string _text;

        public string Text
        {
            get { return _text; }
            set { _text = value; }
        }

        public int Order
        {
            get { return _order; }
            set { _order = value; }
        }

        public int Points
        {
            get { return _points; }
            set { _points = value; }
        }
    }

    public class CheckboxQuestion : Question
    {
        private List<string> _answers;
        private List<string> _choices;

        public List<string> Choices
        {
            get { return _choices; }
            set { _choices = value; }
        }

        public List<string> Answers
        {
            get { return _answers; }
            set { _answers = value; }
        }
    }

    public class SelectQuestion : Question
    {
        private string _answer;
        private List<string> _choices;

        public List<string> Choices
        {
            get { return _choices; }
            set { _choices = value; }
        }

        public string Answer
        {
            get { return _answer; }
            set { _answer = value; }
        }
    }

    public class RadioQuestion : SelectQuestion { }

    public class FreeTextQuestion : Question
    {
        private List<string> _acceptableAnswers;
        private string _isAnswerCaseSensitive;

        public string IsAnswerCaseSensitive
        {
            get { return _isAnswerCaseSensitive; }
            set { _isAnswerCaseSensitive = value; }
        }

        public List<string> AcceptableAnswers
        {
            get { return _acceptableAnswers; }
            set { _acceptableAnswers = value; }
        }
    }
}

The beauty of it is the absolute simplicity of working with this model.  Aside from a few attributes, there's not much thought that needs to be given to collections and inheritance hierarchies (though there are additional attributes and classes that can be used to control how these are stored if so desired).  I love it because this approach keeps your domain models clean.

How do we interact with this model?

#region prologue

// Charles Chen

#endregion

using System;
using System.Collections.Generic;
using MongoDB.Bson;
using MongoDB.Driver;
using MongoDB.Driver.Builders;

namespace MongoAssessmentsTest
{
    internal class Program
    {
        private static void Main(string[] args)
        {
            Program program = new Program();
            program.Run();
        }

        private void Run()
        {
            MongoServer server = MongoServer.Create(); // Uses default connection options to localhost

            MongoDatabase database = server.GetDatabase("assessments_db");

            if (!database.CollectionExists("assessments"))
            {
                CommandResult result = database.CreateCollection("assessments");

                if (!result.Ok)
                {
                    Console.Out.WriteLine(result.ErrorMessage);
                    return;
                }
            }

            MongoCollection<Assessment> assessments = database.GetCollection<Assessment>("assessments");

            if (assessments.FindOne(Query.EQ("Title", "Sample 1")) == null)
            {
                // Insert an assessment.
                Assessment a = new Assessment
                {
                    Created = DateTime.Now,
                    Description = "A sample assessment",
                    Title = "Sample 1",
                    Items = new List<ContentItem>
                    {
                        new ContentItem
                        {
                            Questions = new List<Question>
                            {
                                new SelectQuestion
                                {
                                    Text = "Who was the first president of the United States?",
                                    Answer = "George Washington",
                                    Choices = new List<string>
                                    {
                                        "George Washington",
                                        "Abraham Lincoln",
                                        "John Adams"
                                    }
                                },
                                new CheckboxQuestion
                                {
                                    Text = "Which of these is NOT a former US President?",
                                    Answers = new List<string>
                                    {
                                        "Benjamin Franklin",
                                        "Hillary Clinton"
                                    },
                                    Choices = new List<string>
                                    {
                                        "Benjamin Franklin",
                                        "Bill Clinton",
                                        "Hillary Clinton",
                                        "Andrew Jackson",
                                        "James Garfield"
                                    }
                                }
                            }
                        }
                    }
                };

                assessments.Insert(a);
            }

            // Get it as BSON - great for writing straight to a web page
            BsonDocument a1 = assessments.FindOneAs<BsonDocument>(Query.EQ("Title", "Sample 1"));

            Console.Out.WriteLine(a1);

            // Get it as an object - great if you want to work with it on the server.
            Assessment a2 = assessments.FindOneAs<Assessment>(Query.EQ("Title", "Sample 1"));

            Console.Out.WriteLine(a2.Title);
        }
    }
}

Brilliantly simple.  Just fire up mongod.exe and you're ready to rock and roll.  Download the example from here: MongoAssessmentsTest.zip

Filed under: .Net, Dev, Mongo No Comments
22Jun/11Off

FluentNHibernate vs. Code First in EF 4.1

Posted by Charles Chen

First, having been in the SharePoint space exclusively for so long now, I have to admit: it's been a while since I've had to deal with non-SharePoint data access.  I don't know if I miss it or not 😀 I've become really comfortable with my little SharePoint ORM approach thingy that generates models and such from content types (before you say SPMetal, this was developed for 2007 and I think still works better than SPMetal).

In the past, I've mostly avoided Entity Framework (EF), preferring LINQ-to-SQL due to EF1's shortcomings such as being less performant, creating more obtuse queries, my general disdain for the entity designer, the overly complex API, the lack of POCO support, etc.  I've also spent some time with NHibernate and FluentNHibernate and found it more pleasant to work with as an ORM solution.

Only recently have I discovered the "code first" approach released with EF4.1 which makes it much more appealing in the same way that FNH made NH more appealing by doing away with hbm.xml files for mapping your entities.  So I decided to take it for a spin and see how it measures up to NH+FNH.

If you're interested in much more in depth (and partisan) debate on the merits of one or the other, there's plenty to go around.  I won't get into that 🙂 I'm just concerned with the basics for now and I anticipate this being a series of blog posts as I test the merits -- and demerits -- of each.

For this first post, the basics are:

  • Create a simple console application that manages contacts
  • The application should auto-generate and auto-provision the schema
  • Basic queries should just work and not generate "dumb" SQL (i.e. counts should use COUNT in SQL, basic paging should be SQL based).

First up: Entity Framework.

So let's jump right into the code:

using System;
using System.Data.Entity;
using System.Linq;

namespace EFTest
{
    public class Program
    {
        private static void Main(string[] args)
        {
            Database.SetInitializer<ContactContext>(
                new DropCreateDatabaseAlways<ContactContext>());

            using (ContactContext context = new ContactContext())
            {
                // Basic add
                Contact chuck = new Contact {FirstName = "Charles", LastName = "Chen"};
                Contact sandra = new Contact {FirstName = "Sandra", LastName = "Chen"};
                Contact charlotte = new Contact {FirstName = "Charlotte", LastName = "Chen"};
                Contact judy = new Contact {FirstName = "Judy", LastName = "Chen"};

                context.Contacts.Add(chuck);
                context.Contacts.Add(sandra);
                context.Contacts.Add(charlotte);
                context.Contacts.Add(judy);

                context.SaveChanges();

                // Basic paged read
                var query = from c in context.Contacts
                               select c;

                var results = query.OrderBy(c => c.FirstName).Skip(2).Take(2);

                foreach(Contact c in results)
                {
                    Console.Out.WriteLine(c);
                }

                // Basic count
                int total = context.Contacts.Count();

                Console.Out.WriteLine(total);
            }
        }
    }

    public class Contact
    {
        public int ContactId { get; set; }
        public string FirstName { get; set; }
        public string LastName { get; set; }
        public override string ToString()
        {
            return string.Format("{0} {1}", FirstName, LastName);
        }
    }

    public class ContactContext : DbContext
    {
        public DbSet<Contact> Contacts { get; set; }
    }
}

I like that it's fairly compact and straightforward.  The development experience was a bit challenging, though.  First, EF doesn't like it when you try to use the schema with an existing database.  It insists that you let it provision the database.  Okay, fine (though there are workarounds).  It's often thrown around in these debates that one of the benefits of EF is that it's "out-of-the-box" but in reality, at least with the code first bits, it's anything but.  You have to download EF4.1 first and install it just like you would with NH+FNH (though certainly, that may change in the future).

The walkthrough on the ADO.NET team blog is also broken.  To get DbContext, you need to add a reference to EntityFramework.dll, not System.Data.Entity as posted in the blog.  Overall, I found the code to be more compact and easier to work with.  The one downside is the that one has to consistently update the class inheriting from DbContext as new entities are added.

Second up: NH+FNH:

Now lets take a look at code that does the same thing in NH:

using System;
using System.Collections.Generic;
using FluentNHibernate.Automapping;
using FluentNHibernate.Cfg;
using FluentNHibernate.Cfg.Db;
using NHibernate;
using NHibernate.Cfg;
using NHibernate.Tool.hbm2ddl;

namespace FNHTest
{
    internal class Program
    {
        private static void Main(string[] args)
        {
            ISessionFactory sessionFactory = CreateSessionFactory();

            using (ISession session = sessionFactory.OpenSession())
            using (ITransaction transaction = session.BeginTransaction())
            {
                // Basic add
                Contact chuck = new Contact {FirstName = "Charles", LastName = "Chen"};
                Contact sandra = new Contact {FirstName = "Sandra", LastName = "Chen"};
                Contact judy = new Contact {FirstName = "Judy", LastName = "Chen"};
                Contact charlotte = new Contact {FirstName = "Charlotte", LastName = "Chen"};

                session.SaveOrUpdate(chuck);
                session.SaveOrUpdate(sandra);
                session.SaveOrUpdate(judy);
                session.SaveOrUpdate(charlotte);

                transaction.Commit();

                // Basic paged read
                IList<Contact> results = session.QueryOver<Contact>()
                    .OrderBy(c => c.FirstName).Asc.Skip(2).Take(2).List();

                foreach (Contact c in results)
                {
                    Console.Out.WriteLine(c);
                }

                // Check the count
                int total = session.QueryOver<Contact>().RowCount();

                Console.Out.WriteLine(total);
            }
        }

        private static ISessionFactory CreateSessionFactory()
        {
            return Fluently.Configure()
                .Database(
                    MsSqlConfiguration.MsSql2008.ConnectionString("Data Source=CHARLES-E6410;Initial Catalog=FNHTest;Integrated Security=SSPI;Application Name='FNHTest'")
                )
                .Mappings(m => m.AutoMappings.Add(AutoMap.AssemblyOf<Program>()))
                .ExposeConfiguration(BuildSchema)
                .BuildSessionFactory();
        }

        private static void BuildSchema(Configuration config)
        {
            SchemaExport schema = new SchemaExport(config);

            schema.Drop(false, true); // Drops the tables only.
            schema.Create(false, true);
        }
    }

    public class Contact
    {
        public virtual int Id { get; set; }
        public virtual string FirstName { get; set; }
        public virtual string LastName { get; set; }

        public override string ToString()
        {
            return string.Format("{0} {1}", FirstName, LastName);
        }
    }
}

It's slightly more verbose, but not terribly so.  One note is that unlike the case with the ContactContext in EF, you won't need to continually update a "registry" with new entity types.

For this basic scenario, it's hard to say I prefer one over the other, but I'd have to give the edge to EF so far simply for the intangibles (read: Microsoft supported - in other words, it'll be easier from a convincing-your-team-to-not-roll-your-own standpoint).

Comparing the SQL

Of course, the next step is to compare the SQL generated from each of these.

Let's take a look at each query:

/*EF*/
SELECT TOP (2)
[Extent1].[ContactId] AS [ContactId],
[Extent1].[FirstName] AS [FirstName],
[Extent1].[LastName] AS [LastName]
FROM (
    SELECT
        [Extent1].[ContactId] AS [ContactId],
        [Extent1].[FirstName] AS [FirstName],
        [Extent1].[LastName] AS [LastName],
        row_number() OVER (ORDER BY [Extent1].[FirstName] ASC) AS [row_number]
    FROM [dbo].[Contacts] AS [Extent1]
)  AS [Extent1]
WHERE [Extent1].[row_number] > 2
ORDER BY [Extent1].[FirstName] ASC
/*NH*/
exec sp_executesql
N'SELECT TOP (@p0)
    Id0_0_,
    FirstName0_0_,
    LastName0_0_
FROM
(
    SELECT
        this_.Id as Id0_0_,
        this_.FirstName as FirstName0_0_,
        this_.LastName as LastName0_0_,
        ROW_NUMBER() OVER(ORDER BY this_.FirstName) as __hibernate_sort_row
    FROM
        [Contact] this_
) as query
WHERE
    query.__hibernate_sort_row > @p1 ORDER BY query.__hibernate_sort_row',N'@p0 int,@p1 int',@p0=2,@p1=2

You can see that for the first, paged read, EF uses a straight SQL statement whereas NH uses a parameterized dynamic SQL statement.  For this small dataset, there is no discernible difference in performance, but I would gather that for larger datasets, we'd see a performance boost with NH.

For the second query to count, we see that again, there is a small difference in how the two go about generating queries:

/*EF*/
SELECT
[GroupBy1].[A1] AS [C1]
FROM ( SELECT
	COUNT(1) AS [A1]
	FROM [dbo].[Contacts] AS [Extent1]
)  AS [GroupBy1]
/*FNH*/
SELECT count(*) as y0_ FROM [Contact] this_

As far as I can tell, for this basic scenario where there is no additional filtering on columns, there should be no practical performance difference between the two (though obviously, EF generates an extra nested select statement, the real world performance impact is negligible).

Next up: I want to do some more tests with more complex queries and figure out how each of these frameworks handles dealing with schema changes.  Seems like a wash for these basic scenarios and one of personal preference.

Filed under: .Net, Dev, SQL Server 2 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
4Nov/10Off

HTML5 and the Future of Silverlight and WPF

Posted by Charles Chen

Peter Bright has an interesting, in-depth write up of the aftermath of PDC and the future of Silverlight and WPF.

Of particular interest to me is that it seems to imply the impending doom of WPF.  This genuinely surprised me and I would not have seen it coming (but I can kind of see where they're going with this).  But first, Silverlight.

In the comments, Peter actually gets a lot of flack for his opening paragraph with members of the community asking whether this is just a case of hindsight being 20/20:

For reasons that are not immediately clear to me, it seems that a lot of developers who attended Microsoft's recent PDC event were surprised to hear that the company now sees HTML5 as the way forward for developing rich Internet applications—and not, as they had been expecting, Silverlight. Their surprise surprises me, because past statements by the company had already made this repositioning obvious, though perhaps not explicit.

I would say that this isn't a case of hindsight being 20/20 and I agree with Peter: this has been apparent from day one that Silverlight was never the future of the web.  For about two years now, I've told anyone that asked -- including peers, clients, senior architects, sales folk that I've had to deal with -- that there's no point to using Silverlight unless the application in question fell into one of four categories:

  1. Streaming media, audio/video -- quite simply, this isn't supported in HTML4.  However, HTML5 addresses this and "iDevices" have been a core driver to moving video on the web towards support for HTML5.
  2. Scalable 2D vector graphics and animation -- HTML4 supported SVG, but it wasn't performant and it wasn't consistent across browsers.  HTML5 addresses this.
  3. 3D graphics and animation -- non-existent in HTML4 without some plugin like Java, but supported by canvas in HTML5.
  4. Interactive games -- well, this is really just a recombination of the three previous bullets.  Certainly, I can see the case for this staying in Flash/Silverlight, but HTML5 will also address this use case.

Beyond that, I couldn't really see a space for Silverlight.  If it doesn't fall into one of those categories, there isn't a point to using it, I would say to them.  Fancy animations like flipbooks were always clumsy to me.  I don't really want to watch your silly animation; I want to see the next picture.  In a way, it was even worse than those silly, cheesy DX transitions that were so fashionable in the earlier part of the decade because now you have to download a plugin for the "privilege".  Not only that, I want to be able to use my browser navigation controls and keyboard shortcuts, damn it!

Some of it should have been obvious by the evolution of Flash in websites.  Remember the days when the hot fad was creating your whole website in Flash?  Yeah, fuck that.  That sucked!  Nowadays, Flash is -- thankfully -- mostly relegated to a few use cases like streaming media, audio/video, interactive games, 2D and 3D graphics and animation, and ads (fucking ads).  So I've always asked myself: why should we expect anything different from Silverlight?

Yet time and again, I'd have managers and sales folks ask me "What do you think about Silverlight" or "Can we do this in Silverlight?"  I'd stare at them quizzically and wonder why they wanted to do something in Silverlight that I could do even more easily, even more expediently, and even more maintainably in Javascript and DHTML (say using jQuery and jQueryUI or any number of fine Javascript frameworks).  It's been apparently clear to me that, for the web, that Silverlight was merely a stop-gap until HTML5 could be nailed down and widespread support could be obtained.

One of the biggest pushes that the product managers on FirstPoint tried to make in the days before I left the team was to shoehorn Silverlight in there, somehow.  As if adding Silverlight would sell the product?  And now there's a whole fad of Silverlight and SharePoint.  I don't get it and I keep getting asked about it.  Unless the requirement falls into one of the four key scenarios, why bother with it?  I can create slide-out menus and fading elements with Javascript.  Why do I need a less accessible, less performant, less usable solution?

With that said, what baffles me is the implication that WPF is on its way out, too.  But Peter's write up concludes with the following paragraph:

Perhaps Microsoft is already hinting at its plans. Direct2D is currently the best way for developers to create high performance user interfaces in Windows, and the easiest way to use Direct2D is with HTML5, in Internet Explorer 9. Could Redmond embrace and extend HTML5 to fill in all the gaps that today need Flash or Silverlight, and then use it as the basis for Windows development? Such an approach is already popular on iOS. Stranger things have happened.

Now that's an interesting take that I wouldn't have seen coming but it would certainly be glorious.  I'd trade WPF for hardware accelerated HTML5 everywhere in a heartbeat.

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
27Oct/10Off

Updating SharePoint Taxonomy Fields

Posted by Charles Chen

I tripped up on it a bit recently on SharePoint taxonomy fields (or managed metadata fields) as I found that I wasn't able to push down values from a SharePoint list item to a Word document programmatically in SharePoint.

I was able to set the property in SharePoint and the list item would reflect the property in string form like so:

Japan|c2a154e3-8bb0-4b56-b083-297526964fd9

But the problem with this is that when the user opens the document, the Document Information Panel (DIP) will not have the value pre-selected.  All of the other values are set properly and show up in the DIP.  I found that if I saved the item manually in SharePoint through the UI, it would push the changes down to the Word document, but it took quite a while to figure out how to set the taxonomy field values.

In the Word document itself, I could see that the document properties were clearly different.  The first code block shows what a functioning document properties XML looks like:

<RegionTaxHTField0 xmlns="3a290427-a0ba-4a8f-bf50-36a9ad2bef07">
    <Terms xmlns="http://schemas.microsoft.com/office/infopath/2007/PartnerControls">
        <TermInfo xmlns="http://schemas.microsoft.com/office/infopath/2007/PartnerControls">
            <TermName>Japan</TermName>
            <TermId>c2a154e3-8bb0-4b56-b083-297526964fd9</TermId>
        </TermInfo>
    </Terms>
</RegionTaxHTField0>

And here is what it looks like if you set the property using the string value only:

<RegionTaxHTField0 xmlns="3a290427-a0ba-4a8f-bf50-36a9ad2bef07">
    <Terms xmlns="http://schemas.microsoft.com/office/infopath/2007/PartnerControls"></Terms>
</RegionTaxHTField0>

I chased this around for hours trying to figure out how to set the field values in SharePoint so that they'll be pushed down into the Word document.  It turns out, there's a new API for handling taxonomy fields.  The following sample assumes that you've iterated through the fields and collected all of the taxonomy fields:

/// <summary>
///   Updates the taxonomy fields.
/// </summary>
/// <param name="target">The target file.</param>
/// <param name="taxonomyFields">
///   The taxonomy fields which have been collected already.
/// </param>
private static void UpdateTaxonomyFields(SPFile target,
    List<TaxonomyField> taxonomyFields)
{
    SPListItem item = target.Item;
    SPSite site = item.Web.Site;

    TaxonomySession session = new TaxonomySession(site, false);

    foreach(TaxonomyField t in taxonomyFields)
    {
        TermStore termStore = session.TermStores[t.SspId];

        TermSet termSet = termStore.GetTermSet(t.TermSetId);

        string value = Convert.ToString(item[t.Id]);

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

        value = parts[parts.Length - 1];

        Term term = termSet.GetTerm(new Guid(value));

        if(term == null)
        {
            Log.Write("Term could not be found for value \"{0}\"", value);
            continue;
        }

        t.SetFieldValue(item, term);
    }
}

Using this approach, you can set the property in SharePoint and push it down to the document as well.

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

Programmatically Submitting Files to the SharePoint Content Organizer

Posted by Charles Chen

The content organizer in SharePoint 2010 is an interesting feature, particularly from an application development perspective as it gives you a fairly competent rules builder (why does it post back when you select a content type?  Like seriously, Microsoft?) and routing engine, for free.  We built something similar in FirstPoint for SharePoint 2007, but it was quite a bit of work.  You can do all sorts of interesting things with this such as moving documents around based on metadata changes or externalizing the rules that determine where server generated documents should be routed to.

The content organizer can be accessed from the Official Files web service but it can also be accessed directly when building server applications such as event receivers or custom web services.  One common scenario, for example, is using an event receiver for moving a document to a different folder or library if a user changes a metadata field.  If I have a list of contacts organized into folders by last name (A-C, B-E, etc.), I'd want the system to automatically move a contact into the correct folder if a contact changes his or her last name.  Without the content organizer and externalized rules, you'd either have to hard code the routing rules or design and code a system to externalize the routing rules, both sub-optimal solutions compared to the content organizer.

Behind the scenes, content organizer users a class called OfficialFileCore, you'll need to add a reference to Microsoft.Office.Policy.dll (found in the ISAPI directory) to your project.  The following is some code that should give you an idea of how to call the OfficialFileCore.SubmitFile() method:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.SharePoint;
using Microsoft.SharePoint.Utilities;
using Microsoft.Office.RecordsManagement.RecordsRepository;
using System.Collections;

// Namespace and class omitted for formatting purposes...

static void Main(string[] args)
{
    Microsoft.SharePoint.OfficialFileResult result = Microsoft.SharePoint.OfficialFileResult.UnknownError;
    string destination = null; // New location of the file is assigned to this string.

    using (SPSite site = new SPSite("http://moss.dev.com/"))
    using (SPWeb web = site.OpenWeb())
    using (new SPMonitoredScope("Official File Receiver", uint.MaxValue, new ISPScopedPerformanceMonitor[] { new SPSqlQueryCounter(100) }))
    {
        SPFile file = web.GetFile("http://moss.dev.com/dropofflibrary/test1.docx");
        byte[] bytes = file.OpenBinary();
        string fileType = file.Item.ContentType.Name;
        SPFieldCollection fields = file.Item.Fields;

        List<Microsoft.SharePoint.RecordsRepositoryProperty> properties
            = new List<Microsoft.SharePoint.RecordsRepositoryProperty>();

        // Create a RecordsRepositoryProperty for each metadata field.
        foreach (SPField field in fields)
        {
            try
            {
                string value = Convert.ToString(
                    field.GetFieldValue(Convert.ToString(file.Item[field.Title])));

                Console.Out.WriteLine("Name:{0}, Type:{1}, Value:{2}",
                    field.Title, field.TypeAsString, value);

                Microsoft.SharePoint.RecordsRepositoryProperty property =
                    new Microsoft.SharePoint.RecordsRepositoryProperty
                    {
                        Name = field.Title,
                        Type = field.TypeAsString,
                        Value = value
                    };

                properties.Add(property);
            }
            catch (Exception exception)
            {
                // Some fields fail; not sure if they're consequential yet!
                Console.Out.WriteLine(" - Failed to process field {0}", field.Title);
            }
        }

        result = OfficialFileCore.SubmitFile(web, bytes, properties.ToArray(), fileType,
            "http://moss.dev.com/dropofflibrary/test1.docx", "Charles", false, out destination);

        // Seems that you have to manually delete the file.
        file.Item.Delete();
    }

    Console.Out.WriteLine("1 > {0}", result);
    Console.Out.WriteLine("2 > {0}", destination);
}

In this case, the source library is the drop off library.  Note that if you programmatically add a file to the library, it doesn't actually get routed according to the content organizer rules; you'll still have to submit it manually in the UI or use the code outlined above to actually trigger the routing.

One final note: as far as I can tell, it seems that you have to manually delete the file from the source library once you successfully route the file.

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

Getting All Content Controls using OpenXML

Posted by Charles Chen

If you're trying to get all of the content controls in an OpenXML document, the most obvious way to do it would be:

// Get the document instance
WordprocessingDocument document = ...; 

// Get all of the SdtBlock elements
document.MainDocumentPart.Document.Descendants<SdtBlock>();

But this will only get you some of the content controls.  Basically, it won't return any nested, inline content controls (nested, non-inline content controls are still returned).  Nested, inline content controls are still tagged as <sdt/>, but the corresponding class name is actually SdtRun, not SdtBlock.

To get all of the content controls, you need to use the following code instead:

// Get the document instance
WordprocessingDocument document = ...; 

// Get all of the SdtElement elements
document.MainDocumentPart.Document.Descendants<SdtElement>();

The resultset will include SdtBlock and SdtRun elements.  This had me coding in circles for a few hours....

Note that likewise, the content element is different between the two.  For SdtBlock, the content element is an SdtContentBlock.  For SdtRun, the content element is an SdtContentRun.

Filed under: .Net, Office No Comments
6Oct/10Off

Programmatically Adding an Event Receiver to a Content Type

Posted by Charles Chen

You'll recall from a previous post that you can add an event receiver to a content type.

That's all well and good if you're deploying your event receiver with your content type from the get go, but what if you need to associate an event receiver with an existing content type?

// Remove existing definition for the assembly name, class, and type.
foreach(SPEventReceiverDefinition definition in contentType.EventReceivers)
{
	if(definition.Class != className && definition.Assembly != assemblyName
		&& definition.Type != eventReceiverType)
	{
		continue;
	}

	definition.Delete();
	contentType.Update(true);
	break;
}

SPEventReceiverDefinition eventReceiverDefinition = contentType.EventReceivers.Add();
eventReceiverDefinition.Class = className; // String
eventReceiverDefinition.Assembly = assemblyName; // String
eventReceiverDefinition.Type = eventReceiverType; // SPEventReceiverType
eventReceiverDefinition.Data = documentType; // Arbitrary input data (String)
eventReceiverDefinition.Update();

contentType.Update(true);

Easy!

Filed under: .Net, SharePoint No Comments