Chapter 5. Exposing Data from the Server: Using WCF RIA Services

By their very nature, business applications are highly data-centric applications. Therefore, one of the primary concerns you will have when designing your application is to how it will consume data from a server.

If you're used to writing applications that communicate directly with a database (such as SQL Server), you may be surprised to find that there is no object in Silverlight to enable you to do so (SqlConnection, OdbcConnection, etc.). This is because Silverlight is a client platform, designed to be run from within a browser anywhere in the world, and thus it does not make sense for it to be able to access databases directly (as databases are generally hidden behind a firewall). Instead, data must be explicitly exposed externally from the server, using something such as a web service to sit on this boundary and provide a conduit between the data in the database and external applications.

There are numerous means to expose data from a server and consume it in a client application (many of which Silverlight supports); therefore, it can often be difficult to determine the best and most appropriate technology to use. Windows Communication Foundation (WCF) is generally the most popular technology for creating services supported by the .NET Framework.

Although you can consume WCF services in Silverlight (with a few limitations detailed later in this chapter), you will probably find that creating and maintaining your services becomes somewhat of a chore—it will require a lot of repetitive boilerplate code, it will become more complex when you add support for filtering/sorting/grouping/paging, it will require you to constantly update your service references, and it will force you to write/maintain logic (such as validation and business rules) on both the server and the client.

To try to combat these problems and simplify access to data in Silverlight applications, Microsoft has introduced a new technology called WCF RIA Services (commonly referred to as RIA Services), specifically designed for use by Silverlight applications (but with a view to support additional presentation technologies in the future).

RIA Services is a layer on top of WCF that provides a prescriptive pattern and framework that you can use when building data-driven applications that consume data from a server. Another way of saying that is that RIA Services simplifies the process of exposing data from a server, consuming it in your Silverlight application, and sharing business logic between the two—which it does by providing a set of patterns to follow in creating your services, and providing a framework to support them.

In this chapter, we'll be focusing on how to use RIA Services to expose data from the server, and write logic that can be shared between the server and the client (consuming these from your Silverlight application will be covered in the following chapters). I'll also discuss some of the alternative means and technologies for doing so if you find that RIA Services does not suit your needs.

What Is WCF RIA Services?

In a typical server/client scenario, you need to pass data back and forth between the server and the client, with data being passed back and for the over a boundary (such as the Internet, a network, or even applications on the same machine). For example, the client requests data from the server (which queries a database for the requested data), possibly makes changes to it, and then passes the changes back to the server (which updates the database accordingly).

To accommodate this scenario, a typical system consists of a number of layers/tiers that provide a clean separation of concerns between each logical module. Often you will find three tiers in an n-tier design; this design will separate the presentation tier from the middle tier, and the middle tier from the data tier (with the presentation and data tiers having no direct connection with each other).

The problems that this structure creates, however, is that it requires an often complex underlying framework to effectively transfer data between the tiers. Also, a duplication of the same code is required on multiple tiers (such as data classes, and validation/business rules/logic). Duplication of code requires additional effort to write, test, and maintain, and code can easily get out of sync between the tiers, potentially leading to problems that are difficult to track down. RIA Services has been designed to solve these problems, and simplify the scenarios of architecting and developing an end-to-end Silverlight application. Therefore, developers can focus on solving the actual business problem rather than focusing on architectural concerns.

RIA Services is partly a data-centric design pattern for structuring your application; partly a framework that provides advanced data management, authentication/authorization management, and querying features; and partly a code generator that generates code in one project based on the code written in another project (so common code to be used in multiple tiers can be written in one place). It piggybacks on top of WCF Services, which provides the communication mechanism between the middle tier and the presentation tier.

What Is WCF RIA Services?

The focus of the RIA Services pattern is very much directed toward the middle tier. It's not tied to a specific data access technology, although it works particularly well with both the Entity Framework and LINQ to SQL (the latter requiring the WCF RIA Services Toolkit to be installed, as discussed later in the chapter). It is also not designed to be tied to a specific presentation technology, although the focus is currently primarily on Silverlight, with limited support for other presentation technologies such as ASP.NET, WPF, and Windows Forms.

RIA Services is an end-to-end technology; therefore, you need to have control over both the server and the client aspects of the application. If you need to access existing services (without routing the requests through the server), or to use services being developed by a separate team, RIA Services may not be suitable.

Although this book focuses on the use of RIA Services to expose data and logic on the server, Silverlight is by no means limited to this technology. Some of these other technologies that you can use for client/server communication will be discussed at the end of this chapter, and most of the concepts discussed in this book will still apply to your business application even if you choose not to use RIA Services.

The framework portion of RIA Services provides a host of functionality out of the box that vastly simplifies communicating between the presentation tier and the middle tier. Included is a data source control that can be used in XAML to communicate with a service, management of changes made to data on the client and processing these changes on the server, the ability to write a LINQ query on data exposed by a service on the client and have that query executed on the server, and an out-of-the-box working authentication service.

How the WCF RIA Services Code Generator Works

RIA Services requires both the server and the client projects to be within the same solution and linked together. One contains services that expose data (acting as a server), and the other communicates with the services and consumes the data (acting as a client).

When a service is written in the server project that follows the RIA Services design pattern, the RIA Services build task in Visual Studio generates corresponding code in the client project (i.e., a code generation process) that simplifies the process of communicating with the service. It also generates corresponding proxy data object classes (which are generally referred to as "entities") in the client project for those that are exposed by the service, applies attributes (such as those that denote validation logic) on the entities and their properties, and copies code marked as shared code from the server project into the client. This code generation is generally referred to as "projection," because code is projected from the server project onto the client project.

Let's investigate what is happening under the covers when you link the two projects via RIA Services. For this, we'll use the solution that we created back in Chapter 2. This solution was created using the Silverlight Business Application project template, and is already configured to use RIA Services, with the two projects in the solution already linked with an RIA Services link.

Start by toggling the Show All Files option (the second button from the left in the Solution Explorer window's toolbar) to "on" for the Silverlight project. There is a Generated_Code folder (as shown in Figure 5-1) into which code generated by the RIA Services build task is placed, as code is written in the web project on the fly and when the Silverlight project is compiled. When you make a change on the server (such as adding a property to a class), the change will be almost immediately propagated to the Silverlight project so you can use it (if not, try compiling your project to force the code generation). The code that is generated enables you to access all the operations and data from the client that has been exposed on the server via RIA Services.

The Silverlight project structure, showing the hidden Generated_Code folder

Figure 5.1. The Silverlight project structure, showing the hidden Generated_Code folder

Note

You can have a look at the code that has been generated for the client, but don't change anything because any changes you make will be overwritten when the Silverlight project is next compiled (which regenerates the code in this folder). The generated classes are partial classes, which enables you to extend them on the client with client-only logic if you find the need. We will take a closer look at this generated code later in the chapter, in the "Inspecting the Generated Code in the Silverlight Project" section.

The code generator is intelligent enough to ascertain the nature of operations on the services. The logic it uses is discussed in the "Domain Operations" section later in this chapter.

You can govern how entity classes are generated in the client project (what properties are included or excluded) by decorating the entity's properties with attributes (also known as data annotations). How you do this is discussed in the "Decorating Entities" section later in this chapter.

Code that should be shared between the server and the client (copied from the server project into the client project by the RIA Services build task) must be placed into files that have the .shared.cs extension, so that the build task knows to copy these files from the server project to the client project. Sharing code is discussed in the "Sharing Code Across Tiers" section later in this chapter.

How Do You Use WCF RIA Services?

This section goes through a brief overview of the process of working with RIA Services before drilling down into the details.

Linking Your Silverlight and Web Projects

Using RIA Services requires you to follow a pattern in structuring your application. First, your server (web) project and your client (Silverlight) project must be linked so that the RIA Services build task/code generator can project code from the server project onto the client project and create the supporting objects used to interact with the server. This means that both your client and server projects must exist in the same solution.

Note

There is support to have your entities and metadata classes in a separate project from your web project (using the WCF RIA Services Class Library project template discussed later in this chapter).

Creating Your Domain Services

The next step is to create one or more domain services. Think of a domain service as much like a standard WCF service, but following a given pattern (and consisting of some base functionality).

Note

In fact, each of your domain services is actually a WCF service. RIA Services generates code in the client project to automatically communicate with the domain service, meaning that you don't have to worry about adding service references to your domain services. However, because domain services are standard WCF services, you can add them as service references to your projects if you wish (including Windows Forms, WPF, and ASP.NET projects).

The RIA Services build task automatically generates code in the client project that enables it to interact with the domain service on the server. It creates a domain context for each domain service, and proxy classes for each of the entities exposed by the domain service.

Creating Domain Operations on Your Domain Services

On your domain service you create domain operations, which will typically expose data to the client and accept changes in a create, read, update, and delete (CRUD) pattern.

The entities in the server project that are exposed by the domain operations can be decorated with attributes (such as specifying data validation rules), either directly or by applying the attributes to associated classes (known as metadata classes) that enable you to apply attributes to the entities without modifying them directly (important when the entities are code generated, such as Entity Framework entities that can't or shouldn't be modified directly). You can share code between the server and the client projects by simply placing the code to be shared in one or more files with the .shared.cs extension.

Consuming the Domain Services

Once this is set up on the server and you are exposing data from your domain services, you can turn your attention to the client project in order to call the operations on the domain service and consume the data. You can do this in code by creating an instance of a domain context and calling the exposed operations. Alternatively, you can make use of the DomainDataSource control to interact with a domain service declaratively in XAML.

Entities are generally exposed from a domain operation via an IQueryable<T> expression or as an IEnumerable collection. By returning an IQueryable<T> expression, the power of the RIA Services framework is demonstrated in that you are able to write a LINQ query on the client specifying how you want the resulting entity collection filtered, sorted, grouped, and paged. RIA Services serializes the LINQ query and sends it to the server, where it is appended to the query expression returned from the query operation before it is executed. This enables the collection to be filtered/sorted/grouped/paged on the server without requiring the entire collection to be sent back to the client first (minimizing the required data traffic).

As this collection is updated on the client, the domain context that exposes it maintains a changeset, which includes the entities that have been inserted into, deleted from, or updated in the collection. Changeset management is another extremely powerful feature of the RIA Services framework that greatly simplifies managing the collection and sending updates back to the server. The SubmitChanges method on the domain context will send the changes made to an entity collection back to the server (which based on the changes made will automatically call the insert/update/delete operations to update the back-end database accordingly, plus any custom operations that were called) as appropriate.

This discussion should have given you a broad overview of the process of working with RIA Services and introduced you to some of the key concepts. The next section looks at how to actually put these concepts into practice.

Getting Started

The project we created in Chapter 2 using the Silverlight Business Application project template is set up to use RIA Services by default, and already provides some basic functionality using RIA Services. Therefore, we will continue working from that base and the minor modifications we made in Chapter 2. Figure 5-2 displays the structure of this solution again.

The structure of the Web Application project

Figure 5.2. The structure of the Web Application project

Note from Figure 5-1 that there is a Services folder and a Models folder in the project. The Services folder already contains two domain services (AuthenticationService and UserRegistrationService) for providing authentication and user registration operations to the client (these will be discussed further in Chapter 8). The Models folder contains two data classes (User and RegistrationData) that are passed between the server and the client. You will also find a Shared folder under the Models folder, which has a file called User.shared.cs that contains code to be shared between the server and the client projects.

As demonstrated in Chapter 2, the Silverlight and web projects are linked together. The purpose of this link is to copy the compiled XAP file generated by your Silverlight application to a location under the web project when the project/solution is compiled. However, RIA Services requires another link to be created between the projects. The purpose of this link is to configure what project contains the services that the Silverlight application will be consuming data from. This will enable the RIA Services build task to generate the required code to communicate with those services in your Silverlight project accordingly.

Whereas the project link to copy the Silverlight application's XAP file into the web project was configured in the web project's project properties, the RIA Services link is configured in the project properties of the Silverlight project, as shown in Figure 5-3.

The project properties for the Silverlight project, with the WCF RIA Services link configuration at the bottom

Figure 5.3. The project properties for the Silverlight project, with the WCF RIA Services link configuration at the bottom

This link will already be set up by using the Business Application project template; however, if you have an existing Silverlight project or web application that you want to use RIA Services with, you can manually link the projects together by linking the Silverlight project to an ASP.NET project with this option.

Now you are ready to start writing some code. The example you'll work through in this chapter demonstrates some of the key concepts of the RIA Services pattern. Your aim will be to expose data and operations from the server, and make them accessible from the client. In doing so you will also be sharing some validation logic and code between the server and the client, which will be executed on both sides. First, however, you need to set up a data source/layer from which you will expose data to the client.

Creating a Data Access Layer Using the Entity Framework

As this book is focused on Silverlight as a technology, I won't be going into too much depth on database-related concepts such as database terminology and principles, creating and managing databases in SQL Server Management Studio, or methods of accessing data (such as ORMs, the Entity Framework, LINQ to SQL, ADO.NET, etc.). These are considered prerequisite knowledge for this book, and instead I'll focus here on how to actually expose the data publicly via a service, and how to consume it in a Silverlight application.

Note

If you're not familiar with the Entity Framework (which we'll be using throughout this book), you can find more information and videos on using it here: http://msdn.microsoft.com/en-us/data/videos.aspx.

Configuring the Database

Throughout this book you'll be creating a business application in Silverlight for a fictional bicycle sales chain called Adventure Works Cycles, which will enable employees to manage the various aspects of the business, including product inventory, sales, and purchasing. For this we'll be using SQL Server 2008 as our database server (the free Express version installed with Visual Studio will be sufficient), and the database that will be supporting this scenario will be the AdventureWorks2008 OLTP sample SQL Server database from Microsoft, which you can download from http://msftdbprodsamples.codeplex.com. This database has been chosen because it is an example of a typical business database with a suitably complex structure, and is also well populated with sample data.

Note

The AdventureWorks2008 database comes with an installer that includes a number of other databases. The only database that you need to install is the AdventureWorks2008 OLTP database. Ensure that the check box next to this database is selected in the installer so that it will actually be created as a database in the selected SQL Server instance (otherwise the database installer script will be installed but not executed).

About the Entity Framework

In order to retrieve and persist data to the database, we'll be using the Entity Framework as our data access layer. The Entity Framework is an object-relational mapper (ORM) that enables you to create a conceptual object model (known as an entity model) and map it to the database schema (e.g., tables, views, and stored procedures). You can then query this object model in code using LINQ to Entities (LINQ specifically designed for querying an entity model).

The Entity Framework takes care of translating the LINQ to Entities queries to SQL queries in the database, and returns the data as the objects that you've defined in your model. Essentially, the Entity Framework makes working with database data in code a much more pleasant and robust experience, as it enables you to work with the data as objects and collections (instead of rows and tables), and to write strongly typed queries against the entity model.

The Entity Framework has been subjected to some controversy since its initial release, including a vote of no confidence petition signed by many prominent members in the developer community (including a number of Microsoft MVPs). It has undergone numerous improvements since then (with the .NET Framework version 4) to counter the issues that were raised with the first version, and is working its way toward becoming a robust yet easy-to-use ORM.

One of the benefits of using the Entity Framework for this project is that RIA Services has been designed to work very smoothly in conjunction with it, enabling services with the standard CRUD operations to be created very easily for given entities.

However, the Entity Framework is by no means the only data access technology supported by RIA Services—it also has support for LINQ to SQL models (which can be found in the WCF RIA Services Toolkit, discussed later in the chapter), and in fact it can work with any data source (although doing so involves a degree of additional work when creating a domain service). So if you have an existing data access layer to access your database, or you want to use a different ORM (such as nHibernate), RIA Services can also support these scenarios.

In fact, employing the built-in support for exposing entities directly from your Entity Framework model could in some ways be considered bad practice. While taking advantage of this support to expose given entities to the client makes creating and maintaining services incredibly easy, the entities are essentially a model of your data layer, and ideally these entities should not be exposed to the presentation layer. Whether or not you pass entities or plain-old CLR objects (referred to as POCOs, but also known as presentation model types in RIA Services) of your own design back and forth is a decision you will have to make, dependent on many factors. Using entities will make development much faster, but will also be less flexible than using custom presentation model types. RIA Services works just as well using presentation model types as it does with entities, despite more work being involved in initially creating the domain services. Therefore, the best practice would be to use presentation model types as the data transfer mechanism (which can be populated with data from your entity model), although we will take a look at both of these methods (starting with directly exposing entities because they provide the easiest and fastest means to get started).

Creating an Entity Model

Once you've installed the database you can create your entity model. This is a very straightforward process, as the Entity Data Model Wizard will guide you through this process.

Add a new item to your Web Application project, select the ADO.NET Entity Data Model item template, and name it AdventureWorksModel.edmx. Following through the wizard steps, you want to create a model from a database, create a connection to the database, and select the tables in the database to be included in the model. This chapter will demonstrate exposing product data, so select the Product table to be added to your entity model. You can add other tables from your database to your entity model at a later time if you wish.

Ensure that the Include Foreign Key Columns in the Model check box is selected, as this will greatly simplify dealing with foreign keys in scenarios where an entity is being updated and combo boxes are enlisted to select an item from a dictionary and assigned to a property on the entity.

When you click the Finish button, the entity model will be generated and displayed in the entity model designer.

Domain Services

Once you have connected your Silverlight and web projects via RIA Services and configured a data source on the server, the next task is to expose some data and operations from the server (which you will consume from your client in the following chapters). This is achieved through the use of a domain service. A domain service typically consists of the CRUD operations and any other custom operations you wish to expose for a given entity that will be available to the client.

Understanding the Domain Service Life Cycle

Let's take a quick look at how a domain service works and the various phases in its life cycle. The core interaction that a client has with a domain service is to retrieve a collection of objects (or a single object) from the domain service, and return any of the objects that have been modified, or additional objects that have been inserted into the collection back to the server to update the database accordingly (i.e., the create/update/delete in CRUD). The objects in the collection that have been changed (inserted/updated/deleted) are tracked on the client and returned back to the domain service, together with the domain operation that should be applied to each object on the server as a changeset (when the SubmitChanges method is called on the domain context associated with the domain service that was created in the client project).

This calls the Submit method on the domain service, initiating the start of the domain service's life cycle. The domain service will work through the following phases in order:

  • The authorize changeset phase essentially verifies that the user passing through the changeset is actually authorized to execute the operations in that changeset. It checks that each operation to be called from that changeset is permitted according to the security-related rules defined on each of these operations in the domain service (by decorating the operations with security rule attributes). Configuring security rules on domain operations is covered in Chapter 8.

  • The validate changeset phase performs server-side validation of the entities being passed through in the changeset according to the validation rules applied to each object and its properties (by decorating them with validation rule attributes) within the changeset. Although the objects and their properties are validated on the client side, the domain service validates them again according to the validation rules to verify that someone didn't circumvent the validation rules on the client (as is proper practice). Configuring validation rules on objects and their properties is covered shortly in this chapter.

  • Once the changeset has been fully authorized and validated, the execute changeset phase can begin. This phase enumerates through each entry in the changeset and calls the specified domain operation (i.e., the insert, update, delete, or custom operation on the domain service), passing in the given object to perform that operation upon.

  • The final phase in the domain service's life cycle is the persist changeset phase. When you are using a data access layer that maintains its own changesets (such as the Entity Framework or LINQ to SQL), the final task is to persist that changeset to the database. For example, when using an Entity Framework model as your data access layer, this phase would be used to call the SubmitChanges method on the data context (and complete the transaction). If changes are immediately committed to the database in the domain operations, then this phase will usually be ignored.

Each of these phases is automatically performed by the domain service; however, there is a corresponding method on the domain service that you can override and hook into if you need to provide some custom behavior in addition to what is provided by the base domain service class. These methods are

  • AuthorizeChangeSet

  • ValidateChangeSet

  • ExecuteChangeSet

  • PersistChangeSet

You can also override the Submit method, which encompasses each of these phases in order to hook into that, too. While in a phase, the logic for that phase will be run over every object in the changeset. If an exception is thrown on any of these objects (during any of these phases), then the entire changeset submission will be cancelled.

Creating a Domain Service

Let's work through the process of exposing a Product entity from the entity model. The first step is to add a domain service to your web project.

Note

After creating an entity model that you want to expose data from (and before attempting to create a domain service), make sure that you compile your solution. If you don't, you will find that the Domain Service Class Wizard won't display your entity model in the available DataContexts/ObjectContexts drop-down list.

Add a new item to the Services folder in your web project, using the Domain Service Class (under the Web category) as the item template. We will be using this service to serve up product data, so we'll call it ProductService.cs. This will initiate the Domain Service Class Wizard, as shown in Figure 5-4.

The Domain Service Class Wizard dialog

Figure 5.4. The Domain Service Class Wizard dialog

Select your AdventureWorks2008 entity model from the Available DataContexts/ObjectContexts drop-down list (if it isn't already selected). This will display each entity in your entity model in the list.

Note

If you're using POCO types instead of Entity Framework or LINQ to SQL types, you will need to select the <empty domain service class> option from the Available DataContexts/ObjectContexts drop-down list, and implement the domain operations yourself. In addition, if you want to use a LINQ to SQL model as your data access layer, you will need the WCF RIA Services Toolkit installed, which will be discussed later in this chapter.

Let's now take a look at each element of this dialog and what it does.

Entity Selection

The entity list enables you to select the entities that you want to expose via this domain service (using the check boxes). A domain operation will be created in the domain service for each selected entity, and this operation will return a collection of the corresponding entity to the client. If you want to also be able to modify an entity from the client (i.e., add, update, and delete entities), select the Enable Editing option next to it. For each entity marked as editable, the wizard will create insert, update, and delete domain operations for that entity on the domain service.

Enabling Client Access

Make sure that the Enable Client Access check box is checked before moving on from this dialog. This will decorate the domain service class with the EnableClientAccess attribute, which will indicate to the RIA Services code generator that it should generate code in the Silverlight project to enable it to access the domain service on the server.

Exposing an OData Endpoint

The Expose OData Endpoint check box enables your domain service to expose data using the Open Data (OData) protocol. OData is a set of extensions for the ATOM protocol to standardize the creation of HTTP-based data services, somewhat similar to REST data services—returning the data as an ATOM feed and providing CRUD operations on the data. While this is a new protocol, it has Microsoft backing, and Microsoft is integrating support for OData into a number of its products (such as SharePoint and PowerPivot). Selecting this check box will enable your domain service to be consumed by products supporting this OData protocol.

Generating Associated Classes for Metadata

The Generate Associated Classes for Metadata check box creates a metadata class for each entity selected in the entity list. Metadata classes will be covered later in this chapter, but in summary they enable you to decorate the properties on your entities with attributes (such as those specifying data validation rules) without actually needing to modify the entities themselves. Entities are generated code (created by the Entity Framework modeler), and modifying generated code is not a good idea. Doing so would prevent you from regenerating the code again at a later stage—if you did regenerate the code after manually altering it, then you would lose the changes you had made.

Metadata classes provide the solution to this problem. Each entity can have a corresponding metadata class, with each property on the entity having a corresponding field in the metadata class. You can then decorate the fields in the metadata class with attributes that RIA Services will map to the entity and use at design time when generating the corresponding entity on the client, and at runtime when persisting entities passed from the client to the database (to revalidate the data).

The Domain Service Class Wizard can generate these metadata classes for you for the entities you have selected from the list. These will go into a .metadata.cs file—for example, the ProductService domain service will have a corresponding ProductService.metadata.cs file created, containing metadata classes for all the entities that the ProductService domain service exposes.

It's not essential that you generate metadata classes for the entities that you expose from your domain service. However, if you choose not to create them when creating the domain service, and then decide you wish to use them at a later time, you will need to either create them manually or create another temporary domain service for the entities (from which you can get the generated metadata classes, and then delete the domain service).

Creating the ProductService Domain Service

To finish creating the ProductService domain service (which we'll use throughout this chapter and the following two chapters), take the following steps:

  1. Select the Product entity and its corresponding Enable Editing check box.

  2. Ensure that the Enable Client Access check box is selected.

  3. Ensure that the Generate Associated Classes for Metadata check box is selected.

  4. Click the OK button.

This will then create the domain service and metadata class(es) for you in your web project.

Domain Operations

Domain operations are methods on a domain service that can be invoked from a client. These operations will typically be CRUD operations, but you can also have invoke operations and custom operations.

In order for the RIA Services code generator to ascertain what type of operation a given method is (so that it can correctly generate the corresponding code in the client project), it uses a convention-based approach (by default) to do so. The conventions prescribe how the operation methods should be named and what pattern their signature should follow.

Alternatively, you can decorate the operation's method with an attribute that identifies what type of operation it is. In fact, you may even wish to explicitly decorate the operation's method with an attribute specifying the type of operation regardless. The naming/signature conventions (and alternative attribute decorations) for each operation will be included in this section's discussion.

Note

Some people prefer to decorate their operations even when they follow the naming/signature convention in order to explicitly define what type of operation is being represented.

Let's now take a look at each of the various types of domain operations that you might have in a domain service.

Query Operations

A query operation enables you to expose data to a client. This data may be either a collection of entities/objects or a single entity/object. Let's look at each of these cases separately.

Returning a Collection of Entities

In the ProductService class created by the wizard, you will find a query operation that returns a collection of Product entities to the client, as follows:

public IQueryable<Product> GetProducts()
{
    return this.ObjectContext.Products;
}

The GetProducts operation returns an expression (via an IQueryable<T> interface type) that when executed by a LINQ provider will return a collection of the Product entity. Throughout this book, the LINQ provider we are using is the Entity Framework, although any LINQ provider can be used. Although you could return an actual collection from the query operation (such as a List<T>), returning an IQueryable<T> expression provides numerous benefits, and enables one of the most powerful features that RIA Services provides.

By returning an expression (that hasn't been executed against the LINQ provider as yet), you enable RIA Services to add additional query criteria to the expression, which it can then execute, and can subsequently return the results to the client. As you will see in Chapter 6, the client can specify filtering, sorting, grouping, and paging criteria as a LINQ query when calling a query operation on the server. This LINQ query will then be serialized and sent to the server when calling the query operation, and these criteria will be appended to the existing query before it is executed and the results are returned to the client. This provides a neat, easy, and elegant way of passing criteria from the client to the server, and having them included them in the query to the database.

Query operations can accept parameters as part of their signature, which you can use as an alternative means to pass filter and other criteria to the query operation. For example, the following query operation accepts a string that it will use to filter the results (and must be provided when calling the operation from the client):

public IQueryable<Product> GetProducts(string nameFilter)
{
    return this.ObjectContext.Products.Where(p => p.Name.StartsWith(nameFilter));
}

Returning a Single Entity

In addition to returning a collection of entities, you will often want to add a domain operation that returns a single entity matching the key you pass through to it as a parameter (this will support the drill-down-type scenario that will be covered in Chapter 7). To demonstrate this, we'll add a new domain operation to the ProductService domain service called GetProduct, which will return the Product entity with the matching ProductID passed through to it as a parameter:

public Product GetProduct(int productID)
{
    return ObjectContext.Products
        .Where(p => p.ProductID == productID)
        .FirstOrDefault();
}

Note

GetProduct (which returns a single Product entity) is very similar in name to the default GetProducts domain operation, which returns a collection of Product entities. It's best not to have such similarly named operations on your domain service (it will cause confusion and lead to mistakes being made on both the server and the client); therefore, it's best to rename the GetProducts domain operation to something such as GetProductCollection or GetProductList, for example.

Naming/Signature Convention

A query operation has no convention for its naming, and can accept any or no parameters. For it to be implicitly identified by the RIA Services code generator as a query operation, however, it must return an IQueryable<T>, IEnumerable<T>, or an entity. Otherwise, decorate the method with the Query attribute.

Note

You can configure the query operation to cache the results in order to save calls back to the server (from the client), or calls to the database (depending on how you configure the caching). Data caching is ideal when you have data that rarely (or never) changes. To utilize output caching, decorate the query operation with the OutputCache attribute, and configure the caching behavior by specifying values for its constructor parameters. These parameters include where the data should be cached (most commonly either on the server and/or the client), and how long the data should be cached (in seconds).

Insert/Update/Delete Operations

When a client application submits a changeset to the server, RIA Services will call the appropriate insert/update/delete operation for each entity in the changeset, based on the action specified for that entity in the changeset. These operations cannot be called explicitly from the client.

Naming/Signature Convention

The insert/update/delete operations must accept an entity as their parameter, upon which it will perform the corresponding operation (with no additional parameters) and not return a value. When naming the methods, note the following:

  • Insert operation method names must start with Insert, Create, or Add. Otherwise, you will need to apply the Insert attribute to the method—for example, public void InsertProduct(Product product).

  • Update operation method names must start with Update, Change, or Modify. Otherwise, you will need to apply the Update attribute to the method—for example, public void UpdateProduct(Product currentProduct).

  • Delete operation method names must start with Delete or Remove. Otherwise, you will need to apply the Delete attribute to the method—for example, public void DeleteProduct(Product product).

Example Insert/Update/Delete Operations

If you have enabled editing on the Product entity in the Domain Service Class Wizard, you will find that insert, update, and delete operations have been created and implemented for you in the ProductService domain service.

The following code is performs an insert operation:

public void InsertProduct(Product product)
{
    if (product.EntityState != EntityState.Added)
    {
        if (product.EntityState != EntityState.Detached)
        {
            this.ObjectContext.ObjectStateManager.ChangeObjectState(product,
                                                                EntityState.Added);
        }
        else
        {
            this.ObjectContext.AddToProducts(product);
        }
    }
}

The following code performs an update operation:

public void UpdateProduct(Product currentProduct)
{
    if (currentProduct.EntityState == EntityState.Detached)
    {
        this.ObjectContext.Products.AttachAsModified(currentProduct,
                                this.ChangeSet.GetOriginal(currentProduct));
    }
}

The following code performs a delete operation:

public void DeleteProduct(Product product)
{
    if (product.EntityState == EntityState.Detached)
    {
        this.ObjectContext.Attach(product);
    }
    this.ObjectContext.DeleteObject(product);
}

Invoke Operations

An invoke operation is a nonspecific domain operation, and behaves the same way as a service operation in a standard WCF service. As a general rule, invoke operations don't return or act upon entities. They either initiate an action on the server (e.g., requesting the server to send an e-mail) or request data from the server, which returns a simple value or data for which changeset tracking is not required (e.g., requesting the current currency exchange rate).

Like query operations, invoke operations are methods that the client application can call directly. These operations are called immediately (unlike custom operations, which are queued and only called when a changeset is submitted to the server).

Note

Unfortunately (at the time of writing), despite invoke operations being conceptually identical to service operations in WCF, they are not able to return complex types (other than entities). This is possibly the biggest limitation you will face when using RIA Services, as it means that slightly more complicated (although common) requirements such as returning a variety of objects to the client as a batch is sadly not possible (even if they don't require change management). The only way to work around this limitation is to drop back to plain WCF communication, which is hardly ideal. Hopefully this limitation will be removed in the near future.

Naming/Signature Convention

Invoke operations do not need to adhere to any naming or method signature convention, and essentially any operation in a domain service that does not fall into any of the other operation categories (via its name or method signature) is considered by RIA Services to be an invoke operation. To explicitly define a method as an invoke operation, apply the Invoke attribute to the method.

An Example Invoke Operation

The following code is an example of an invoke operation:

public decimal GetExchangeRate(string fromCurrency, string toCurrency)
{
    return ExchangeRates.GetRate(fromCurrency, toCurrency);
}

Custom Operations

Custom operations are operations that perform an action on an entity. For example, you might have an operation that discontinues a product, with all the logic to do so to be performed on the server. However, whereas an invoke operation is called immediately from the client, custom operations have their execution on the server deferred until the client submits a changeset to the server (hence treated in the same way as insert/update/delete operations).

When a custom operation is generated in the client project, it will be created as a method on the entity that it acts upon. In addition, it will also be created as a method on the domain context associated with the domain service.

Note

Custom operations are called after any insert or update operations are called on the server. If a custom operation is called on an entity that is later deleted (before the changes are submitted to the server), the custom operation will not be called and will be discarded when the changes are submitted.

Naming/Signature Convention

Custom operations do not having a naming convention; however, they must not return a value, and they must accept an entity as their first parameter (any number of additional parameters is permitted). There is no specific attribute for identifying a custom operation, but instead you should use the Update attribute and set its UsingCustomMethod property to true.

An Example Custom Operation

The following code is an example of a custom operation:

public void DiscontinueProduct(Product product)
{
    // Logic to discontinue the product...
}

Decorating Entities

Decorating entities is the process of applying attributes to entity classes and properties to convey a certain intention. There are three primary reasons why you would want to decorate your entities with RIA Services attributes and data annotations:

  • So you can control how the entity is created on the client (i.e., the properties from the server entity class that will be created on the client entity), and hence what data is passed over the wire

  • To apply validation rules to the entity and its properties (validating what values are acceptable)

  • To apply miscellaneous data annotations (such as whether the property is a key that uniquely identifies the entity, whether the property should be used for concurrency checks, display options for how the property should be presented on the client, etc.)

You can decorate entities with the required attributes directly or via metadata classes.

Note

If you have used the ASP.NET Dynamic Data scaffolding framework, then you will be quite familiar with these attributes, as RIA Services uses the same attributes that it does (defined in the System.ComponentModel.DataAnnotations namespace).

Metadata Classes

As previously described, metadata classes enable you to decorate your entity with attributes without actually modifying the entity class itself (useful when you code-generate your entities and a regeneration re-creates the entity class). A metadata class is associated with your entity via an attribute on the entity class that points to it. In order to not have to modify the entity class to apply this attribute, the usual method is to create a partial class for the entity and apply the attribute to it instead.

Open up the metadata class that was created for the Product entity (from your Entity Framework model) to see how it is structured. The file name for metadata class files are generally of the form <DomainServiceName>.metadata.cs.

This file contains a partial class that extends the Product entity. This is merely so the MetadataType attribute can be applied to the entity, designating the corresponding metadata class.

[MetadataTypeAttribute(typeof(Product.ProductMetadata))]
public partial class Product
{
    internal sealed class ProductMetadata
    {
        // Field definitions (removed for the purposes of brevity)
    }
}

This attribute requires the type for the metadata class associated with this entity as its constructor parameter. As you can see from the example, it is associating the ProductMetadata class contained within the Product class (i.e., the ProductMetadata class is a nested, or inner, class of the Product class). This provides the link between the entity class and the metadata class, which is used by RIA Services to associate the two together. It's not essential to use a nested class as your metadata class—you can actually use a class anywhere that can be referenced, but by default the metadata classes created by RIA Services are created as nested classes. You can then treat the metadata class as if it were the actual entity class, and decorate it with the required attributes.

Note that the metadata class that was created by the Domain Service Class Wizard contains a public field representing each property on the associated entity, which you can decorate with attributes as required.

Controlling Client Entity Generation

You may not necessarily want the entity that is created (by the RIA Services code generation) in the client project to be the same as the entity that is in your server project. For example, the entity may consist of properties that shouldn't be transferred to the client and/or made accessible to it (such as password properties, images, calculated properties, credit card numbers, etc.). Let's look at customizing what's generated on the client-side entity and what data is transferred to the client from the server.

Including/Excluding Properties

You can explicitly state that a given property is created on the client using the Include attribute, or not created by using the Exclude attribute (both found in the System.ServiceModel.DomainServices.Server namespace).

By default, all public properties on an entity that return simple types (such as string, int, and bool), or a small predefined group of complex types (such as byte arrays), will be automatically created as properties (i.e., included) on the client entity (unless they have the Exclude attribute applied). However, this means that other complex values, such as an entity collection associated with each requested entity being returned (i.e., entities associated with the associated entities via a foreign key relationship), will not be transferred to the client when the entity is transferred, and the collection property providing the association will not be created on the client's corresponding entity.

Including/Excluding Associated Entities

For example, by default the collection of product inventory entities for each product entity is not returned or accessible in the client project when the Product entity (or entity collection) is requested. You can, however, explicitly specify that these associated product inventory entities should be returned to the client along with the product entities, and a property to access them should be created on the product entity on the client.

You need to make two changes to enable this: one to the product's domain service that was created for you, and one to the metadata class for the product entity.

In the query operation that returns a collection of products in the domain service, you will need to request (from the Entity Framework) that the product inventory records associated with each product entity are retrieved from the database (using the Include method in the query):

public IQueryable<Product> GetProducts()
{
    return ObjectContext.Products.Include("ProductInventory");
}

Note

You can chain additional Include methods to the statement in order to include additional associated entities in the request to the database.

The other change that needs to be made is to decorate the ProductInventory field (in the metadata class) with the Include attribute to indicate that it should be included and created on the entity generated in the client project (being a complex property and thus not included by default):

[Include]
public EntityCollection<ProductInventory> ProductInventory;

This property represents an association between the Product entity and the ProductInventory entity. However, RIA Services needs to know how these two entities relate (i.e., what property on each entity represents the relationship). The Association attribute is used for this purpose; however, because we are returning an entity from our entity model, this attribute will have already been applied to the property. You do, however, need to explicitly decorate properties representing entity relationships such as this when you are exposing presentation model types. An example of using this attribute can be found in the Presentation Model Types section later in this chapter.

Enabling Associated Entities to Be Editable

If the ProductInventory entities are to be updatable, you will need to create the insert/update/delete operations for the ProductInventory entity on the ProductService domain service. Otherwise, you will receive an exception when you attempt to insert, update, or delete an inventory entity in the collection on the Silverlight client. Alternatively, you can configure your entity association as a compositional hierarchy by decorating the property with the Composition attribute. This will cause the parent entity to be marked as updated when one of its children's entities is updated, and will send all the children entities (regardless of whether they have been modified or not) back to the server when performing an update—effectively treating both the parent and child entities as a single unit. You can then handle updates of the children in the parent entity's update operation rather than requiring them to have their own update operations.

The ExternalReference attribute is designed to be used in conjunction with the Association attribute, and is used to specify that the related entity in an association relationship should not be created on the client side. The reason for using this attribute is that RIA Services only permits an entity to be exposed from a single domain service. By configuring the association, you are actually exposing the related entity type from this domain service. If you already expose the related entity from another domain service, then you will receive an error stating, "Entity types cannot be shared across DomainServices" when you attempt to compile the application, as it is attempting to create the same entity twice on the client. Decorating the property representing the association with the ExternalReference attribute prevents the domain service from exposing the related entity and circumvents the issue.

Validation

One of the important problems that RIA Services tries to resolve is where validation logic needs to be written and executed on both the server and the client. Not only is it a duplication of code and effort to write the same logic in both places, but this logic can easily get out of sync (with a change being made in one place but not the other). It's important to execute the validation logic in both places because you want to have a responsive user interface (without waiting for the server to tell the user that something is invalid), but you also want to validate the data on the server because there is no guarantee that the data came from your own client application (and that its rules weren't circumvented). By enabling you to define your validation logic on your entities on the server, and automatically applying the same validation attributes when it generates the corresponding entities in the client project, RIA Services overcomes this problem. Your validation logic only needs to be written (and maintained) in a single location, and is kept synchronized by the code generation of the entities in the client project.

Predefined Validation Attributes

You can apply a number of basic validation rules to your properties via attributes (from the System.ComponentModel.DataAnnotations namespace), including the following:

  • The Required attribute enables you to specify that the property must have a value (i.e., nonnull). It also enforces that a string must not be empty (by default—although the AllowEmptyStrings named property allows you to disable this behavior).

  • The Range attribute can be used to specify the minimum and maximum values permissible for the property.

  • The StringLength attribute can be used to set a minimum and maximum number of characters permissible for a string property. The default constructor accepts a maximum length (with a default minimum length of 0 characters), but you can also specify the minimum length using the MinimumLength named property).

  • The RegularExpression attribute can be used to validate the value of the property with a regular expression. For example, you may want to validate that an e-mail address conforms to a valid e-mail address format using one of the many commonly used regular expressions for that purpose (of which many aren't perfect but are considered acceptable).

Examples of these rules are shown in the following code:

[Required]
[StringLength(50)]
[RegularExpression(@"(?<user>[^@]+)@(?<host>.+)")]
public string EmailAddress;

[Range(0, 150)]
public int Age;

Custom Property Validation Attributes

Of course, these are only basic validation rules, so what if you need a validation rule that isn't included in the ones provided? In that case you can write your own. However, in writing the code for a validation rule, you will need it to be available on the client too. Luckily, RIA Services helps because it contains code-sharing functionality (between the server and the client), where code written on the server can be duplicated on the client by the RIA Services code generator. This feature is covered later in this chapter, but in the meantime let's take a look at what's required to write a validation rule. You have two ways of creating your own validation rule. One way uses the CustomValidation attribute to decorate the property (or class) or be validated with, to which you can pass a type (i.e., class name) and the name of the method in the class that does the validation. This method isn't ideal, because it requires you to pass the method name as a string, which is likely to cause problems when refactoring. A better and much neater way is to create your own validation attribute, which is the method we'll use here.

Let's create a simple rule that validates the entries in the Class property to limit it to a small set of predefined values. The valid values for this property are validated in database using check constraints; however, ideally we'd like to validate the property value before it gets to this stage. The first step is to create a class for your validation rule that inherits from the ValidationAttribute class (in the System.ComponentModel.DataAnnotations namespace). For example, we will call our class ProductClassValidationAttribute. The next step is to override the IsValid method from the base class. The value of the property to be validated will be passed in as a parameter to this method, along with a validation context—which contains information about the property being validated, the entity instance that the property belongs to, and so on. This method must return either a result of ValidationResult.Success (if the validation rule is passed) or an instance of the ValidationResult class to which you've passed an error message as the constructor parameter value. An example of a custom validation rule to validate the Class property on the product entity to a set of predefined classes is as follows:

public class ProductClassValidationAttribute : ValidationAttribute
{
    protected override ValidationResult IsValid(object value,
                                ValidationContext validationContext)
    {
        string productClass = value.ToString();
        bool isValid = false;

        string[] validClasses = new string[] { "H", "M", "L", "" };
        isValid = validClasses.Contains(productClass.ToUpper());

        return isValid ? ValidationResult.Success : new ValidationResult(
                                                      "The class is invalid");
    }
}

Note that this validation class should be included in a shared code file (which will be described later in the chapter).

Now you simply need to apply the attribute to the property to be validated—very easy!

[ProductClassValidation]
public string Class;

Custom Class Validation Attributes

Now, what if you need to compare two properties in a validation (e.g., if the finish date property's value must be later than the start date property's value)? Again, you can create a custom validation rule (in the same way as before) and apply it to the entity as a whole (rather than at the individual property level). In this scenario you don't want the validation rule to run as soon as you modify one of the properties, because it will prove frustrating to users to raise a validation error when they make a change to one but have yet to make a change to the other. Specifying a validation rule at the entity level solves this problem by only validating the entity when it is saved (or navigating to another one). Creating a validation rule to validate an entity is the same as creating the ProductClassValidation attribute, but instead of a property value being passed in to be validated, the entire entity is passed in. For example, here is a custom validation rule for ensuring that the sell finish date on a product is later than the sell start date:

public class SellDatesValidationAttribute : ValidationAttribute
{
    protected override ValidationResult IsValid(object value,
                                        ValidationContext validationContext)
    {
        Product product = value as Product;
        return product.SellEndDate > product.SellStartDate ?
            ValidationResult.Success : new ValidationResult(
                "The sell end date must be greater than the sell start date");
    }
}

Then you can apply the validation rule to the entity like so:

[SellDatesValidation]
internal sealed class ProductMetadata

Presentation

Decorating your entities (which are essentially middle-tier components) with information for the presentation tier is somewhat controversial—it can make creating forms in your application very easy (using the drag-and-drop features in Visual Studio 2010), but it blurs the line between the middle tier and the presentation tier. Ideally, the middle tier should know nothing about how the presentation tier will display the data. In any case, some attributes are available that enable you to provide presentation information on your entities—whether you use these attributes or enforce a strict separation of concerns is up to you.

The primary attribute used for this purpose is the Display attribute. The DataForm control (from the Silverlight Toolkit) uses this attribute (if available) when it is automatically creating a data entry form from an entity, and the DataGrid control uses it when creating a data grid with automatically generated columns. The Display attribute has a number of properties that suggest to the presentation tier how the property should be displayed. Some of these properties control the following:

  • Whether a field should be created in a data form or data grid for this property (AutoGenerateField)

  • A "friendly" name for the property that will be used as the label of a corresponding field/column in a data form or data grid (Name/ShortName)

  • A description for the property that will be displayed in a data form as a tooltip when the user moves the mouse cursor over an information icon next to the field (Description)

  • The order (in relation to the other properties) in which a property will be displayed in a data form or data grid (Order)

  • What grouping the field for this property should be displayed under in a data form (GroupName)

Miscellaneous Data Annotations

A number of important miscellaneous data annotations don't fit in either of the previous annotation type categories, but are used to control how RIA Services manages the entity, or to provide information to the presentation tier on how this entity should be displayed.

Some of the more important data annotations used by RIA Services are as follows:

  • The Key attribute is used to specify that a property (or multiple properties) on an entity uniquely identifies that entity (i.e., represents a primary key).

  • The Editable attribute is used to specify whether a property should be editable or not. By default, properties are editable, but you can decorate a property with this attribute, passing a value of false to its allowEdit constructor parameter to specify that the value of the property should not be able to be altered. Any attempt to do so will raise an exception on the client. You would typically apply this to the property that contains the entity key.

  • The ConcurrencyCheck attribute is used to specify that a property should be used when performing concurrency checks back on the server before updating (or deleting) the entity in the database. The original value of the property (as retrieved from the server) will be passed back to the server when performing an update/delete operation so that it can be used in the concurrency check. Identifying and handling concurrency violations is discussed later in the chapter in the "Handling Data Concurrency Violations" section.

  • The RoundtripOriginal attribute is used to specify that the original value of the property (as retrieved from the server) will be passed back to the server when performing an update/delete operation.

  • The Timestamp attribute is used to specify that the property is a binary number (note that it is not a date/time) that is being used to version the entity, and is often used in concurrency checks. The original values of properties with this attribute will (like those with the ConcurrencyCheck or RoundtripOriginal attributes) be passed back to the server when performing an update/delete operation.

Presentation Model Types

The examples so far have passed entities from an Entity Framework model on the server to the client, but what if you're not using an Entity Framework (or LINQ to SQL) model in your application? In fact, because entities from the model could be considered data access layer objects, it could be considered bad practice to pass these to your client and consume them in the presentation tier. Alternatively, perhaps the entities in your model just don't match one-to-one what is required to be displayed in the presentation tier (i.e., the objects to be displayed need data aggregated from multiple entities), requiring you to design an alternative and more effective means of transferring the data. Or perhaps you just aren't using the Entity Framework or LINQ to SQL as your data access layer, yet still want to take advantage of the benefits that RIA Services provides.

There are many additional reasons why you wouldn't want to pass entities between the server and the client—although it is very easy to do so, the practice often creates problems due to the tight coupling between the data access layer and the presentation layer.

You'll therefore often find the need to transfer custom objects (designed to cater to the needs of the presentation layer) rather than entities between the server and the client—populating these objects on the server from your data access layer, and then (if these objects are editable) manually persisting the changeset returned from the client back to the data access layer. Implementing custom objects rather than entities will help you maintain a clean separation of concerns between your data access layer and presentation layer, which is generally considered best practice despite requiring additional work to wire up this structure.

You may think of these objects in traditional terms where they might be called data transfer objects (DTOs) or POCO types. Generally, when using them with RIA Services, they are termed presentation model types.

Note

Once upon a time, a common practice was to return data from web services in DataSets. Despite being a "heavy" way of exposing data, many developers liked to use DataSets for their simplicity and ability to modify the data structure being returned without needing to update references to the services exposing them. However, time has shown that the use of DataSets is a source of numerous problems in projects, and their use is now considered bad practice. Thus, many developers have turned to exposing data using lightweight, strongly typed DTOs instead. If no other reason compels you to stop using DataSets, the fact that Silverlight has no support for DataSets puts the final nail in their coffin. Therefore, don't try to expose DataSets from your domain service in an attempt to return an untyped object whose structure you can modify at will. Neither RIA Services nor Silverlight has any support for DataSets—consider them finally dead!

Although RIA Services works very nicely with the Entity Framework and LINQ to SQL models (by creating domain services with operations for the selected entities automatically for you with the Domain Service Class Wizard, and implementing some core behavior in the base class to work with these types), fortunately it's not limited to just transferring entities from these technologies. Let's take a look at how to implement RIA Services that pass presentation model types between the server and the client.

Creating the Presentation Model Class

The first step is to create a standard class containing just the properties that should be transferred between the client and the server. As with the examples provided earlier with entities from the Entity Framework, you can decorate this class (either directly or using a metadata class) with validation rule attributes and such.

Note that one property (or more as required) must be decorated with the Key attribute to indicate that that value of the property uniquely identifies the object (otherwise you will receive a compilation error, because the changeset manager on the client cannot manage the changes without it).

For this example we will create a presentation model class named ProductPM with similar properties (although we'll use only a subset for the purpose of brevity) to our Product entity, but demonstrate exposing presentation model objects from a domain service instead of Entity Framework entities. This object will not have any knowledge of how it is populated or persisted—it will be handled in the domain service.

Here is the code for the presentation model class:

public partial class ProductPM
{
    [Key]
    [Editable(false)]
    public int ProductID { get; set; }

    public string Name { get; set; }
    public string ProductNumber { get; set; }
    public decimal ListPrice { get; set; }
    public DateTime ModifiedDate { get; set; }
}

Note

You can also use metadata classes with presentation model objects in the same way as was demonstrated with entities. For simplicity's sake, however, we are simply decorating the ProductPM class's properties directly with the attributes required by RIA Services without going to the trouble of creating a separate metadata class for it. Therefore, you will need to add a using directive to the System.ComponentModel.DataAnnotations namespace at the top of the file. Also worth noting is that RIA Services does not require your presentation model class to be defined in the web project (or even in a WCF RIA Services Class Library project, as described later in this chapter). For example, you may have the objects you want to expose from your domain service in a referenced assembly for which you don't have the code, and this is possible because RIA Services simply reflects over the given object and generates the code it requires accordingly in the client project—no source required.

Populating and Exposing Your Presentation Model Types

Now we need to create a domain service so the client can retrieve a collection of these objects, and update/insert/delete objects in the collection if appropriate. Unfortunately, the Domain Service Class Wizard only generates the CRUD operations in your domain service when you are using entities from the Entity Framework or objects from a LINQ to SQL model—it doesn't allow you to select objects of other types (because the CRUD logic for these objects does not necessarily follow a specific model). Therefore, we must create the domain service ourselves from scratch.

Add a new item to your project (in the Services folder), named ProductPMService, using the Domain Service Class item template, and select <empty domain service class> from the Available DataContexts/ObjectContexts drop-down list in the Domain Service Class Wizard dialog. This creates an empty domain service to which we can add the required operations. Notice that the domain service inherits from DomainService, instead of the generic LinqToEntitiesDomainService class that the previous domain services (that served up entities from the Entity Framework model) inherited from (providing the base functionality for how the domain service would handle the entities).

[EnableClientAccess()]
public class ProductPMService : DomainService
{
}

Now we need to add the query operation to the domain service. Because the data is coming from a query to the Entity Framework model, we can still take advantage of the benefits of returning an IQueryable expression by projecting data from the Entity Framework model onto a collection of the presentation model objects, and returning this query from the operation. Therefore, the client can add any additional sorting/filtering/grouping/paging that it requires. The following code demonstrates how this is done:

[EnableClientAccess()]
public class ProductPMService : DomainService
{
    private AdventureWorks2008Entities context = new AdventureWorks2008Entities();
public IQueryable<ProductPM> GetProducts()
    {
        return from p in context.Products
               select new ProductPM()
               {
                   ProductID = p.ProductID,
                   Name = p.Name,
                   ProductNumber = p.ProductNumber,
                   ListPrice = p.ListPrice,
                   ModifiedDate = p.ModifiedDate
               };
    }
}

When executed, this expression will populate and return a collection of our presentation model object (ProductPM) to the client.

Updating Your Presentation Model Types

You can also implement the insert, update, and delete operations by taking the ProductPM object that will be passed into each one as a parameter, and manually updating the Entity Framework model with the changes accordingly.

Particularly when inserting (and sometimes updating) data on the server, you will want to return values that have been generated on the server back to the client (such as a primary key value generated by the database when inserting a row, or a timestamp value being used for row versioning in the database). You can do this using the Associate method on the domain service's ChangeSet object. Pass this method the presentation model object, the entity that was updated, and a callback function. Once the database has been updated, the callback function will be called, at which point you can update the presentation model object as required from the entity.

The following code demonstrates updating the ProductPM object with the ProductID assigned to the Product entity when inserting a new record into the database:

public void InsertProduct(ProductPM productPM)
{
    Product product = new Product();
    product.Name = productPM.Name;
    product.ProductNumber = productPM.ProductNumber;
    product.ListPrice = productPM.ListPrice;
    product.ModifiedDate = DateTime.Now;
    context.Products.AddObject(product);
    context.SaveChanges();
    ChangeSet.Associate(productPM, product, UpdateProductPMKeys);
}

private void UpdateProductPMKeys(ProductPM productPM, Product product)
{
    productPM.ProductID = product.ProductID;
}

Note

If your presentation model object contains a property exposing a collection of another object that you want to be passed to the client, as discussed earlier in this chapter (see the "Controlling Client Entity Generation" section), you must explicitly specify that the property should be generated on the client by decorating it with the Include attribute. If the objects in the collection are to be updatable, you will need to add corresponding insert/update/delete operations to the domain service for the object type. For example, you may want to expose the collection of product inventory objects associated with each product object (with the ProductPM object containing a property representing a collection of related ProductInventoryPM objects). However, if you attempt to compile the project, you will find that you receive a compile error stating that you have an invalid Include specification. This is because you need to provide an association between the child object and its parent by decorating the property that exposes the collection on the parent with the Association attribute, which takes as its parameters a name for the association (of your own choosing) and the property names that link the parent and the child together. The following example demonstrates implementing a property that provides an association between the ProductPM and ProductInventoryPM objects:

[Include]
[Association("ProductPM_ProductInventory", "ProductID", "ProductID")]
public IEnumerable<ProductInventoryPM> ProductInventory { get; set; }

Sharing Code/Logic Across Tiers

At times you may want to share custom code between the middle tier and the presentation tier. For example, you might want to share custom validation logic (and some types of business logic) between the server and the client. This may be a case of extending an entity with a partial class (such as adding additional methods and computed properties), or you may want to share completely new classes (such as one of the custom validation attribute class discussed earlier in the "Validation" section).

The RIA Services code generator enables this by automatically copying any files in the server project with a .shared.cs extension into the client project untouched. Therefore, you can write the code in one place (the server) and know that the client will be updated accordingly. Of course, because the shared code will be running on both platforms, you should ensure that the code you write will successfully run on both platforms, and that it doesn't use any platform-specific functions.

For example, say you want to extend your Product entity to add a calculated property called ProfitMargin to it (which determines the expected profit to be made on the product by subtracting the StandardCost from the ListPrice) that will be used on both the server and the client.

To do so, you simply need to create a partial class of the Product entity somewhere in the server project, and be sure to include .shared in the file name. For example, create a file called Product.shared.cs in the ModelsShared folder in the server project. Then add the following code to extend the Product entity with the ProfitMargin property:

namespace AdventureWorks.Web
{
    public partial class Product
    {
public decimal ProfitMargin
        {
            get { return ListPrice - StandardCost; }
        }
    }
}

This file will automatically be copied to the Silverlight project (due to its file name), and thus extend the Product entity accordingly in both the server and client projects.

Note

If the property or method you are sharing is only required on the client (not the server), shared code may not be the most appropriate means of extending the entity. In this case you would be better off writing the partial class in the Silverlight project directly to extend the entity with that functionality on the client only.

Inspecting the Generated Code in the Silverlight Project

We'll look at how to actually consume a domain service in your Silverlight project over the following chapters; however, for the time being let's take a look at the code that RIA Services generates in the Silverlight project. As discussed earlier in the chapter, RIA Services automatically generates code in a hidden folder (named Generated_Code) under the Silverlight project that enables it to communicate with the domain services in your web project. You can view the code generated in this folder (as shown in Figure 5-5) by selecting the Silverlight project in Solution Explorer and toggling the Show All Files button to "on" (using the second button from the left in the Solution Explorer window's toolbar). The core file code generated by RIA Services is named [webprojectname].g.cs. For example, in the project structure shown in Figure 5-5, this file is named AdventureWorks.Web.g.cs.

The Silverlight project structure, showing the hidden Generated_Code folder

Figure 5.5. The Silverlight project structure, showing the hidden Generated_Code folder

When you open this file, you will find that each domain service in the web project will have a corresponding domain context class created in this file (that can then be used to communicate with the domain service from the client). If you follow the standard naming convention for naming domain services (XXXService, where XXX can be anything of your own choosing), the RIA Services code generator will name the domain context XXXContext. For example, a domain service named ProductService in the web project will have a corresponding domain context created in this file named ProductContext.

Each query, invoke, and custom operations defined on the domain service will have a corresponding method created on this domain context class that enables it to be called from the client. However, the insert/update/delete operations do not have corresponding methods created on the domain context, as they are never explicitly called from the client. Instead, as changes are made to an entity collection that's returned from a query operation, the domain context will maintain a changeset—consisting of the actions performed on the entities in the collection. When the SubmitChanges method is called on the domain context, RIA Services will send the changeset to the server and call the corresponding insert/update/delete operations on the domain service as required.

In addition, each entity (or presentation model class) exposed by a domain service in the web project will have a corresponding class created in this file. Note that any property whose corresponding field has been decorated with attributes in the metadata class for that entity on the server will have those same attributes applied directly to this generated client-side entity.

Note

Inspecting the code generation files can also be useful when attempting to identify code generation problems. When debugging, you can also step into these files and set breakpoints to help identify issues. Just be sure not to modify these generated files, because any changes you make will be overwritten by the RIA Services code generator the next time it updates that file. If you want to add additional functionality to a generated class, create a separate file defining a partial class and extend the class in that manner.

Encapsulating Logic in a Separate WCF RIA Services Class Library

By default, when using RIA Services, your Silverlight project is linked directly to your web project (which is what the examples so far have demonstrated). You are defining your business objects and business logic in your web project, and this is being copied directly into your Silverlight project. However, from a design perspective this is possibly not an ideal structure for your solution (depending on the nature and scale of your web application), because this tightly couples both your Silverlight project and your business objects/logic to your web project (and limits their reusability).

It is possible, however, to move the business logic out of the web project and into a separate class library, using the WCF RIA Services Class Library project template. Unfortunately, the process to do so is not particularly straightforward, but let's first look at the structure that using RIA Services class libraries gives us.

The WCF RIA Services Class Library project template was designed to be added to an existing Silverlight project solution (rather than providing a full solution template). When you add a new project to your solution using this template, it will actually add two projects to your solution (one with .Web appended to the name you specified)—both placed into a solution folder that the project template creates (as shown in Figure 5-6). These two projects will share an RIA Services link, meaning that you can now remove the link directly between the web project and the Silverlight project, removing the tight coupling between the two. Now you can move your domain services, entity model, metadata classes, presentation model classes, and shared code into the .Web class library, and RIA Services will generate the corresponding code into the Silverlight class library (in the same way it previously did into the Silverlight application itself).

The structure of the RIA Services Class Library projects

Figure 5.6. The structure of the RIA Services Class Library projects

Now it should simply be a case of adding a reference to the .Web class library to your web project, and adding a reference to the other class library to your Silverlight application. Unfortunately, however, this will most likely not be the case. If you are using the authentication features of RIA Services (which will be discussed in Chapter 8), then you will find that by putting the AuthenticationService in the class library, the WebContext object will not be generated by RIA Services in the Silverlight application (which is required to access the authentication service). You can work around this problem, however, by manually implementing the WebContext class and instantiating it when the application starts.

Note

The sample project associated with this book uses this class library approach, and is configured with the authentication service workaround that you can work from.

Handling Server Errors

When errors occur on the server, it's useful to log them (such as to the event log, the database, or a text file) in order to help track down the problem that led to the exception, and often you may wish to add some additional handling behavior such as sending an e-mail notification to the system administrator. You could handle this in each domain operation, but that would mean repetitive exception handling code in each operation. An easy way to handle errors with RIA Services is to simply override the OnError method on the domain service:

protected override void OnError(DomainServiceErrorInfo errorInfo)
{
    // Log exception here
}

This method will be called each time an exception is thrown in the domain service. You can then log the error and add in an additional error handling logic. Useful information that you should include with any logged error includes when the error occurred (i.e., the date and time), who encountered the error (if the user is logged in), where the error occurred (i.e., the method), the error message, a stack trace, and any data associated (such as the current changeset, which can be obtained simply using the ChangeSet property on the domain service itself) that may help identify the cause of the issue. You will find most of these as properties on the errorInfo object.

To test your error logging and simulate something going awry in your domain service, you can simply throw an exception in one of the domain operations, which will then call your OnError override method, enabling you to log the error.

Note

You can choose whether to directly propagate these exceptions to the client, or sanitize them first (in order to avoid exposing the potentially sensitive details of the inner workings of your service and the stack trace to the client). The steps to configuring your web project to sanitize the exceptions are detailed in Chapter 8.

Handling Data Concurrency Violations

When you have multiple users updating data in your application, you are sure to eventually be faced with data concurrency violations. Say you have two users who retrieve the entity representing the same product in the database at roughly the same time (such that the Product entity returned to both is identical). If both of those users decide to update that product, however, then you face a conundrum. If one user were to submit their changes, followed later by the other user, the first user's changes would be overwritten by the second user's changes, leading to the first user's updates being lost, and creating potential data integrity issues in your database.

This scenario is what is known as a concurrency violation. Ideally, you want the second user to be informed that the product has changed on the server, since they retrieve the data when they attempt their update, and to be able to respond to that situation accordingly (which may differ from application to application, or even based upon the particular data being modified).

Note

Although concurrency violations are rare, you must cater for them accordingly in your code. Otherwise, you will find people complaining that the system is "losing data," with no idea why.

Methods of Identifying Concurrency Violations

To appropriately handle this type of scenario, you will need to create (and design) a strategy to identify, handle, and resolve these concurrency violations, on both the server and the client. RIA Services uses optimistic concurrency (i.e., the record in the database isn't locked from being updated by other users after being retrieved), so you will need to design your strategy within these boundaries. Optimistic concurrency violations are generally identified by comparing given properties on the original version of an entity (before it had been modified on the client) with those on the stored version of the entity (as it currently exists in the database), immediately before persisting data to the database. Therefore, concurrency checks generally involve three versions of the entity:

  • The original version containing the state of the entity as originally retrieved from the server

  • The current version, which generally consists of updates made on the client to the original version of the entity that are now being sent back to the server to be committed to the database

  • The stored version, which can be currently found in the database prior to being updated

For example, a common strategy to identify concurrency violations is to have a column on your database table that is updated whenever the row is changed. Common column types for this purpose include

  • A timestamp/rowversion column

  • A uniqueidentifier (i.e., GUID) column

  • A datetime column

When performing an update operation on an entity, RIA Services will retrieve the entity as it is currently stored in the database and compare the original value of the given concurrency check property sent back from the client with its stored value as found in the database. If the values are different, then a concurrency violation has been identified. A possible reason the value of this property might have changed since it was retrieved from the database is if another user has updated the entity since the original version had been retrieved from the server.

Alternatively, if you don't have a single property on the entity that you can use for concurrency checks, then you may compare a number of (or all) properties on the entity between its original version and the stored version, and raise a concurrency violation if any of the values are different. This method is far more cumbersome than the single-column method, although it does let you pick and choose which properties whose changes you don't want to be lost, while ignoring changes to others whose changes may be unimportant.

Configuring the Entity Model to Check for Concurrency Violations

By default, RIA Services (and the Entity Framework) will simply update an entity without performing any concurrency checks. You will need to configure it to perform these checks before updating/deleting data in the database.

The first part of the puzzle to implement concurrency checks is to send the original values of given properties back from the client that you want to compare for concurrency violations before an update operation. In order to minimize data traffic passing between the client and the server, only the original values of the entity's properties that are decorated with the ConcurrencyCheck attribute, the TimeStamp attribute, or the RoundtripOriginal attribute will be sent back to the server when performing an update operation. Therefore, you need to specifically identify these properties to RIA Services so their original values are returned to use in the concurrency check. However, despite providing the framework to identify the fields to use on concurrency checks, RIA Services doesn't actually provide the logic to implement the concurrency checks themselves. Instead, it leaves the task of identifying concurrency violations to the data access layer.

If you are using the Entity Framework as your data access layer (as we are in this book), then you can simply configure the entities in your entity model to perform concurrency checks prior to an update, specifying which of the entity's properties will participate in the comparison check. In your entity model, select one of these properties on the entity, and from the Properties window change its Concurrency Mode property from None to Fixed.

If you need to compare more than one property on the entity for a concurrency violation, then repeat this for each property that should participate in the comparison. Now, prior to any update of that entity, the stored version of the entity will be loaded from the database, and the stored values of the specified properties retrieved will be compared to their values as returned from the client, and a concurrency violation exception will be thrown if any of the values being checked are different.

Note

If exposing entities from your entity model directly to the client, the entity properties identified in the entity model to be used in concurrency checks will already be decorated with the appropriate attributes to send their original values back from the client, removing the need to do so in your entity metadata class. Therefore, in this case, implementing concurrency checks is as simple as configuring them in your entity model. However, a little more work is required when you are using presentation model objects, as will be discussed shortly.

For example, the Product entity contains a property called ModifiedDate (with a type of DateTime), which we can use to perform concurrency checks. When updating the entity, we want the original value of this property to be sent back to the server along with the updated entity, and if its value has changed in the database since the entity was originally retrieved, then a concurrency exception should be thrown. To specify that this property should be used for the concurrency check, find the Product entity in your entity model, select the ModifiedDate property, and from the Properties window set its Concurrency Mode property to Fixed.

Note

The ModifiedDate field on the Products table in the AdventureWorks2008 database is not automatically updated by a database trigger when the record is updated. Therefore, you will need to explicitly update this property when the Product entity is being added/updated. You should set this value on the server, not the client side, as doing so on the client side would assign the client PC's date/time (which can vary greatly based upon the user's time zone, or if they have the incorrect date/time set on their machine)—potentially misrepresenting the actual time that the record was updated. The easiest way to update this property is in the insert/update domain operations to the Product entity passed in as a parameter before continuing on with the add/update. The value you set here will be automatically passed back to the client.

Testing Your Solution

Assuming you have implemented functionality in your Silverlight application to retrieve and update Product entities, you can test it using two copies of your Silverlight application that are open at the same time in different browser windows. Retrieve a given Product entity from the server in one instance of the application, and retrieve exactly the same entity in the other. Make a change to the entity in one instance, and send it to the server to update the database with the change. Now make a change to the entity in the other instance and send the update to the server. Because you updated the entity in the first instance, the ModifiedDate property of that entity on the server will have changed from its original value that is being sent through with the update in the second instance. Therefore, when the Entity Framework compares these values and finds that they are different, it will throw a concurrency exception.

Resolving Conflicts in the Domain Service

In this sort of scenario your domain service will inherit from LinqToEntitiesDomainService, which provides the ResolveConflicts method that you can override, which will be called when a concurrency conflict has occurred, and enable you to handle it using some custom logic on the server side if you wish. This method returns a Boolean value—if it returns false, then the client will simply be notified of the conflicts. If your method returns true, then the changeset will be resubmitted to the database once again (however, this will happen only once).

As mentioned earlier, a little more work is required if you are exposing presentation model objects to the client instead of entities. In this case you will need to specify which properties should have their original values sent back to the server when updating an object, and then handle the concurrency check logic yourself when updating the database.

The first step will be to decorate the properties on the presentation model object that will participate in the concurrency check (generally in the object's metadata class) with one of the attributes that ensure that the original value of that property will be sent back with any update of the object to the server (i.e., ConcurrencyCheck, TimeStamp, or RoundtripOriginal). For example:

[ConcurrencyCheck]
public DateTime ModifiedDate { get; set; }

You then need to compare the original value of this property with its stored value in the update operation of the domain service. However, you will find that only the updated version of the entity is passed into the operation as a parameter, whereas you also need the original version of this entity for your comparison. You can retrieve the original version of the object using the GetOriginal method of the ChangeSet object. Note that on this "original" object, only the properties specified to be round-tripped will have values.

ProductPM originalProduct = ChangeSet.GetOriginal<ProductPM>(currentProduct);

The next step depends on how you have implemented your data access layer. If your data access layer already performs concurrency checks, then no additional work is required. Otherwise, your update domain operation will need to manually retrieve the stored version of the entity from the database and compare the values of the properties between this and the original version to identify a concurrency violation. If one is found and you want to notify the client, you will need to get the ChangeSetEntry object for the object being updated, and assign values to its ConflictMembers, StoreEntity, and IsDeleteConflict properties. For example:

ProductPM originalProduct = ChangeSet.GetOriginal<ProductPM>(currentProduct);
ProductPM storedProduct = GetProduct(currentProduct.ProductID);

if (storedProduct.ModifiedDate != originalProduct.ModifiedDate)
{
    ChangeSetEntry entry =
        ChangeSet.ChangeSetEntries.Single(p => p.Entity == currentProduct);

    List<string> conflicts = new List<string>();
    conflicts.Add("ModifiedDate");
    entry.ConflictMembers = conflicts;

    entry.IsDeleteConflict = false;
    entry.StoreEntity = storedProduct;
}
else
{
    // Save ProductPM...
}

Note

The OnError method (as discussed in the "Handling Errors" section of this chapter) is thrown when a concurrency violation has been identified. If you are logging errors, you will probably want to ignore the OptimisticConcurrencyException exception, as the errors do not indicate a failure in the system and thus are of little value being logged.

Handling concurrency violations on the client side will be covered in Chapter 7.

Transactions

Transactions are often used when updating data in a database, such that if an update fails (such as if a concurrency violation is identified), the updates made since the transaction was started will be rolled back, leaving the database in the same state as it was before the update started. The use of transactions ensures that the database doesn't get into an unknown or inconsistent state because some changes but not others have been written to the database, potentially compromising its integrity.

Often you will want to encapsulate the changes being submitted to the database within a transaction, particularly when using a data access layer other than the Entity Framework or LINQ to SQL (as these both already automatically implement transactions when submitting changes). You can do so in a domain service by overriding the Submit method from the base class, and encapsulating the call to the Submit method in the base class in a transaction scope, as demonstrated here:

public override bool Submit(ChangeSet changeSet)
{
    bool result = false;

    TransactionOptions transactionOptions = new TransactionOptions();
    transactionOptions.IsolationLevel =
        System.Transactions.IsolationLevel.ReadCommitted;

    using (TransactionScope transaction = new TransactionScope(
TransactionScopeOption.Required, transactionOptions))
    {
        result = base.Submit(changeSet);
        transaction.Complete();
    }

    return result;
}

Note

You will need to add a reference to the System.Transactions assembly and a using statement to the System.Transactions namespace in your class in order to implement the preceding example.

WCF RIA Services Toolkit

The WCF RIA Services Toolkit consists of a number of out-of-band features that are (currently) not included in the core RIA Services framework. These typically are features that haven't quite reached the maturity required to be full-fledged members of the core RIA Services framework, but are updated frequently and made available for you to use while they gain maturity (without any guarantees that they will work as required, or won't radically change and/or break your code with future updates). By making them available in the toolkit, Microsoft is able to garner feedback from developers and update them accordingly in a timely manner without needing to wait for a new release of the full framework. In turn, Microsoft is providing new features on a regular basis for you to use, much earlier than you would otherwise be able to.

At the time of writing, the following features can be found in the WCF RIA Services Toolkit:

  • LinqToSqlDomainService: Unfortunately, there is currently no support for LINQ to SQL models out of the box in the RIA Services framework. However, you can find the LinqToSqlDomainService class in the toolkit (similar to the LinqToEntitiesDomainService in the framework, but for LINQ to SQL) to provide this support.

  • SOAP Endpoint: This enables you to expose a SOAP endpoint for your domain services.

  • JSON Endpoint: This enables you to expose a JSON endpoint for your domain services.

  • ASP.NET DomainDataSource: This is a data source control that enables you to communicate with your domain services in an ASP.NET application or web site.

A link to download the WCF RIA Services Toolkit can be found on the WCF RIA Services page on the Silverlight web site, at www.silverlight.net/getstarted/riaservices.

Alternative Communication Technologies

If you have developmental control of the server-side aspects of your application (i.e., you are building the services that will expose data to the application), RIA Services will probably (in most cases) be the most appropriate technology to use for consuming data in a Silverlight application. RIA Services has been specifically designed for Silverlight applications, and with its built-in changeset management functionality, in conjunction with its ability to return an IQueryable expression (such that filters/sorting/grouping/paging can be applied on the client but executed on the server), it is undeniably the most powerful communication framework available to Silverlight applications.

That said, it's not the only means to consume data from a Silverlight application—in fact, there are a plethora of technologies available for you to use—so much so that choosing the right one can be rather difficult.

You may be forced to consume data using another technology if you need to consume data from services that you have no control over, need to use a feature not supported by RIA Services (such as duplex communication), or simply find that the architecture of RIA Services does not fit in with the architecture of your application. Although this book specifically focuses on consuming data in Silverlight using RIA Services, let's take a look at some of these alternative technologies that you can use to consume data in a Silverlight application.

Note

All server communication must be performed asynchronously. The Silverlight team made the decision to not support synchronous communication in order to avoid service/HTTP/socket calls blocking the Silverlight application's user interface (and in turn, the browser, as Silverlight runs in the browser's user interface thread) while waiting for a response. Regularly hanging the browser would not make Silverlight a good plug-in citizen. Therefore, you will need to make these calls asynchronous instead—starting the call and adding an event handler to the Completed event to handle the result.

If you're a developer only used to writing synchronous code, this will require you to think a little differently in how you structure the flow of your code. For example, if you wanted to queue service calls (in instances where the next service call to be made relies upon the result of the previous call), the asynchronous-only behavior complicates matters somewhat.

Despite the service/HTTP/socket call being performed on a background thread, the Completed event is actually raised on the user interface thread. Therefore, you don't need to worry about the cross-thread synchronization issues often experienced after making an asynchronous call (due to attempting to update the user interface from an event raised on a background thread). Therefore, there is no need to update the user interface via thread-safe calls using the Invoke method. This also applies to the other technologies listed here, with some exceptions. As a general rule, if the call raises an event when it completes, it will usually be raised on the user interface thread. However, if it uses asynchronous callback delegate (taking an AsyncCallback object as a parameter instead), then the callback method will be called on the background thread.

WCF Services

As previously mentioned, RIA Services is built on top of WCF. You will no doubt be familiar with WCF if you have ever written an application that consumes data from a server. In essence, WCF is a unified communication layer that implements a single programming model that can target different communication technologies. It enables you to write the messaging features for an application in a technology-agnostic manner, with the actual technology to be used (referred to as bindings) specified through configuration files. In other words, you can write the communication functionality for your application without worrying about what technology it will use and how that communication will take place, and without tying your application to a specific messaging technology. This is achieved by WCF providing a common model for you to write services against, which it can then map to the configured messaging technology. This model is broken into three components—the service's address, its bindings, and its contract, with a clear separation maintained between each. You define the contract (i.e., the functionality to be exposed by your service), and then you can configure the address and the bindings that the service supports to form a service endpoint.

Configuring Bindings

The biggest limitation to consuming WCF services in Silverlight is that not all bindings (i.e., messaging technologies) supported by WCF in the full .NET Framework are supported by Silverlight (for consumption). Therefore, you must ensure that the WCF service that you wish to consume is actually configured to use a binding that Silverlight supports. The bindings supported by Silverlight are

  • basicHttpBinding: Uses the XML-based SOAP 1.1 protocol when communicating with the server. This is the protocol used by the older-style ASMX web services, and sends messages in clear text over HTTP.

  • pollingDuplexHttpBinding: Enables the server to push messages to the client in addition to simply handling client requests. As its name suggests, this binding works over HTTP, with the client using HTTP long-polling the server (behind the scenes), waiting for it to send it a message. This is useful when you want the server to send notifications to the clients, although the TCP binding is now a better and more performant choice to implement this type of behavior.

  • netTcpBinding: Used for similar purposes as pollingDuplexHttpBinding; however, instead of working over HTTP, it uses the net.tcp protocol. The performance of this binding is much better than pollingDuplexHttpBinding (primarily because there is no polling of the server); however, it is better suited to intranet scenarios, as it uses ports other than HTTP port 80 (although it's restricted by Silverlight to ports 4502 to 4534), which are often blocked by firewalls. Issues that you may face when using this binding include the fact that you cannot use transport-level security (it's not supported by Silverlight with the net.tcp protocol), there are a number of features you need to configure on your machine to support net.tcp communication, and you need to expose a socket policy file on port 943 in order to permit TCP connections from Silverlight applications.

  • customBinding: Enables you to tailor a binding to suit your requirements where a standard binding matching these requirements does not exist. Defining a custom binding enables you to customize the configuration of the various layers forming the binding, which includes the encoding of the messages (such as clear text, binary, etc.) and the transport over which they will be transferred (such as HTTP, HTTPS, TCP, etc.).

Note

The wsHttpBinding often used by the default endpoints configured by Visual Studio for WCF services is not supported in Silverlight. Therefore, you will need to explicitly define an additional endpoint (or modify the existing configuration of an endpoint) that implements a binding supported by Silverlight. If you are creating the WCF service yourself, use the Silverlight-Enabled WCF Service item template (instead of the standard WCF Service item template), which will be configured in a manner friendly to being consumed by a Silverlight application (using a binding supported by Silverlight, and allowing the service to run in ASP.NET compatibility mode).

You may have noticed that neither RIA Services nor WCF services created from the Silverlight-Enabled WCF Service item template use any of the standard bindings. Instead, they define a custom binding to binary-encode the messages before sending them over an HTTP connection. The reason they need to define a custom binding is because none of the bindings supported by Silverlight implement binary encoding of messages. The big advantage achieved by binary-encoding messages (instead of sending them as plain text) is that you have much smaller messages passed between the server and the client.

Consuming a WCF Service

Once you have the service configured, you can then add a reference to it (using the Add Service Reference context menu item when right-clicking the Silverlight project in the Solution Explorer window), and consume it in exactly the same way as you would consume a service (asynchronously) in any .NET application.

If you attempt to add a reference to a service that has no endpoint with a binding supported by Silverlight, the service reference will still be added to your project, but a warning will appear in the Error List window stating that the endpoint is not compatible with Silverlight. Your project will compile, but attempting to make a call to the service will result in an InvalidOperationException exception.

Common Pitfalls

There are a couple of pitfalls to be aware of when calling WCF services in Silverlight. As both RIA Services and Data Services (discussed shortly) are built upon WCF, these issues also apply to those technologies.

Maximum Message Size

The WCF service on the server has a limit to the maximum message size it will return as a response, and the Silverlight client also has a maximum message size that it will accept. Although the default maximum message size accepted by the Silverlight client is configured at an impossibly large 2GB, the service in the web project has a default maximum message size of 64KB. Attempting to return a response larger than this from your service (common when returning large lists of data) will result in an exception being thrown. You can modify this limit by customizing your binding properties in the web.config file, setting an alternative maximum message size (in bytes). That said, it's probably better to design your server/client communication to transfer smaller messages (where possible). Unless the user requires all that data at the one time, you would be better aiming to minimize data traffic (and hence the time that the user is left waiting for a response from the server), only sending the data back to the client that the user requires at that time. For example, when displaying a list of data to the user, request the data in pages rather than as a single unit.

ASP.NET Session State

Despite Silverlight enforcing that server calls must be performed asynchronously, if you have ASP.NET session state turned on in your web project, you will find that calls are executed on the server sequentially, resulting in the calls appearing slower than expected.

You can tell if ASP.NET session state is turned on by using a tool such as Fiddler (which you can download from www.fiddler2.com) to intercept the responses from the server. If a cookie with the name ASP.NET_SessionId appears in the header, then ASP.NET session state is turned on. Generally, ASP.NET session state will be turned on automatically when you add a Global.asax file to your project.

This behavior only occurs when using the browser HTTP stack (the default when the application is running inside the browser). To work around this behavior, you can either stop using ASP.NET session state or simply use the client HTTP stack instead (discussed later in this chapter).

Sharing Code, Business Objects, and Logic Between the Server and Client

One of the great features of RIA Services is the ability to share code, business objects, and business logic between the server and the client. However, there is a little-known method for sharing these when using plain WCF services. The trick is to put the classes and shared code in a Silverlight Class Library project, and then add a reference to this assembly in both your Silverlight and web projects. Specifically create this project as a Silverlight class library (rather than one targeting the full .NET Framework), as you can only add a reference to an assembly targeting the Silverlight runtime to a Silverlight project; however, projects targeting the full .NET Framework can reference assemblies that target the Silverlight runtime. Now you have any code and business objects that you include in this assembly shared between both the client and the server.

Note that this still leaves you the task of manually populating your business objects after data has been retrieved from the server. This leads to trick number two. When you create your WCF service in the server project, expose data using the business objects from this shared assembly. Usually, adding a service reference to a project creates a proxy containing objects with the structure matching the objects being exposed by the service. It then populates these proxy objects with data returned from the server, and only accepts objects of these types when sending data back to the server. However, you can configure your service reference to populate the types defined in one or more given assemblies (instead of populating the proxy objects). After selecting the service you want to add a reference to (in the Add Service Reference dialog), click the Advanced button to open the Service Reference Settings dialog. Ensure that the "Reuse types in referenced assemblies" check box is selected. You can then select specific assemblies for it to use, or have it search for matching types in all the assemblies referenced by the project. This means that it will automatically populate objects from the shared assembly when you request data from the server, and accept objects of those types when you pass data back to the server—hence saving you the need to copy the data between the service's proxy objects and the business objects from your shared assembly when communicating with the WCF service.

WCF Data Services

WCF Data Services (or Data Services for short) is another data-centric communication technology built on the WCF stack that exposes data to clients as JSON- or ATOM-serialized RESTful resources using the OData protocol. The OData protocol specifies how a client can query, navigate, and update data exposed by a service. Using this protocol, the client can communicate with the service in a RESTful manner by retrieving data using a standard HTTP GET call (using specially constructed URIs), and sending updates to the server using a standard HTTP POST call.

Data Services vs. RIA Services

Data Services shares a number of similarities with RIA Services, and in a way you could consider the two to be sibling technologies, at times both jostling for attention and attempting to outdo each other. Both are built on top of WCF and conform to a pattern to expose data to clients, but each has a somewhat different focus. RIA Services (at least for the time being) specifically targets Silverlight clients, whereas Data Services is client agnostic. While RIA Services generates code in the client project in order for the client to communicate with the services (an aspect that concerns some developers who are against "magic" code generation that they have no control over), Data Services does not need a link to the client project, and no code generation is required. Instead, Data Services provides a client library (discussed shortly) to help the client interact with the service.

It can be difficult to identify whether you should use RIA Services or Data Services in your project, and how the two differ. However, you can break it down to the following:

  • RIA Services has a rich model designed for the development of end-to-end Silverlight applications (with the server tightly linked with its client via the code generation process).

  • Data Services is designed simply to expose data as RESTful resources in a standalone manner that a range of clients can consume (such as reporting tools that can consume data exposed over HTTP and serialized as either ATOM or JSON).

That said, RIA Services can also expose data as RESTful resources that can be consumed by a range of clients (by enabling OData or exposing a JSON endpoint using the functionality provided in the WCF RIA Services Toolkit). Also, together with its ability to share code and validation logic with the client (among the numerous other features it provides), you will find it the best solution when writing an end-to-end Silverlight application. However, if you want your service to support a wide variety of clients without having a tight relationship to your Silverlight project, Data Services is a viable alternative.

Using Data Services

When you add a data service to your project using the WCF Data Service item template, you configure it to expose your entire entity model. You can then configure permissions for each entity in that model (if you wish) to restrict which entity sets can be read, modified, and so on, and which service operations are available.

Once your service is configured, add a service reference to it in your Silverlight project. This will also automatically add a reference to the Data Services client library (System.Data.Services.Client.dll). Then you can start interacting with the service and performing CRUD operations on the entities exposed by the service.

In a similar manner to RIA Services, Data Services exposes queryable endpoints (by returning IQueryable expressions from operations). By using the client library, you can write LINQ queries on the client against these endpoints and have them executed on the server. This was one of the most powerful features provided by RIA Services, and it's also available (with a slightly different implementation) in Data Services.

Data Services does manage a changeset on the client; however, by default the changeset entries are processed individually (i.e., each operation is processed one at a time when you submit the changes). This creates an issue if one of these operations fails, potentially leaving your database in an unknown or inconsistent state. You can send these changes to the server as a batch; however, they will still be processed individually on the server side, with no transaction and rollback if one of the updates fails. This is one of the downsides of using Data Services, although you can implement transactions by hooking into the processing pipeline.

HTTP Requests

Despite being considerably more work (since you will have to parse the results yourself), another means of communicating with a server is through the use of standard HTTP requests. For example, you may wish to download an XML file from the server, or even consume RESTful services (aka WebHTTP services). You can also use this method to download files from a server (such as images).

Making an HTTP Request

There are two ways to make HTTP requests in Silverlight. Silverlight has both a WebClient and a WebRequest class that can be used for uploading and downloading data over HTTP to/from the server. WebRequest gives you finer control over the request; however, WebClient is the easiest to use for simple HTTP download tasks. It provides two means of downloading data (DownloadString and OpenRead) and two means of uploading data (UploadString and OpenWrite). DownloadString/UploadString simply downloads/uploads the data as a string (making communicating plain text easy), and OpenRead/OpenWrite uses a stream (best for binary data). For example, this is all it takes to download a text file from the server and load the contents into a string variable:

private void StartDownload(string url)
{
    WebClient client = new WebClient();
    client.DownloadStringCompleted += client_DownloadStringCompleted;
    client.DownloadStringAsync(new Uri(url));
}

private void client_DownloadStringCompleted(object sender,
                                            DownloadStringCompletedEventArgs e)
{
    string result = e.Result;
    // Do something with the result
}

Choosing a Networking Stack

By default, Silverlight uses the browser's networking stack (when running inside the browser). This places some limits on what you can do when making HTTP requests from Silverlight, as the browser's stack has limitations, and it sometimes modifies the response before passing it back to your application (particularly in regard to HTTP status codes). In order to support Out Of Browser (OOB) mode, the client networking stack was introduced in Silverlight 3, and you can invoke this networking stack instead to work around the browser's networking stack's limitations. Using this stack enables you to access HTTP headers and cookies, to use HTTP verbs other than GET and PUT, and to obtain the actual HTTP status codes returned from the server. It also provides various other benefits. There are some downsides, however, such as the lack of content caching and authentication support. Simply call the RegisterPrefix method on the WebRequest (or HttpWebRequest) class, passing in a prefix (which can be the scheme, or even a domain) to which future calls will use the given networking stack, and passing in the networking stack that those calls will use (with ClientHttp representing the client networking stack):

WebRequest.RegisterPrefix("http://", WebRequestCreator.ClientHttp);

Sockets

Sockets enable you to communicate bidirectionally with the server (i.e., the client can send messages to the server, and the server can send messages to the client). This is particularly useful when you want the server to send the clients notifications, or if you are writing a chat client, for example. Silverlight supports socket communication (with classes in the System.Net.Sockets namespace for support. For scenarios where you want to broadcast messages to all clients, IP multicasting is also supported.

As discussed earlier, you are restricted to only using ports 4502 to 4534 unless you are running in OOB mode with elevated privileges. Sockets are best used in intranet applications (rather than public-facing applications), as the ports will possibly be blocked behind a firewall. In this case you may be better off using a WCF service using the pollingDuplexHttpBinding binding to implement bidirectional duplex communication with the server.

Summary

This chapter taught you how to use WCF RIA Services to expose data and operations from the server to your client Silverlight application, and how to implement all the server-based requirements associated with doing so (such as handling errors, identifying and handling concurrency violations, and sharing code across tiers). The following chapters will show you how to consume this data and call these operations to create a functional Silverlight application.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset