A while back I wrote a blog post about how Silverlight 4’s asynchronous network calls make a layered client-side architecture difficult. In that post I talk about wanting to have a loosely coupled, testable, n-tier architecture on my Silverlight client application. Basically, I wanted to have a client-side architecture that was nicely layered like Figure 1 rather than a flat, 1-tier architecture like Figure 2.
Figure 1 – A layered client-side Silverlight architecture
Figure 2 – A flat client-side Silverlight architecture
Although I didn’t write it in that original post, unit testing and unit testability was one of my top reasons for wanting a layered architecture. If everything is clumped in to one big XAP/DLL, it’s going to be difficult to test small “units” of functionality. My top priority was to keep the WCF data access code (aka. the code that calls the back-end WCF services) separated from the rest of my code behind Repository Pattern interfaces. Keeping those Repositories separated and interface-driven is essential for eliminating unit test dependencies on those running WCF services.
A Change In Plans
When I wrote that original post back in May of 2010, I was *fighting* the asynchronous WCF network calls. After a little bit of time, I had one of those moments where I asked myself “self, why is this so *&#$@!& difficult? There’s no way that this is right. I think we’re doing something wrong here.” I’ve since changed my approach and I’ve embraced the asynchronous nature of Silverlight and make it a core part of my architecture.
The Problem with WCF Data Access
Let’s take the Repository Pattern. The Repository Pattern is an encapsulation of logic to read and write to a persistent store such as a database or the file system. If you can write data to it, pull the plug on your computer, and you’re still be able to read that data the next time that you start your computer, it’s a persistent store. When you’re writing a Silverlight application that talks to WCF Services, your persistent store is your WCF Service.
If you look at IRepository
Figure 3 – Server-side, non-Silverlight Repository Pattern implementation
In Silverlight, if your Repository calls a WCF service to get it’s data, this *CAN NOT BE IMPLEMENTED LIKE THIS* unless you do something complex like what I did in my previous post (translation: unless you do something that’s probably wrong). The reason is that the majority of the work that happens in the IPersonRepository.GetById() method is actually going to happen on a different thread from the thread that called the GetById() method. IPersonRepository.GetById() will initiate the process of loading that IPerson but it won’t be responsible for finishing it and won’t even know when the call has been finished. This means that IPersonRepository.GetById() can’t return an instance of IPerson anymore…it now has to return void!
I know what you’re thinking. You’re *sure* that I’m wrong. Give it a second. Think it over. It’s kind of a mind-bender. Re-read the paragraph a few more times. Maybe sketch some designs out on paper. Trust me, I’m right.
These asynchronous WCF calls mean that none of your repositories can return anything other than void. Think this out a little bit more. This also means that anything that in turn calls a WCF Repository is also not permitted to return anything but void. It’s insidious and it destroys your plans for a layered architecture (Figure 1) and pushes you closer to the “lump” architecture (Figure 2).
A New Hope
Letting these asynchronous WCF calls ruin my layered architecture was simply unacceptable to me. What I needed was to get something like return values but still be async-friendly. I also needed a way to handle exceptions in async code, too. If you think about it, exceptions are kind of like return values. If an exception happens on the thread that’s doing the async work, because there isn’t a continuous call stack from the client who requested the work to the thread that’s throwing the exception, there’s no way to get the exception back to the original caller because throwing exceptions relies on the call stack. No blocking call, no continuous call stack.
Enter ReturnResult
An implementation of IPersonRepository that uses ReturnResult
public class WcfPersonRepository : IPersonRepository
{
public void GetById(ReturnResultcallback, int id)
{
PersonServiceClient service = null;try
{
// create an instance of the WCF PersonServiceClient proxy
service = new PersonServiceClient();
// subscribe to the completed event for GetById()
service.GetByIdCompleted +=
new EventHandler(
service_GetByIdCompleted);// call the GetById service method
// pass "callback" as the userState value to the call
service.GetByIdAsync(id, callback);
}
catch
{
if (service != null) service.Abort();
throw;
}
}void service_SearchCompleted(object sender, GetByIdCompletedEventArgs e)
{
var callback = e.UserState as ReturnResult<IList>; if (callback == null)
{
// this is bad
throw new InvalidOperationException(
"e.UserState was not an instance of ReturnResult.");
}if (e.Error != null)
{
// something went wrong
// "throw" the exception up the call stack
callback.Notify(e.Error);
return;
}try
{
IPerson returnValue;// do whatever needs to be done to create and populate an
// instance of IPerson
callback.Notify(returnValue);
}
catch (Exception ex)
{
callback.Notify(ex);
}
}
}Figure 5 – Asynchronous IPersonRepository implementation with ReturnResult
Now, let’s look at an example of something that calls IPersonRepository. A common example of this would be PersonViewModel needing to call in to the IPersonRepository to get a person to display. In Figure 6 you can see this implemented and the key line is repository.GetById(new ReturnResult
public class PersonViewModel
{
public void LoadById(int id)
{
IPersonRepository repository = GetRepositoryInstance();// initiate the call to the repository
repository.GetById(
new ReturnResult(LoadByIdCompleted) , id);
}private void LoadByIdCompleted(ReturnResult
result)
{
// handle the callback from the repositoryif (result == null)
{
throw new InvalidOperationException("Result was null.");
}
else if (result.Error != null)
{
HandleTheException(result.Error);
}
else
{
IPerson person = result.Result;if (person == null)
{
// person not found...invalid id?
Id = String.Empty;
FirstName = String.Empty;
LastName = String.Empty;
EmailAddress = String.Empty;
}
else
}
}// the rest of the implementation details go here
}Figure 6 – PersonViewModel calls IPersonRepository.GetById()
Summary
ReturnResult
Click here to download the sample code.
-- Looking for help with your Silverlight architecture? Need some help implementing your asynchronous WCF calls? Hit a wall where you need to chain two asynchronous calls together? Drop us a line at info@benday.com.