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.

2 comments:

David H said...

Hi Dave,
Great content on these Yammer posts :) Very helpful for a novice like myself.
I'm trying to create a similar desktop app using RestSharp. Could you provide a little more info on the code for the Models and ViewModel declared in another class? I'm not sure what needs to be in there.
Thanks a lot

David Gardiner said...

Hi David,

The ViewModel is pretty simple - just the class with properties you want to get rendered in the XAML:

public class MessageViewModel
{
public string Body { get; set; }
public DateTime Created { get; set; }
public string Sender { get; set; }
public Uri PhotoUrl { get; set; }
}


The Model class maps the properties that are coming from the JSON data. I'm not a RestSharp expert, but this class is an example of what I'm using:

public class Message
{
public DateTimeOffset created_at { get; set; }
public Uri client_url { get; set; }
public bool system_message { get; set; }
public int network_id { get; set; }
public int thread_id { get; set; }
public Uri web_url { get; set; }
public int Id { get; set; }
public int sender_id { get; set; }
public string Privacy { get; set; }
public MessageBody Body { get; set; }
}

Possibly the underscores aren't necessary - depending on how RestSharp does it's mapping, but it worked for me.

I'll probably be making the full source code public at some stage too.

-dave