Ask Kathleen
Calling WCF Services in Silverlight 4
We explore the WCF service boundary after upgrading sample applications to Visual Studio 2010 and Silverlight 4.
My recent column, "Adding WCF Services to Silverlight" (March 2010), focused on the overall Model-View-ViewModel (MVVM) structure, not on aspects of the Windows Communication Foundation (WCF) service boundary. This month, I'll revisit that sample and refactor the Silverlight/WCF bounder.
Before refactoring the WCF aspects of the sample, I upgraded the applications to Visual Studio 2010 and Silverlight 4. This involved upgrading the project and solution files with the wizard. While this prepares the project for use in Visual Studio 2010, it doesn't change the assembly targets, so I upgraded each project to target the Microsoft .NET Framework 4 or Silverlight 4. Because I'd used the Managed Extensibility Framework (MEF), I needed to remove the "Browse" references to the CodePlex MEF libraries and replace them with the versions baked into the .NET Framework. Finally, I needed to upgrade the Silverlight Toolkit and fix up some references.
I want to thank Doug Gregory, enterprise architect at The Spitfire Group, for his feedback on my sample project. You can check out his blog at dougrgregory.blogspot.com.
Moving the 'Home' Location to the Server
When you're working with Silverlight, you're working with two different versions of the .NET Framework: the main framework and the smaller Silverlight framework. Your code needs to be compiled against the correct version of the framework. However, certain classes like the data classes are the same for both client and server, and you don't want to have redundant code. I moved the "home" location of the code shared between Silverlight and the server. You can create the code in one project and reference it in another using Add As Link.
In the earlier version, I placed the code in the Silverlight projects and linked to this code from the server project. This was a Silverlight-centric application, so the decision seemed reasonable. However, one of the long-term goals of a well-designed MVVM application is to reuse as much code as possible in alternate UIs. Switching the home location of the code to the server project and linking from the Silverlight project allows Windows Presentation Foundation (WPF) and other types of UIs to use the code with a link to the server.
Another overall design change I tackled was getting the service proxies out of the main Silverlight client project. The main Silverlight client project is the entry point of the Silverlight client application. This project is intimately linked to the Silverlight framework and part of the view layer of the MVVM architecture. The proxies are clearly part of the model layer. I put them in the same project in the March sample for a simple reason: It was the only way I could get the Add Service Reference wizard to not reuse classes. Doug Gregory suggested refactoring the projects a little differently to avoid an issue with the generator and linked files. This allowed a separate project to contain both the service references and the service wrappers. I retained the service wrappers because a future round of refactoring is likely to incorporate IObservable rather than the event model. The wrappers allow the interface to the model while still using the generated proxies.
The main Silverlight client project must contain the ClientConfig file. This file is created by the Add Service Reference wizard in the project where the service references are added, not the main Silverlight project. The simplest way to manage this is to use the Add As Link mechanism in Visual Studio.
In the March sample applications, I'd simplified the server side by placing the service implementations directly in the Web site project. Moving them to a separate assembly allows the service implementations to be reused from different Web sites or server-side projects, and makes them easier to test. Services generally involve two pieces: a service declaration in an .SVC file and an implementation in C# or Visual Basic. The .SVC file remains in the Web site project, but references the implementation in a separate project. The contents of the .SVC file for the customer service are:
<%@ ServiceHost Language="VB" Debug="true" Service="Services.
Server.CustomerService" %>
Services.Server is a separate project containing the CustomerService class. Because the services are now in a normal .NET assembly, rather than a Web site, they're easy to include in a unit test strategy.
I did a bit of cleanup work as part of this refactoring. I added a project-specific namespace to identify my files as different from anyone else's with the same name. This is especially important in fluid applications such as those using dynamic Silverlight downloads or a variety of server-side services. I also combined the Contracts and Common projects on both the server and Silverlight client side to avoid ongoing challenges and avoid circular references.
In the March version I used business-specific interfaces such as ICustomer. This technique can be useful for interchanging models and view models. However, passing interfaces across the WCF boundary requires defining the implementing types, generally via KnownTypes. This complexity in configuration rarely makes sense, so I removed the application-specific interfaces.
Adding a ServiceContext
The most important change I made was adding a ServiceContext. The previous version didn't supply a graceful way to return error and exception information to the client. The ServiceContext acts as a bucket for any information that needs to be carried along with the WCF request. All of your WCF service calls generally use the same ServiceContext. While WCF offers an operation context of its own, using an explicit service context gives you more control and facilitates testing. The ServiceContext in the example carries only the application name and a collection for errors; however, your ServiceContext can contain additional information as required by your project. The ServiceContext is passed by reference so that its values can be changed by the service:
Public Function RetrieveList(ByRef serviceContext As _
ServiceContext, ByVal filter As Filter) _
As List(Of Product) _
Implements IProductService.RetrieveList
Dim ret = New List(Of Product)
Try
' Add items ...
Return ret
Catch ex As System.Exception
serviceContext.Errors.Add(New ServiceError()
With {.ErrorMessage = "Service Failure",
.ErrorSeverity = ErrorSeverity.Severe,
.MoreInformation = ex.ToString()})
Return ret
End Try
From Silverlight, the WCF service call is asynchronous and completion is indicated by an event in the generated proxy. Proxy generation using the Add Service References wizard adds any reference parameters as properties in the event arguments raised on service call completion. The wrapper class captures this event and raises a new event with an argument that also passes on the service context. The view model now exposes a property indicating whether service errors occurred:
Private Sub service_RetrieveListCompletedEvent(
ByVal sender As Object,
ByVal e As RetrieveListCompetedEventArgs(Of T))
Handles _internalService.RetrieveListCompletedEvent
HasErrors = e.ServiceContext.Errors.Count > 0
If e.Result IsNot Nothing Then
For Each x In e.Result
AddInternal(x)
Next
End If
End Sub
Private _hasErrors As Boolean
Public Property HasErrors As Boolean
Get
Return _hasErrors
End Get
Private Set(ByVal value As Boolean)
If value <> _hasErrors Then
_hasErrors = value
OnPropertyChanged(New PropertyChangedEventArgs(
"HasErrors"))
End If
End Set
End Property
The final step is displaying notification when an error occurs. Previously the generalized EditView contained only a DataForm. Adding a TextBlock and binding its visibility property using a converter (for more on this, see "Working with Data in Silverlight," May 2010) allows the error message to be displayed only when errors have occurred:
<Grid x:Name="LayoutRoot" >
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition/>
</Grid.RowDefinitions>
<TextBlock Grid.Row="0" Foreground="Red"
Text="Error retrieving data"
Visibility="{Binding HasErrors,
Converter={StaticResource BooleanToVisibilityConverter}}"/>
<dataFormToolkit:DataForm Grid.Row ="1"
Name="DataForm"
HeaderVisibility="Visible"
IsReadOnly="False" AutoCommit="True"
ItemsSource="{Binding}">
</dataFormToolkit:DataForm>
</Grid>
The code behind sets different DataContexts for the DataForm and the TextBlock to allow use of the custom paged collection view that allows MEF-based insertions:
Me.DataContext = _viewModelMany
Me.DataForm.DataContext = NewPagedCollectionViewMef.
PagedCollectionViewMef(_viewModelMany)
About the Author
Kathleen is a consultant, author, trainer and speaker. She’s been a Microsoft MVP for 10 years and is an active member of the INETA Speaker’s Bureau where she receives high marks for her talks. She wrote "Code Generation in Microsoft .NET" (Apress) and often speaks at industry conferences and local user groups around the U.S. Kathleen is the founder and principal of GenDotNet and continues to research code generation and metadata as well as leveraging new technologies springing forth in .NET 3.5. Her passion is helping programmers be smarter in how they develop and consume the range of new technologies, but at the end of the day, she’s a coder writing applications just like you. Reach her at [email protected].