Async REST Client for the Scrumy API

July 17, 2011 | REST

I am a big fan of Scrumy, I must admit that! Scrumy is a simple and intuitive virtual task board based on some concepts of Scrum that helps organize and manage your projects (scrumy.com)

The last weeks I have been thinking of a Visual Studio Extension for viewing (and interacting with) an entire Scrum (Sprints, Stories, Tasks, etc) from inside the IDE. I started building a client library around the Scrumy REST API. I wanted it to be fully asynchronous because I would like to make the Extension UI responsive.

Back a few months ago, I was watching a 5hr webcast on Windows Azure called Windows Azure Deep Dive with Jeffrey Richter were Jeffrey Richter shared among the (fantastic code samples) a fully asynchronous HttpRestClient class. This class is making heavy use of the AsyncEnumerator class (which I am big fan of, till C# 5.0 with async is out) so I though I should build my client around the HttpRestClient class and make also use of the AsyncEnumerator class.

I started by looking at the GET response:

<scrumy>
    <created-at>2011-06-24T21:49:57Z</created-at>
    <time-zone>Central Time (US & Canada)</time-zone>
    <updated-at>2011-06-24T23:38:50Z</updated-at>
    <url>nikos</url>
</scrumy>

<sprints type="array">
    <sprint>
        <created-at>2011-06-24T21:50:53Z</created-at>
        <id>186884</id>
        <start-date>2011-06-24</start-date>
        <updated-at>2011-06-24T21:50:53Z</updated-at>
        <scrumy-url>nikos</scrumy-url>
    </sprint>
</sprints>

Then I created the corresponding classes:

/// <example>
///     <scrumy>
///         <created-at>2011-06-24T21:49:57Z</created-at>
///         <time-zone>Central Time (US & Canada)</time-zone>
///         <updated-at>2011-06-24T21:57:08Z</updated-at>
///         <url>nikos</url>
///     </scrumy>
/// Editable fields: url, time_zone
/// </example>
public sealed class Scrumy
{
    public DateTimeOffset CreatedAt { get; set; }

    public string TimeZone { get; set; }

    public DateTimeOffset UpdatedAt { get; set; }

    public string Url { get; set; }
}

/// <example>
///     <sprints type="array">
///        <sprint>
///            <created-at>2011-06-24T21:50:53Z</created-at>
///            <id>186884</id>
///            <start-date>2011-06-24</start-date>
///            <updated-at>2011-06-24T21:50:53Z</updated-at>
///            <scrumy-url>nikos</scrumy-url>
///        </sprint>
///    </sprints>
/// Editable fields: url, time_zone
/// </example>
public sealed class Sprint
{
    public DateTimeOffset CreatedAt { get; set; }

    public int Id { get; set; }

    public DateTimeOffset StartDate { get; set; }

    public DateTimeOffset UpdatedAt { get; set; }

    public string ScrumyUrl { get; set; }
}

Next I included generic Begin/End methods for supporting the APM inside my class:

private IAsyncResult BeginRequest(
     ScrumyRequest request, 
     Func<XElement, ScrumyResponse> processor, 
     AsyncCallback callback = null,
     object state = null)
{
    var ae = new AsyncEnumerator<ScrumyResponse>(
         string.Format("Method={0}, Uri={1}", request.Method, request.Uri));
    ae.SyncContext = null;
    return apmWrap.Return(ae,
        ae.BeginExecute(MakeRequest(ae, request, processor),
            apmWrap.Callback(ae, callback), state));
}

private new TResponse EndRequest<TResponse>(IAsyncResult result) 
     where TResponse : ScrumyResponse
{
    return (TResponse)apmWrap.Unwrap(ref result).EndExecute(result);
}

private IEnumerator<int> MakeRequest(
     AsyncEnumerator<ScrumyResponse> ae, 
     ScrumyRequest request, 
     Func<XElement, ScrumyResponse> processor)
{
    base.BeginRequest(request.Method, request.Uri, ae.End());
    yield return 1;

    XElement element = base.EndRequestXElement(ae.DequeueAsyncResult());

    ae.Result = processor.Invoke(element);
} 

With these helper methods, dealing with the APM was trivial when implementing methods for the Scrumy client. Here are the methods I had to write for getting the Sprints:

public IAsyncResult BeginGetScrumy(
     GetScrumyRequest request, 
     AsyncCallback callback = null, 
     object state = null)
{
    Func<XElement, GetScrumyResponse> processor = element =>
    {
        DateTimeOffset createdAt;
        DateTimeOffset.TryParse(
             element.Element("created-at").Value, out createdAt);

        DateTimeOffset updatedAt;
        DateTimeOffset.TryParse(
             element.Element("updated-at").Value, out updatedAt);

        var scrumy = new Scrumy
        {
            CreatedAt = createdAt,
            TimeZone = element.Element("time-zone").Value,
            UpdatedAt = updatedAt,
            Url = element.Element("url").Value
        };

        return new GetScrumyResponse { Scrumy = scrumy };
    };

    return BeginRequest(request, processor, callback, state);
}

public GetScrumyResponse EndGetScrumy(IAsyncResult ar)
{
    return EndRequest<GetScrumyResponse>(ar);
} 

As you notice, only the logic that creates a Sprint object from an XElement is inside the Begin part. Everything else is handled by the helper classes.

Finally, here is how to use it:

AsyncEnumerator ae = new AsyncEnumerator();            
ae.BeginExecute(GetSprint(ae), ae.EndExecute);

private IEnumerator<int> GetSprint(AsyncEnumerator ae)
{
    var request = new GetSprintRequest(client.ProjectName);

    client.BeginGetSprint(request, ae.End());
    yield return 1;

    var response = client.EndGetSprint(ae.DequeueAsyncResult());
    Assert.NotEmpty(response.Sprints);
}

I am looking forward building as much as I can and then to continue with the Visual Studio Extension.

A gist with all the source code can be found here.