Linq to REST

Late Sunday night we released the first public preview of ASP.NET 3.5 Extensions.  For information on the release, see ScottGu's announcement.

As part of that preview, we released the first CTP of the production version of Project Astoria.  For details, see public release announcement by Mike Flasko on the Astoria Team Blog.

Astoria actually has been given its official Microsoft name:  ADO.NET Data Services.  So, in this post I will use the name Astoria and ADO.net Data Services interchangeably.

For a complete overview of Astoria check out the information available on Microsoft Live Labs.

For the next few weeks, I will be writing a series of blog posts to demonstrate what I think is one of the most compelling features in Astoria, namely remote execution of Linq queries via the RESTFUL API exposed by Astoria Data Services.

For these posts, I will be covering:

1)  How to set up an Astoria Data Service which can be used for remote execution of Linq queries.

2)  Dive deep into the Astoria Client API's Linq support including a detailed description of how Linq queries are translated to URIs.

3)  How to make the Astoria Data Service updateable so the results of the Linq queries can be remotely modified and persisted.

So the goal of this initial post is to set up an Astoria Data Service over a Linq to Sql DataContext source and remotely execute a Linq query via the Astoria client library over HTTP.

(The following steps assume Visual Studio 2008 and ASP.NET 3.5 Extensions has been installed.  Also, this demo assumes access to the Sql Server sample Northwind database).

First, Create a standard ASP.NET Web Application:

image 

Next, add an ADO.NET Data Service called Northwind.svc to the project via the Add New Item option:

image 

Next, add a new set of Linq to Sql via the designer called Northwind.dbml.

image  

From my Data Connections in the Server Explorer, I add the Categories, Products, and Supplier tables to Northwind.dbml from the Northwind database

image

Because the keys for the new types follow the Astoria convention of being named [EntityName]Id, I don't have to do anything further.  The Linq to Sql data classes and the strongly typed DataContext (NorthwindDataContext) is totally useable with Astoria without any modifications.

Next, I want to wire up my Astoria Data Service to use NorthwindDataContext as the data source.  To do this, go back to the Northwind.svc.cs file that was created when the Astoria Data Service was added to the project.  Look for the TODO's in the Northwind class:

  
     public class Northwind : WebDataService< /* TODO: put your data source class name here */ >
    {
        // This method is called once during service initialization to allow
        // service-specific policies to be set
        public static void InitializeService(IWebDataServiceConfiguration config)
        {
            // TODO: set rules to indicate which entity sets and service operations are
            // visible, updatable, etc.
            // (for testing purposes use "*" to indicate all entity sets/service
            // operations, but that option should NOT be used in production systems)

            // Example for entity sets (this example uses "AllRead" which allows reads but not writes)
            // config.SetResourceContainerAccessRule("MyEntityset", ResourceContainerRights.AllRead);

            // Example for service operations
            // config.SetServiceOperationAccessRule("MyServiceOperation", ServiceOperationRights.All);
        }

        // Query interceptors, change interceptors and service operations go here
    }

First, put the name of the DataContext in as the generic parameter for the Northwind class.

Next. add the following line to the InitializeService method:

 config.SetResourceContainerAccessRule("*", ResourceContainerRights.AllRead);

By default, Astoria turns off all access to the data source.  By adding this line, I am giving read access to all public IQueryable<T> entry points on the DataContext. 

Note, the wildcard option (*) should only be used for testing purposes.  For actual application development, please explicitly grant access to each IQueryable<T> entry point by via the property name.

Now build the application and press F5 to fire it up.  If everything went ok, you should get back the following Atom service definition when you access the URI https://localhost:18752/Northwind.svc/ (I am using port 18752 with Cassini for debugging).

 

   <service xml:base="https://localhost:18752/Northwind.svc/" xmlns:atom="https://www.w3.org/2005/Atom" xmlns:app="https://www.w3.org/2007/app" xmlns="https://www.w3.org/2007/app">
    <workspace>
       <atom:title>Default</atom:title> 
       <collection href="Categories">
         <atom:title>Categories</atom:title> 
       </collection>
       <collection href="Products">
         <atom:title>Products</atom:title> 
       </collection>
       <collection href="Suppliers">
         <atom:title>Suppliers</atom:title> 
       </collection>
    </workspace>
  </service>

 

Now, try a few URIs to make sure everything it working:

(Note, Internet Explorer by default renders ATOM in a friendly format that makes Astoria feeds unreadable.  To fix this, turn off IE feed viewing by turning off the option :  Tools -> Internet Options -> Content ->  Feeds (settings) -> Turn on feed viewing)

https://localhost:18752/Northwind.svc/Products - gets all the Products

https://localhost:18752/Northwind.svc/Products(2) - gets the Product with ProductId == 2

https://localhost:18752/Northwind.svc/Products?$filter=UnitsInStock gt 100 - gets all the Products where UnitsInStock is greater than 100

https://localhost:18752/Northwind.svc/Products?$orderby=ProductName&$top=10 - orders the Products by ProductName and gets the first 10.

 

For more information about Astoria's URI Addressing Scheme see here.

Now, lets get a sample Linq query working.

First, add another project to the solution.  In this case, a simple console Application will work:

image

In this project, add a reference to the Astoria client library assembly called Microsoft.Data.WebClient.dll. It should be in your /Program Files/Reference Assemblies/Microsoft/Framework/ASP.NET 3.5 Extensions directory.

Now add make sure the namespace Microsoft.Data.WebClient is in scope, and add the following class to your project:

 

     [OpenObject("PropBag")]
    public class Product
    {
        private Dictionary<string, object> propBag = new Dictionary<string, object>();

        [Key]
        public int ProductID { get; set; }
        
        public string ProductName { get; set; }
        
        public int UnitsInStock { get; set; }

        public IDictionary<string, object> PropBag { get { return propBag; } }

    }

Finally, add the following to Program.Main:

  
         static void Main(string[] args)
        {
            WebDataContext context = new WebDataContext("https://localhost:18752/Northwind.svc");

            var query = from p in context.CreateQuery<Product>("Products")
                        where p.UnitsInStock > 100
                        select p;

            foreach (Product p in query)
            {
                Console.WriteLine(p.ProductName + " , UnitsInStock= " + p.UnitsInStock);
            }

        }

 

To get both projects to start up for debugging, select Solution-> Properties and select Multiple startup projects with the Action of Start.

Put a breakpoint on the foreach statement in the client app, and hit F5.

image

If you hover over the query variable, you will actually see the Astoria URI which the Linq query is translated into by the Astoria client library:

https://localhost:18752/Northwind.svc/Products?$filter=(UnitsInStock)%20gt%20(100)

So, there you go.  Linq to Astoria's RESTFUL API.  In other words, Linq to REST. 

In future posts, I will explain how that worked.  What all the Linq capabilities that are supported.  And what one can do with the objects once they are materialized.