• 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 \_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 \_messages; public ObservableCollection 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.

  • 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!)

  • 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.