Friday, August 01, 2008

ADO.NET Data Services: Creating a custom Data Context #2: The Client

In part 1 I showed how to create a simple in-memory custom data context for ADO.NET Data Services. Creating a managed client is also very simple. First we need to provide a similar domain model to our server. In this case the classes are identical except that now Teacher has a List<Course> rather than a simple array (Course[]) as it's Courses property:

using System.Collections.Generic;

namespace Mike.DataServices.Client.Model
{
   public class Teacher
   {
       public int ID { get; set; }
       public string Name { get; set; }
       public List<Course> Courses { get; set; }
   }
}

Next I wrote a class to extend DataServiceContext with properties for Teachers and Courses that are both DataServiceQuery<T>. Both DataServiceContext and DataServiceQuery<T> live in the System.Data.Services.Client assembly. You don't have to create this class, but it makes the subsequent use of the DataServiceContext simpler. You can also use use the 'Add Service Reference' menu item, but I don't like the very verbose code that this generates.

using System;
using System.Data.Services.Client;
using Mike.DataServices.Client.Model;

namespace Mike.DataServices.Client
{
   public class SchoolServiceProxy : DataServiceContext
   {
       private const string url = "http://localhost:4246/SchoolService.svc";

       public SchoolServiceProxy() : base(new Uri(url))
       {
       }

       public DataServiceQuery<Teacher> Teachers
       {
           get
           {
               return CreateQuery<Teacher>("Teachers");
           }
       }

       public DataServiceQuery<Course> Courses
       {
           get
           {
               return CreateQuery<Course>("Courses");
           }
       }
   }
}

Here's a simple console program that outputs teacher John Smith and his courses and then the complete list of courses. The nice thing is that DataServiceQuery<T> implements IQueryable<T> so we can write LINQ queries against our RESTfull service.

using System;
using System.Linq;
using System.Data.Services.Client;
using Mike.DataServices.Client.Model;

namespace Mike.DataServices.Client
{
   class Program
   {
       readonly SchoolServiceProxy service = new SchoolServiceProxy();

       static void Main(string[] args)
       {
           var program = new Program();
           program.GetJohnSmith();
           program.GetAllCourses();
       }

       public void GetJohnSmith()
       {
           Console.WriteLine("\r\nGetJohnSmith");

           var teachers = service.Teachers.Where(c => c.Name == "John Smith");

           foreach (var teacher in teachers)
           {
               Console.WriteLine("Teacher: {0}", teacher.Name);

               // N+1 issue here
               service.LoadProperty(teacher, "Courses");

               foreach (var course in teacher.Courses)
               {
                   Console.WriteLine("\tCourse: {0}", course.Name);
               }
           }
       }

       public void GetAllCourses()
       {
           Console.WriteLine("\r\nGetAllCourses");

           var courses = service.Courses;

           foreach (var course in courses)
           {
               Console.WriteLine("Course: {0}", course.Name);
           }
       }
   }
}

We get this ouput:

ado_net_im_console

Code is here:

http://static.mikehadlow.com/Mike.DataServices.InMemory.zip

7 comments:

Jo and Dave said...

I read your earlier post and thought I'd have a crack at using "LINQ to REST" from Silverlight. It hurts! But can be done. I used the datasvcutil provided to create my client model. Beware though if trying to do this with ASP.NET MVC hosting the silverlight as you can get punished if your routes are iffy.

Mike Hadlow said...

Hi Dave, I guess if your routes don't exclude your *.svc you'll just get a slew of routing failures right.

Jo and Dave said...

Ahh...its the end of a long day. I was using new URI("WebDataService.svc", UriKind.Relative) which triggered the routing dependent on my page. Changed it to absolute for now.

Another horrid thing is that once you move over to the new System.Data.Services.Web from Microsoft.Data.Web you can no longer query though IE! That doesn't sound good for a RESTful service...i'll look into this next.

Mike Hadlow said...

Eh? I'm using SP1 Beta, so System.Data.Services and can use IE fine. The screenshots in part 1 were done with IE.

.. or do you mean something else?

Jo and Dave said...

FYI....

Anyway, the short story is that Astoria's default response format is Atom, which means the Content-Type response header is set to application/atom+xml. IE6 does not recognize that format and offers to save the file to disk. In IE7 the format is recognized as a feed and you're presented with a feed view (which usually is not very useful for Astoria services and you can see pure XML by going to Tools -> Internet Options -> Content (tab) -> Settings button under the "Feeds" section -> uncheck "turn on feed reading view".

Mike Hadlow said...

Ah IE6. I see.

Anonymous said...

Hi,
Thanks for the post.
I'm wondering how can you "intercept" the HTTP messages that you're sending from the client in order to handle other aspects of your application, such as user authentication, cookies, etc.
In other words, how can you make sure that while using ADO.NET Data services from the client (using the auto-generated proxy), you can still add/edit the HTTP headers in the HTTP messages that the Data Services framework is sending.

Thanks,
Ronen.