다음을 통해 공유


Using the yield pattern with HealthVault on Windows Phone 7 (part 2)

In part 1, I described the overall design of the HealthVaultTask class. Now, I’ll go through the implementation. First, I’ll get some of the helper classes. Out of the way. The first is the exception type for errors from HealthVault that captures the error code and error text. Note: this implementation doesn’t make the exception serializable, but that would be straightforward to do if needed.

 public class HealthVaultException : Exception
{
    public HealthVaultException(int errorCode, string errorText)
    : base(errorText)
    {
        this.ErrorCode = errorCode;
    }

    public int ErrorCode { get; set; }
}

Next, is the HealthVaultOperation class. The pattern with this class is that DoOperations yields one of these with the information about the HealthVaultRequest, and when control returns after the yield, the response data will be filled out. It also has a helper function to throw a HealthVaultException in the case of an error. But, it’s up to the DoOperations implementation to call ThrowOnError.

 public class HealthVaultOperation
{
    public HealthVaultOperation(string methodName, string methodVersion, XElement info)
    {
        this.MethodName = methodName;
        this.MethodVersion = methodVersion;
        this.Info = info;
    }

    public string MethodName { get; private set; }
    public string MethodVersion { get; private set; }
    public XElement Info { get; private set; }

    public HealthVaultResponseEventArgs Response { get; set; }

    public void ThrowOnError()
    {
        if (this.Response.HasError)
        {
            throw new HealthVaultException(this.Response.ErrorCode, this.Response.ErrorText);
        }
    }
}

Finally, here is the HealthVaultTask class. The only really interesting code is in RunEnumerator, which enumerates through the HealthVaultOperations returned by DoOperation, creating a HealthVaultRequest for it and executing it. At first glance, this function may look recursive, but the other RunEnumerator call is actually in a lamda function that will be called from the request callback. It also has the code to marshal the result (in case of success or failure) to the right callback function on the UI thread.

 public abstract class HealthVaultTask
{
    public HealthVaultTask(HealthVaultService service)
    {
        this.Service = service;
    }

    public void Run(Dispatcher dispatcher, Action successCallback, Action<Exception> errorCallback)
    {
        RunEnumerator(dispatcher, successCallback, errorCallback, DoOperations().GetEnumerator());
    }

    private void RunEnumerator(Dispatcher dispatcher, Action successCallback, Action<Exception> errorCallback,
                               IEnumerator<HealthVaultOperation> operations)
    {
        try
        {
            if (operations.MoveNext())
            {
                HealthVaultOperation operation = operations.Current;
                HealthVaultRequest request = new HealthVaultRequest(operation.MethodName, operation.MethodVersion,
                                                     operation.Info, (o, e) =>
                    {
                        operation.Response = e;
                        RunEnumerator(dispatcher, successCallback, errorCallback, operations);
                    });
                this.Service.BeginSendRequest(request);
            }
            else
            {
                dispatcher.BeginInvoke(() => successCallback());
            }
        }
        catch (Exception e)
        {
            dispatcher.BeginInvoke(() => errorCallback(e));
        }
    }

    protected abstract IEnumerable<HealthVaultOperation> DoOperations();

    protected HealthVaultService Service { get; private set; }
}

If this doesn’t make sense, please let me know and I’ll explain further.

There’s one design consideration I didn’t think about until after I’d implemented it. One problem with this implementation is that you can’t compose HealthVaultTasks with other HealthVaultTasks. It would be nice if you could build up layers of reusable service code. This could be accomplished by having a common base class between HealthVaultTask and HealthVaultOperation where HealthVaultTask.DoOperation would be an IEnumerable of the base class. That would let you yield either operations or tasks. This addition is left as an exercise to the reader Smile

Finally, a note on unit testing. This framework actually makes the HealthVaultTasks you write very easily unit tested. All you have to do is call GetEnumerator on your task, then call MoveNext/Current to simulate running the task. After you call MoveNext, you can make sure you get the expected operation in Current and you can push whatever results you want into the HealthVaultOperation. What’s great is that the HealthVaultService is completely out of the picture.

I hope this is useful!