Wednesday, July 29, 2009

Images as a Service with ADO.NET Data Services

For those of you who have already put your feet into the waters of .NET 3.5 SP1, you have probably seen some amazing things with ADO.NET Data Services coupled to the Entity Framework. But that coupling may lead to blindness. Allow me to explain.

First of all, I want to clearly state up front that I love the Entity Framework and I love how easily ADO.NET Data Services snaps into it to provide a RESTful representation of your Entity Data Model (EDM). But there is a lot more to Data Services than is shown by this usage.

For instance, many people may not realize that you don't need EF behind your data service. What's more, is that you don't need a SQL database behind your data service. ADO.NET Data Services can be fully customized to expose any queryable "data" that you come up with in a REST-styled, web accessible data store.

Our Scenario - Serving Images

To demonstrate customization with ADO.NET Data Services, our example "data" will simply be a folder with some pictures in it. Our "data model" is nothing more than an image name, size, a thumbnail and the image data itself.

Data Services will give us out-of-the-box 'queryable' functionality such as paging and sorting. It also provides a delivery medium (HTTP) as well as multiple ways to receive the serialized data (Atom, JSON). Let's take a look at our data service and how to consume it in a few creative ways.

As a quick side note, this article will not get into how to do sorting or paging. Rather, it is intended to broaden the understanding of data as a RESTful service using ADO.NET Data Services.

Custom Data Service and IQueryable Properties

Creating an ADO.NET Data Service is easy to do, and the initial setup takes only a few moments. I simply right-click on my project, and go to 'Add New Item', select ADO.NET Data Service and press 'OK'. This is no different than adding a web service to your project. Here is what we have when we add our new data service:

using System;
using System.Collections.Generic;
using System.Data.Services;
using System.Linq;
using System.ServiceModel.Web;
using System.Web;

namespace MyNameSpace
{
public class WebDataService1 : DataService</* your class here */>
{
public static void InitializeService(IDataServiceConfiguration config)
{

// TODO:
set rules to indicate which entity sets

// and service operations are visible, updatable, etc
.

}
}
}

Other than removing some of the comments (for display reasons), I intentionally haven't touched the generated code to show you how simple it is. In fact, most of the content above are comments that describe what to do. So, I'll configure my data service now to use a custom C# class that I built called "ImagesStore". We'll take a look at that class in a moment, but for now it's good that you know that it has a single property on there called "Images" which is of type "IQueryable". So here's my configured data service code:

using System.Data.Services;

namespace MyNameSpace
{
public class WebDataService1 : DataService
{
public static void InitializeService(IDataServiceConfiguration config)
{
config.SetEntitySetAccessRule(
"Images", EntitySetRights.AllRead);
}
}
}

That's it! ADO.NET Data Services now knows exactly what to do. Now my custom C# provider of data is exposed via a RESTful URL and can be queried and transmitted across the network (or Internet). Now that our data service is configured, let's take a look at the "ImagesStore" class to see what kind of data is being provided.

Data Services will reflect your class and look for any IQueryable properties, so here's my simple "ImagesStore" code:

// First I need to make a class that will hold the image data.
// Also, ADO.NET Data Services needs to know which property
is

// the main identifier
for each individual image. I'll use the

// Name property
for that.

[DataServiceKey(
"Name")]
public class Image
{
public string Name { get; set; }

public byte[] ImageData { get; set; }

public int Height { get; set; }

public byte[] ThumbnailData { get; set; }

public int Width { get; set; }

// There are some helper methods here that I'm leaving

//
out, but you can download the source later.

}


// Here
is the ImagesStore class with the single property

// called Images. There's not a lot going
on here.

public class ImagesStore
{
private static List images;

public IQueryable Images
{
get
{
if (ImagesStore.images == null)
{
ImagesStore.images =
new List();

// Loop through the files
in the Images folder...

foreach (string imagePath in Directory.GetFiles(
HttpContext.Current.Server.MapPath(
"~/Images")))
{

// Add the image to the list...

ImagesStore.images.Add(
new Image
{
Name = Image.GetImageName(imagePath),
ImageData = Image.GetImageData(imagePath),
ThumbnailData = Image.GetThumbnailData(imagePath),
Height = Image.GetImageHeight(imagePath),
Width = Image.GetImageWidth(imagePath)
});
}
}


// Return the list
as a queryable source.

return ImagesStore.images.AsQueryable();
}
}
}

The above code doesn't show my methods for pulling out the data from each image, but that code isn't important to show in the article. If you want to see more detail on how this is done, you can download the entire source project, including a windows forms client that consumes the data too.

The Results

When you look at the above service directly (by browsing to the service URL), you will see the Atom serialized representation of our images. Below, I'll show two separate client representations of that data: one a web site, and one a Windows Forms application. Here's what it looks like:


See full detail: http://www.singingeels.com/Articles/Images_as_a_Service_with_ADONET_Data_Services.aspx

No comments: