Tuesday, December 11, 2007

A Much Simpler Way of Building REST Services With ADO.NET Data Services ("Project Astoria")

In my previous post, Simple GET REST Service with WCF, I detailed how to build a simple web service with WCF that routed a simple request and constructed a REST/POX response.  As part of the release of the ASP.NET 3.5 Extensions yesterday, the previously named "Project Astoria" was released as ADO.NET Data Services.  There's some details here about what all ADO.NET Data Services include.  The feature I want to outline in this post is the feature that can serialize these entities into the industry standard AtomPub format.

You'll need Visual Studio 2008 and the ASP.NET 3.5 Extensions bits released yesterday and install them first.  You can get those here, and read Scott Guthrie's post about the release.

After installing the Extensions CTP, you'll see some new options when you are creating a new web project.

create new project dialog

Choose ASP.NET 3.5 Extensions Web Site.  Once the site is created, add a new item and choose "ADO.NET Data Service" from the Item Template window.

After that, you'll want to create a simple LINQ to SQL class.  You can see my post here about how to do that.  For this example, I'm going to connect to the NorthwindEF sample database and just drag the Customers table onto the designer surface.

Once I've created the ADO.NET Data Service and the LINQ to SQL classes, then I'll need to wire up my simple data source into the service.  This is really simple.  First thing to do is to put your DataContext class created by the LINQ to SQL step into the service class declaration.

What looked like this:

public class MySimpleService : WebDataService< /* TODO: put your data source class name here */ >

Should now look like this:

public class MySimpleService : WebDataService<NorthwindDataContext>

After that, you'll want to make sure that you provide access to your data source, as it is off by default.  In this example, I'll give access to every data entity in the data context object (all classes that implement IQueryable<T> by using the wildcard.   After making these changes, your service class should look like this:

using Microsoft.Data.Web;
 
public class MySimpleService : WebDataService<NorthwindDataContext>
{
    public static void InitializeService(IWebDataServiceConfiguration config)
    {
        config.SetResourceContainerAccessRule("*", ResourceContainerRights.AllRead);
    }
}

That's it.  If you fire up your service, you should see something like this:

atom feed collection

From there, you can do all sorts of fun stuff with the URL:

http://localhost:50508/Astoria/MySimpleService.svc/Customers yields all customers:

all customers

http://localhost:50508/Astoria/MySimpleService.svc/Customers('ALFKI') returns just that customer with that primary key.

http://localhost:50508/Astoria/MySimpleService.svc/Customers?$filter=CompanyName%20eq%20'Alfreds%20Futterkiste' returns all customers with that specific customer id.

There's a bunch more stuff you can do with the URI addressing schemes, such as ordering a and paging.  Fantastic!

You can't beat that.  10 minute fully functional REST web service.  This has such great potential.

6 comments:

Raza said...

I'm trying to get this working but keep getting HTTP 404 (Not Found) error. My dbml contains one table only, called USERINFO.

When I browse to the service page (http://localhost:3356/AttendanceService.svc/), I only get "Default" in the atom:title tags.

I get http404 when I try to do ...svc/USERINFO or /USERINFOs

This same service works perfectly when I use an edmx model instead of a dbml datacontext.

I also created a test function to make sure the linq is working, and it is. But just cannot figure out why the service won't get the right collections.

In other words, the following works:

public class AttendanceService : WebDataService<AttendanceModel.AttendanceEntities>

and brings back the USERINFO collection and USERINFO atom:title tags.

But the following doesn't:

public class AttendanceService : WebDataService<AttendanceDataContext>

Any ideas?

Jim Fiorato said...

Raza,

If you open up the dbml designer file, do you see a parameterless constructor that passes the connection string to the base class?

Jim

Raza Ali said...

Hi,

Yes there is a parameter less constructor:

public AttendanceDataContext() :
base( global :: System.Configuration.ConfigurationManager.ConnectionStrings["AttendanceConnectionString"].ConnectionString, mappingSource)
{
OnCreated();
}

The "AttendanceConnectionString is also declared in web.config.

Jim Fiorato said...

Raza

Would you be willing to send me the project and database so I could have a look? jfiorato at gmail dott com

Jim

Jim Fiorato said...

Hey Raza

I've been looking at this a little more, and it appears to have something to do with your database tables. For instance, if you add a new table, and put that in your dbml, it works fine. I'm going to look some more and see if I can't figure out what it is about the tables that is causing the issue. I've tried switching the database version (it's currently 80), and also altering things like ANSI NULLS, QUOTED IDENTIFIERS at the database level, but no luck. I'll post back when I find anything new.

Jim

Jim Fiorato said...

Hey Raza

I figured this out finally. It has to do with the primary key name of your entity. See my latest blog post here to see what the issue is:
http://www.writebetterbits.com/2008/02/common-reason-adonet-data-services-don_11.html