Introduction
Welcome to the second part of this article series on strategies for designing your application for a multitude of different clients. A lot of businesses face this problem today: your customers expect a rich desktop client, an almost equally rich web client, a mobile client, … and of course, on different form factors and for use with different types of input: typically, a mouse-keyboard combination, or touch.
That typically amounts to higher costs. And in reality, it will - after all, you are building multiple client applications. The good news is: your costs don't necessarily have to spin out of control. The aim of this article series is to look into how you can cut costs when you're faced with these demands: where can you reuse existing code, and how?
In the first part, we've had a look at the business case, and the clients we're going to develop. We've also looked at the provided data model. In this part, we'll look at the service layer. We've got a few requirements we need take into account. First of all, the service layer should be able to be consumed by the different clients: Silverlight, Windows Phone, HTML, Windows Phone, Metro. We've also got a few common concerns we should tackle: validation & authentication come to mind. And we require something that allows us to rapidly get our applications to the market: development time is an issue to take into account as well.
The accompanying source code for this article can be downloaded here.
Say hello to WCF RIA Services
If you have been building serious Silverlight Line of Business applications, you've undoubtedly heard of WCF RIA Services. A Silverlight client will typically get its data from a server via asynchronous service calls - so when you're building a Silverlight application, you're forced to build it in an asynchronous, service-oriented way. This can quickly become quite cumbersome: you'll have to write quite a lot of application logic to solve some common concerns, like validation logic and authentication, which quickly results in writing code on both sides (client and server), plugging in some code generator or your own mapping tool, …
Here, we're trying to get our applications to the market quickly, and we're trying to cut down on development time & costs. This is where WCF RIA Services really stands out: it obscures a lot of the plumbing details you'd have to write yourself otherwise, and more importantly: it solves most of our common concerns. You'll typically write the code on the server side, and WCF RIA Services will generate the corresponding code on the client - including your validation logic, your domain model, and all the logic you need to fetch and submit entities.
Working with WCF RIA Services feels a lot like working with the Entity Framework (of course, I’m talking about the “feel” here, not the technicalities): you'll load entities to your client, construct change sets, and submit them back to the server, which will take care of committing them to your data store: it obscures the intermittent service layer. This is, of course, a simplified explanation, and in reality it's not as easy as a typical marketing department would like you to believe, but I've used WCF RIA Services on a multitude of real-life Line of Business projects, and I'm convinced it's the fastest option on the market, while still allowing great extensibility and robustness, making it a good fit for most Line of Business applications. In this series, we won't look into the specifics of developing with WCF RIA Services, as there are already some great resources out there: you can learn a lot more about WCF RIA Services in Brian Noyes excellent series on this site, or have a look at some of the WCF RIA Services related articles I wrote.
What about the other clients?
Ok, so it's a no-brainer for building a Silverlight application, and our two main clients are Silverlight-based (web & out of browser), but what about those other clients? Weren't we supposed to build a mobile client (Windows Phone), an HTML-based client, and even a Windows 8 Metro client? Yes, we are, so let's have a look at those.
The mobile client (Windows Phone 7): when you're developing for Windows Phone, you're developing in Silverlight. However, this isn't the same version as the Web/OOB plugin. Contrary to what you might hope, WCF RIA Services isn't supported as we're used to in regular Silverlight. However, WCF RIA Services can expose its Domain Services as SOAP, REST or OData (read-only) endpoints, making it easily accessible from a Windows Phone client. We'll look into how we can enable this a bit further on in this article.
The HTML-based client: for this client, a WCF RIA Services implementation is available, namely RIA/JS: a jQuery client for WCF RIA Services. With this JavaScript-based framework, you get most of what you're used to with a Silverlight-based WCF RIA Services client, but with using JavaScript. You can load data using your Domain Services (query support, including sorting, paging and filtering), you get your validation logic on the client (your server side validation metadata is exposed in jQuery validation format), you can create change sets and you can submit them.
Windows 8 Metro client: at this point, it's unclear whether or not we'll get WCF RIA Services support for Windows 8 Metro apps. However, even it doesn't happen, the SOAP, REST or OData endpoints can be used.
Other future clients (iOS, Android): these would typically use the SOAP or REST endpoints.
Setting it up: logically dividing your services
It may seem like a no-brainer, but it's often forgotten: you should logically divide your domain model into different services, and a service should only expose what's needed, nothing more. This is especially true if you're building different clients: not every client will provide the users with the same functionality, so a client that provides limited functionality shouldn't consume a service which exposes a lot more - for example, if you look at our business case, the mobile client will deal with quickly inputting new opportunities, and thus shouldn't have access to service methods that allow him to access back-end functionality, or customer administration. So, instead of having only one Domain Service which exposes all your logic & entities, you should divide them in different "modules": a Domain Service for customer administration, a Domain Service for employee/opportunity management (this is the one we'll build), … Also, don’t expose unneeded operations: for example, a Domain Service for managing opportunities will probably require access to the Employee object, but shouldn’t be able to edit it (so there’s no need for Insert, Update or Delete operations).
Using this type of division also offers you more scalability options: often-used modules, by various clients, can be published to servers with higher bandwidth capabilities, whereas little-used modules can kept on low-bandwidth servers.
Same goes for security: a back-end service module can be kept in-house, hosted on your intranet rather than on the internet.
Setting it up: the solution division
Often, the WCF RIA Services class library template is used. This creates a .Web project (full .NET 4 CLR), containing your domain services, and a Silverlight class library project, containing the generated code. However, the naming of these projects is quite unclear, and not exactly correct. We're going to change that a bit, resulting in better naming.
First, we'll add a class library to our solution, SalesDashboard.EmployeeOpportunityService. This will contain the Domain Service we'll use throughout this demo application. You'll typically want one of these for each of your logically divided modules, as explained in the previous paragraph. For the demo application we're building, two are sufficient: one for the EmployeeOpportunity service, and one for an authentication service.
Next, we'll add a Silverlight class library to the project, SalesDashboard.Client.Model. This is the project which will be referenced by our Silverlight clients, and this is where we want the generated code to end up. That can be achieved by setting the WCF RIA Services link to the project containing our Domain Service:
All we need now is a Service Host. Create a new ASP .NET application, and make sure it references SalesDashboard.EmployeeOpportunityService. Next, add an SVC file to it, EmployeeOpportunityService.svc. Double-click it, and put in the following code:
<%@ ServiceHost Language="C#" Debug="true"
Service="SalesDashboard.EmployeeOpportunityService.EmployeeOpportunityDomainService"
Factory="System.ServiceModel.DomainServices.Hosting.DomainServiceHostFactory,
System.ServiceModel.DomainServices.Hosting, Version=4.0.0.0, Culture=neutral,
PublicKeyToken=31bf3856ad364e35" %>
As you can see, we're stating here that the DomainServiceHostFactory should be used to instantiate a new EmployeeOpportunityDomainService - making our service accessible through the explicit EmployeeOpportunityService.svc URI instead of the auto-generated SVC you normally get with WCF RIA Services.
Note: as this is now your service host, this project should contain all necessary WCF RIA Service references and configuration in the web.config - just as you would normally have in a service host. This is how it looks in the provided solution:
<configuration>
<system.webServer>
<modules runAllManagedModulesForAllRequests="true">
<add name="DomainServiceModule" preCondition="managedHandler"
type="System.ServiceModel.DomainServices.Hosting.DomainServiceHttpModule,
System.ServiceModel.DomainServices.Hosting, Version=4.0.0.0, Culture=neutral,
PublicKeyToken=31bf3856ad364e35" />
</modules>
<validation validateIntegratedModeConfiguration="false" />
</system.webServer>
<connectionStrings>
<add name="SalesDashboardAuthenticationEntities"
connectionString="metadata=res://*/SalesDashboardAuthenticationModel.csdl|
res://*/SalesDashboardAuthenticationModel.ssdl|res://*/SalesDashboardAuthenticationModel.msl;
provider=System.Data.SqlClient;provider connection string="data source=.\sql2008;
initial catalog=SalesDashboard;persist security info=True;user id=salesdashboard;
password=salesdashboard;multipleactiveresultsets=True;App=EntityFramework""
providerName="System.Data.EntityClient" />
<add name="SalesDashboardEmployeeOpportunityEntities"
connectionString="metadata=res://*/SalesDashboardEmployeeOpportunityModel.csdl|
res://*/SalesDashboardEmployeeOpportunityModel.ssdl|res://*/SalesDashboardEmployeeOpportunityModel.msl;
provider=System.Data.SqlClient;provider connection string="data source=.\sql2008;
initial catalog=SalesDashboard;persist security info=True;user id=salesdashboard;
password=salesdashboard;multipleactiveresultsets=True;App=EntityFramework""
providerName="System.Data.EntityClient" />
</connectionStrings>
<system.serviceModel>
<behaviors>
<serviceBehaviors>
<behavior name="">
<serviceMetadata httpGetEnabled="true" />
<serviceDebug includeExceptionDetailInFaults="false" />
</behavior>
</serviceBehaviors>
</behaviors>
<serviceHostingEnvironment aspNetCompatibilityEnabled="true" multipleSiteBindingsEnabled="true" />
</system.serviceModel>
<system.web>
<compilation debug="true"></compilation>
<httpModules>
<add name="DomainServiceModule"
type="System.ServiceModel.DomainServices.Hosting.DomainServiceHttpModule,
System.ServiceModel.DomainServices.Hosting, Version=4.0.0.0, Culture=neutral,
PublicKeyToken=31bf3856ad364e35" />
</httpModules>
</system.web>
</configuration>
Setting it up: making the service layer available for other clients
Non-Silverlight clients will communicate with our service layer through OData, SOAP or REST endpoints. Setting these up is quite easy – the OData endpoint is even provided out of the box. When you’re going through the Domain Service wizard, simply check the “Create OData endpoint” checkbox, and it will be created for you (you’ll still need to copy/paste the relevant code to the web.config of the Service Host).
If you want to do this manually, you should add some following code to the web.config on the Service Host. This should be added right underneath the configuration tag:
<configSections>
<sectionGroup name="system.serviceModel">
<section name="domainServices"
type="System.ServiceModel.DomainServices.Hosting.DomainServicesSection,
System.ServiceModel.DomainServices.Hosting, Version=4.0.0.0, Culture=neutral,
PublicKeyToken=31bf3856ad364e35" allowDefinition="MachineToApplication" requirePermission="false" />
</sectionGroup>
</configSections>
And this should be added underneath system.serviceModel:
<domainServices>
<endpoints>
<add name="OData" type="System.ServiceModel.DomainServices.Hosting.ODataEndpointFactory,
System.ServiceModel.DomainServices.Hosting.OData, Version=4.0.0.0, Culture=neutral,
PublicKeyToken=31bf3856ad364e35" />
</endpoints>
</domainServices>
The other two endpoints are provided through the WCF RIA Services Toolkit. The most convenient way of getting these is by using NuGet, as they are provided as a NuGet package. Use NuGet to manage the packages for the Service Host, locate the RIAServices.Endpoints package, and click install. The following endpoints will be added to your web.config file:
<add name="soap" type="Microsoft.ServiceModel.DomainServices.Hosting.SoapXmlEndpointFactory,
Microsoft.ServiceModel.DomainServices.Hosting, Version=4.0.0.0, Culture=neutral,
PublicKeyToken=31bf3856ad364e35" />
<add name="JSON" type="Microsoft.ServiceModel.DomainServices.Hosting.JsonEndpointFactory,
Microsoft.ServiceModel.DomainServices.Hosting, Version=4.0.0.0, Culture=neutral,
PublicKeyToken=31bf3856ad364e35" />
That’s really all there is to it: from now on, our Domain Service is accessible through OData (read-only), SOAP and JSON (REST) endpoints.
Conclusion
In this article, we’ve looked into our service layer options, and we’ve settled on WCF RIA Services. We choose this framework mainly for its extensibility, RAD characteristics and possible reuse across different clients. We’ve looked into some pointers on service layer design, and created the Domain Service for the module we’ll develop. At the end, we’ve made this Domain Service available for other (non-Silverlight) clients through the use of endpoints from the WCF RIA Services Toolkit.
In the next part, we’ll start with the first Silverlight client.
About the author
Kevin Dockx lives in Belgium and works at RealDolmen, one of Belgium's biggest ICT companies, where he is a technical specialist/project leader on .NET web applications, mainly Silverlight, and a solution manager for Rich Applications (Silverlight, Windows Phone 7 Series, WPF, Surface, HTML5). His main focus lies on all things Silverlight, but he still keeps an eye on the new developments concerning other products from the Microsoft .NET (Web) Stack. As a Silverlight enthusiast, he's a regular speaker on various national and international events, like Microsoft DevDays in The Netherlands, Microsoft Techdays in Belgium & Portugal, NDC2011, etc. Next to that, he also authored a best-selling Silverlight book, Packt Publishing's Silverlight 4 Data and Services Cookbook, together with Gill Cleeren. His blog, which contains various tidbits on Silverlight, .NET, and the occasional rambling, can be found at http://blog.kevindockx.com/, and you can contact him on Twitter via @KevinDockx.