This article is compatible with the latest version of Silverlight.
Introduction
In this series of articles we will talk about building N-Tier Silverlight business application, why we want to use multiple tiers, what problems we have to face and how .NET RIA Services can help us solve our issues.
Going N-Tier
If you develop complex business applications with a large codebase that can easily adapt to changing environments you introduce multiple tiers or layers. For example, you create a Data Access Layer that communicates with your data source whatever that is and passes the requested data to the layer above in object oriented form. The layers above don’t know and don’t want to know anything about how persistence actually happens. All that they want to do is: GetProducts, SaveChanges or PersistChanges. You can have many different layers like Business Logic Layer, Service Layer or Presentation Layer to ensure your code is flexible, well-structured and strictly separated.
So you decided going n-tier which is not a really big surprise since you don’t have a choice. If you work with Silverlight you have at least 3 layers. You don’t have ADO.NET libraries in Silverlight so you can’t interact with a database directly (and you shouldn’t do that anyway). So here is what you should have (at least):
- Your Silverlight Application - Presentation layer
- A WCF Service – Service Layer
- Your Database / additional ADO.NET logic to interact with your database – Persistence Layer
Issues with N-Tier development
You have a nice architecture now but it introduces new problems. Now let’s think about how you want to code against these layers. In your Presentation Layer you would like to call a GetProducts method and retrieve your Product list from your Service Layer. You do whatever you want with this list. You modify Product instances in it, add new ones or delete old ones. After you’re done all you want to do is “SaveChanges” back to the database. But you can’t. This would mean that you have client-side change tracking and identity management. Even if you had this on the server side you had lost it as soon as your data has left the server. Solving this problem by yourself would mean a lot of code.
Building a Service Layer Manually for CRUD Support
If you have several entities mapped to tables on the server side you probably want to enable CRUD operations on them. In this case you’d have to write all these operations by yourself. For example, CRUD operations for the Product entity would look something like this:
- GetProducts
- InsertProduct(Product p)
- DeleteProduct(Product p)
- UpdateProduct(Product p)
Validation issues
Now, if you want to save changes created by user input back to the database you need validation. When you use technologies like Entity Framework or LinqToSql you may notice that the entities setters contain a call to a partial method where you can handle validation. And we know that Silverlight validation mechanism is based on throwing exceptions in property setters. So this is pretty much the place where you should write this logic. Validation needs to happen on both client and server side. Server-side validation ensures that invalid data sent by hacked clients won’t cause any problems. Client-side validation will save us round-trips back to the server.
So you have to write the logic on server-side… but serialization mechanisms serialize only data, not logic, so on the client-side you won’t have the validation logic that you implemented on server-side. That means that you have to duplicate your code and maintain the synchronization of validation logic between the client and the server manually.
Restrictions based on Authentication and Authorization
You support full CRUD and custom operations but maybe only authenticated users are capable of submitting an order or only administrators are allowed to delete or insert a new product. You want to define custom operations that can be executed only by certain users or by users in certain roles. Which means that you’ll have to implement authentication and authorization almost from scratch.
Introducing .NET RIA Services
As we discussed earlier, going N-Tier is inevitable but certainly necessary even if it means writing a lot of code. Fortunately, there is a technology called .Net RIA services that can help you out with these issues. .NET RIA Services can offer you out of the box solutions for the issues mentioned above.
If you install .NET RIA Services you’ll get new Silverlight application templates in Visual Studio. One of the new templates is the Silverlight Business Application template. This template generates a solution structure that provides a lot of functionality out of the box. If you run the empty project you’ll see that you have an application that supports navigation. You already have a home and an about page, it has a nice error window to display error messages (just try to navigate to a page that does not exist yet to see it) and it has a login and a register window as well. If you take a closer look at the project structure in the ASP.NET project you’ll see a Services folder where you can find prepared services for supporting user related operations like registration or login.
The structure of a DomainService
.NET RIA Services defines a class called DomainService which services as a base class to your Domain Services. A domain service contains different operations like CRUD or custom operations and is completely independent of the underlying layers. Let’s take a look at how a domain service implementation looks like:
[EnableClientAccess()]
public class SampleDomainService : DomainService
{
public IQueryable<Products> GetProducts()
{
return DataAccessLayer.ProductRepository.GetProducts();
}
}
Our SampleDomainService inherits from the DomainService abstract base class and it is decorated with the EnableClientAccessAttribute. This attribute means that the service will be visible from the client-side and static code generation will create the necessary client-side proxy objects.
Now we have a GetProducts method. The method returns an IQueryable<T> which means that this operation can be part of a more complex query. GetProducts will be the source of the complex query and any additional query methods can be appended to it. The actual implementation of the method could be anything. In this case it forwards the request to a custom data access layer. A “select” method must meet the following requirements:
- returns IQuerable<T>, IEnumerable<T> or a singleton instance of type T
- It should take 0 or more parameters
- It may be decorated with the QueryAttribute to explicitly indicate the method as a query method
The structure of a Domain Context
Now if you build the project and you click show all files on the Silverlight Project you’ll get an [ApplicationName].Web.g.cs file in the Generated_Code folder. Let’s see what’s inside:
public sealed partial class SampleDomainContext : DomainContext
{
public EntityList<Products> Products
{
get
{
return base.Entities.GetEntityList<Products>();
}
}
public EntityQuery<Products> GetProductsQuery()
{
return base.CreateQuery<Products>("GetProducts", null, false, true);
}
...
As you can see with static code generation without using Add Service Reference…, a DomainContext was created for you. Domain Context provides identity management, change tracking and operation invocation. This is the client context for your server-side domain service. You can compose complex queries using the GetProductsQuery method. As you can see it creates an EntityQuery that calls GetProducts on the server-side. The results of the query will be loaded into the exposed Products property of type EntityList<Product>. EntityList is a very friendly collection with rich data binding support.
So what happened? Static code generation happened based on conventions. We exposed a DomainService and defined a method that met some requirements in order to be identified as a query operation. A domain context was created on the client-side with access to our domain service and to our domain operation.
Writing a Simple Query using DomainContext
Now all you have to do is to create a query, run it and load the results into a DataGrid for example. Creating a query can be done in code-behind using a domain context instance:
SampleDomainContext ctx = new SampleDomainContext();
var query = from p in ctx.GetProductsQuery()
where p.Discontinued == false
select p;
dataGrid.ItemsSource = ctx.Products;
ctx.Load(query);
We used the GetProductsQuery() to serve as the source of the query composition. The DataGrid’s ItemsSource property is assigned to the domain context’s Products property of type Entity List which implements INotifyCollectionChanged so as soon as the data is loaded into the property it will be displayed on the UI.
Finally we have to explicitly call the Load method passing the composed EntityQuery as a parameter. This operation calls the domain service and fills up our local EntityList and of course it is done asynchronously.
Summary
In this introduction article we learned why using n-tier architecture is necessary and what kind of issues do we have to face. .NET RIA services aims to solve most of our problems using convention based static code generation. We got to know two important objects - the DomainService and the DomainContext.
Later we will see that more complex queries can be written and they can also be composed declaratively in XAML using a DomainDataSource object.