Pages

Wednesday, 11 August 2010

I've been scammed! A follow-up to Twitter Auth Issues

This is a follow-up post to Social Network Authorisation Needs to Change.

Having written the above post over a month ago and considering myself to be quite net-savvy, I'm hugely embarrassed and mortified to admit that I've just been victim to a Twitter-related scam. This is the scam site that duped me: http://www.ipadappstesting.com/. It's safe to browse to it - JUST DON'T LOG IN!

I received a Twitter Direct Message (DM) from a trusted friend that invited me to go to the site so that I could sign up to be an iPad tester. At the end of the test period I would get to keep the hardware. Superb! Yeh, right.

My spidey-senses were working well enough that I didn't complete the in-depth financial survey they put in front of me. What did happen, however, was their servers sent DMs to, presumably ALL, my friends inviting them to do the same. Needless to say that this was without my knowledge - let alone my consent.

Twitter, seriously guys, this needs to change quickly otherwise you're going to go the way of Facebook.

The access granted to my account for an application needs to be segmented and I need to have the ability to REVOKE any aspect I'm not entire happy with at login time. For instance, the shill application in question should have had to request DM read / write access during their registration with Twitter. This should then have appeared as a checkbox on the Twitter OAuth screen. I would then have unchecked it.

Feeling rather violated now but, hey, how was I to know? I currently just have to put my trust in the application developers and I don't think that's either fair or sustainable.

Monday, 12 July 2010

Faking PivotViewer in Blend 4

If you've been using the new PivotViewer Silverlight control then you've probably come across the Blend problem. Basically, it doesn't work and you get the following error...

"Error HRESULT E_FAIL has been returned from a call to a COM component."

This problem does not occur in Visual Studio 2010 (VS2010) - although the page view does look a little suspect. Some people have suggested commenting out the PivotViewer element when opening the page in Blend, but there's a much better approach - fakes.

First, create a new "Silverlight Class Library" project in VS2010 called FakePivot and, once loaded, delete the Class1.cs file. Now add a new Code File called PivotViewer.cs with the following starting code:

using System;

namespace FakePivot
{
    public class PivotViewer
    {

    }
}

Next add a reference to the PivotViewer library (System.Windows.Pivot.dl) and the SharedUI library (System.Windows.Pivot.SharedUI.dll). If you don't find them under the .Net tab you can browse for them at <Program Files>\Microsoft SDKs\Silverlight\v4.0\PivotViewer\Jun10\Bin\. Now update your PivotViewer class to look like this...

using System;

namespace FakePivot
{
    public class PivotViewer : System.Windows.Pivot.PivotViewer
    {

    }
}

Right click on Microsoft's PivotViewer and choose "Go To Definition". You should now see the following metadata file:

#region Assembly System.Windows.Pivot.dll, v2.0.50727
// C:\Program Files\Microsoft SDKs\Silverlight\v4.0\PivotViewer\Jun10\Bin\System.Windows.Pivot.dll
#endregion

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Resources;
using System.Windows;
using System.Windows.Browser;
using System.Windows.Controls;

namespace System.Windows.Pivot
{
    [ScriptableType]
    [TemplatePart(Name = "PART_Container", Type = typeof(Grid))]
    public class PivotViewer : Control, INotifyPropertyChanged
    {
        public PivotViewer();
        public PivotViewer(ResourceDictionary colorScheme);

        public IDictionary<string, IList<string>> AppliedFilters { get; }
        public int CollectionItemCount { get; }
        public string CollectionName { get; }
        public Uri CollectionUri { get; }
        public string CurrentItemId { get; set; }
        public ICollection<string> InScopeItemIds { get; }
        public string SortFacetCategory { get; }
        public string ViewerState { get; }

        public event EventHandler CollectionLoadingCompleted;
        public event EventHandler<CollectionErrorEventArgs> CollectionLoadingFailed;
        public event EventHandler<ItemActionEventArgs> ItemActionExecuted;
        public event EventHandler<ItemEventArgs> ItemDoubleClicked;
        public event EventHandler<LinkEventArgs> LinkClicked;
        public event PropertyChangedEventHandler PropertyChanged;

        protected virtual List<CustomAction> GetCustomActionsForItem(string itemId);
        public PivotItem GetItem(string id);
        public void LoadCollection(string collectionUri, string viewerState);
        public override void OnApplyTemplate();
        public static void SetResourceManager(ResourceManager resourceManager);
    }
}


What we want to do next is extract the interface for the standard PivotViewer class. If you have a refactoring tool I'd use it, otherwise you just have to do it manually. Either way, we should now have an interface in our project called IPivotViewer.cs ...

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Windows.Pivot;

namespace FakePivot
{
    public interface IPivotViewer : INotifyPropertyChanged
    {
        IDictionary<string, IList<string>> AppliedFilters { get; }
        int CollectionItemCount { get; }
        string CollectionName { get; }
        Uri CollectionUri { get; }
        string CurrentItemId { get; set; }
        ICollection<string> InScopeItemIds { get; }
        string SortFacetCategory { get; }
        string ViewerState { get; }

        event EventHandler CollectionLoadingCompleted;
        event EventHandler<CollectionErrorEventArgs> CollectionLoadingFailed;
        event EventHandler<ItemActionEventArgs> ItemActionExecuted;
        event EventHandler<ItemEventArgs> ItemDoubleClicked;
        event EventHandler<LinkEventArgs> LinkClicked;
        
        PivotItem GetItem(string id);
        void LoadCollection(string collectionUri, string viewerState);
    }
}

Next, we want to go back to our PivotViewer class and make it extend Control and implement our new IPivotViewer interface like this...

using System;
using System.Collections.Generic;
using System.Windows.Pivot;
using System.ComponentModel;
using System.Windows.Controls;

namespace FakePivot
{
    public class PivotViewer : Control, IPivotViewer
    {
        public IDictionary<string, IList<string>> AppliedFilters { get { throw new NotImplementedException(); } }

        public int CollectionItemCount { get { throw new NotImplementedException(); } }

        public string CollectionName { get { throw new NotImplementedException(); } }

        public Uri CollectionUri { get { throw new NotImplementedException(); } }

        public string CurrentItemId { get { throw new NotImplementedException(); } set { throw new NotImplementedException(); } }

        public ICollection<string> InScopeItemIds { get { throw new NotImplementedException(); } }

        public string SortFacetCategory { get { throw new NotImplementedException(); } }

        public string ViewerState { get { throw new NotImplementedException(); } }

        public event EventHandler CollectionLoadingCompleted;

        public event EventHandler<CollectionErrorEventArgs> CollectionLoadingFailed;

        public event EventHandler<ItemActionEventArgs> ItemActionExecuted;

        public event EventHandler<ItemEventArgs> ItemDoubleClicked;

        public event EventHandler<LinkEventArgs> LinkClicked;

        public event PropertyChangedEventHandler PropertyChanged;

        public PivotItem GetItem(string id) { throw new NotImplementedException(); }

        public void LoadCollection(string collectionUri, string viewerState) { throw new NotImplementedException(); }
    }
}

OK, I know, I know - this is pretty convoluted. But, what we now have is a 'real' Silverlight control that exposes the same interface as Microsoft's control. Here's how to use it...

Let's add a new "Silverlight Application" project to our solution - we'll call it PivotViewerApp. For this example we don't need to "Host the Silverlight application in a new Web site". Now add three references to this project (2 of which we already added to our FakePivot project):

  1. System.Windows.Pivot
  2. System.Windows.Pivot.SharedUI
  3. Our FakePivot project

VS2010 should have opened the MainPage.xaml file in "split view" mode. Let's add some references to our 2 namespaces of interest (pivot and fakepivot) and then create our pivot control...

<UserControl x:Class="PivotViewerApp.MainPage"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"             
    xmlns:pivot="clr-namespace:System.Windows.Pivot;assembly=System.Windows.Pivot"
    xmlns:fakepivot="clr-namespace:FakePivot;assembly=FakePivot"
    mc:Ignorable="d"
    d:DesignHeight="300" d:DesignWidth="400">

    <Grid x:Name="LayoutRoot" Background="White">
        <pivot:PivotViewer x:Name="pivotViewer1" />
    </Grid>
</UserControl>

If you now right click on MainPage.xaml in the Solution Explorer and choose "Open in Expression Blend" you'll see the exception I mentioned at the start of this post. So let's go back to VS2010 and change the namespace of our pivotViewer1 control from pivot to fakepivot...


<UserControl x:Class="PivotViewerApp.MainPage"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"             
    xmlns:pivot="clr-namespace:System.Windows.Pivot;assembly=System.Windows.Pivot"
    xmlns:fakepivot="clr-namespace:FakePivot;assembly=FakePivot"
    mc:Ignorable="d"
    d:DesignHeight="300" d:DesignWidth="400">

    <Grid x:Name="LayoutRoot" Background="White">
        <fakepivot:PivotViewer x:Name="pivotViewer1" />
    </Grid>
</UserControl>

Because both namespaces contain a PivotViewer class our xaml is perfectly happy and the solution will still compile. Now go ahead and reopen the same file in Blend - ta dah, no exceptions.

The real beauty of this approach, however, becomes evident when working with the fake control in Blend. Because it exposes exactly the same events, methods and properties as the Microsoft control we can use the Blend UI to hook into these. Selecting pivotViewer1 in our Objects and Timeline window we can then click the Events button in the Properties tab and see all the events that we'd expect our real control to expose.

Once we've done your design work in Blend we, obviously, have to revert our fakepivot namespace to pivot before we can build anything useful. One option is to go back to VS2010 to do this. However, if you hide the Design window in Blend and just have the XAML window visible you can change it there and successfully compile it.

Hopefully, that wasn't too complicated and, once you have your FakePivot library, you can reuse it in any related projects. If anyone want me to do a video of this process please let me know in the comments.

Finally, click here to download the example solution for this post.

Tuesday, 6 July 2010

Social Network Authorisation Needs to Change

A few weeks ago I took a look at a website that needed my twitter login to work. The nature of the site was overtly read-only so I was happy to grant it access via twitter's OAuth process. Yesterday I took another exploratory look at a Facebook application which requested access to my account. Again, the nature of this application was completely read-only. Both apps were mildly interesting and I'd achieved what I'd set out to do. Done.

Imagine my [surprise | outrage | fury] (you choose!) when I discovered that both apps had posted public comments from my account. WTF!? Both used the familiar template of 'I have just used [appX] to do [functionY]. Go to [urlA] to try it yourself.'.

OK, so nothing malicious in that - but I didn't authorise either of these posts. Facebook does give you the ability to deny an application from posting in your name, but only after you've installed it. If the app posts immediately there's nothing you can do about it.

Now, don't get me wrong, good applications deserve to be blown along on the virtual word-of-mouth jet stream; but, and here's the critical bit:

"It should be my decision to publicise my usage of your site."

At TweetPivot we made a very conscious decision to enable a user to promote our site easily but not to presume that that's what they wanted. If the site's good enough they will, but automatically doing it for them removes any worth from the act.

So, what should happen now?

Well, you have to apply to Twitter if you want your application to be able to use their OAuth process. At that point you are asked whether your application requires read-only or read-write access to users' accounts. When I enter my details into the popup OAuth window I want to be told whether I'm giving write rights to the app and, if that's not acceptable to me, I want to be able to decline that 'write' request. If you want me to try out an application that I know has no reason to write to my account then I need confirmation that you can't.

I would hate to see the Twitter authentication process get as complicated as Facebook's became; but it does need improving. The API that all 3rd-parties hook into has very specific, well defined methods. Developers should have to declare, individually, which ones they need to invoke. For instance, if I gave you read-only access to my account how can I be sure that you haven't just farmed off all my private Direct Messages?

Ultimately, this is going to be bad news for application developers that require integration to social networks. The next time I'm asked to try something like this I might hesitate. The time after that I might decline. Good developers are going to be punished and their great apps ignored by the unacceptable actions of the few.

Friday, 18 June 2010

Adding Twitter Search to Chrome

This article will show you how to add 2 new Search Engines ('Twitter User' and 'Twitter Hashtag') to Chrome in just a few minutes.

First, right click on the Chrome Omnibox and choose 'Edit search engines...'. Alternatively, go to Chrome Options and click on the 'Manage' button in 'Default search:' area. Either way you should now be looking at the 'Search Engines' dialog...



Click the Add button to create the following search engine...

Name: Twitter User
Keyword: @
URL: http://twitter.com/@%s



Click OK.

Next, click the Add button again to create our second search engine like this...

Name: Twitter Hashtag
Keyword: #
URL: http://twitter.com/search?q=%23%s



Click OK and then close any remaining open dialog windows.

So now, whenever you start typing the @ symbol Chrome will activate the Twitter User search



and whenever you want to search for a hashtag just start typing with the #symbol...

Tuesday, 8 June 2010

If Klingons Wrote Software

I like Star Trek. Not in a uniform-wearing, funny handshake, cross-to-the-other-side-of-the-street-to-avoid-me kind of way; but I like it and I do seem to be able to remember a lot of details from it. For instance: when Klingons go into battle they assume that they are already dead. If they survive it's a bonus and if they do actually die then, well, it was expected so no surprise. This is the way we should be developing software and it has a name - "Lean Startup".

I've written a lot of applications targeting all manner of media and platforms - but they all had the same sentiment: "If we only get this right, everyone will buy it". I now think this is wrong. What we should be thinking is "This is a pile of crap and no-one's gonna buy it".

"What?! You mean we should actual aim to fail?"

No.

What I mean is this: if you assume that your software is probably worthless then this changes the way you write it. Your job now becomes "I'm probably gonna fail, so how do I fail quickly?". Eric Ries talks about failing products not equating to failing companies.

So, your business plan becomes "I have a lot of ideas for products. One of them might succeed. How do I quickly discover which one that is without wasting time and money on those that no one is interested in?"

What does this mean in practice? Here's a small sample list:

  1. Minimum Viable Product. Get it out there! It'll be crap, but do it and do it quickly.
  2. Measure everything. How do you know which parts of your application people are interested in if you don't capture that information?
  3. Smoke Tests. See how many people click on the fake 'Buy It Now' button. That tells you more than any theoretical pricing model or traditional market research.
  4. Pivot. You might have the basis of a great idea. Don't be fixated on what you think it should do - listen to your users.

The Lean Startup movement has radically changed how I develop software and it's not just for startups. Any project, even in the enterprise, that has a level of uncertainty will fit this model.

For more information check out Eric Ries' blog.

Introduction to Code Contracts

Firstly, to those who went to DDDSW last weekend. Apologies if this post just re-hashes what Barry Carr said during his Code Contracts talk. I didn't see if myself although it was the existence of this item on the agenda that prompted me to investigate this topic.

Now, let me start by saying "I love Code Contracts". They will save you time, code and headaches if you implement them correctly. Rather than just talk theory I thought it would be more informative to go through a worked example together...


The Bank Demo


We've been asked to add a simple feature to an existing banking application that allows transfers to be made from one account to another. Here are the, entirely sensible, starting classes we've created...

interface IAccount
{
  string Name { get; }
  double Balance { get; }
}

class BankAccount : IAccount
{
  string Name { get; set; }
  double Balance { get; set; }
}

static class TransferService
{
  public static void TransferMoney(IAccount from, IAccount to, double amount)
  {
    from.Balance -= amount;
    to.Balance += amount;
  }
}

Well, that would work - mostly; but we have no exception handling or validation. So, here's how we would normally amend that...

Ugly Validation


interface IAccount
{
  string Name { get; }
  double Balance { get; set; }
}

class BankAccount : IAccount
{
  // Name is required, so add it to the constructor...
  public BankAccount(string name)
  {
    if (string.IsNullOrEmpty(name))
      throw new ArgumentNullException("name");

    Name = name;
  }
  public string Name { get; private set; }
  public double Balance { get; set; }
}

static class TransferService
{
  public static void TransferMoney(IAccount from, IAccount to, double amount)
  {
    // None of the accounts can be null and the amount must be positive...
    if (from == null)
      throw new ArgumentNullException("from");
    if (to == null)
      throw new ArgumentNullException("to");
    if (from.Balance < amount)
      throw new ArgumentOutOfRangeException("from account does not have enough money to transfer");

    from.Balance -= amount;
    to.Balance += amount;
  }
}

Ok, so that's better; but we're starting to really muddy our previously clean classes with all the new validation code. The other problem is that the interface (IAccount) doesn't define the full contract. What we actually want to enforce is 'has a Name property', 'has a Balance property' AND 'the Name property cannot be empty'. Clearly there's no mechanism in the language to specify our final requirement - but it's important. To fix this we find ourselves leaking contract information into classes - in this case our BankAccount class. Now, we could solve this by creating an abstract Account class but then the TransferService would have to change as well and, to be honest, you may as well just get rid of the interface because you cannot use it without the abstract class.

Whilst we've been thinking about this the 'boss' reminds us that the Balance on any account can never be negative. OK, well that destroys our nice, clean auto-implemented property! The BankAccount class now needs to be amended to this...

class BankAccount : IAccount
{
  // Name is required, so add it to the constructor...
  public BankAccount(string name)
  {
    if (string.IsNullOrEmpty(name))
      throw new ArgumentNullException("name");

    Name = name;
  }
  public string Name { get; private set; }

  private double _balance;

  public double Balance
  {
    get
    {
      return _balance;
    }
    set
    {
      if (value < 0)
        throw new ArgumentOutOfRangeException("Your Bank does not allow your account to be negative.");
      _balance = value;
    }
  }
}

I think I prefer the cleanness of our original BankAccount class - how do I get that back?

Well, wouldn't it be the best thing you'd heard this month if you could define the whole of your contract just via the interface? 3rd party developers could then create their own account classes to use in your Transfer Service without you having to tell them about the extra Name or Balance validation. This is where Code Contracts come in.

Contract Requirements

The Contract class in the System.Diagnostics.Contracts namespace has a number of static methods that can be used in a similar way to assertions in unit tests. With them we can tidy up our validation code...

interface IAccount
{
  string Name { get; }
  double Balance { get; set; }
}

class BankAccount : IAccount
{
  // Name is required, so add it to the constructor...
  public BankAccount(string name)
  {
    Contract.Requires<ArgumentNullException>(!string.IsNullOrEmpty(name));

    Name = name;
  }
  public string Name { get; private set; }

  private double _balance;

  public double Balance
  {
    get
    {
      return _balance;
    }
    set
    {
      Contract.Requires<ArgumentNullException>(value >= 0, "Your Bank does not allow your account to be negative.");

      _balance = value;
    }
  }
}

static class TransferService
{
  public static void TransferMoney(IAccount from, IAccount to, double amount)
  {
    Contract.Requires<ArgumentNullException>(from != null, "from cannot be null");
    Contract.Requires<ArgumentNullException>(to != null, "to cannot be null");
    Contract.Requires<ArgumentNullException>(from.Balance >= amount, "'from' account does not have enough money to transfer");

    from.Balance -= amount;
    to.Balance += amount;
  }
}

So now you're thinking "Well, that's nice. My code is a little tidier, but I've effectively just changed syntax.". And you'd be right - almost. The benefit to using the Contract methods is that they can be evaluated at compile time and during static analysis. However, in order to reach the tipping point where you might start thinking "OK, I'm going to use this!" we have to start using some of the Contract Attributes.

Contract Attributes

Contract Attributes allow you to move your validation to a separate class.

[ContractClass(typeof(AccountContract))]
interface IAccount
{
  string Name { get; }
  double Balance { get; set; }
}

[ContractClassFor(typeof(IAccount))]
class AccountContract : IAccount
{
  public string Name
  {
    get
    {
      return string.Empty;
    }
  }

  public double Balance
  {
    get
    {
      return 0;
    }
    set
    {
      Contract.Requires<ArgumentNullException>(value >= 0, "Your Bank does not allow your account to be negative.");
    }
  }
}

class BankAccount : IAccount
{
  public string Name { get; private set; }
  public double Balance { get; set; }

  public BankAccount(string name)
  {
    Name = name;
  }
}

Now that's much nicer. Now all my validation logic is in a separate class and, with the use of the ContractClass and ContractClassFor attributes, my interface now defines my entire contract.

Contract Invariance

Another very useful attribute is ContractInvariantMethod. This allows you to decorate one, and only one, method in your class that contains contract logic that will be validated every time a property changes on your class. You don't even have to worry about calling it yourself! We could use that to tidy up our AccountContract class...

[ContractClassFor(typeof(IAccount))]
class AccountContract : IAccount
{
  public string Name { get; private set; }

  public double Balance { get; set; }

  [ContractInvariantMethod]
  private void ObjectInvariant()
  {
    Contract.Invariant(!string.IsNullOrEmpty(Name), "Name cannot be empty.");
    Contract.Invariant(Balance >= 0, "Balance cannot be negative.");
  }
}

In order to introduce another feature of the Contracts namespace I will change the IAccount interface so that Balance is only gettable. This, then, requires the addition of 2 new methods to withdraw and deposit money to the account...

[ContractClass(typeof(AccountContract))]
interface IAccount
{
  string Name { get; }
  double Balance { get; }

  void WithdrawMoney(double amount);
  void DepositMoney(double amount);
}

Change Awareness (OldValue)

So, what contract should we define for these new methods? In each case 'amount' needs to be non-negative but also the Balance needs to change by the correct amount. If I deposit £10 I expect my Balance to increase by the same amount. We can make good use of Contract.OldValue for this...

[ContractClassFor(typeof(IAccount))]
class AccountContract : IAccount
{
  public string Name { get; private set; }

  public double Balance { get; set; }

  [ContractInvariantMethod]
  private void ObjectInvariant()
  {
    Contract.Invariant(!string.IsNullOrEmpty(Name), "Name cannot be empty.");
    Contract.Invariant(Balance >= 0, "Balance cannot be negative.");
  }

  public void WithdrawMoney(double amount)
  {
    Contract.Requires<ArgumentNullException>(amount >= 0);

    Contract.Ensures(Balance == Contract.OldValue(Balance) - amount);
  }
  public void DepositMoney(double amount)
  {
    Contract.Requires<ArgumentNullException>(amount >= 0);

    Contract.Ensures(Balance == Contract.OldValue(Balance) + amount);
  }
}

Putting it all together

Finally, you're going to need to download a Microsoft DevLabs addon to Visual Studio to make all this work. DevLabs: Code Contracts automatically changes your code during compilation to bind you contract classes referenced in your interfaces to you concrete classes.

Conclusions

Code Contracts simplify validation, complete your interface definitions and keep you code clean. It enhances Design Patterns, for instance the Adapter Pattern, by allowing your interface to express your entire intent. I haven't covered anywhere near what this namespace can do for you, so go check out the rest yourselves!

Sunday, 23 May 2010

Pivot API for .Net

Firstly, I don't know whether this is strictly an API, a wrapper or an object model for Pivot. So, shall we agree to gloss over the semantics and just say that it is a bunch of related classes that will allow you to easily create your Pivot collections in a .Net environment and then save them as .cxml files? OK? Good.

Once you've read through all this there are links to download the source code and binaries at the end of this post.

If you don't already know Microsoft Live Labs is in the process of releasing a pretty cool piece of data "analysis" called Pivot. At the time of writing this you need to download their browser to experience the 'goodness' but by summer 2010 we've been assured a Silverlight plugin.

Let's get another thing straight: you could just go write your own xml files for this; after all, that is the product we ultimately need to create. However, why not let someone else (me) do the hard work so that all you need to do is just new-up some classes?

Finally, I need to assume that you have a reasonable understanding of the architecture and components involved in the Pivot application. You can get all you need from here.

The School Class Collection


The Pivot API contains very little documentation or comments. This is because, I hope, the classes and methods document themselves with their names. So, the best way I could think to demonstrate the behaviour and usage was through a worked example.

For this example let's assume that I have a pre-populated collection of Pupil objects (IList<Pupil>) that represents the raw data that I wish to create a Pivot Collection from.

class Pupil
{
  public long ID { get; set; }
  public string Name { get; set; }
  public DateTime DateOfBirth { get; set; }
  public string Class { get; set; }
  public string ReportCard { get; set; }
  public string Description { get; set; }
  public Pupil BestFriend { get; set; }
}

Our starting object will be an instance of the PivotCollection class. You'll need to supply this with a name, a version and a reference to an already created Deep Zoom Collection. I will not cover how to perform the latter but there's plenty of information out there to help you with this. You can also, optionally, add some copyright information to the collection.

var collection = new PivotCollection("School Collection", "schoolCollection.dzc", "1.0")
{
  Copyright = new CollectionCopyright { Name = "Chris Arnold", Href = "http://goodcoffeegoodcode.blogspot.com/" }
};

Next we have to define what information (Facets) the items in our collection will expose. The API defines a number of different Facet Category classes that encapsulate some inherent functionality.

collection.FacetCategories.Add(new StringFacetCategory("Class"));
collection.FacetCategories.Add(new DateTimeFacetCategory("Date of Birth"));
collection.FacetCategories.Add(new LongStringFacetCategory("Report Card"));
collection.FacetCategories.Add(new LinkFacetCategory("Best Friend"));

You could also encapsulate all of this into your own class to make your code more readable:

class SchoolPivotCollection : PivotCollection
{
  public SchoolPivotCollection()
  : base("School Collection", "schoolCollection.dzc", "1.0")
  {
    Copyright = new CollectionCopyright { Name = "Chris Arnold", Href = "http://goodcoffeegoodcode.blogspot.com/" };

    FacetCategories.Add(new DateTimeFacetCategory("Date of Birth"));
    FacetCategories.Add(new LongStringFacetCategory("Report Card"));
    FacetCategories.Add(new LinkFacetCategory("Best Friend"));
  }
}

We've now set up all of the pre-requisites for our collection. Time to start populating it. To do this we just need to iterate over our collection of Pupils, create a Pivot Item for each one and add it to the Pivot collection...

var pupils = GetPupils();

  foreach (var pupil in pupils)
    AddPupilToCollection(pupil, collection);

void AddPupilToCollection(Pupil pupil, PivotCollection collection)
{
  string PUPIL_URL = "http://www.myschool.com/pupils?id={0}";
  string PUPIL_COLLECTION_URL = "http://www.myschool.com/collection/pupil_{0}.cxml";

  var item = new Item()
  {
    Name = pupil.Name,
    Href = string.Format(PUPIL_URL, pupil.ID),
    Description = pupil.Description,
    Img = "#" + imageCounter,   /* This assumes that the order of the pupils is the same as their images in the deep zoom collection  */
    Id = pupil.ID
  };

  item.Facets.Add(FacetFactory.Create(collection.FacetCategories["Class"], pupil.Class));
  item.Facets.Add(FacetFactory.Create(collection.FacetCategories["Date of Birth"], pupil.DateOfBirth.ToShortDateString()));
  item.Facets.Add(FacetFactory.Create(collection.FacetCategories["Report Card"], pupil.ReportCard));

  var facet = FacetFactory.Create(collection.FacetCategories["Best Friend"], string.Format(PUPIL_COLLECTION_URL, pupil.BestFriend.ID)) as LinkFacet;
  facet.Name = pupil.BestFriend.Name;

  collection.Items.Add(item);

  imageCounter++;
}

The final step in the process is to save the collection to an xml document. This is handled using the classes in the PivotAPI.XmlWriter namespace...

using (var writer = new XmlTextWriter("pupils.cxml", System.Text.Encoding.Default) { Formatting = Formatting.Indented })
{
  var collectionWriter = new PivotAPI.XmlWriters.PivotCollectionXmlWriter(writer, collection);

  collectionWriter.Write();
}

And that's the only client code you'll need to create a collection document (.cxml).

Here's the complete code example. Create a new console application, reference the PivotAPI library and replace the Program.cs with the following...

using System;
using System.Collections.Generic;
using System.Xml;

using PivotAPI;

namespace BlogPivotExample
{
    class Program
    {
        static int imageCounter;

        static void Main()
        {
            var collection = new SchoolPivotCollection();

            var pupils = GetPupils();

            foreach (var pupil in pupils)
                AddPupil(pupil, collection);

            using (var writer = new XmlTextWriter("pupils.cxml", System.Text.Encoding.Default) { Formatting = Formatting.Indented })
            {
                var collectionWriter = new PivotAPI.XmlWriters.PivotCollectionXmlWriter(writer, collection);

                collectionWriter.Write();
            }
        }

        static void AddPupil(Pupil pupil, PivotCollection collection)
        {
            string PUPIL_URL = "http://www.myschool.com/pupils?id={0}";
            string PUPIL_COLLECTION_URL = "http://www.myschool.com/collection/pupil_{0}.cxml";

            var item = new Item()
            {
                Name = pupil.Name,
                Href = string.Format(PUPIL_URL, pupil.ID),
                Description = pupil.Description,
                Img = "#" + imageCounter,   /* This assumes that the order of the pupils is the same as their images in the deep zoom collection  */
                Id = pupil.ID
            };

            item.Facets.Add(FacetFactory.Create(collection.FacetCategories["Class"], pupil.Class));
            item.Facets.Add(FacetFactory.Create(collection.FacetCategories["Date of Birth"], pupil.DateOfBirth.ToShortDateString()));
            item.Facets.Add(FacetFactory.Create(collection.FacetCategories["Report Card"], pupil.ReportCard));

            var facet = FacetFactory.Create(collection.FacetCategories["Best Friend"], string.Format(PUPIL_COLLECTION_URL, pupil.BestFriend.ID)) as LinkFacet;
            facet.Name = pupil.BestFriend.Name;

            collection.Items.Add(item);

            imageCounter++;
        }
        static IList GetPupils()
        {
            var pupils = new List();

            //TODO: this is just a test pupil. You'll have to write your own routine if you want more data than this!
            var testPupil = new Pupil()
            {
                Name = "Chris Arnold",
                ID = 34873,
                Class = "Upper 5A",
                Description = "Lovely child",
                DateOfBirth = DateTime.Parse("1/1/1973"),
                ReportCard = "Chris has worked very hard this year. Well done!"
            };

            pupils.Add(testPupil);

            return pupils;
        }
    }
}

Now create 2 new classes in you application...

using System;
using PivotAPI;

namespace BlogPivotExample
{
    class SchoolPivotCollection : PivotCollection
    {
        public SchoolPivotCollection()
            : base("School Collection", "schoolCollection.dzc", "1.0")
        {
            Copyright = new CollectionCopyright { Name = "Chris Arnold", Href = "http://goodcoffeegoodcode.blogspot.com/" };

            FacetCategories.Add(new DateTimeFacetCategory("Date of Birth"));
            FacetCategories.Add(new LongStringFacetCategory("Report Card"));
            FacetCategories.Add(new LinkFacetCategory("Best Friend"));
        }
    }
}
using System;

namespace BlogPivotExample
{
    internal class Pupil
    {
        public long ID { get; set; }
        public string Name { get; set; }
        public DateTime DateOfBirth { get; set; }
        public string Class { get; set; }
        public string ReportCard { get; set; }
        public string Description { get; set; }
        public Pupil BestFriend { get; set; }
    }
}

Caveats


  • The API uses LINQ and so you'll need version 3.5 or 4.0 of the .Net framework.
  • The API is v1.0.0 and is not complete. Missing functions include the Supplement file, BrandImage, AdditionalSearchText, Icon. Also, some of the extensions have not been implemented yet e.g. DateRange, SortOrder and SortValue. These will all follow very quickly.

Tweet Pivot


Just to demonstrate that this isn't just all smoke and mirrors I wrote a website called Tweet Pivot. This utilises the Pivot API to get Twitter data and create dynamic collections. This is also where I've hosted the API for convenience.

So, if you like Tweet Pivot or the Pivot API please re-tweet them! Thanks.

Download Pivot API Source Code and Binaries