RIA Architecture with Silverlight in mind

Rich Internet Application (RIA) is an interesting topic. More and more business apps are moving into to the cloud, and more and more will have better User Experience (Ux). According to Gartner Research, nearly 60 percent of all new application development will include RIA technology by 2010. Today we can use different technologies to build a RIA, for example, AJAX, Flash and Silverlight etc. When it comes to AJAX we will soon reach the top of what we can do with it, because of the limitation of HTML, Client-side scripts and the browsers etc. That makes Silverlight and Flash more interesting regarding to me. But what is RIA? RIA It’s an internet applications that looks and feels like desktop applications, it also provide most of the maintainability and deployment benefits of Web applications. A RIA client can have state, so to increase User Experience, the user can work with data for a while until it will be passed down to the server for processing. Because the client side of a RIA can have state and work with data for a while, it’s extra important to have concurrency in mind. Some other users can have changed the data during the time we have worked with our copy. When developing RIA Clients we need to have several things in mind, for example a RIA is often running within an execution container in the Browser, so people can hit the refresh button or use the back and forward buttons, which can be reload the whole app and the current state can be gone. When we creates a large RIA, we can’t let users wait for hours until the application is loaded and started, so we need to load views etc on demand, and split them into small “modules”. Developing a RIA is not like developing a stateless ASP.NET app.

In this post I will write about the architecture and design I use when building a RIA with Silverlight.

The following is a figure of an Architecture of a typical RIA implementation:


RIA_arch

The Presentation Layers purpose is to make sure users gets the best possible User Experience. The Presentation Layer of a RIA should not include too much business logic, instead it will communicate with a Web Server to perform business operations. This is done by accessing a Application Service Layer which in turn will communicate with the Domain Model [Evans DDD]. In this post I will focus more on the Application Service Layer and the Presentation layer, rather then the other layers or tiers.

Business Logic, where to place it?

Because a RIA often look and feel like a desktop application, and can have state, it can be appealing to add a lot of business logic to the Presentation Layer. But try to not do that. The only kind of business logic that should be in the Presentation Layer is user input validations, and business logic that can increase user experience and increases performance overall, for example by avoiding communications to the Web Server or some expensive business operations, which can for example improve the UI responsiveness. Start with your business logic implementation on  the server and expose them through services. It will make sure you don’t start adding a lot of business logic to the client at the first place. One problem with the business logic is that same business logic can exists on both the client and server side. For example validations. To address this issue you can for example use the Microsoft .NET RIA Services. If you don’t want to use the .NET RIA Services, make sure you group the business logic into a separate assembly, which you can share both with the Server and client, or at least make sure you reuse the same kind of code and language, so you can easily replace the code when changes is made to the business logic on the server and should also be changed on the client side. Some logic should never be on the client side, such as highly sensitive business logic. Add it to the server for security reasons. If the client side are going to need a lot of business logic that can be changed often, make sure to add it to a downloadable module. It will make it easy to replace the logic without re-downloading the entire RIA application.

How to share Business logic between tier with .Net RIA Services

The .NET RIA Services lets us create a DomainService where we can add our CRUD operations and custom queries. We can also use metadata to add shared validation logic and also add code that should be shared between the client and the server. .NET RIA Services will locate our DomainServices, metadata and shared code, and generate client-side classes for us. The following code is a simple DomainService which will return a list of an “Entity” (You can find more about design considerations etc when creating entities later in this post):

[EnableClientAccess()]
public class UserService : DomainService
{
    private UserDataService _userDataService = new UserDataService();

    [Query(PreserveName=true)]
    public IEnumerable<User> Followers()
    {
        return this._userDataService.Users;
    }
}


Here is en implementation of the User Entity:


public partial class User
{
   [Key]
   public string ID { get; set; }

   public string Name { get; set; }

   public string Description { get; set; }
}


To add validations that are applied on both tiers we can create a metadata class:

[MetadataType(typeof(UserMetaData))]
public partial class User
{
    internal sealed class UserMetaData
    {
        [Required]
        public string Name;

        [Required]
        [StringLength(255, MinimumLength = 0)]
        public string Description;
    }
}


The above metadata class will make sure that the generated Entity for the client-side will have the validations specified by using attributes. When it comes to validation, make sure to validate both on the client side to improve user experience, and server side validation for security. The magic to share business logic between the client and server is by using code generation. There is only one place the business logic is added, and it’s on the server side, but can be shared with the client side without adding duplications.


Communication

To increase the user experience and performance, the communication from the client side to the server must be made asynchronous. If not we can block the UI thread. If long running code is needed, it’s worth to consider if a background thread should be used or not. Here is an example how the Silverlight’s BackgroudWorker can be used to run a thread in the background:


public MainPage()
{
    InitializeComponent();

    var bw = new BackgroundWorker();
    bw.RunWorkerCompleted += bw_RunWorkerCompleted;
    bw.DoWork += bw_DoWork;
    bw.RunWorkerAsync();
}

void bw_DoWork(object sender, DoWorkEventArgs e)
{
    //perform the work
    e.Result = result of the operation if any
}

void bw_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
    if (e.Error != null)
       //handle exception

    //get the result from e.Result and do for example update UI
}

Note: When performing operations in a background thread, it can be advisable to show the user the progress of the operation (if the user should see any result after the operation is completed). The BackgroundWorker class has a ProgressChanged support.

To communicate to the Server, WCF, APS.NET WebServices, .NET RIA Services, ADO.NET Data Service, WebClient etc can be used. When passing data from server to the client or back, try to remember that there can be some bandwidth limitations. When creating a distributed system, objects shouldn’t be distributed. So don’t distribute your domain model’s entity. Instead create new kind of “resources”. For example by using formats like JSON, XML or DataContracts, and only pass the data that is needed by the presentation layer. Try to focus on what data a specific View needs. If you need to save data on the client-side for offline support, make sure to use the Isolated Storage or the new SaveFileDialog added to Silverlight 3.0. Don’t use a local databases, instead make sure you make a call to a Service. If it would be catastrophic to lose state data on the client, make sure to store it on the server. Try to minimize the number of round-trips to the server. Make sure to filter all data at server rather than at the client to reduce the amount of data that must be sent over the network. By using .NET RIA Services, we can without adding new methods to the DomainService, make sure a Query is passed as an argument to the server and the server will do the filtering. Try to avoid creating too much queries on the client side, if you have common quires witch is located on several Views on the client side, make sure you add a common query as a method to a DomainService instead. It will make it much easier for you to maintain  the application.

The following is an example of how WebClient together with XDocument can be used to retrieve data from the server, with a REST like way:

public MainPage()
{
     InitializeComponent();

     var wc = new WebClient();

     wc.OpenReadCompleted += wc_OpenReadCompleted;
     wc.OpenReadAsync(new Uri("/User/Followers", UriKind.Relative));
}

void wc_OpenReadCompleted(object sender, OpenReadCompletedEventArgs e)
{
    XDocument xmlUsers = XDocument.Load(e.Result);

    var users = from user in xmlUsers.Descendants("user")
                select new
                       {
                         ID = (string)user.Element("ID").Value,
                         Name = (string)user.Element("Name").Value,
                         Description = (string)user.Element("Description").Value
                       }; 

}



The “/User/Followers” URI used by the WebClient, can for example be a REST API created by using ASP.NET MVC:


public class UserController : Controller
{
    public ActionResult Followers()
    {
        var userDataService = new UserDataService();

        return View(userDataService.Users);
    }
}

Followers.aspx 

<%@ Page Language="C#" 
         Inherits="System.Web.Mvc.ViewPage<List<Models.User>>" %> 
<users type="array"> 
    <% foreach (var user in ViewData.Model) { %>
        <user> 
            <id><%= Html.Encode(user.ID)%></id> 
            <name><%= Html.Encode(user.Name)%></name> 
            <description><%= Html.Encode(user.Description)%></description> 
        </user>
    <% } %> 
    </user> 
</users>


The following is how we can use a WCF Service to retrieve data from the server. The WCF Service is part of the Application Service Layer:


[DataContract]
public class User
{
    [DataMember]
    public string ID { get; set; }

    [DataMember]
    public string Name { get; set; }

    [DataMember]
    public string Description { get; set; }
}

 

public class UserService { private UserDataService _userDataService = new UserDataService(); [OperationContract] public IEnumerable<User> GetFollowers() { return _userDataService.Users; } }

 

MainPage.xaml.cs

public MainPage()
     {
         InitializeComponent();

         var us = new UserServiceClient();
         us.GetFollowersCompleted += us_GetFollowersCompleted;
         us.GetFollowersAsync();
         
         
         myGrid.ItemsSource = _uc.Users;
         _uc.Followers(_uc.Users.AsQueryable<User>().Where( c => c.ID == "1") , null);

     }

     void us_GetFollowersCompleted(object sender, GetFollowersCompletedEventArgs e)
     {
         myGird.ItemSource = e.Result;
     }


As you can see in the above code, a DataContract is created, this contract should only return the information that the client needs. To minimize the data sent to the client over the network, it can be good to create data contracts for each View (Client side “forms”), but be pragmatic. If we will have more than one View that need the same data, we can reuse the same contracts if the different is only one or two properties, it may be unnecessarily to create two data contracts to reduce the data sent over the wire. But still have bandwidth in mind. I often create one Service per View, and if there are some other Views that should reuse the exact same kind of operations I reuse the Service, or create a new Service which in turn will access the other Service. It depends on the app and the Views etc.

To make a call to a WCF Service, a Service Reference is needed to locate the Service and to create a client-side proxy class to make it easy to communicate with the Service.

In the first example in this post I showed some .NET RIA Services code, where I created a DomainService. When the solution is build a generated proxy class will be created and added to the Silverlight project. This class is called a DomainContext. Here is an example where .NET RIA Services DomainContext is used to communicate to the DomainService:


public partial class MainPage : UserControl
{
    UserContext _uc = new UserContext();

    public MainPage()
    {
        InitializeComponent();

        var us = new UserServiceClient();
        us.GetFollowersCompleted += us_GetFollowersCompleted;
        us.GetFollowersAsync();
        
        myGrid.ItemsSource = _uc.Users;
        _uc.Followers();

    }

    void us_GetFollowersCompleted(object sender, GetFollowersCompletedEventArgs e)
    {
        myGird.ItemSource = e.Result;
    }
}


Now when you have seen some different ways to communicate with a Service form the Client, we can focus more on the data passed to the client from the server and back..

Data

When creating a distributed system we should never distribute an object. So don’t try to pass entities from the Domain Model to the client side. Instead create a new model only for presentation purpose. For example with DataContracts or if you are using REST a resource, or a thin object only used for presentation purpose if you are using .NET RIA Services. Here is a figure over how I often build my applications:

ria_arch2

Note: The View Model is not the Pattern ViewModel. It’s a model created for presentation purpose only, for example a DataContract, or at .NET RIA Services Entity etc.

One of the important part when creating a RIA, is to focus on the “model” that the View should use. If we for example should list all Customers, and we only need display three properties, we creates a class/resource or DataContract for that purpose, for example:

public partial class Customer
{
   [Key]
   public string ID { get; set; }

   public string Name { get; set; }

   public string Country { get; set; }
}


If we need more detail about a Customer, we create a new class with the properties needed:


public partial class CustomerDetail { [Key] public string ID { get; set; } public string Name { get; set; } public string Country { get; set; } public string Description { get; set; } public string Email { get; set; }

... }


When building RIA, performance and bandwidth is something we should have in consideration, so it’s not sure a user will show detailed information about a Customer, so in this case we make sure we have two methods in our Application Service Layer, GetCustomer and GetCustomers. The GetCustomer will return the CustomerDetail class and the GetCustomers the Customer class. If we know that a user will show all the information of a customer, and will get a set of Customers to always work with. We can create a method that will return a list of CustomerDetails. If we create a small applications for few users and it’s an intranet application, the bandwidth may not be a problem, so in that case we can return more data that isn’t needed in specific scenario but is needed for other scenarios to reduce the number of classes created etc. So everything depends on!

Presentation Layer Pattern

I often use a UI patterns like the Presentation Model/ViewModel pattern. The MVC pattern could be used, but I prefer the Presentation Model pattern in a RIA. If you are interested in how a Presentation Model could look like when using Silveright, you can read the following post on my blog: http://weblogs.asp.net/fredriknormen/archive/2009/01/30/how-the-presentation-model-could-look-like-when-using-silverlight-2-0.aspx

The end

There is so much to write about when it comes to the topic of this blog post but I hope you have found some useful tip. If there is something you don’t agree on, please let me know, I only share my experience ;)

3 Comments

Comments have been disabled for this content.