Silverlight applications face some novel architectural challenges compared to desktop or web applications. One of the most fundamental concerns, data access, is heavily affected by the constraints of running in a browser. The prevailing pattern, WCF service proxies, is solid, but has some rough edges.
Here is a technique I use to make WCF service proxies more writable, readable, composable, testable, and maintainable. That is just my opinion, of course, so see if you agree.
Data Access in Silverlight is Different
Silverlight applications are executed by a plugin which runs in the browser on a client’s machine. This means they cannot directly connect to server resources, such as as databases, for security reasons. They do, however, have access to their host web site, which can act on behalf of the Silverlight applications to fetch or update data.
To do so, Silverlight applications require points of contact on the server dedicated to carrying out those actions. These are often in the form of WCF Services, endpoints in a web site which process requests sent from Silverlight applications.
Each of these services exposes one or more contracts, sets of operations which comprise the functionality of the endpoint. Each operation has a signature composed of its name, inputs, and outputs. The data types in the signature each has its own contract describing its structure. A service contract and the associated data contracts together make a service definition, the unit of communication between Silverlight applications and their host sites.
A service reference is how Silverlight code communicates with a WCF service. Visual Studio generates strongly-typed client code from services’ metadata, machine-readable descriptions of service and data contracts. The generated code is commonly referred to as a proxy for the service.
The proxy programming model is straightforward: all types resides in a single namespace, where client classes implement the contracts of the referenced service. Operations are methods on those classes, and every custom data type in every operation is also generated.
Calls made with proxies look like this:
Proxies work like this because the networking infrastructure in Silverlight is asynchronous, a necessity in a browser environment where the UI thread can’t be tied up by expensive calls. Network calls do not start and finish on the same thread of execution. Instead, we package the handler code in a delegate and attach it to the client’s event. The client calls the delegate at some future time, whenever the response is received or a timeout elapses. WriteVisitorCountAsync returns immediately after the call to GetVisitorCountAsync, without blocking the thread.
A few elements of this pattern are less than optimal:
- We create the client instance directly with no parameters, which assumes the proper service configuration exists. Is the point of usage the best place to make that decision?
- The whole invocation is three statements, making calls feel heavy
- The handler code lives in a separate method, making calls feel heavy
- We have to check for an error or cancellation, adding branching logic to the handler and making calls feel heavy
- We control the client’s lifetime, meaning everyone who reads the code has to work out for themselves what happens to the client object once the reference goes out of scope (it is held by the Silverlight infrastructure in order to do the callback)
Ideally, the point of usage would have these characteristics:
- Client creation, configuration, and lifetime management are done elsewhere
- Declarative invocation
- No control flow statements
These minor touches would make calls feel more lightweight and approachable by clearing decisions from developers’ paths. We want the focus on the bricks, not the mortar.
A New Style
Let’s imagine the call to GetVisitorCountAsync with these traits in mind:
What a mouthful! Let’s break it down piece by piece.
Proxy Call – We declare we are executing a call whose completed event handler takes the specified argument type:
Event Subscription – Given a client instance and some event handler, we subscribe the handler to the client’s completed event. _siteInfoProxy is responsible for creating the instance and the handler. We just wire it to the right event:
Client Call – Given the client, we make the actual call:
Success Handler – Given the completed arguments, we write Result to the screen:
Error Handler – Given the exception, we write it to the screen:
Cancellation Handler – Given no arguments, we write a message to the screen:
It’s a bit to take in at first, but becomes natural fairly quickly. The IServiceProxy<> implementation takes care of client creation, configuration, and lifetime. The handlers are specified in the logical order of the pattern, and the statement contains all the code from the original form’s three statements and separate event handler. We’ve factored the approach so we only specify the unique parts of each call.
Code as Data
This is the interface which enables the new style:
Each parameter is a delegate in the Action family, which represent methods with no return value. The type arguments of each action correspond to the parameters of the previous lambda expressions. ExecuteAsync<> parameterizes portions of the algorithm by allowing custom code to be packaged into arguments (see the Template Method pattern).
The first two parameters define the code which initiates the call; the last three define the handling of the response. The success, error, and cancelled handlers are all optional because each has a reasonable default:
|Default behavior if null|
|onCompleted||No action (equivalent to a one-way call)|
|onError||throw exception from event arguments|
Finally, we constrain TCompletedEventArgs to derive from AsyncCompletedEventArgs, the base type for event argument types in generated proxies. It defines the Cancelled and Error properties, allowing us to process all responses in the same way.
The Method Behind the Madness
This is a reasonable implementation of IServiceProxy<>:
The constructor accepts a method which creates client instances, which serves our goal of creating and configuring clients elsewhere. Different factories can be used to control client lifetimes. For example, a factory may return the same instance for every call, or use some heuristic to pool instances.
First, we get a client instance. Then, we invoke subscribedToCompletedHandler, passing in the client and a lambda expression for the handler. In the handler, we check for the error and cancelled states and take the appropriate actions. Otherwise, if there is a success handler, we invoke it.
Now that the handler is subscribed, we invoke execute, passing in the client, and that’s it! ExecuteAsync<> looks very similar to the original code; it puts these details in a single place, allowing us to work at a level of abstraction above them.
This is how we would put ServiceProxy<> in action:
We state that when creating clients, we create an instance per call using the default c onstructor. This will use the configuration found in the ServiceReferences.ClientConfig file generated with the proxy. However, we could easily configure the client any other way, such as with alternate endpoints or bindings.
However, the constructor call has a lot of ceremony. We shouldn’t need to specify the client type twice. To make this a little nicer, we can have the compiler infer the client type from the return type of the lambda expression:
The WithFactory method simply reads TClient from the parameter:
We identified friction points in the standard approach to using generated proxies and created a solution which encapsulates many of the pattern’s details. The resulting API requires less decisions and rote code at the point of usage. It also externalizes client configuration and lifetime from the core concern of making service calls, increasing the cohesion of the solution while creating extension points which allow for its graceful evolution.
A win all around.