In my previous post I wrote about how the Presentation Model could be implemented within Silverlight 2.0, in this post I will show how we can use Async. calls to Web Services.
To make it possible to handle Async. calls from a Repository on the client side, we need to make sure we can specify a callback method which should be called when the Async. operation is completed. I decided to create a new EventArgument called AsyncCallCompletedEventArgs:
public class AsyncCallCompletedEventArgs<T> : EventArgs
{
public T Result { get; set; }
}
public class AsyncCallCompletedEventArgs : EventArgs
{
}
I have created two version of the AsyncCallCompletedEventArgs class, one which returns a Result, and one which will not return anything at the moment. The last on can be used for operations like Save or Delete etc, where we don’t need to return a Result. Some other properties we could have added to the new EventArgs are Cancelled and Error etc. But I will make this example short and only use it as basic demonstration.
The interface of the Repository on the client side need to be updated for async. calls. Here is the new version of the INameRepository interface:
public interface INameRepository
{
void GetNameAsync(EventHandler<AsyncCallCompletedEventArgs<NameEntity>> callback);
void SaveNameAsync(NameEntity name, EventHandler<AsyncCallCompletedEventArgs> callback);
}
Note: I use the EventHandler delegate here, but I could have skipped it and created my own one that may be more suitable for what I want to do,
The NameRepository is updated to make a call to a WCF service asynchronous, and will make a call to a callback method passed as an argument to the Repositories methods, when the WCF service are completed.
public class NameRepository : INameRepository
{
public void GetNameAsync(EventHandler<AsyncCallCompletedEventArgs<NameEntity>> callback)
{
if (callback == null)
throw new ArgumentNullException("callback");
var nameServiceClient = new NameServiceClient();
nameServiceClient.GetCompleted += nameServiceClient_GetCompleted;
nameServiceClient.GetAsync(callback);
}
void nameServiceClient_GetCompleted(object sender, GetCompletedEventArgs e)
{
var callback = e.UserState as EventHandler<AsyncCallCompletedEventArgs<NameEntity>>;
if (callback == null)
throw new ArgumentException("No callback is specified or are of wrong type");
callback(this, new AsyncCallCompletedEventArgs<NameEntity>()
{
Result = new NameEntity() { Name = e.Result }
});
}
public void SaveNameAsync(NameEntity name, EventHandler<AsyncCallCompletedEventArgs> callback)
{
if (name == null)
throw new ArgumentNullException("name");
if (callback == null)
throw new ArgumentNullException("callback");
var nameServiceClient = new NameServiceClient();
nameServiceClient.SaveCompleted += nameServiceClient_SaveCompleted;
nameServiceClient.SaveAsync(name.Name, callback);
}
void nameServiceClient_SaveCompleted(object sender, AsyncCompletedEventArgs e)
{
var callback = e.UserState as EventHandler<AsyncCallCompletedEventArgs>;
if (callback == null)
throw new ArgumentException("No callback is specified or are of wrong type");
callback(this, new AsyncCallCompletedEventArgs());
}
}
When I make a call to the WCF Service GetAsync method, I pass the callback as an argument to the GetAsync method, so the callback is passed as a UserState. From the GetCompleted event, I can get the callback from the AsyncCompletedEventArgs UserState property.
Now when the Repository is updated to suppert Async calls and also take a callback as an argument (I could have added Completed events to the Repository, but I prefer to pass a callback as an argument), the Presentation Model must be updated. I decided to rename the Load and Save method to LoadAsync and SaveAsync, only to make sure the user of the code, will know that a Async. call will be done when they call the methods. Here is the new version of the Load and Save method of the NamePresentationModel class:
public void LoadAsync(EventHandler<AsyncCallCompletedEventArgs<NamePresentationModel>> callback)
{
if (callback == null)
throw new ArgumentNullException("callback");
_nameRepository.GetNameAsync((sender, args) =>
{
_nameEntity = args.Result;
callback(this, new AsyncCallCompletedEventArgs<NamePresentationModel>() { Result = this });
});
}
public void SaveAsync(EventHandler<AsyncCallCompletedEventArgs> callback)
{
_nameRepository.SaveNameAsync(_nameEntity, callback);
}
Note: I have only changed the Load and Save method from the previous post.
Now when the Presentation Model also support Async. calls we need to do a final update to the code behind file “Page.xaml.cs”. The code behind fill will pass a callback to the Presentation Model’s SaveAsync and LoadAsync method, the callback methods will be executed when the WCF service returns a value from the server.
private void Button_Click(object sender, RoutedEventArgs e)
{
_namePresentationModel.SaveAsync((source, args) =>
{
messageTextBlock.Text = "NameEntity Saved";
});
}
private void LoadFromPresentationModel()
{
_namePresentationModel.LoadAsync((sender, args) =>
{
myStackPanel.DataContext = args.Result;
});
}
This is one way to make sure we can handle Async. calls, I decided to use a callback method instead of a Completed event, but we could simply use a Completed event for the Async. operations if we wants to, but I like the way to pass a anonymous delegate to the SaveAsync and LoadAsync method as you can see in the last code above.