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.