Programming, Policitcs, and uhhh Pineapples.
# Friday, February 26, 2010

SharePoint 2010 Mobile Rendering (And Device Emulator Fun!)

Friday, February 26, 2010 5:11:57 PM UTC

I was recently tasked with taking an exploratory look at the mobile rendering capabilities of SharePoint 2010 to get a feel for it.  The short of it is that it's sad to say that in 2010 (the year, that is), we are still doing not much better than 3-4 years ago.  Of course, this raises the question of whether it's an issue with the browser or an issue with the software (SharePoint 2010).  Based on my early experience, I have to say it's an issue with 2010 - it seems like the SharePoint team simply took the easy way out.

If you've ever used the Gmail web page from a mobile browser (like Opera), you'll see that there is a "mobile" version and a "basic HTML" version available (links on the bottom).  For all intents and purposes, the "basic HTML" version looks and works pretty much like the full web version (minus the fancy drag/drop stuff, chat, etc.) and loads fine on my nearly two year old HTC Touch Pro.  This is what we should expect from a company as big as Microsoft with vast resources at their disposal.  This is what we should expect from them given that it's 2010 and the mobile space is an increasingly bigger piece of the pie. 

What's worse is that there's no apparent way from the browser client to access the "full view".  What happens when more capable mobile browsers are deployed that can match the capabilities of the current desktop browsers?  Seems like you'll have to make configuration changes on the server to enable proper browser detection but it would be much easier to simply have the option, like on mobile Amazon.com, to load the full view in lieu of Microsoft actually offering a more usable "basic HTML" view.

Alas, instead, we get neutered functionality and weak rendering to mobile browsers for a product that's supposed to carry us to 2013 or 2014.

The following screenshots are taken using my desktop IE8, Microsoft's Windows Mobile 6.1.4 emulator, and RIM's Blackberry 9630 emulator:

Lading Page

List View

Calendar View

New Event

In analysis, I think what makes matters worse is that they've dramatically increased the capabilities of the desktop browser version, but neglected to carry forward some of the basic functionality that seems like it wouldn't be too had to implement like the ratings or tagging functionality.  This discrepancy makes the shortcomings of the mobile offering all the more apparent.

Certainly, I'm not expecting that the desktop experience to be fully emulated on a mobile device, knowing the limitations of the small screens and limited resources and capabilities of mobile browsers.  That said, I would expect a more worthy effort than this.  I would expect something similar to Gmail's "basic HTML" option and a link to the full version.  It's an embarassment, especially the new event screen; at the least, the developers could have aligned the fields (it's pretty offensive).

It's just sloppy and lazy all around and I suggest Microsoft spend some time with the mobile browsers from their competitors.  People are accustomed to a certain level of mobile browser capabilities these days and it's nowhere near as low as Microsoft would like to believe.

For comparison's sake, here's Gmail's rendering in "mobile" and "basic HTML" mode:

Gmail desktop browser, "mobile", and "basic HTML" rendering

This is much more usable and a much more congruous experience across platforms.  Come on Microsoft, stop being lazy!  As Mark Jackson would say "You're better than that!"

On a side note, working with the emulators was a bit challenging.  If you'd like to try it out for yourself, here are some resources:

Mobile IE 6

Blackberry Devices

My experience is that working with the Blackberry emulator is extremely frustrating because you'll want to click on the screen with your mouse but then you remember that the devices don't support touch so it would only be logical that the emulator doesn't either.  However, this logic leads to a hair-pulling experience when working with the emulator.

# Friday, February 19, 2010

FluentNHibernate And NHibernate.Linq

Friday, February 19, 2010 7:44:43 PM UTC

Just a little blurb on FluentNHibernate and NHibernate.Linq.

I've been working through the samples for FNH and decided to try out some different query scenarios to see how the queries would be generated.  I stumbled a bit on the first rather simple scenario: selecting an item based on the total count of related items.  In this case, from the FNH demo, I wanted to select "all stores with more than 2 employees".  Seems like a simple enough query, right?

You can see from the full code that this should return "Bargain Basement".  However, in going through the documentation, it wasn't exactly apparent how this could be done; it seemed a lot more convoluted than necessary using criteria queries and it simply wasn't working.  Via Google, I came across a blog post that mentioned using either DetachedCriteria or HQL.  Quite frankly, neither was very appealing.

So I figured I'd download NHibernate.Linq and see if it would be better.  Not knowing what I would get, SQL-wise, I wrote the following query:

using (session.BeginTransaction())
{
    IQueryable<Store> stores = from s in session.Linq<Store>()
                               where s.Staff.Count > 2
                               select s;

    foreach (Store store in stores)
    {
        Console.WriteLine("STORE: {0}", store.Name);
    }
}

Of course, the interesting question is whether the the Staff list would be loaded to perform the count and to my pleasant surprise, it was not.  Here's the query in profiler (reformatted for legibility):

exec sp_executesql N'
    SELECT 
        this_.Id as Id1_0_, 
        this_.Name as Name1_0_ 
    FROM 
        [Store] this_ 
    WHERE 
        @p0 <   (
                SELECT 
                    count(staff1_.Id) as y0_ 
                FROM [Store] this_0_ 
                    left outer join [Employee] staff1_ 
                        on this_0_.Id=staff1_.Store_id 
                WHERE 
                    this_.Id = this_0_.Id
                )',N'@p0 int',@p0=2

Sweet! The framework correctly builds a sub-select query to count the staff members.

I guess I'm just easy to impress :-) but I'm digging it.

I think what I like about FluentNHibernate the most is that I can stop building database applications.  What I mean by this is an application built from the database up.  The main issue this raises is complexity with regards to mapping to a data layer and of course continuously having to synchronize your DDL and SQL with your class files.  The following code configures the database, including dropping and creating the tables based on mapping classes in my assemblies:

private static ISessionFactory CreateSessionFactory()
{
    return Fluently.Configure()
        .Database(
        MsSqlConfiguration.MsSql2008.ConnectionString(
            @"Data Source=SCOOBY;Initial Catalog=FluentNHDemo;
            Integrated Security=SSPI;Application Name='FNHDemo'"))
        .Mappings(m =>
                  m.FluentMappings.AddFromAssemblyOf<Store>())
        .ExposeConfiguration(BuildSchema)
        .BuildSessionFactory();
}

private static void BuildSchema(Configuration config)
{
    // this NHibernate tool takes a configuration (with mapping info in)
    // and exports a database schema from it
    SchemaExport schema = new SchemaExport(config);

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

With no mapping files to speak of, persistence plumbing becomes trivial and I can work entirely within Visual Studio.  Of course, for complex queries and queries that need to be highly performant, you may still be better off writing stored procedures (as I've advocated in the past), but the productivity gains to be had can't be ignored and I find the compile-time validation of the queries eases some of my indifference towards dynamic SQL.

# Tuesday, February 16, 2010

Cloud Hype Rant

Tuesday, February 16, 2010 7:34:56 PM UTC

A few years ago, I read an excellent article in Wired magazine titled "The Thin Pill":

 "FOR PATIENTS, disease puts a name to an affliction. It answers that question we all face at one time or another: What's the matter with me? If there is a clear and precise explanation for what's wrong, then surely there is an equally clear way to get better."

 "Cunningham is among the first wave of Americans to be diagnosed with metabolic syndrome, a condition that, though only concretely defined five years ago, is now said to afflict as many as 75 million Americans...Metabolic syndrome is, in fact, almost indistinguishable from obesity – at least 85 percent of those who have the syndrome are obese or overweight.

 "The tidiness of that correlation makes it tempting to view metabolic syndrome not as an emerging fact of medicine, but as a fiction, wholly devised and disseminated by the pharmaceutical industry. After all, drug companies have long eyed obesity as the ultimate growth market -- and they just happen to have an arsenal of pills poised to target it."

I see some parallels in all of this cloud hype: The diagnosis is that your enterprise is spending too much on capital outlays for hardware; the cure is to pay a subscription fee to us services providers instead!

In an InfoWorld article, David Linthicum has a great bit from Miko Matumura:

 Miko is referring to the fact that "cloud," as a term and a symbol, has long referred to portions of the architecture you didn't want to explain. As Miko says, "Basically whenever someone didn't feel like drawing all of the network entities, they would just draw a puffy white cloud. In essence the puffy white cloud is shorthand for 'don't worry your pretty little head about this stuff.'" He's right about that, and also the fact that many considered "clouds" to be where the "miracle" occurs in the architecture. That was a running joke for years.

While it's told as a joke, I find this to be incredibly reflective of what I encounter when it comes to the cloud and non-techies: it's supposed to be some miracle pill that fixes all of our problems; don't worry about the composition, just take it and trust us, you'll feel better.  And it is exactly this foggy perspective that is cluttering this space with hype.

(With Matumura's anecdote in mind it's hard to ignore the parallels of the hype of this movement with the hype and mass shift towards outsourcing a few years ago: take your development and technical resources and make it an abstract, "puffy white cloud"...that's a topic for another rant.)

What makes the matter all the worse is that the term cloud computing is as nebulous as the name implies.  Jon Brodkin at NetworkWorld.com writes:

 I just performed a quick search of the archives on PRNewswire, and found 204 press releases mentioning the word “cloud” from the past 30 days. My e-mail inbox is full of cloud pitches, some of them useful, some of them not. A company called 3tera claims on the front page of its Web site to offer “the industry’s only cloud computing platform.” Really? It’s the only one?

 Other vendors are trying to confuse the definitions of the various types of cloud computing. The phrase “private cloud” seemed strange at first, but by now most people agree that it refers to some kind of highly virtualized, internal network with lots of self-service attributes, essentially an Amazon-like cloud designed by a business for its own users.

 One of the key attributes of a private cloud is that it’s run within your network – it’s not outsourced. But that didn’t stop Rackspace from claiming that it now offers “private clouds,” hosted in the Rackspace infrastructure. In other words, Rackspace is offering hosted data center services, similar to what they have always offered, and are now using the phrase “private cloud” to make them seem different and new.

Quite honestly, I'm sick of the hype and the excessive overusage of the term "cloud".  Are there tangible benefits to cloud-style application architecture?  Sure, you bet.  But I think it's important to sit back with a healthy dose of skepticism and evaluate how much of what's out there is hype and how much of what's out there is actually useful and cost effective. 

With specific regards to Microsoft's Azure, the time I spent with it was anything but pleasant: the documentation was lacking, the account and service management web interfaces were horrible and clunky, and the community knowledge is simply a void (but admittedly, this was early in the beta).  As I've seen with SharePoint, Microsoft has a habit of over-selling and under-delivering.  With two successive clients, I've found a consistent pattern of being sold a capability as "out-of-the-box" only to find that while you could do it, out-of-the-box, it would hardly be a productive or friendly user experience to do it that way.  My perspective is that as the current state of the platform comes into focus, it'll play out much the same way and we'll find that business folks, sales folks, CEOs, and CTOs will have to face the reality of an immature platform that's not yet worthy of the hype.

At least from my perspective, it's always better to under-sell and over-deliver; whether it's the fault of the vendors or not, the current hype is leading us down the opposite path, at least in the near term.

# Friday, February 12, 2010

My Grapefruit Addiction

Friday, February 12, 2010 12:33:57 AM UTC

Ever since I discovered that I could actually cut out grapefruit wedges (I lived for some 25 years before I discovered this fact!), I've been addicted to the tangy, juicy globes of goodness.  Half the fun is playing with sharp knives; half the fun is the tasty, tasty reward.  As a bonus, eating grapefruit allows you to bask in your manly skills.

Here's a guide to help you get your grapefruit addiction on:

Step 1: Take off the rind with a sharp knife

Step 2: Cut out the wedges (hat and scarf optional; happy face: mandatory!)

Step 2a: Check out that skill and precision!

Step 3: Squeeze the juice from the membranes (only manly men need apply)

Step 4: Squeeze the juice from the rinds

Step 5: Victory!

Did you know that Texas is one of the largest producers of Ruby Red grapefruits?  As a grapefruit loving Liberal, I'm highly conflicted by this!  Well, at least they're good for something; we can't let them secede just yet!

# Thursday, February 11, 2010

Google Buzz > Facebook

Thursday, February 11, 2010 11:43:04 PM UTC

Well, at least for me (and I suspect many others).

I caught Pete Cashmore's analysis:

There are arguably better video sites than YouTube and better photo hosts than Photobucket, but network effects tend to trump technical prowess in the social networking realm.

Google Buzz certainly isn't groundbreaking, but it will achieve critical mass virtually overnight. Thanks to integration with Gmail, the new tool is in the eye-line of the millions of users who obsessively check their inboxes for new mail. ComScore pegged Gmail at 176.5 million unique visitors in December.

But I think he just narrowly missed the mark, at least for me.  One critical difference is that because Buzz relies on your Gmail contacts, it creates a more focused social network; in other words, these are people that you actually communicate with already and thus content in Buzz seems to be much more relevant and interesting than Facebook.

Consider someone like my sister.  She has 643 friends in Facebook.  The question is what % of those people does she actually communicate with on a daily or even weekly basis?  How many of those people are just incidental contacts?  How many of those people are just sort of there? How many of those people does she actually care about?  How many of those people would invite her to their wedding?  I would guess that it's somewhere around 10-20%.

By integrating with Gmail, Google's big win is that your network is based on people that you actually communicate with.  In my opinion, this makes the social network more valuable and the information much more relevant.  Integration with a mail client will help the adoption rates for sure, but I think that the big win that will carry it forward as a success -- at least for users like me, who don't use Facebook as a network building or discovery tool -- is that the quality of content is much improved over Facebook.

# Friday, February 05, 2010

Paging With SPListItemCollectionPosition

Friday, February 05, 2010 10:48:01 PM UTC

Let it be known that Microsoft's terrible design of list paging is inexecusable and to make matters worse, the documentation is practically non-existent.  You would think that paging with a SPQuery would be a piece of cake!  I mean, I write paged data queries all the time.

NOT!

Colby Africa has a good overview of the issues and some basics that gave me some good insight into the core issues.  Of course, the difficulty with the SPQuery and paging isn't really with going forward, it's with going backwards.  One MSDN poster suggested storing the previous pages (see the last post here), which when you think about it, isn't a really good solution given the amount of paging strings you'd have to store (you can't just store the last navigation since going "Previous" twice requires going back twice).

Since going forward is the easy part, I won't get into that.  With regards to paging to previous pages, I hacked around a bit and spent a good 2-3 hours trying to deduce how the paging query string worked before I finally figured it out.

The algorithm works like so:

  1. When paging FORWARD, capture the ID <AND SORT FIELD VALUE> of the first item on the newly retrieved result collection.
  2. When paging BACKWARDS, use the previously captured ID <AND SORT FIELD VALUE> of the first item on the current result collection to generate the paging string.
  3. Once paged BACKWARDS, capture the ID <AND SORT FIELD VALUE> of the first item on the loaded page for the next BACKWARDS operation (just leave this off of the input query if going forwards again).

By capture, I mean to store the data and persist the data somehow (perhaps in the ViewState or SessionState).

And there you have it: three simple steps :-D

As an example, consider the following data set, sorted by Title:

ID		TITLE
---------------------------PAGE 1
17		Apple
21		Banana
18		Currant
---------------------------PAGE 2
19		Durian
5		Elderberry
1		Fig
---------------------------PAGE 3
7		Guava
10		Honey Dew
12		Indian Gooseberry

Consider a scenario where we're building a web application.  After the result set for page 3 of the data is loaded, I want to capture three pieces of data if I want to be able to load the previous page:

  1. The value of the ID of the FIRST item on the page
  2. The value of the TITLE of the FIRST item on the page (or whatever field you are sorting on)
  3. The PagingInfo string after the query is executed

The PagingInfo is already set for paging foward again.  When I page backwards, I will need to generate a new query using the PagingInfo.  To do so, I will need to:

  1. Replace the p_ID and set it to p_ID=7
  2. Replace the p_Title and set it to p_Title=Guava (replace p_Title with the static name of your sort field)
  3. Add two parameters:
    1. PagedPrev=TRUE
    2. PageLastRow=6 (last index of the second page, where we're going - this is easily calculated if you know your page size and your current page (keep these in the ViewState))

I used the following two regular expression patterns:

Regex _pidPattern = new Regex("p_ID=(?'pid'\\d+)");
Regex _pTitlePattern = new Regex("p_Title=[^&]+");

To replace the values in the string like so:

pagingInfo = _pidPattern.Replace(pagingInfo, 
	string.Format("p_ID={0}", firstItemId));
pagingInfo = _pTitlePattern.Replace(pagingInfo, 
	string.Format("p_Title={0}", firstTitle));

In this case, if I'm on page 3, the string for going back to page 2 should be:

Paged=TRUE&PagedPrev=TRUE&p_ID=7&p_Title=Guava&PageLastRow=6

And once I'm on page 2, the string required to go back to page 1 should be:

Paged=TRUE&PagedPrev=TRUE&p_ID=19&p_Title=Durian&PageLastRow=3

Well, actually, you don't need the string to go back to page 1; but this is just to give you the general idea.  In summary, the trick is pretty simple: you always need to store the PageInfo string for paging SharePoint queries and when you retrieve a resultset, you want to capture the ID and sort field of the first item.  When you go forward, the PageInfo string is enough.  When you go backwards, you need to use the captured pieces of info.

Hope that this has shed some light on the otherwise nebulous paging functionality with SharePoint list queries!

# Tuesday, January 26, 2010

Simple (?) AJAX Upload For ASP.NET

Tuesday, January 26, 2010 11:59:36 PM UTC

As I was working on an AJAX upload web part for SharePoint, I looked around to see if there was anything out there that would be suitable before I rolled my own after discovering that the ASP.NET UpdatePanel doesn't play nicely with file inputs.  Since I'm using jQuery, I figured I'd start there and see if there were any plugins which would meet the need.

To my dismay, it seems as if kids these days are all using flash (flash!) to implement asynchronous upload.  This was wholly unacceptable to me for some reason (not to mention it might cause compatibility issues for downstream clients) so I ended up tackling it myself :-D

The basics of getting this to work are simple enough; the solution is made of three main components:

  1. The main page that is displaying the upload control.  Since I was using this in a SharePoint site, I wanted to write my uploader as a web part (the control) that could be placed on any page.  The main page is simply the container for your control.  It does not post back.
  2. The control that basically just renders the <iframe/>.  The control is relatively simple.  It provides a container for the <iframe/> and also the scripts which are executed when the frame is loaded and unloaded.  The control also holds the progress layer since we want this to be shown when the user starts the upload.   This also does not post back.
  3. The upload page that receives the actual file.  This page is the target for the <iframe/> and contains the logic that validates the posted file.  This is the only page that posts back.

Before we go into the details, let's look at the screens of this in action (pay particular attention to the times):

The first screen shows the general layout of the page in the default state.  In case you're wondering, I found a handy guide for hacking the file input control and used that to customize the appearance.  Again, note the time.

Once you click "Upload", a progress layer is shown over the control.  You can also see that we've got an unloaded time now as well.

And finally, you can see that the loaded time changed once the form upload completes.

The control ASCX file contains two very simple scripts:

    function handleFrameLoaded() {
        // Do animation here.
        $("#progress").hide();
        
        $("#load").html("<b>Loaded!</b> " + 
            (new Date()).toTimeString());
    }

    function handleFrameUnloaded() {
        // Do animation here.
        $("#progress").show().fadeTo("fast", .90);
        
        $("#unload").html("<b>Unloaded!</b> " + 
            (new Date()).toTimeString());
    }

The first function is called when the frame is loaded and the second function is invoked when the file upload is submitted.  Note that both functions are called from the upload page in the <iframe/>.  In this case, I've just added simple animation calls to show and hide a progress panel.  You can hook up whatever custom code you want here.

On the upload page itself, we need to wire the events to the functions above:

    $(window).load(function() {
        parent.handleFrameLoaded();
    });

    $(document).ready(function() {
        $(".upload-button").click(function() {
            parent.handleFrameUnloaded();
        });

        /* simulate hover */
        $("#fake-container").hover(
            function() {
                $(this).addClass("hover");
            },
            function() {
                $(this).removeClass("hover");
            });

        /* simulate populating the file value since 
            we can't see the file input */
        $("input.file").change(function() {
            $("#fake input").val($(this).val());
        });
    });

It's important to note that the upload page gets its own set of window events since it's loaded inside of the frame.  The upload page makes calls to functions in the control.  I've highlighted the points of interest; you'll note that I only bind the load event of the window (I don't bind the unload).  It's also possible to do this using the onbeforeunload event, but I found that this would fire the progress layer even if I was browsing away from the page (which may confuse your users).  So it made more sense to just do it simply from the upload button click.

The upload page itself is remarkably simple:

<body>
    <form id="_form" runat="server">
    <div>
        <div id="fake-container">            
            <input type="file" id="_file" runat="server" class="file"/>            
            <div id="fake">
                <input type="text" />
            </div>            
        </div>
        <asp:Button runat="server" ID="_upload" 
            Text="Upload" OnClick="HandleUploadClick" 
            CssClass="upload-button"/>
    </div>
    </form>
</body>

The control isn't much more complex either:

<div id="frame">
    This is an asynchronous upload control.  
    The control load time is <asp:Label ID="_time" runat="server"/>

    <iframe id="upload-frame" src="Upload.aspx" 
        frameborder="0" scrolling="no" height="100px">

    </iframe>  
    <div id="progress" style="display:none;"></div>
</div>
<div>
    <div id="load"></div>
    <div id="unload"></div>
</div>

There's no codebehind for the control to speak of. The only place where you need to implement custom code is in the codebehind of the upload page to receive the posted file:

using System;
using System.Threading;
using System.Web.UI;

namespace AsyncUploadControlTest
{
    /// <summary>
    /// This is the actual page that handles the upload.
    /// </summary>
    public partial class Upload : Page
    {
        /// <summary>
        /// Handles the Load event of the Page control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="System.EventArgs"/> 
        /// instance containing the event data.</param>
        protected void Page_Load(object sender, EventArgs e)
        {
            
        }

        /// <summary>
        /// Handles the upload click.
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="e">The <see cref="System.EventArgs"/> 
        /// instance containing the event data.</param>
        protected void HandleUploadClick(object sender, EventArgs e)
        {
            // Just fake a long running upload for dramatic effect
            Thread.Sleep(2500);

            // Add your logic here
        }
    }
}

There you have it. Works in SharePoint 2007 just fine.  Works in IE7 and Firefox 3.5 as well.  Because the receiving upload page is just an ASPX page, you can simply output your errors or success messages to the page itself; no special hackery required.

Download the source code here: AsyncUploadControlTest.7z (17.54 KB)

# Wednesday, January 06, 2010

SharePoint 2010 Content Type Publishing Setup

Wednesday, January 06, 2010 9:46:35 PM UTC

One of the cool features of SharePoint 2010 is the simplification of content type management/synchronization.

In SharePoint 2007, site collections formed a metadata boundary meaning that content types defined in one site collection could not be used in another site collection without redeploying the content type to the other site collection.  This works well enough if you are using features to deploy your content types (and I mean, who isn't!?), but what about managing those content types and keeping them synchronized after you've deployed them?

Nightmare.

While it's relatively easy to set up, I came across an immediate error when trying to publish my first content type:

No valid proxy can be found to do this operation.

Using the correlation ID, I looked through the logs and found the following:

No proxy associated with the site http://metadata.dev.com
recognizes the site as a hub site d4d22525-2e9d-4ea4-becc-fe1d42735ee4 

A user failed to publish or unpublish a content type
Microsoft.SharePoint.Taxonomy.ContentTypeSync.ContentTypeSyndicationException:
No valid proxy can be found to do this operation.

I came across Chakkaradeep Chandran's instructions here, but his instructions seemed to have a small gap.

To access the functionality, you need to go to Central Administration -> Application Management -> Manage service applications (under Service Applications)

In this screen, select (don't click the link) Managed Metadata Service.

Again, very important: don't click the link (that takes you to the screen for managing keywords/terms/taxonomy; click somewhere next to the text and select the row to enable the Properties button at the top.

Next click Properties:

This will bring up the pane where you can set the Content Type hub setting (scroll to the bottom of the screen).  Just enter the URL of your hub application (as an example, http://metadata.dev.com)

Next, you need to select the Managed Metadata Service Connection (the indented Managed Metadata Service) and again, click Properties.  This brings up the pane that will allow you to check the Consumes content types from the... setting.

As Chakaradeep mentions, you need to run two timer jobs to get the content types to sync up.  You can access this via Central Administration -> Check Job Status (under Monitoring):

Then page through that list looking for Content Type hub and Content Type subscriber.  Clicking on the link to the job will open it up and allow you to click the Run now button to execute the job immediately.

In any case, it's a neat feature.  I wonder, though, if there are improvements for managing deployment of content types via features (i.e. an incremental feature which adds new fields to exsiting content types and pushes the changes down) as opposed to UI based management of content types (which I'm not a terribly big fan of).  My guess is "no".

Hope this helps someone!

# Wednesday, December 30, 2009

C# and ASP.NET Syntax Highlighting in Trac

Wednesday, December 30, 2009 1:08:37 AM UTC

Well, spent the good amount of time trying to figure this out.  See the configuration info below from my trac.ini file.

[mimeviewer]
max_preview_size = 262144
mime_map = text/x-dylan:dylan,text/x-idl:ice,text/x-ada:ads:adb,
php_path = php
pygments_default_style = trac
pygments_modes = text/x-csharp:csharp:7,text/plain:aspx-cs:7
tab_width = 4
treat_as_binary = application/octet-stream,application/pdf,application/postscript,application/rtf

Oh yeah, it helps if you actually install Pygments, too.

# Tuesday, December 22, 2009

SharePoint Design Patterns: Entry 2.5

Tuesday, December 22, 2009 3:11:48 AM UTC

In the previous entry, we looked at how we can model a SharePoint list item using a more domain specific model to simplify programmatic access to the list item thus reducing otherwise error prone data access code and making the overall framework easier to use.  Again: the idea is to promote reuse and decrease complexity through domain specific code that abstracts the underlying SharePoint object models, making it easier for a team to build functionality on top of this framework.

One interesting point is that if you're already building your fields and content types using features XML, the work required to generate the domain specific wrappers can be simplified dramatically using automation.  In a sense, a content type is basically a class (this is generally how I map them in my domain design); why double your effort and write both the content types and the classes?

So how do we go about this?

(Side note: this isn't so much a "design pattern" as it is an "implementation pattern")

As an example, here is a simple XML file which defines a set of fields and content types which use those fields:

<?xml version="1.0" encoding="utf-8" ?>
<Elements xmlns="http://schemas.microsoft.com/sharepoint/">
  <Field DisplayName="Model Code"
    Name="Model_Code"
    StaticName="Model_Code"
    ID="{F0000000-0000-0000-0000-000000000001}"
    Type="Integer"
    SourceID="http://schemas.someusedcarinventory.com"
    Group="My Custom Columns"/>
  <Field DisplayName="VIN"
    Name="VIN"
    StaticName="VIN"
    ID="{F0000000-0000-0000-0000-000000000002}"
    Type="Text"
    SourceID="http://schemas.someusedcarinventory.com"
    Group="My Custom Columns"/>
  <Field DisplayName="Make"
    Name="Make"
    StaticName="Make"
    ID="{F0000000-0000-0000-0000-00000000003}"
    Type="Text"
    SourceID="http://schemas.someusedcarinventory.com"
    Group="My Custom Columns"/>
  <ContentType Name="Vehicle"
    ID="0x0100FC000000000000000000000000000001"
    Description="Used car inventory"
    Group="My Custom Content Types" >
    <FieldRefs>
      <FieldRef ID="{c042a256-787d-4a6f-8a8a-cf6ab767f12d}" Name="ContentType" />
      <FieldRef ID="{fa564e0f-0c70-4ab9-b863-0177e6ddd247}" Name="Title" 
        Required="TRUE" ShowInNewForm="TRUE" ShowInEditForm="TRUE" />
      <FieldRef ID="{F0000000-0000-0000-0000-000000000001}" Name="Model_Code"/>
      <FieldRef ID="{F0000000-0000-0000-0000-000000000002}" Name="VIN"/>
      <FieldRef ID="{F0000000-0000-0000-0000-000000000003}" Name="Make"/>
    </FieldRefs>
  </ContentType>  
  <Field DisplayName="Dealership Code"
    Name="Dealership_Code"
    StaticName="Dealership_Code"
    ID="{F0000000-0000-0000-0000-00000000004}"
    Type="Integer"
    SourceID="http://schemas.someusedcarinventory.com"
    Group="My Custom Columns"/>
  <Field DisplayName="Dealership Fax Number"
    Name="Dealership_Fax_Number"
    StaticName="Dealership_Fax_Number"
    ID="{F0000000-0000-0000-0000-00000000005}"
    Type="Text"
    SourceID="http://schemas.someusedcarinventory.com"
    Group="My Custom Columns"/>
  <ContentType Name="Dealership"
    ID="0x0100FC000000000000000000000000000002"
    Description="Dealerships"
    Group="My Custom Content Types" >
    <FieldRefs>
      <FieldRef ID="{c042a256-787d-4a6f-8a8a-cf6ab767f12d}" Name="ContentType" />
      <FieldRef ID="{fa564e0f-0c70-4ab9-b863-0177e6ddd247}" Name="Title" 
        Required="TRUE" ShowInNewForm="TRUE" ShowInEditForm="TRUE" />
      <FieldRef ID="{F0000000-0000-0000-0000-000000000004}" Name="Dealership_Code"/>
      <FieldRef ID="{F0000000-0000-0000-0000-000000000005}" Name="Dealership_Fax_Number"/>
    </FieldRefs>
  </ContentType>    
</Elements>

When approaching this problem, I considered three ways of handling the class file generation:

  1. Use an object model and StringTemplate to create .cs files.  This invovled writing POCO classes (or generating them from the schema) which I could deserialize the XML to and then passing those objects to a template instance.  This seemed like too much work, given that I really didn't feel like maintaining all of that code as well.  Plus, while StringTemplate isn't - by any sense of the imagination - hard, it is a non-standard syntax that someone would have to learn to maintain and/or extend the conversion.
  2. Use an XDocument and CodeDom to create .cs files.  This seemed like even more work!  While it's framework supported, I feel like this solution would be hard to extend and maintain for most developers.
  3. Use an XSL transform to create .cs files.  This seemed to be the most natural solution given that the source file is already in XML format and a the target content structure is far from complex (the basic class file is fairly simple).  Plus, while XSLT isn't trivial, it's not that hard either (and the syntax is "standard").

One of the cool features of XSL 2.0 is the xsl:result-document element which allows you to create multiple documents from one source document.  Only one problem: .NET's XSLT engine doesn't implement XSLT 2.0!  What a bummer; it seemed like if I wanted to get this to work and generate multiple output files, it was going to take some work in code or find an XSLT 2.0 capable processor.

Enter Saxon, which provides an XSLT 2.0 processor for .NET.  The following code takes the XML above and uses the xsl:result-document to create two class files, one for each content type:

using System;
using System.IO;
using Saxon.Api;

namespace FeatureToClass
{
    internal class Program
    {
        private static void Main(string[] args)
        {
            Uri xmlFile = new Uri(
                @"C:\Users\Charles\Desktop\elements.xml");

            // Create a Processor instance.  
            Processor p = new Processor();

            // Load the source document.  
            XdmNode node = p.NewDocumentBuilder().Build(xmlFile);

            using (Stream stream = File.OpenRead("core-transform.xslt"))
            {
                // Create a transformer for the stylesheet.  
                XsltTransformer transformer = 
                    p.NewXsltCompiler().Compile(stream).Load();

                // Set the root node of the source document
                // to be the initial context node.  
                transformer.InitialContextNode = node;

                // BaseOutputUri is only necessary for xsl:result-document.  
                transformer.BaseOutputUri = xmlFile;

                transformer.SetParameter(
                    new QName("ct", "http://www.customtransform.com", "namespace"), 
                    new XdmAtomicValue("My.Custom.Package"));

                // Create a serializer.  
                Serializer serializer = new Serializer();
                transformer.Run(serializer);
            }
        }
    }
}

The code above is a simple console program that takes a (hardcoded) path to a source XML file (a SharePoint elements.xml file) and (hardcoded) namespace and loads an XSL file to transform the XML to C# class files.

Here's the transform (it's a bit messy for output formatting reasons, so you're best off copying it into an XML aware text editor to get a better view):

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE stylesheet [
    <!ENTITY space "<xsl:text> </xsl:text>">
    <!ENTITY cr "<xsl:text>
</xsl:text>">
]>

<xsl:stylesheet
  xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
  xmlns:sp="http://schemas.microsoft.com/sharepoint/"
  xmlns:xs="http://www.w3.org/2001/XMLSchema"
  xmlns:functx="http://www.functx.com"
  xmlns:ct="http://www.customtransform.com"
  version="2.0">
    <xsl:param name="ct:namespace"/>
    <xsl:output method="text"/>

    <xsl:template match="/">
        <xsl:for-each select="//sp:ContentType">
            <xsl:variable name="classname" select="replace(@Name, ' ', '')"/>
            <xsl:variable name="filename" select="concat($classname,'.cs')" />
            <xsl:value-of select="$filename" />
            <!-- Creating  -->
            <xsl:result-document href="{$filename}">
using System;

    namespace <xsl:value-of select="$ct:namespace"/>
    {
        public partial class <xsl:value-of select="$classname"/>
        {
            private string _contentTypeId;
            
            public string ContentTypeId {
                get { return _contentTypeId; }
                set { _contentTypeId = value; }
            }

            public <xsl:value-of select="$classname"/>()
            {
                _contentType = "<xsl:value-of select='@Name'/>";
                _contentTypeId = "<xsl:value-of select='@ID'/>";
            }

            <xsl:apply-templates/>
    }
}
            </xsl:result-document>
        </xsl:for-each>
    </xsl:template>

    <!--///
        Templates
    ///-->
    <xsl:template match="sp:FieldRef">
        <xsl:variable name="fieldname" select="functx:lower-first(replace(@Name, '_', ''))"/>
        <xsl:variable name="fieldid" select="@ID"/>
        <xsl:variable name="dotnettype">
            <xsl:choose>
                <xsl:when test="//sp:Field[(@ID = $fieldid) and (@Type = 'Integer')]">int</xsl:when>
                <xsl:otherwise>string</xsl:otherwise>
            </xsl:choose>
        </xsl:variable>

        private <xsl:value-of select="$dotnettype"/> _<xsl:value-of select="$fieldname" />;
        <xsl:call-template name="attribute"><xsl:with-param name="fieldid" select="$fieldid"/></xsl:call-template>
        public <xsl:value-of select="$dotnettype"/>&space;<xsl:value-of select="replace(@Name, '_', '')"/>
        {
            get { return _<xsl:value-of select="$fieldname" />; }
            set { _<xsl:value-of select="$fieldname" /> = value; }
        }
    </xsl:template>

    <xsl:template name="attribute">
        <xsl:param name="fieldid"/>
    <xsl:if test="exists(//sp:Field[@ID = $fieldid]/@ID)">[Caml(Id="<xsl:value-of select='//sp:Field[@ID = $fieldid]/@ID'/>", StaticName="<xsl:value-of select='//sp:Field[@ID = $fieldid]/@StaticName'/>", Type="<xsl:value-of select='//sp:Field[@ID = $fieldid]/@Type'/>")]</xsl:if></xsl:template>


    <!--///
        Custom functions
    ///-->
    <xsl:function
        name="functx:lower-first" as="xs:string"
        xmlns:functx="http://www.functx.com" >
        <xsl:param name="arg" as="xs:string"/>
        <xsl:sequence select="concat(lower-case(substring($arg,1,1)), substring($arg,2))"/>
    </xsl:function>
</xsl:stylesheet>

I borrowed one function from the FunctX library to create the camelCased field names.  The XSL probably isn't nearly as clean or optimized as it should be (my XSL is admittedly a bit rusty), but it gets the job done.  Here's one of the two classes (and class files) which get generated at the source directory of the input XML file:

using System;

namespace My.Custom.Package 
{
    public partial class Dealership
    {
        private string _contentTypeId;
        public string ContentTypeId {
            get { return _contentTypeId; }
            set { _contentTypeId = value; }
        }   

        public Dealership()
        {
            _contentType = "Dealership";
            _contentTypeId = "0x0100FC000000000000000000000000000002";
        }
                        
        private string _contentType;
        public string ContentType
        {
            get { return _contentType; }
            set { _contentType = value; }
        }      

        private string _title;
        public string Title
        {
            get { return _title; }
            set { _title = value; }
        }
    
        private int _dealershipCode;
        [Caml(Id="{F0000000-0000-0000-0000-000000000003}", 
            StaticName="Dealership_Code", Type="Integer")]
        public int DealershipCode
        {
            get { return _dealershipCode; }
            set { _dealershipCode = value; }
        }
    
        private string _dealershipFaxNumber;
        [Caml(Id="{F0000000-0000-0000-0000-000000000004}", 
            StaticName="Dealership_Fax_Number", Type="Text")]
        public string DealershipFaxNumber
        {
            get { return _dealershipFaxNumber; }
            set { _dealershipFaxNumber = value; }
        }
    }
}

Awesome!  It's amazing how little code was required to get this basic scenario working.

Now we have a single source which defines our SharePoint artifacts and our code artifacts; I love it.  Write your fields and content types in your feature and you get class files for free!  You'll note that I've added a simple CamlAttribute where applicable.  This will prove handy when it comes time to automate construction of the object instance from a SharePoint list item, which we'll look at next time (for the time being, feel free to modify the XSL and remove the line for it or write an implementation of CamlAttribute).

Again, to reiterate: the goal is make it easy to build applications on top of a SharePoint deployment by adding a layer of domain specific APIs and objects so that a team can be productive while reducing duplication and the ramp up time required to understand the business domain. 

Other points for improvement and enhancement (look for these in a future installment):

  1. Parameterize the program.
  2. Consider making it a Visual Studio add-in or a custom tool.
  3. Make it go the other way; in other words: generate the content type and field XML from class files (which would be cool, too).

But even as it is, it's incredibly useful.  On the next installment, we'll see how we can build more intelligence into the model and make it more useful.

RSS 2.0 Atom 1.0 CDF