
Creating a custom WCF Data Service provider
So far we've explored various ways to build an OData service with .NET Framework platform including WCF Data Service with ADO.NET Entity Framework, LINQ to SQL, custom CLR objects, and WCF RIA service.
However, what if we want to expose some custom data through OData endpoints but none of the above means can help? Such conditions do exist, for example, we might have some data that is not of relational database structure, or the data object types are previously defined, which haven't applied those WCF Data Service specific attributes (necessary for using custom CLR objects based data source).
Don't worry, the WCF Data Service framework has already provided a powerful extension model, which can let you create a custom provider in order to expose arbitrary format custom data in a WCF Data Service. In this recipe, we will see how to create a custom WCF Data Service provider and use it to expose some custom data.
Getting ready
In this recipe, we will choose filesystem as an example and build a WCF Data Service, which exposes the information of all files within a given directory. Also, we will create several custom classes in order to implement the custom WCF Data Service provider. The following class diagram (generated via Visual Studio Architecture Modeling tools) can help you get an overview of these custom types and their dependency relationships:

The source code for this recipe can be found in the \ch01\FileDataServiceSln\
directory.
How to do it...
- Create a new ASP.NET Empty Web Application.
- Create the custom class that represents individual file objects (see the following
FileEntity
class definition).public class FileEntity { public int ID { get; set; } public string FileName { get; set; } public string Extension { get; set; } public DateTime Created { get; set; } public long Length { get; set; } }
- Create the data context class that represents the data source and entity sets container of the sample service.
The following code snippet shows the
DirectoryFileDataContext
class of the sample service:public class DirectoryFileDataContext { public DirectoryInfo DirInfo { get; set; } public List<FileEntity> Files { get; set; } public DirectoryFileDataContext(): this(Environment.CurrentDirectory) { } public DirectoryFileDataContext(string dirPath) { DirInfo = new DirectoryInfo(dirPath); int i=0; Files = (from fi in DirInfo.GetFiles() select new FileEntity { ID = ++i, FileName = fi.Name, Extension = fi.Extension, Created = fi.CreationTime, Length = fi.Length }).ToList(); } }
- Create a metadata provider class implementing the
IDataServiceMetadataProvider
interface underSystem.Data.Services.Providers
namespace.The following code snippet shows the overall definition of our metadata provider class in this sample:
public class DirectoryFileDataServiceMetadata: IDataServiceMetadataProvider { private string _containerName = ""; private string _namespace = ""; private Dictionary<string, ResourceSet> _resSets = null; private Dictionary<string, ResourceType> _resTypes = null; ...... #region IDataServiceMetadataProvider Members public string ContainerName { get { return _containerName; } } public string ContainerNamespace { get { return _namespace; } } ...... public IEnumerable<ResourceSet> ResourceSets { get { return _resSets.Values; } } ...... #endregion }
In the constructor of the metadata provider, we need to add code to register the resource types and resource sets mapping to the data entities we want to expose in the WCF Data Service.
public DirectoryFileDataServiceMetadata(DirectoryFileDataContext ctx) { _containerName = "DirectoryFiles"; _namespace = "http://odata.test.org/directoryfiles"; _resSets = new Dictionary<string, ResourceSet>(); _resTypes = new Dictionary<string, ResourceType>(); // Init ResourceType set var fileEntityType = typeof(FileEntity); var fileResourceType = new ResourceType( fileEntityType, ResourceTypeKind.EntityType, null, fileEntityType.Namespace, fileEntityType.Name, false ); AddPropertyToResourceType(fileResourceType, "ID", true); AddPropertyToResourceType(fileResourceType, "FileName", false); AddPropertyToResourceType(fileResourceType, "Extension", false); AddPropertyToResourceType(fileResourceType, "Created", false); AddPropertyToResourceType(fileResourceType, "Length", false); _resTypes.Add(fileResourceType.FullName, fileResourceType); // Init ResourceSet set var fileResourceSet = new ResourceSet ("Files", fileResourceType); _resSets.Add("Files", fileResourceSet); }
- Create a query provider class, which implements the
IDataServiceQueryProvider
interface underSystem.Data.Services.Providers
namespace.The following code snippet shows the main part of our sample
DirectoryFileDataServiceQueryProvider
class:public class DirectoryFileDataServiceQueryProvider: IDataServiceQueryProvider { private DirectoryFileDataContext _ctx = null; private DirectoryFileDataServiceMetadata _metadata = null; public DirectoryFileDataServiceQueryProvider (DirectoryFileDataContext ctx, DirectoryFileDataServiceMetadata metadata) { _ctx = ctx; _metadata = metadata; } #region IDataServiceQueryProvider Members ...... public IQueryable GetQueryRootForResourceSet (ResourceSet resourceSet) { // Our service provider only provides Files entity set return _ctx.Files.AsQueryable(); } public ResourceType GetResourceType(object target) { return this._metadata.Types.Single (rt => rt.InstanceType == target.GetType()); } .... #endregion }
In the previous code snippet, the
GetQueryRootForResourceSet
method is the one in which we return the entity set data based on the requested entity set type parameter. - Create the main service provider class, which derives from the
DataService<T>
base class (underSystem.Data.Services
namespace) and implements theIServiceProvider
interface.The following is the definition of the main provider class (see the following
DirectoryFileDataService
class) in this sample. It takes a generic parameter which is derived from the data context class we defined earlier.public abstract class DirectoryFileDataService<T> : DataService<T>, IServiceProvider where T : DirectoryFileDataContext { private DirectoryFileDataServiceMetadata _metaProvider = null; private DirectoryFileDataServiceQueryProvider _queryProvider = null; ...... #region IServiceProvider Members public object GetService(Type serviceType) { if (serviceType == typeof(IDataServiceMetadataProvider)) { if (_metaProvider == null) { InitServiceProviders(); } return _metaProvider; } else if (serviceType == typeof(IDataServiceQueryProvider)) { if (_queryProvider == null) { InitServiceProviders(); } return _queryProvider; } else { return null; } } #endregion }
To simplify the code logic, we will define a helper function to encapsulate the initialization code (see the following
InitServiceProviders
function).private void InitServiceProviders() { var dsObj = this.CreateDataSource(); // Create metadata provider _metaProvider = new DirectoryFileDataServiceMetadata(dsObj); // Set the resource types and resource sets as readonly foreach (var type in _metaProvider.Types) { type.SetReadOnly(); } foreach (var set in _metaProvider.ResourceSets) { set.SetReadOnly(); } // Create query provider _queryProvider = new DirectoryFileDataServiceQueryProvider (dsObj, _metaProvider); _queryProvider.CurrentDataSource = dsObj; }
- Create a new WCF Data Service based on the main service provider and the data context classes created in the previous steps.
The WCF Data Service class is derived from the
DirectoryFileDataService
class, which takes theDirectoryFileDataContext
class as the generic parameter (see the following code snippet).public class FileDataService : DirectoryFileDataService<DirectoryFileDataContext> { public static void InitializeService (DataServiceConfiguration config) { config.DataServiceBehavior.MaxProtocolVersion = DataServiceProtocolVersion.V2; config.DataServiceBehavior.AcceptProjectionRequests = true; config.SetEntitySetAccessRule ("*", EntitySetRights.AllRead); } protected override DirectoryFileDataContext CreateDataSource() { var dc = new DirectoryFileDataContext (@"C:\Users\Public\Pictures\Sample Pictures"); return dc; }
In addition, we need to override the
CreateDataSource
function of the service class and put the file directory initialization code there. You can specify any directory (avoid using Windows system directories for potential permission issues) on the local machine for testing purpose. - Launch the sample service in the web browser and query the
Files
entity set exposed in it.The following screenshot shows the default query result against the
Files
entity set:We can also add additional query options to filter the query result based on the public properties defined in the
FileEntity
class (see the following screenshot).
How it works...
In the previous steps, we created all the custom provider classes from bottom to top according to the class structure diagram shown earlier. Now, let's have a look at how they work together in a top-to-bottom approach.
The DirectoryFileDataService<T>
class is the top most type among all the custom provider classes. This class is derived from the DataService<T>
base class so that it can be directly used by WCF Data Service as service class. The DirectoryFileDataService<T>
class also implements the IServiceProvider
interface because it will be asked to provide certain implementations of various kind of custom service providers. In this case, we have implemented the metadata
provider (IDataServiceMetadataProvider
interface) and the query
provider (IDataServiceMetadataProvider interface), which are used for publishing service metadata and exposing entity sets. In addition, there are other providers used for implementing more advanced features, for example, the IDataServiceUpdateProvider
interface for implementing update functions, the IDataServicePagingProvider
interface for implementing paging functions, and the IDataServiceStreamsProvider
interface for implementing data streaming functions. The following diagram shows the calling pipeline from WCF Data Service class to custom service providers and the backend data source objects:

The DirectoryFileDataService
type uses instances of the DirectoryFileDataServiceQueryProvider
and DirectoryFileDataServiceMetadata
types to serve the metadata and query service requests. These two provider instances also use the DirectoryFileDataContext
type instance for retrieving the underlying data entity sets' type information and query root object.
Tip
Both the DirectoryFileDataServiceQueryProvider
and DirectoryFileDataServiceMetadata
classes have defined a parameter of the DirectoryFileDataContext
class in their constructors. This is a common pattern when implementing custom service providers. Because most of the providers will need to get type information (for the entity or entity sets they will handle) from the data context object, the class constructor is a good place for them to hold such an object reference.
Finally, we come to the FileEntity
class. You might think it is quite similar to the custom entity types we defined in the Using custom data objects as the data source of WCF Data Service recipe. The important difference is that we do not have to apply any additional attributes (such as the DataServiceKey
and DataServiceEntity
attributes) on the FileEntity
class (compared to those entity types used by a custom CLR objects based data source). In other words, by using a custom WCF Data Service provider, we can make use of existing predefined custom data types (whether they have applied those special attributes under System.Data.Services
namespace or not) as OData service entities.
There's more...
WCF Data Service providers have opened the door for developers to extend an OData service to their own data sources in a very flexible way. By using the provider-based model, we can control almost every aspect of WCF Data Service customization (such as the querying and updating processes). Whenever you want to customize a certain part of a WCF Data Service, just find the corresponding provider interface and implement it.
For more information about building custom WCF Data Service providers, you can refer to the following MSDN reference:
Custom Data Service Providers available at http://msdn.microsoft.com/en-us/data/gg191846
See also
- Using custom data objects as the data source of WCF Data Service recipe