Introduction
The world of web services encounters another trend by REST-full services after the
invention of SOAP based protocols. The acronym REST stands for Representational State Transfer; this basically means that each unique URL is a reference to a resource. The main
advantage of REST services is their simplicity compare to the SOAP based services.
REST services use the tradition HTTP methods to do the basic CRUD operations.
For example, the HTTP GET method is used to address an object or a resource,
whist the DELETE HTTP method is used to delete a resource on the server. The
POST and PUT HTTP methods are used to update or create data on the server too.
REST services can be used by a simple web browser without any special knowledge
about protocols or any special tool and it makes them available to a variety
of different clients and nowadays using REST services is a robust approach for
publishing data on the web.
Microsoft makes a big stride in developing REST services by introducing ADO.net Services. Known as WCF Data Services in the Framework 4.0, they allow a very simple way to
expose database tables as REST-full services. Data Services can be published
on a Web Server or any other WCF Host environment and in one word; there is a
bunch of different hosting options. The process of creating them is very simple
too. At first, one should create an Entity Framework and then maps the tables to conceptual classes in the entity framework. Secondly,
he/she should create a Data Service and finally, configuring the Entity Framework in the InitilizeService method of the data service. Pretty simple! Isn’t it?
On the client side, a .NET Framework client can get advantages of System.Data.Services.Client namespace classes to access data services. The heart class of the namespace is DataServiceContext. It is similar to the DataContext class of the LINQ to SQL technology. According to the MSDN “The DataServiceContext represents the runtime context of an ADO.NET data service.
ADO.NET Data Services are stateless, but the DataServiceContext is not. State
on the client is maintained between interactions in order to support features
such as update management. This class, and the DataServiceQuery class that represents a particular HTTP request to a data service, are the two main
classes in the client library.”
Developing a three tier application using the data services has clear advantages
such as simplicity, RAD development, less code, easy maintenance a few to mention.
But it has some limitations too. The main advantage of three tier applications
is the ability to do some processing and business logic in the middle tier, something
that data services have poor support on it.
Focus of the Paper
The focus of this paper is providing an approach that allows developers to do some
processing in the server side before forwarding the data to the clients. The
proposed method can be used easily and it doesn’t impose too complexity. Here
is some characteristic of the approach:
- It uses ReflectionProvider and LINQ to SQL framework to create data services.
- It doesn’t complicate the process of creating data services.
- By Wrapping LINQ to SQL Tables<T>, it provides a QueryCompleted event that
allows developer to have access to the queried data before forwarding it to the
clients.
In order to simplify my approach and the reason of its existence, I will describe
some validation features of Data Services plus the different providers of data
services in .NET framework. After that, I will delve into the proposed approach.
Data Services Providers
There are three types of providers in the .NET framework for creating Data Services.
The main one is Entity Framework provider. In the first line of a DataService,
public class WebDataService1 : DataService< /* TODO: put your data source class name here */ >
if one uses an ObjectContext in the commented TODO part, then the DataService is
based on Entity Framework Provider. Entity Framework Provider uses the ADO.NET Entity Framework to enable you to use
relational data with a data service by defining a data model that maps to relational
data [MSDN]. Using the Entity Framework Provider is simpler than the other providers. The other
option here is using Reflection Provider. Reflection provider uses reflection to enable you to define a data model based on
existing data classes that can be exposed as instances of the IQueryable interface. Updates are enabled by implementing theIUpdatable interface. You should use this provider when you have static data classes that are
defined at runtime, such as those generated by LINQ to SQL or defined by a typed
DataSet [MSDN]. The last option is creating a custom provider. There are a bunch of interfaces
that should be implemented in order to have a custom provider. Such providers
are very flexible, but indeed creating them is not that easy. An interested reader
can find a full functional implementation here.
Interceptors
Interceptors are .NET attributes that provide a flexible way to do entity level validation
and authorization in the ADO.NET data services. QueryInterceptorAttribute is
used to do query authorization and validation whilst ChangeInterceptor is used
to do change authorization and validation. Here is an example on how to use QueryInterceptorAttribute.
[QueryInterceptor("Orders")]
public Expression<Func<Order, bool>> FilterOrders()
{
return o => o.Customer.Name == "test";
}
Methods of a data service that decorated with the QueryInterceptorAttribute must
have no parameters and their return type must be Expression<Func<Order, bool>>. Theses functions are used to validate and authorize the client access to resources.
The above method only allows queries on the “Orders” IQueryable that their Customer.Name
is “test”. Such technique for entity-base validation is quite flexible, but it
doesn’t provide a way for server side processing before forwarding the data to
the client. The other shortcoming of Interceptors is the fact that they do not
provide field base authorization, they only provide entity base validation. Sometimes
we need field base authorization. For example, consider a situation that all
users have access to the “Orders”, but only admin users have right to see some
fields of the “Orders”. These shortcomings can be covered using server side processing.
My Approach
A data service can publish the data of any class that implements IQueryable<T> interface. IQueryable<T> Provides functionality to evaluate queries against
a specific data source wherein the type of the data is known. Table<T> class of LINQ to SQL framework and ObjectQuery<TEntity> of Entity Framework are examples of classes that implement IQueryable<T> interface
against a database. The signature of the interface is as follows:
public interface IQueryable<T> : IEnumerable<T>, IQueryable, IEnumerable
{
}
Where the generic IQueryable is as follows:
public interface IQueryable : IEnumerable
{
Type ElementType { get; }
Expression Expression { get; }
IQueryProvider Provider { get; }
}
The main property of the IQueryable is its Provider. The IQueryProvider Defines methods
to create and execute queries that are described by an IQueryable object. Implementing an IQueryable plus its provider to translate LINQ Expressions
to SQL queries is not an easy task at all. Interested user can find a complete
example on how to implement an IQueryProvider here.
My idea about enabling business process in the Data Services is based on wrapping
the existing Table instances of the LINQ to SQL framework by a class in such
a way that the wrapper class provides a QueryCompleted event. The mentioned event
has access to the returned data, so that a developer can process the queried
data once it has been ready. Here is the class diagram of the proposed approach.

There are two main classes here. The BQueryable implements IQueryable and wraps Table<Entity>
and BQueryProvider implements IQueryProvider. Here is the code of BQueryProvider.
public class BQueryProvider<T> : IQueryProvider where T : class
{
#region Constructor
public BQueryProvider(Table<T> t)
{
table = t;
}
#endregion
#region Private members
private Table<T> table;
#endregion
#region IQueryable implementation
IQueryable IQueryProvider.CreateQuery(Expression expression)
{
IQueryable<T> result = (table as IQueryProvider).CreateQuery<T>(expression);
onQueryCompleted(result);
return result;
}
IQueryable<TElement> IQueryProvider.CreateQuery<TElement>(Expression expression)
{
IQueryable<T> result = (table as IQueryProvider).CreateQuery<T>(expression);
onQueryCompleted(result);
return result as IQueryable<TElement>;
}
object IQueryProvider.Execute(Expression expression)
{
object value = (table as IQueryProvider).Execute(expression);
return value;
}
TResult IQueryProvider.Execute<TResult>(Expression expression)
{
return (table as IQueryProvider).Execute<TResult>(expression);
}
#endregion
#region Events
public event EventHandler<QueryResultEventArgs<IQueryable<T>>> QueryCompleted;
protected void onQueryCompleted(IQueryable<T> t)
{
if (QueryCompleted != null)
{
QueryCompleted(this, new QueryResultEventArgs<IQueryable<T>>(t));
}
}
#endregion
}
The class doesn’t implement an IQueryProvider from the scratch; instead it wraps
the existing one of the LINQ to SQL framework. Using this approach, the risk
of using a third party implementation of the IQueryProvider has been reduced.
The class has a QueryCompleted event that is fired in the CreateQuery methods,
before returning the data. This event enables the BQueryable class to have access
to the returned data. Here is the code of the BQueryable class.
public class BQuerable<T> : IQueryable<T>, IQueryable, ITable where T : class
{
#region Constructor
public BQuerable(Table<T> t)
{
table = t;
queryProvider = new BQueryProvider<T>(t);
queryProvider.QueryCompleted += new EventHandler<QueryResultEventArgs<IQueryable<T>>>(queryProvider_QueryCompleted);
}
#endregion
#region Events
private void queryProvider_QueryCompleted(object sender, QueryResultEventArgs<IQueryable<T>> e)
{
if (QueryCompleted != null)
{
QueryCompleted(this, new QueryResultEventArgs<IQueryable<T>>(e.Result));
}
}
protected void onEnumeratorReturned(IEnumerator enumerator)
{
if (QueryCompleted != null)
{
QueryCompleted(this, new QueryResultEventArgs<IQueryable<T>>(this));
}
}
public event EventHandler<QueryResultEventArgs<IQueryable<T>>> QueryCompleted;
#endregion
#region private members
private Table<T> table;
BQueryProvider<T> queryProvider;
#endregion
#region IEnumerable
IEnumerator IEnumerable.GetEnumerator()
{
IEnumerator enumerator = table.GetEnumerator();
onEnumeratorReturned(enumerator);
return enumerator;
}
IEnumerator<T> IEnumerable<T>.GetEnumerator()
{
IEnumerator<T> result = table.GetEnumerator();
onEnumeratorReturned(result);
return result;
}
#endregion
#region ITable
DataContext ITable.Context
{
get
{
return table.Context;
}
}
bool ITable.IsReadOnly
{
get
{
return table.IsReadOnly;
}
}
void ITable.Attach(object entity)
{
table.Attach(entity as T);
}
void ITable.Attach(object entity, bool asModified)
{
table.Attach(entity as T, asModified);
}
void ITable.Attach(object entity, object original)
{
table.Attach(entity as T, original as T);
}
void ITable.AttachAll(IEnumerable entities)
{
table.AttachAll(entities.Cast<T>());
}
void ITable.AttachAll(IEnumerable entities, bool asModified)
{
table.AttachAll(entities.Cast<T>(), asModified);
}
void ITable.DeleteAllOnSubmit(IEnumerable entities)
{
table.DeleteAllOnSubmit(entities.Cast<T>());
}
void ITable.DeleteOnSubmit(object entity)
{
table.DeleteOnSubmit(entity as T);
}
ModifiedMemberInfo[] ITable.GetModifiedMembers(object entity)
{
return table.GetModifiedMembers(entity as T);
}
object ITable.GetOriginalEntityState(object entity)
{
return table.GetOriginalEntityState(entity as T);
}
void ITable.InsertAllOnSubmit(IEnumerable entities)
{
table.InsertAllOnSubmit(entities.Cast<T>());
}
void ITable.InsertOnSubmit(object entity)
{
table.InsertOnSubmit(entity as T);
}
#endregion
#region IQueryable
Type IQueryable.ElementType
{
get
{
return (table as IQueryable).ElementType;
}
}
Expression IQueryable.Expression
{
get
{
return (table as IQueryable).Expression;
}
}
IQueryProvider IQueryable.Provider
{
get
{
return queryProvider;
}
}
#endregion
}
Similar to the BQueryProvider, the BQueryable class has a QueryCompleted event too.
This event is fired, once the BQueryProvider fires its own QueryCompleted event.
Developers should listen to this event in order to have access to the returned
data.
Using the Code
In order to use the code, a developer should create a LINQ to SQL DataContext. The
following capture is the one of the attached demo project.

Next, developer should create a new class that inherits from the BusinessDataContext
class. In the new class, developer should specify the Tables<T> that he/she
wants to expose via the DataService by wrapping them using BQueryable class.
He/she can listen to their QueryCompleted events too. Here is the code in the
attached demo project.
[DataServiceKeyAttribute("ProductID")]
public partial class Product { }
[DataServiceKeyAttribute("ProductModelID")]
public partial class ProductModel { }
public class CustomDataContext : BusinessDataContext<AdventureDataContext>
{
public CustomDataContext()
{
DataContext = new AdventureDataContext();
Products = new BQuerable<Product>(DataContext.Products);
ProductModels = new BQuerable<ProductModel>(DataContext.ProductModels);
Products.QueryCompleted += new EventHandler<QueryResultEventArgs<IQueryable<Product>>>(Products_QueryCompleted);
ProductModels.QueryCompleted += new EventHandler<QueryResultEventArgs<IQueryable<ProductModel>>>(ProductModels_QueryCompleted);
}
void ProductModels_QueryCompleted(object sender, QueryResultEventArgs<IQueryable<ProductModel>> e)
{
}
void Products_QueryCompleted(object sender, QueryResultEventArgs<IQueryable<Product>> e)
{
foreach (Product product in e.Result)
{
product.Name += " has been processed";
}
}
public BQuerable<Product> Products
{
get
{
return products;
}
set
{
products = value;
}
}
private BQuerable<Product> products;
public BQuerable<ProductModel> ProductModels { get; set; }
}
And now, the created data context is ready to feed the DataService. Here is the code
of the DataService.
[System.ServiceModel.ServiceBehavior(IncludeExceptionDetailInFaults = true)]
public class DataService : DataService<CustomDataContext>
{
// This method is called only once to initialize service-wide policies.
public static void InitializeService(IDataServiceConfiguration config)
{
try
{
config.SetEntitySetAccessRule("ProductModels", EntitySetRights.AllRead);
config.SetEntitySetAccessRule("Products", EntitySetRights.AllRead);
}
catch (Exception ex)
{
}
}
protected override void OnStartProcessingRequest(ProcessRequestArgs args)
{
base.OnStartProcessingRequest(args);
}
protected override void HandleException(HandleExceptionArgs e)
{
try
{
e.UseVerboseErrors = true;
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
}
}
The code can be downloaded here. The zip file contains Client application, Server application and the database.
You need SQL EXPRESS 2005 and Visual Studio 2008 SP1 to run the code. The zip
file also includes the scripts of the database too.