WCF Data Services, OData, WP7, and EF

I have another rabbit-hole of a project I’m pursuing at the moment. It is partly because I want it, but it is also a way for me to explore some very cool new technology. This, builds on several things you probably already know:

  • WCF – the common way that .NET applications communicate with “something” else (i.e. process, server, etc). This includes host REST-based services as well.
  • OData – the XML-based open data format for communicating between technologies, while maintaining data and referential integrity.
  • WP7 – Windows Phone 7, the really great phone and platform that is horribly marketed.
  • EF – Entity Frameworks, the very powerful object-relational mapper that comes with .NET 4.0

So what can we do with all of these? Well, here is the scenario. I will have an MVC3 web site that lets people log in, and view their account, update data, etc. However, I also want to be able to have a WP7 app that can do the same thing. Although I will have a mobile-friendly version of the site, I want a native WP7 app because A) it’s pretty and B) this seems like a fun technology to learn.

There are plenty of articles that explain this stuff, but it did take me some time to get a “hello, world” example going, so I wanted to write down what I did.

WCF Data Services:
There is this new concept that came out in .NET 4.0 where you can expose an Entity Frameworks model over WCF – automatically, and securely. This is called WCF Data Services. Again, there are many, many blogs and MSDN articles that explain this in more detail. However, if you have an Entity Frameworks model setup, you would simple need a WCF Data Service:

image

which looks like this:

using System.Data.Services;

using System.ServiceModel.Web;

using SederSoftware.WcfRestfulService.Web.Models;

 

namespace SederSoftware.WcfRestfulService.Web.Services

{

    public class HealthManagerService : DataService<HealthManagerEntities>

    {

        // This method is called only once to initialize service-wide policies.

        public static void InitializeService(DataServiceConfiguration config)

        {

            config.SetEntitySetAccessRule(“*”, EntitySetRights.All);

            // Paging requires v2 of the OData protocol.

            config.DataServiceBehavior.MaxProtocolVersion =

                System.Data.Services.Common.DataServiceProtocolVersion.V2;

        }

    }

}

 

WARNING: In case it’s not obvious, note that the .SetEntitySetAccessRule() is currently set so that all users can see and modify everything, so this is obviously not something you would want to go to production with!

OK, so what does that give us? Well, we can get ATOM-ized data via the URL with something like:

http://localhost:5000/Services/HealthManagerService.svc/People

Which returns something like this:

<?xml version=1.0 encoding=utf-8 standalone=yes?>

<feed xml:base=http://localhost:5000/Services/HealthManagerService.svc/

      xmlns:d=http://schemas.microsoft.com/ado/2007/08/dataservices

      xmlns:m=http://schemas.microsoft.com/ado/2007/08/dataservices/metadata

      xmlns=http://www.w3.org/2005/Atom>

    <title type=text>People</title>

    <id>http://localhost:5000/Services/HealthManagerService.svc/People</id>

    <updated>2011-06-06T13:50:11Z</updated>

    <link rel=self title=People href=People />

    <entry>

        <id>http://localhost:5000/Services/HealthManagerService.svc/People(guid’3c42ae1e-8135-484b-88e7-1bb9a6b8da00&#8242;)</id>

        <title type=text></title>

        <updated>2011-06-06T13:50:11Z</updated>

        <author>

            <name />

        </author>

        <link rel=edit title=Person href=People(guid’3c42ae1e-8135-484b-88e7-1bb9a6b8da00′) />

        <link rel=http://schemas.microsoft.com/ado/2007/08/dataservices/related/PersonActivityDates

              type=application/atom+xml;type=feed

              title=PersonActivityDates

              href=People(guid’3c42ae1e-8135-484b-88e7-1bb9a6b8da00′)/PersonActivityDates />

        <category term=Models.Person scheme=http://schemas.microsoft.com/ado/2007/08/dataservices/scheme />

        <content type=application/xml>

            <m:properties>

                <d:PersonId m:type=Edm.Guid>3c42ae1e-8135-484b-88e7-1bb9a6b8da00</d:PersonId>

                <d:FirstName>John</d:FirstName>

                <d:LastName>Doe</d:LastName>

                <d:Address>123 Main Street</d:Address>

                <d:City>Enfield</d:City>

                <d:Province>CT</d:Province>

                <d:PostalCode>06082</d:PostalCode>

                <d:HomePhone>860-000-0000</d:HomePhone>

                <d:MobilePhone>0</d:MobilePhone>

                <d:WorkPhone>0</d:WorkPhone>

                <d:DateOfBirth m:type=Edm.DateTime>1976-04-17T00:00:00</d:DateOfBirth>

            </m:properties>

        </content>

    </entry>

    <entry>

        <id>http://localhost:5000/Services/HealthManagerService.svc/People(guid’445b561f-9fbf-498e-b6da-97480db654bb&#8217;)</id>

        <title type=text></title>

        <updated>2011-06-06T13:50:11Z</updated>

        <author>

            <name />

        </author>

        <link rel=edit title=Person href=People(guid’445b561f-9fbf-498e-b6da-97480db654bb’) />

        <link rel=http://schemas.microsoft.com/ado/2007/08/dataservices/related/PersonActivityDates

              type=application/atom+xml;type=feed

              title=PersonActivityDates

              href=People(guid’445b561f-9fbf-498e-b6da-97480db654bb’)/PersonActivityDates />

        <category term=Models.Person scheme=http://schemas.microsoft.com/ado/2007/08/dataservices/scheme />

        <content type=application/xml>

            <m:properties>

                <d:PersonId m:type=Edm.Guid>445b561f-9fbf-498e-b6da-97480db654bb</d:PersonId>

                <d:FirstName>Sally</d:FirstName>

                <d:LastName>Smith</d:LastName>

                <d:Address>234 Elm Street</d:Address>

                <d:City>Hartford</d:City>

                <d:Province>CT</d:Province>

                <d:PostalCode>06103</d:PostalCode>

                <d:HomePhone>860-000-0000</d:HomePhone>

                <d:MobilePhone></d:MobilePhone>

                <d:WorkPhone></d:WorkPhone>

                <d:DateOfBirth m:type=Edm.DateTime>1974-05-22T00:00:00</d:DateOfBirth>

            </m:properties>

        </content>

    </entry>

</feed>

 

Sure enough, you can do all sorts of regular queries this way. But what about consuming this data from something else – let’s say a Windows Phone 7? Well, you could very well hit these URL’s, try to parse the data, etc. But – wouldn’t it be cool if you could just load some library and everything would just sort of magically work?

You guessed it, that is the case here!

Consuming Data from WP7:
This approach is described in detail here: http://blogs.msdn.com/b/priozersk/archive/2010/03/19/accessing-wcf-data-services-from-wp7-ctp.aspx

Also, writing things for WP7 is just basically the same as Silverlight or WPF – so the ideas here should translate to regular Silverlight or WPF as well.

I used %windir%Microsoft.NETFrameworkv3.5DataSvcUtil.exe to generate my client-side data structures (something like: %windir%Microsoft.NETFrameworkv3.5DataSvcUtil.exe /language:CSharp /out:”$(ProjectDir)ModelsHealthManagerEntities.cs” /uri:http://localhost:5000/Services/HealthManagerService.svc)

Then, I imported those into my WP7 project. On my main page, I have some code like this in my main page constructor:

 

DataContext = App.ViewModel;

this.Loaded += new RoutedEventHandler(MainPage_Loaded);

 

 

// Initialize the context

HealthManagerEntities healthManager = new HealthManagerEntities(new

                    Uri(http://localhost:5000/Services/HealthManagerService.svc/&#8221;));

// Get the  query

var personQuery = healthManager.People;

// Execute the query

personQuery.BeginExecute(

delegate(IAsyncResult asyncResult)

{

    Dispatcher.BeginInvoke(

    () =>

    {

        DataServiceQuery<Person> query = asyncResult.AsyncState as

                                                DataServiceQuery<Person>;

        if (query != null)

        {

            var people = query.EndExecute(asyncResult);

 

            this.DataContext = new PeopleViewModel(people);

        }

    }

    );

},

    personQuery

    );

 

I also have a PeopleViewModel that exposes an ObservableCollection of type Person:

public ObservableCollection<Person> Items { get; private set; }

Then, on my main page, I set the DataContext to this ViewModel, and start binding:

    xmlns:vm=”clr-namespace:SederSoftware.WcfRestfulService.WP7.ViewModels”>

    <UserControl.DataContext>

        <vm:PeopleViewModel />

    </UserControl.DataContext>

and then I have some text blocks that are bound:

<TextBlock Text=”{Binding FirstName}” />

<TextBlock Text=”{Binding City}” />

and low and behold, it works:

image

Next, want it so when I click on “Sally” with my finger, I want a panorama (or pivot) page to load with the details. Per the regular technique, this is easy:

// Handle selection changed on ListBox

private void MainListBox_SelectionChanged(object sender, SelectionChangedEventArgs e)

{

    // If selected index is -1 (no selection) do nothing

    if (MainListBox.SelectedIndex == -1)

        return;

 

    // Navigate to the new page

    NavigationService.Navigate(new Uri(“/PanoramaPage1.xaml?selectedItem=” + ((Person)MainListBox.SelectedItem).PersonId.ToString(), UriKind.Relative));

 

    // Reset selected index to -1 (no selection)

    MainListBox.SelectedIndex = -1;

}

OK – so when someone selects the item, it is going to navigate to PanoramaPage1.xaml and pass it the selectedItem’s “PersonId”. But wait, how do we now go and get the detail for this person, now that we’re on a different page?

First, override the OnNavigatedTo event handler. In here, you can use a similar technique to make a different call to that WCF Data Service. Now, in theory, you are SUPPOSED to be able to call something like:

MyService.svc/People(1234)

to bring back a Person with PersonId 1234. In this case, I’m using GUIDs by the way. But here is the problem, that I still don’t know the answer to… There were various sources that describe that I could use a regular Linq query here, and that the DataServiceQuery<T> would translate that. However, if I did do a LInq query directly or used .Where(), the return type is IEnumerable or IQueryable, and it would not cast/convert back to DataServiceQuery<T> without losing the URL and connection information.

SO, To get around that, I just specified the primary key in the filter. That makes a slightly different URL, but I believe that is OK. What I mean is, instead of the URL being something like HealthManagerService.svc/People(guid’F416FFAF-259B-4EBA-8101-FC5542E7280B’) – the URL would be something like: HealthManagerService.svc/People()?$filter=PersonId eq guid’445b561f-9fbf-498e-b6da-97480db654bb’

protected override void OnNavigatedTo(System.Windows.Navigation.NavigationEventArgs e)

{

    string selectedItemValue;

    if (NavigationContext.QueryString.TryGetValue(“selectedItem”, out selectedItemValue))

    {

        Guid personId = new Guid(selectedItemValue);

 

        // Initialize the context

        HealthManagerEntities healthManager = new HealthManagerEntities(new

                            Uri(http://localhost:5000/Services/HealthManagerService.svc/&#8221;));

 

        //// Get the  query

        DataServiceQuery<Person> personQuery =

            healthManager.People.AddQueryOption(“$filter”, String.Format(“PersonId eq guid'{0}'”, personId));

 

        // Execute the query

        personQuery.BeginExecute(

        delegate(IAsyncResult asyncResult)

        {

            Dispatcher.BeginInvoke(

            () =>

            {

                DataServiceQuery<Person> query = asyncResult.AsyncState as

                                                        DataServiceQuery<Person>;

                if (query != null)

                {

                    QueryOperationResponse<Models.Person> people = query.EndExecute(asyncResult) as QueryOperationResponse<Models.Person>;

                    this.DataContext = people.FirstOrDefault();

 

                }

            }

            );

        },

            personQuery

            );

    }

 

    base.OnNavigatedTo(e);

}

So now, if I set up my ViewModel for this page to be of-type Person, and do some databinding to textblocks, sure enough, this works:

image

Bottom Line:
Despite the learning curve and trouble finding information, I am actually quite impressed with this idea. With extremely little code, we can expose an entity frameworks model, then consume it from Silverlight, Silverlight for WP7, or WPF using ATOM/OData/etc – and securely (although this example doesn’t show that).

Next steps?

  • I would like this to use OAuth from the phone for authentication.
  • I need to figure out how to better lock down the Data Service to only expose some tables
  • I need to figure out how to do authorization too

Useful links:

Tagged with: , , , ,
Posted in .NET 4.0, ASP.NET MVC, Entity Framework, Linq, Mobile, New Technology, Professional Development, SQL, Uncategorized, WCF, WPF and MVVM
6 comments on “WCF Data Services, OData, WP7, and EF
  1. Jamie Dixon says:

    Can you consume OData using JQuery?

    Like

  2. Rob Seder says:

    Yes!

    Download and reference the WCF Data Services Toolkit from here: http://wcfdstoolkit.codeplex.com/releases/view/65900

    Then, change your data service class to inherit from ODataService instead of DataService.

    Then, to consume, use a URL like this: http://localhost:5000/Services/HealthManagerService.svc/People()?format=json

    that returns this:

    {
    “d” : [
    {
    “__metadata”: {
    “uri”: “http://localhost:5000/Services/HealthManagerService.svc/People(guid’3c42ae1e-8135-484b-88e7-1bb9a6b8da00′)”, “type”: “Models.Person”
    }, “PersonId”: “3c42ae1e-8135-484b-88e7-1bb9a6b8da00”, “FirstName”: “John”, “LastName”: “Doe”, “Address”: “123 Main Street”, “City”: “Enfield”, “Province”: “CT”, “PostalCode”: “06082”, “HomePhone”: “860-000-0000”, “MobilePhone”: “0”, “WorkPhone”: “0”, “DateOfBirth”: “/Date(198547200000)/”, “PersonActivityDates”: {
    “__deferred”: {
    “uri”: “http://localhost:5000/Services/HealthManagerService.svc/People(guid’3c42ae1e-8135-484b-88e7-1bb9a6b8da00′)/PersonActivityDates”
    }
    }
    }, {
    “__metadata”: {
    “uri”: “http://localhost:5000/Services/HealthManagerService.svc/People(guid’445b561f-9fbf-498e-b6da-97480db654bb’)”, “type”: “Models.Person”
    }, “PersonId”: “445b561f-9fbf-498e-b6da-97480db654bb”, “FirstName”: “Sally”, “LastName”: “Smith”, “Address”: “234 Elm Street”, “City”: “Hartford”, “Province”: “CT”, “PostalCode”: “06103”, “HomePhone”: “860-000-0000”, “MobilePhone”: “”, “WorkPhone”: “”, “DateOfBirth”: “/Date(138412800000)/”, “PersonActivityDates”: {
    “__deferred”: {
    “uri”: “http://localhost:5000/Services/HealthManagerService.svc/People(guid’445b561f-9fbf-498e-b6da-97480db654bb’)/PersonActivityDates”
    }
    }
    }
    ]
    }

    Like

  3. Nilav Ghosh says:

    Are you using the latest release of the OData Client Libraries. Apparently this example will not work with the final release
    http://blogs.msdn.com/b/astoriateam/archive/2010/10/28/data-services-client-for-win-phone-7-now-available.aspx
    as
    “LINQ support in the client library has been removed as the core support is not yet available on the phone platform. That said, we are actively working to enable this in a future release. Given this, the way to formulate queries is via URIs.”

    Do please add the disclaimer. Although a neat and otherwise helpful example this may take up time of developers trying to implement the newest version and they should rather use the example at
    http://phonetocloud.info/2011/03/03/introducing-the-odata-client-library-for-windows-phone-part-3/

    Like

  4. nilavghosh says:

    Hi Rob.
    The “healthManager.People” IQueryable enitity is not available any more with the latest version. I am not sure if you are able to get that property and LINQ to that “People” property.
    IT is possible that i may be missing something and would like to continue investigating.
    Thanks for a quick reply.

    Like

  5. pruebare says:

    Can you help me i need a app in Windows Phone for Logon using a SQL Azure DB

    Like

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

Archives
Categories

Enter your email address to follow this blog and receive notifications of new posts by email.

Join 2 other followers

%d bloggers like this: