Business Apps Example for Silverlight 3 RTM and .NET RIA Services July Update: Part 8: WCF Based Data Source

More updates on my Mix09 talk “building business applications with Silverlight 3”.

You can watch the original  video of the full session

The demo requires (all 100% free and always free):

  1. VS2008 SP1 (Which includes Sql Express 2008)
  2. Silverlight 3 RTM
  3. .NET RIA Services July '09 Preview

Also, download the full demo files

In the original demo I showed getting your data from a database directly from the web tier.  Many enterprise customers have found that it their systems are more maintainable and secure if they isolate the database access behind a set of web services possibly in a DMZ.  This means that no apps talk directly to the database.   Yet there is often a need to add application specific validation and application logic as well as data shaping and aggregation in the web-tier.  

To show this, I have refactored the example application from the first part of this walk through to access its data from a WCF service rather than EF directly.   Notice that all the other UI bits stayed the same,

image

 

Defining the Service

Let’s start by defining the WCF service.  In a real word application this service is likely defined by another group and you are only allowed to access it, not modify it. 

 

Right click on the solution and add a new project… WCF Service Application.  I called it MyApp.Service, but you can choose anything you’d like.

image

 

First we create an Entity Framework model for our database.. this is done exactly the same way as part 2, but this time it is part of our Service rather than in the web-tier.   The demo would work exactly the same no mater what source of data you use… EF was just easy for me to get going, it is not required for this scenario as we are encapsulating everything behind a the WCF services layer. 

Next, we define the interface for our service

 [ServiceContract]
 public interface ISuperEmployeeService
 {
  
     [OperationContract]
     IEnumerable<SuperEmployee> GetSuperEmployees(int page);
  
     [OperationContract]
      SuperEmployee GetSuperEmployee(int empId);
  
  
     [OperationContract]
     void UpdateEmployee(SuperEmployee emp);
  
 }

Then we implement it…

 public IEnumerable<SuperEmployee> GetSuperEmployees(int page)
 {
     using (var context = new NORTHWNDEntities()) {
     var q = context.SuperEmployeeSet
         .OrderBy(emp=>emp.EmployeeID)
         .Skip(page * PageSize).Take(PageSize);
     return q.ToList();
 }

Notice here we are implementing paging by taking a page parameter…  After a brief inventory of real world services on the net, i find this a very common pattern.    It is very easy with EF to access just the page of data we want. 

 

Consuming the Service

Now let’s consume this service from the web-tier. The reason we do this here is to get all the benefits of the RIA Services in terms of validation logic, etc and be able to customize and arrogate the data so the view is just right for the client.

First, we define a SuperEmployee type that is shaped just right for the client.   In this simple example I left it pretty much the same as the service returned, but you can use any shape you’d like. 

Notice we are using attributes to specify the different validation we want to have done on this data on the client AND the web-tier. 

 public class SuperEmployee
 {
  
         [ReadOnly(true)]
         [Key]
         public int EmployeeID { get; set; }
     
  
         [RegularExpression("^(?:m|M|male|Male|f|F|female|Female)$", 
             ErrorMessage = "Gender must be 'Male' or 'Female'")]
         public string Gender {get;set;}
  
         [Range(0, 10000,
             ErrorMessage = "Issues must be between 0 and 1000")]
         public Nullable<int> Issues {get;set;}
  
         public Nullable<DateTime> LastEdit {get;set;}
  
         [Required]
         [StringLength(100)]
         public string Name {get;set;}
  
         public string Origin {get;set;}
  
         public string Publishers {get;set;}
  
         public string Sites {get;set;}
     }

 

Now, let’s add a reference to the service we just created.

Right click on the project and select Add Service Reference..

image

 

Now we modify our DomainService…

 

    1: public class SuperEmployeeDomainService : DomainService
    2:   {
    3:       SuperEmployeeServiceClient Context = new SuperEmployeeServiceClient();
    4:  
    5:       public IQueryable<SuperEmployee> GetSuperEmployees(int pageNumber)
    6:       {
    7:           return this.Context.GetSuperEmployees(pageNumber)
    8:                      .Where(emp => emp.Issues > 100)
    9:                      .OrderBy(emp => emp.EmployeeID)
   10:                      .Select(emp =>
   11:                          new MyApp.Web.SuperEmployee()
   12:                          {
   13:                              EmployeeID = emp.EmployeeID,
   14:                              Gender = emp.Gender,
   15:                              Issues = emp.Issues,
   16:                              LastEdit = emp.LastEdit,
   17:                              Name = emp.Name,
   18:                              Origin = emp.Origin,
   19:                              Publishers = emp.Publishers,
   20:                              Sites = emp.Sites,
   21:                          }).AsQueryable();
   22:       }

Notice in line 1, we no longer need to derive from EFDomainService as there is no DAL access code in this web-tier now… we have factored all that into the service.

In line 3, we create an instance of the WCF web Service Proxy..

In line 5, you can see we are taking a page number – we will need to pass that from the client.

In line 7 we are creating a simple LINQ query to change the shape of the data we get back from the service. 

 

 

In the Silverlight Client

Now, to consume that in the silverlight client is very easy..   We just make a few tweaks to the Home.xaml we created in part 2..

    1: <riaControls:DomainDataSource x:Name="dds" 
    2:         AutoLoad="True"
    3:         QueryName="GetSuperEmployeesQuery"
    4:         LoadSize="20">
    5:  
    6:     <riaControls:DomainDataSource.QueryParameters>
    7:         <datagroup:ControlParameter ParameterName="pageNumber"
    8:                                     ControlName="pager"
    9:                                     RefreshEventName="PageIndexChanged"
   10:                                     PropertyName="PageIndex">                            
   11:         </datagroup:ControlParameter> 
   12:         
   13:     </riaControls:DomainDataSource.QueryParameters>
   14:  
   15:     <riaControls:DomainDataSource.DomainContext>
   16:         <App:SuperEmployeeDomainContext/>
   17:     </riaControls:DomainDataSource.DomainContext>
   18:  
   19:     <riaControls:DomainDataSource.GroupDescriptors>
   20:         <datagroup:GroupDescriptor PropertyPath="Publishers" />
   21:     </riaControls:DomainDataSource.GroupDescriptors>
   22:  
   23:  
   24:     
   25: </riaControls:DomainDataSource>
   26:  

Notice in line 7 we are getting the page number from the GetSuperEmployees() method from the DataPager… This is set up such that as the DataPager changes pages, the DDS request data from the server.

If you are paying close attention to the whole blog series, you will notice I removed the sorting and filtering for this example.  I did this because you are basically limited by the expressiveness of your back-end data… If your back end data does not support sorting and filtering, then it is tough to do that on the client!  You could do caching on the web tier but that is a subject for another day.  

As an fun exercise, assume you could change the WCF Service definition, how would you add filtering?   You could follow the exact pattern we use for paging.  Add an argument to the WCF Service, add the same one to the GetSuperEmployees() query method on the server, then add another ControlParamater to the DDS getting its data from a control on the form.  Pretty easy!  Let me know if you try it.

The final step is to bind the DataPager to a shim collection, just to make it advance.  

    1: <data:DataPager x:Name="pager" PageSize="1" Width="379" 
    2:                 HorizontalAlignment="Left"
    3:                 DisplayMode="FirstLastPreviousNext"
    4:                 IsEnabled="True"
    5:                 IsTotalItemCountFixed="False"
    6:                 Margin="0,0.2,0,0">
    7:     <data:DataPager.Source>
    8:         <paging:PagingShim PageCount="-1"></paging:PagingShim>
    9:     </data:DataPager.Source>
   10: </data:DataPager>
 

You can find the implementation of PagingShim in the sample.  (thanks to David Poll for his help with it). 

Hit F5, and we have something cool!  It looks pretty much the same as the previous app, but this one gets all its data via WCF!

image