Application communication ways – WCF Data Services with EF Code First, WPF and WP7

By Mirek on (tags: OData, WCF, WP7, categories: architecture, infrastructure)

Let’s assume there is an application database and the various client applications developed in Windows Presentation Foundation, Silverlight, Windows Phone 7 and ASP.NET MVC 3. The database is either MS SQL Server or MS SQL Server CE. Now the potential customer may use one or many of these applications/platforms so the connections to the database must be performed from all of these clients.
The following scenarios are considered. I tried to list some points that should be considered when choosing particular connection approach. The pictures represents the general architecture of the approach.

Direct database access

    • Application connects directly to database server
    • Need to maintain connection string
    • Authentication and authorization managed by SQL Server
    • No additional application layer is needed
    • Any change in db requires updating all client applications
    • Useful in ONPREMISE model or SQL CE database
    • Possibility to get the connection string and get full access to the database

Common usage: desktop or web hosted applications in the intranet environment with sql server.

DirectDBAccess

 

Software as a Service

    • Application available only as a website
    • No customer side applications, simpler maintenance
    • Forms built-in authentication
    • Generally no security issues
    • Via web browser access only

Common usage: web hosted applications in the intranet environment with sql server; web sites

SaaS

 

 

Access via web service

    • Special code to maintain on service and client side
    • Authentication and authorization must be performed on service side
    • Only a set of functionality is exposed to the public
    • Database resides on secure area

Common usage: client applications (WPF, WP7, Silverlight) located away from the data store server; communication between system modules in Service Oriented Architecture (SOA)

AccessWebService

 

 

Access via OData service

    • Small additional code to maintain
    • Authentication using Web Forms, OAuth or Windows Authentication
    • Optimal solution for connections through public network
    • Customizable queries – using LinqToSql or filters in URI on client side
    • No need to write service interface on service side and service interpreter on client side

Common usage: client applications (WPF, WP7, Silverlight) located away from the data store server; communication between system modules in Service Oriented Architecture (SOA)

odata

 

 

 

We will try focus a little more on OData services.

 

The Open Data Protocol (OData) is a Web protocol for querying and updating data that provides a way to unlock your data and free it from silos that exist in applications today. OData does this by applying and building upon Web technologies such as HTTP, Atom Publishing Protocol(AtomPub) and JSON to provide access to information from a variety of applications, services, and stores. The protocol emerged from experiences implementing AtomPub clients and servers in a variety of products over the past several years.  OData is being used to expose and access information from a variety of sources including, but not limited to, relational databases, file systems, content management systems and traditional Web sites.

OData implementation in Microsoft technologies is called WCF Data Services (WCF DS later in this article). WCF DS is nothing more than a service based on Windows Communication Foundation which exposes particular functionality of, generally speaking, data access.
Lets see it on example.

We will try to expose our data which is stored in MS SqlServer CE and accessed using Entity Framework with Code First approach. Then we will try to consume the service in wpf and wp7 client application.

 

Model POCO class

   1: [DataServiceKey("Id")] 
   2: public class Person 
   3: { 
   4:     public Person() 
   5:     { 
   6:         Orders = new List<Order>(); 
   7:     } 
   8:      
   9:     public int Id { get; set; } 
  10:   
  11:     public string Name { get; set; } 
  12:   
  13:     public IList<Order> Orders { get; set; } 
  14: } 

Entity Framework Code First database context class

   1: public class PersonContext : DbContext 
   2: { 
   3:     public DbSet<Person> People { get; set; } 
   4:   
   5:     public DbSet<Order> Orders { get; set; } 
   6: }


This is all we need to create and access our data. The only thing left is connection string in application configuration file and some optional objects such as database initializer. But for simplicity we will left it as it is.

WCF Data Services as regular WCF services must be somewhere hosted. We choose ASP.NET MVC 3 application. On solution select Add new project and in the dialog choose ASP.NET MVC 3 Web Application. This will create and empty web application, in which we will create our OData service. Choose Add, new item, WCF Data Service and set the name of the service to  Service.svc.

We have our OData service class which we implement like following

The parameter of DataService generic class must be type that define data source. It must expose

   1: public class Service : DataService<PersonContext> 
   2: { 
   3:     // This method is called only once to initialize service-wide policies. 
   4:     public static void InitializeService(DataServiceConfiguration config) 
   5:     { 
   6:         config.SetEntitySetAccessRule("*", EntitySetRights.All); 
   7:         config.SetServiceOperationAccessRule("GetPeople", ServiceOperationRights.All); 
   8:         config.DataServiceBehavior.MaxProtocolVersion = DataServiceProtocolVersion.V3; 
   9:         config.UseVerboseErrors = true; 
  10:          
  11:     }        
  12:   
  13:     [WebGet()] 
  14:     public IEnumerable<Person> GetPeople() 
  15:     { 
  16:         return CurrentDataSource.People.ToList(); 
  17:     } 
  18: }

at least one property that returns an entity set that is an IQueryable collection of entity types. This class must also implement the IUpdateable interface to enable updates to be made to entity resources. In our case this is an EF Code First DbContext class, which exposes two IQueryable properties: People and Orders.

Unfortunately there is a bug in WCF DataService that does not allow to update data when using Entity Framework Code First as a data source. This bug is fixed in Microsoft WCF Data Services 2011 CTP2.

Let’s explain some. Line number 6 defines access rules for exposed entity sets. Here it means that all entities are accessible for read/ write and delete. If we would want to, for example, restrict access to the Orders set we would write something similar

   1: config.SetEntitySetAccessRule("Orders", EntitySetRights.None); 
   2: config.SetEntitySetAccessRule("*", EntitySetRights.All);

which would mean: first deny access to Orders entities and then allow for all rest. The order of setting these rules is important. For more information about setting the permissions to entities see MSDN library.

WCF Data Service can also expose regular operations as normal web service. Here you can see operation named GetPeople which returns the list of all Person entities taken from People property of PersonContext class. Then in line 7 you can see that there is also possibility to set permissions to service operations in way similar to setting permissions to entities.

Ok, let’s build our service, start it and try to query it a little from the web browser.

odata1

 

WCF Data Service exposes Atom feed content. As you can see here the entity sets are taken from PersonContext from its DBSet<> properties.

OData is base on query able entities, so you can query them in url.
For instance if you want to take Person of ID=1 and with preloaded all orders you must type
[service host]/Service.svc/People(1)?$expand=Orders

odata2

 

To get Person with Name equal ‘Mike’ we type
[service host]/Service.svc/People?$filter=Name eq 'Mike'

odata3

For more querying options go to this article. To play a little with OData querying you can use public available OData service Northwind database.

WCF Data Services allows some other customization and offers a range of controlling functionality. We can use a QueryInterceptor to catch the query request before they are processed.

   1: [QueryInterceptor("People")] 
   2: public Expression<Func<Person, bool>> OnQueryPeople() 
   3: { 
   4:     if (!HttpContext.Current.User.Identity.IsAuthenticated) 
   5:         return p => false; 
   6:     var url = HttpContext.Current.Request.RawUrl; 
   7:     return p => true; 
   8: }

Above method will intercept request that tries to query People entities. In line 4. the user authentication is checked. Next line shows how to get the raw query string. The QueryInterceptor method is used to filter resulting entity set so that the entity for which the interceptor returns false will not be included into the result set.

There is also possibility to intercept CRUD operations

   1: [ChangeInterceptor("Orders")] 
   2: public void OnChangeOrders(Order item, UpdateOperations operations) 
   3: { 
   4:     if (operations == UpdateOperations.Change) 
   5:     { 
   6:         //intercept changing orders 
   7:     } 
   8:     if (operations == UpdateOperations.Add) 
   9:     { 
  10:         //intercept adding orders 
  11:     } 
  12: }

This method intercept operations made on Orders set. The UpdateOperations is a set of (Add, Change, Delete, None). The item in parameter indicates the deleted, changed or created entity respectively.

 

Building WPF client

Consuming WCF Data Service in WPF application is as simple as adding the service reference and creating corresponding PersonContext object on the client side. Then if we want to query all People with preloaded Orders we have to write following

   1: PersonContext ctx = new PersonContext( 
   2:     new Uri("http://localhost:64471/Service.svc")); 
   3: var people = ctx.People.Expand("Orders").ToList();

In line 1 the database context is created providing the url of the Data Service and in line 2 the People are queried. Method Expand, has been shown here before, is used to load entity related properties on the service side, so the all needed data is being sent at one request.
If we want to load Orders manually later or only for specific persons then we can use LoadProperty method of PersonContext

   1: foreach (Person person in people) 
   2: { 
   3:     ctx.LoadProperty(person, "Orders"); 
   4: }

On the service side in the QueryInterceptor we can see that each execution of line number 3 is queried with something similar to this

   1: "/Service.svc/People(1)/Orders()" 

So the WCF Data Services client library translates the Linq methods and generates proper url to query data service.

Creating new entities is simplified with use of auto generated methods AddToPeople and AddToOrders, so adding new PersonI will looks as follows

   1: Person CurrentPerson = new Person { Name = NewPersonName }; 
   2: ctx.AddToPeople(currentPerson); 
   3: ctx.SaveChanges();

Updating entity is done with use of UpdateObject method

   1: CurrentPerson.Orders.Add(new Order { ProductName = NewOrderName }); 
   2: ctx.UpdateObject(currentPerson); 
   3: ctx.SaveChanges();
Here new order object is added to the CurrentPerson, which is then updated. Deleting objects looks simillar.

   1: ctx.DeleteObject(currentPerson); 
   2: ctx.SaveChanges(); 

That’s it. We are now able to load, create, update and delete entities through data services. The things are a little bit more complicated in Silverlight/WP7 application.

 

Consuming Data Services in WP7 application.

Calling WCF Data Services is performed in an asynchronous process. This avoids the client application (mobile phone) to be frozen in case of longer communication with the server. On the other hand this behavior required to write a little bit more code.

Windows Phone Developer Tools 7.0 are not yet compatible with Data services so it does not provide a method for automatically adding a service reference in Visual Studio. More over its functionality is a bit limited. For example we have no support for Linq syntax, we have to generate raw url’s and query the service with use of them. There are no entity sets properties generated in the proxy context class.
So we can go this harder way, which I will try to describe later in this article, or we can use some updates. When I write this article there is available Windows Phone Developer Tools 7.1 Beta which contains the WCF Data Services client for WP7 and fixes many other problems and inconvenients.

We will use version 7.0, since using beta is not advisable. Before you start you have to install:

  1. Windows Phone Developer Tools RTW
  2. Windows Phone Developer Tools January 2011 Update
  3. ODataClient_BinariesAndCodeGenToolForWinPhone.zip

The third download contains just the OData client assemblies and code generation tools for use on Windows Phone 7. These libraries are permitted for use in production applications and thus can be used to build applications that are submitted to the Windows Phone application marketplace.

Let’s start. Create new Windows Phone application. Go to the directory of the project and use DataSvcUtil.exe providing the url of the data service

DataSvcUtil.exe /uri:http://localhost:64471/Service.svc /out:PersonServiceRef.cs
/version:2.0 /DataServiceCollection

This will generate a service proxy class in PersonServiceRef.cs file.

Creating service reference client looks the same as in WPF application

   1: PersonContext ctx = new PersonContext(new Uri("http://localhost:64471/Service.svc"));

To load entities we may use DataServiceCollection in following way

   1: private void loadPeople() 
   2: { 
   3:     People = new DataServiceCollection<Person>(ctx); 
   4:     People.LoadCompleted += new EventHandler<LoadCompletedEventArgs>(people_LoadCompleted); 
   5:     People.LoadAsync(new Uri("/People", UriKind.Relative)); 
   6: } 
   7:   
   8: void people_LoadCompleted(object sender, LoadCompletedEventArgs e) 
   9: { 
  10:     if (e.Error != null) 
  11:     { 
  12:         MessageBox.Show(e.Error.Message); 
  13:     } 
  14:     else 
  15:     { 
  16:         MessageBox.Show("Load completed"); 
  17:         personList.ItemsSource = People.ToList(); 
  18:     } 
  19: }

Adding new entity

   1:  
   2: private void AddNew_Click(object sender, RoutedEventArgs e) 
   3: { 
   4:     if (!String.IsNullOrEmpty(NewPerson.Text)) 
   5:     { 
   6:         Person newperson = new Person 
   7:         { 
   8:             Name = NewPerson.Text 
   9:         }; 
  10:         ctx.AddToPeople(newperson); 
  11:         ctx.BeginSaveChanges(OnChangesSaved, ctx); 
  12:     } 
  13: } 
  14:   
  15: private void OnChangesSaved(IAsyncResult result) 
  16: { 
  17:     var ctx = result.AsyncState as PersonContext; 
  18:     Deployment.Current.Dispatcher.BeginInvoke(() => 
  19:     { 
  20:         try 
  21:         { 
  22:             ctx.EndSaveChanges(result); 
  23:         } 
  24:   
  25:         catch (Exception ex) 
  26:         { 
  27:             throw new Exception("Error saving Person", ex); 
  28:         } 
  29:   
  30:     }); 
  31: } 

Updating the entity

   1: private void Save_Click(object sender, RoutedEventArgs e) 
   2: { 
   3:     ctx.UpdateObject(CurrentPerson); 
   4:     ctx.BeginSaveChanges(OnChangesSaved, ctx); 
   5: } 
   6:   
   7: private void OnChangesSaved(IAsyncResult result) 
   8: { 
   9:     var ctx = result.AsyncState as PersonContext; 
  10:     Deployment.Current.Dispatcher.BeginInvoke(() => 
  11:     { 
  12:         try 
  13:         { 
  14:             ctx.EndSaveChanges(result); 
  15:         } 
  16:         catch (Exception ex) 
  17:         { 
  18:             throw new Exception("Error saving Person", ex); 
  19:         } 
  20:   
  21:     }); 
  22: } 

and deleting entity

   1: private void Save_Click(object sender, RoutedEventArgs e) 
   2: { 
   3:     ctx.DeleteObject(CurrentPerson); 
   4:     ctx.BeginSaveChanges(OnChangesSaved, ctx); 
   5: } 
   6:   
   7: private void OnChangesSaved(IAsyncResult result) 
   8: { 
   9:     var ctx = result.AsyncState as PersonContext; 
  10:     Deployment.Current.Dispatcher.BeginInvoke(() => 
  11:     { 
  12:         try 
  13:         { 
  14:             ctx.EndSaveChanges(result); 
  15:         } 
  16:         catch (Exception ex) 
  17:         { 
  18:             throw new Exception("Error saving Person", ex); 
  19:         } 
  20:   
  21:     }); 
  22: }

For more information about CRUD operations in WP7 application see this article.

Ok we have fully functional and responsive Windows Phone 7 client application.

 

Resources

  1. Open Data Protocol
  2. WCF Data Services (Polish)
  3. Using Microsoft ADO.NET Data Services