Friday, 16 March 2012

MetroYam–Windows Phone Edition

Part 3 of a series on developing .NET clients for Yammer.
For the Windows Phone version of MetroYam, I'm again making use of RestSharp to handle the REST API work. Once difference when using RestSharp on Windows Phone compared to the desktop is that it only exposes asynchronous methods.
So instead of calling Execute, we have to call the ExecuteAsync method. Here's the equivalent code:
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using Gardiner.MetroYam.Core;
using RestSharp;
using RestSharp.Authenticators;

namespace Gardiner.MetroYam.PhoneClient
{
    public class Network
    {
        private readonly ObservableCollection<MessageViewModel> _messages;

        public Network( ObservableCollection<MessageViewModel> messages )
        {
            _messages = messages;
        }

        public void Request()
        {
            var client = new RestClient();

            client.BaseUrl = "https://www.yammer.com/";
            string consumerKey = "12312341234243";
            string consumerSecret = "kciud84kf0943kdi4kdk3kr";

            var oauth_token = "84944kf984444";
            var oauth_token_secret = "kdiekemfckcfiejekmekduj454j4";

            var request = new RestRequest( "api/v1/messages.json", Method.GET );
            client.Authenticator = OAuth1Authenticator.ForProtectedResource(
                consumerKey, consumerSecret, oauth_token, oauth_token_secret
                );

            // 2011/03/28 20:39:12 +0000
            request.DateFormat = "yyyy/MM/dd HH:mm:ss zzzzz";

            client.ExecuteAsync<MessageList>( request, Callback );
        }

        private void Callback( RestResponse<MessageList> response, RestRequestAsyncHandle arg2 )
        {
            var users = new Dictionary<int, User>();
            foreach ( Reference reference in response.Data.References )
            {
                if ( reference.Type == "user" )
                    users.Add( reference.Id, new User { Fullname = reference.FullName, Photo = reference.mugshot_url } );
            }

            foreach ( var message in response.Data.messages )
            {
                var vm = new MessageViewModel();
                vm.Body = message.Body.Plain;
                vm.Created = message.created_at.LocalDateTime;
                User user = users[ message.sender_id ];
                vm.Sender = user.Fullname;
                if ( user.Photo != null )
                    vm.PhotoUrl = user.Photo;
                else
                    vm.PhotoUrl = new Uri( "https://c64.assets-yammer.com/images/no_photo_small.gif" );

                _messages.Add( vm );
            }
        }
    }
}
Rather than a single method that returns the list of messages, we now have a class that populates an ObservableCollection. A reference to this is passed in via the constructor. The ViewModel page that calls this code is straight forward:
using System;
using System.Collections.ObjectModel;
using Caliburn.Micro;
using Gardiner.MetroYam.Core;

namespace Gardiner.MetroYam.PhoneClient
{
    public class MainPageViewModel : Screen
    {
        private ObservableCollection<MessageViewModel> _messages;
        public ObservableCollection<MessageViewModel> Messages
        {
            get { return _messages; }
            set { _messages = value;
                NotifyOfPropertyChange( () => Messages );
            }
        }

        public MainPageViewModel()
        {
            Messages = new ObservableCollection<MessageViewModel>();
        }

        protected override void OnInitialize()
        {
            base.OnInitialize();

            var network = new Network( Messages );

            network.Request();
        }
    }
}
Through the magic of Caliburn Micro, the public properties exposed by this ViewModel class are automatically bound to the View. In this case we are using a simple Panorama-based Windows Phone application template. The result is an application that looks like this:
Screenshot of MetroYap on Windows Phone
It would be nice leverage the Reactive Extensions (Rx) library to simplify access to the ExecuteAsync method, but because this method doesn't just return an IAsyncResult this is a little more involved. Sergy posted to the RestSharp group an example last year, but I assume the RestSharp and Rx libraries have changed since then as his code no longer compiles for me.

Thursday, 15 March 2012

Internal MSBuild Error: MSB0001

Encountered this weird error from MSBuild today:

Unhandled Exception: Microsoft.Build.Shared.InternalErrorException: MSB0001: Internal MSBuild Error: MSB0001: Internal MSBuild Error: Global Request Id 6 has not been assigned and cannot be retrieved. 
============= 
Microsoft.Build.Shared.InternalErrorException: MSB0001: Internal MSBuild Error: Global Request Id 6 has not been assigned and cannot be retrieved. 
   at Microsoft.Build.Shared.ErrorUtilities.ThrowInternalError(String message, Object[] args) 
   at Microsoft.Build.Shared.ErrorUtilities.VerifyThrow(Boolean condition, String unformattedMessage, Object arg0) 
   at Microsoft.Build.BackEnd.SchedulingData.GetScheduledRequest(Int32 globalRequestId) 
   at Microsoft.Build.BackEnd.Scheduler.HandleRequestBlockedOnInProgressTarget(SchedulableRequest blockedRequest, BuildRequestBlocker blocker) 
   at Microsoft.Build.BackEnd.Scheduler.ReportRequestBlocked(Int32 nodeId, BuildRequestBlocker blocker) 
   at Microsoft.Build.Execution.BuildManager.HandleNewRequest(Int32 node, BuildRequestBlocker blocker) 
   at Microsoft.Build.Execution.BuildManager.Microsoft.Build.BackEnd.INodePacketHandler.PacketReceived(Int32 node, INodePacket packet) 
   at Microsoft.Build.BackEnd.NodeManager.RoutePacket(Int32 nodeId, INodePacket packet) 
   at Microsoft.Build.BackEnd.NodeProviderInProc.RoutePacket(Int32 nodeId, INodePacket packet) 
   at Microsoft.Build.BackEnd.NodeEndpointInProc.SendData(INodePacket packet) 
   at Microsoft.Build.BackEnd.InProcNode.OnNewRequest(BuildRequestBlocker blocker) 
   at Microsoft.Build.BackEnd.BuildRequestEngine.RaiseRequestBlocked(BuildRequestBlocker blocker) 
   at Microsoft.Build.BackEnd.BuildRequestEngine.IssueBuildRequest(BuildRequestBlocker blocker) 
   at Microsoft.Build.BackEnd.BuildRequestEngine.IssueUnsubmittedRequests() 
   at Microsoft.Build.BackEnd.BuildRequestEngine.EngineLoop()

Turns out I had a <Target /> with a DependsOnTarget attribute which referenced a target that didn’t exist (because I’d forgotten to <Import /> the related .targets file in which it was defined!)

Wednesday, 14 March 2012

MetroYam–WPF desktop edition

Part 2 of a series on developing .NET clients for Yammer.

The first client that I've attempted is the WPF version. This version of MetroYam uses the MahApps.Metro library to enable a Metro interface.

Once the UI framework is in place it's time to register with Yammer for an API key. You need this to access their REST API. Initially this just allows you to access the Developer Yammer group, but it may be possible to apply to Yammer to make it useable by any Yammer user.

Using the OAuth1 integration test from the RestSharp source code repository as a guide, I created a class with the following method prototype accessing a feed of messages (keys changed to protect the innocent!)

public List<MessageViewModel> Request()
{
    var client = new RestClient();
            
    client.BaseUrl = "https://www.yammer.com/";
    string consumerKey = "0121212121212";
    string consumerSecret = "abcdefgkskdlasllkjsfien3234sdfsdf";

    client.Authenticator = OAuth1Authenticator.ForRequestToken( consumerKey, consumerSecret );
    var request = new RestRequest( "oauth/request_token", Method.POST );
            
    var response = client.Execute( request );

    var qs = HttpUtility.ParseQueryString( response.Content );
    var oauth_token = qs[ "oauth_token" ];
    var oauth_token_secret = qs[ "oauth_token_secret" ];

    request = new RestRequest( "oauth/authorize" );
    request.AddParameter( "oauth_token", oauth_token );
    var url = client.BuildUri( request ).ToString();
    Process.Start( url );

    var verifier = "01234"; // <-- Breakpoint here (set verifier in debugger)

    request = new RestRequest( "oauth/access_token", Method.POST );
    client.Authenticator = OAuth1Authenticator.ForAccessToken(
    consumerKey, consumerSecret, oauth_token, oauth_token_secret, verifier
    );
    response = client.Execute( request );

    qs = HttpUtility.ParseQueryString( response.Content );
    oauth_token = qs[ "oauth_token" ];
    oauth_token_secret = qs[ "oauth_token_secret" ];

    Debug.WriteLine("oauth_token {0}", oauth_token);
    Debug.WriteLine( "oauth_token_secret {0}", oauth_token_secret );

    request = new RestRequest( "api/v1/messages.json", Method.GET );
    client.Authenticator = OAuth1Authenticator.ForProtectedResource(
    consumerKey, consumerSecret, oauth_token, oauth_token_secret
    );

    // 2011/03/28 20:39:12 +0000
    request.DateFormat = "yyyy/MM/dd HH:mm:ss zzzzz";

    RestResponse<MessageList> responseList = client.Execute<MessageList>( request );

    var users = new Dictionary<int, User>();
    foreach ( Reference reference in responseList.Data.References )
    {
        if ( reference.Type == "user" )
            users.Add( reference.Id, new User() { Fullname = reference.FullName, Photo = reference.mugshot_url});
    }

    var messages = new List<MessageViewModel>();

    foreach ( var message in responseList.Data.messages )
    {
        var vm = new MessageViewModel();
        vm.Body = message.Body.Plain;
        vm.Created = message.created_at.LocalDateTime;
        User user = users[ message.sender_id ];
        vm.Sender = user.Fullname;
        if ( user.Photo != null )
            vm.PhotoUrl = user.Photo;
        else
            vm.PhotoUrl = new Uri( "https://c64.assets-yammer.com/images/no_photo_small.gif" );

        messages.Add(vm);
    }

    return messages;

}

The Models (Message, User etc) and ViewModel (MessageViewModel) are declared in the 'portable' class library. This library is shared between the WPF, Windows Phone and Metro-style WinRT applications.

This particular method causes a web browser to load part-way through to obtain a user authentication code. Normally an application would cache the oauth_token and oauth_token_secret values returned by the "oauth/access_token" call so the user wouldn't need to re-authenticate the app each time.

The relevant XAML to render this is pretty straightforward (not that I'm a XAML expert like Nigel!)

<ListView Grid.Column="1" Grid.Row="1" Margin="20,0,0,0" x:Name="Messages" 
            HorizontalContentAlignment="Stretch"
            ScrollViewer.HorizontalScrollBarVisibility="Disabled">
    <ListView.ItemTemplate>
        <DataTemplate>

            <Border Padding="0,0,0,10">
                <DockPanel ScrollViewer.HorizontalScrollBarVisibility="Disabled" >
                    <Image Width="48" Height="48" Source="{Binding PhotoUrl, IsAsync=True}" DockPanel.Dock="Left" VerticalAlignment="Top" />
                    <TextBlock Text="{Binding Path=Sender}" DockPanel.Dock="Top" FontWeight="Bold" />
                    <TextBlock Text="{Binding Path=Created}" DockPanel.Dock="Top" />
                    <TextBlock Text="{Binding Path=Body}" DockPanel.Dock="Bottom" TextWrapping="WrapWithOverflow" />

                </DockPanel>
            </Border>
        </DataTemplate>
    </ListView.ItemTemplate>
</ListView>

And to confirm that this does actually render something, here's a screenshot of work in progress:

SNAGHTML86cea1b

I like how the <Image/> element can take an Internet URL so that it just loads the photos automatically.

My plan is that the client will default to the 'company' list in the first column, but let you add additional columns to follow other groups that you are a member of.

Monday, 12 March 2012

MetroYam–a .NET Yammer client (or three)

I first came across Yammer via Rob Farley when I was working at LobsterPot Solutions. Imagine Twitter and Facebook morphed together, but just for a single organisation instead of publishing your thoughts for all the world (or your friends) to see. Yammer knows what information to show you because it's based around your email address – so for example everyone who has a @gardiner.net.au email address would be able to collaborate on Yammer together, without worrying that their posts would be seen by non @gardiner.net.au users.

It's quite a useful platform for sharing resources and information amongst colleagues, and a useful adjunct to other communication methods (face to face, phone, email, IM). The basic offering is free, but if you want to have more administrative control you pay for the privilege.

Access is via their website, but they also provide a number of platform-specific clients too. Unfortunately the Windows client isn't the greatest. I've had problems where it wouldn't auto-update, and the current version doesn't seem to have working notifications. It also uses Adobe AIR, which is just one more think to keep patched. It's enough to make you want to write a decent client and maybe learn a few things along the way – queue 'MetroYam' – my name for a Yammer client built on .NET.

My plans are to build a number of clients:

  • a WPF-based Windows desktop application
  • a Metro-style app for Windows 8 (WinRT)
  • a Windows Phone app

It will be interesting to see how much code I can share and reuse between the 3 different platforms.

The inspiration for the user interface I plan to build is MetroTwit (my Twitter client of choice) – a great example of the Metro design that Microsoft are building into Windows 8.

Screenshot of MetroTwit

It won't be a carbon-copy but they've got some good ideas worth emulating.

Resources

Yammer API

Lucky for me, Yammer have a REST API that they provide for things just like this. It uses OAuth for authentication and JSON.

Caliburn Micro

I plan to use Caliburn Micro for all three clients. I've used CM before, for my 'Aussie Toilets' Windows Phone app, so this will be a chance to explore it's support for WPF and WinRT.

RestSharp

For the WPF and Windows Phone clients, I'm going to make use of the RestSharp library to access the Yammer API as it provides support for JSON as well as OAuth. RestSharp doesn't support support WinRT, so I'll need to take a different approach there.

Visual Studio 2010 + 2012 beta

And finally it's happened! You can open solutions and projects in 2012 not be restricted from still using them in 2010. I'll need to use VS 2012 to build the WinRT projects, but I'll also need to still use VS2010 to build the phone app (as the 2012 beta doesn't come with Windows Phone support yet).

Thursday, 8 March 2012

Updating Castle Windsor from v2.5 to 3.0

Late last year the Castle Windsor project released a major update to some of their components - from version 2.5 to 3.0.
There are some breaking changes (most of which are documented in the BreakingChanges.txt files for Core and Windsor), so I thought I’d highlight a few issues that may be more common.

ILogger

The logging abstraction interface has been simplified, with a number of the overloaded methods removed. Whereas before you might have called ILogger.Debug("A message {0}", arg1), you now would use ILogger.DebugFormat() instead.

Component Registration

In 2.5 you could use the .Unless() method to add a condition (usually to skip registration if component already exists). This method has been replaced with the equivalent OnlyNewServices() method.

IHandler

This used to have a 'Service' property. Now refer to the IHandler.ComponentModel property which has a Services collection property.

Facilities

Previously, the IWindsorContainer.AddFacility() method required a string 'key' parameter. This is has been removed.

IHandler.AddCustomDependencyValue

This method has been removed, but you can now use the new Depends() method instead during registration. eg.
Component.For<IService>()
.ImplementedBy<Service>()
.DependsOn(
    Dependency.OnValue("parameterName", "CustomValue")
);

IKernel.RemoveComponent

This method has been removed. There is now no way to remove a component.

Compatibility

NServiceBus 2.x

NServiceBus 3 is compatible with Castle 3.0, but if you're still using NServiceBus 2 and don't want to upgrade, you'll need to update your WindsorObjectBuilder class. Have a look at the source code in NServiceBus 3 and adapt it to your implementation - https://github.com/NServiceBus/NServiceBus/blob/master/src/impl/ObjectBuilder/ObjectBuilder.CastleWindsor/WindsorObjectBuilder.cs

NHibernate

As of NHibernate 3.2, there is no longer a requirement to have a Castle-compatible bytecode provider.
The old NHibernateIntegration facility is not compatible with Castle 3.0 or NHibernate 3.2.
There is an alternate facility- 'Castle NHibernate' that leverages FluentNHibernate for configuration. This has been patched to work with Castle 3.0 but not NHibernate 3.2 (partly because FluentNHibernate hasn't been updated for 3.2). It also depends on Castle.Transactions, which ironically whilst at version 3.0 isn't actually compatible with Castle 3.0 either.
So for the meantime if you want to use NHibernate with Castle 3.0 you're going to have to get your hands dirty and update those dependant projects yourself.
I've done some of this on forks of these projects, and will update this post to the source repositories when they are available.

Friday, 2 March 2012

Windows 8 first impressions

Microsoft made the "Consumer Preview" of Windows 8 available yesterday, and last night I downloaded it and installed it on my laptop (using Boot to VHD so I wouldn't affect my existing Windows 7 install).

First impressions are very positive. The install went smoothly, and I was able to add in my GMail account for the email and calendar apps.

One of the first things that I noticed was the weather app was using Fahrenheit. This is when I discovered a handy keyboard shortcut [Windows]+[C], which brings up the 'Charms' which are where you can change settings for the current application (and so it was changed to Celsius).

The new interface is quite a contrast from the traditional Windows desktop. Windows Phone users will find more similarities with the new desktop Metro interface. You can see the 'Metro-style' apps on the screen-shot above. They're the ones with the nice icons/tiles. The other icons are from the install of Visual Studio 2011. (This is a regular Windows application so you get regular icons). Again, like Windows Phone, the tiles for the new apps can update themselves (eg. the calendar, email etc).

The email and calendar apps are simple but functional. A number of other apps aren't fully working at the moment (most due to 'regional' restrictions).

So far it has been rock solid - no crashes to speak of.

The Metro interface lends itself well to touch interaction. Unfortunately I don't have a touch screen on my laptop but I can certainly see how touch would work pretty well with it - again the similarities to Windows Phone help.

I'll be experimenting with seeing what's involved with porting some of my Windows Phone apps to work on Windows 8.