Book Image

OData Programming Cookbook for .NET Developers

By : Juntao Cheng
Book Image

OData Programming Cookbook for .NET Developers

By: Juntao Cheng

Overview of this book

Odata (Open Data Protocol) is a Web protocol for querying and updating data that provides a way to unlock your data and free it from silos that exist in applications today. OData enables data access among a variety of applications, services, and stores by adopting existing Web technologies such as HTTP, XML, and JSON. This book deals with common OData programming cases over the Microsoft .NET Framework platform and eases the learning curve for a .NET developer to start incorporating OData in data service development.This book provides a collection of recipes that help .NET developers to get familiar with OData programming in a quick and efficient manner. The recipes cover most OData features from the former ADO.NET Data Service to the current WCF Data Service platform. In addition, all the sample cases here are based on real-world scenarios and issues that .NET developers might come across when programming with OData in application development.This book will be your handy guide with basic to advanced walkthroughs of common OData programming cases for the Microsoft .NET Framework platform. You will learn quick solutions to necessary tasks to integrate the power of OData at both server-side and client-side.This book will help you master the use of OData with .NET Framework by taking you through hands-on and practical recipes. It starts by talking about the common means for building OData services and consuming OData services in client applications. Then, some more specific topics like hosting, configuration and security are discussed. The book also covers many popular and interesting topics such as integrating OData in web applications, and developing data-driven mobile applications with OData. Moreover, you can also find quite a few recipes discussing real-world OData producers and new features in latest and future versions.Within "OData Programming Cookbook for .NET Developers", all the recipes are selected based on real-world scenarios that you will commonly come across. Each recipe covers a specific topic, going from the description of the problem, through a conceptual solution, to a solution containing sample code. By following these recipes, you can acquire how to program with OData in a simple, effective, and easy manner.
Table of Contents (15 chapters)
OData Programming Cookbook for .NET Developers
Credits
About the Author
About the Reviewers
www.PacktPub.com
Preface

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...

  1. 1. Create a new ASP.NET Empty Web Application.

  2. 2. 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; }
    }
    
  3. 3. 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();
    }
    }
    
  4. 4. Create a metadata provider class implementing the IDataServiceMetadataProvider interface under System.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);
    }
    
  5. 5. Create a query provider class, which implements the IDataServiceQueryProvider interface under System.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.

  6. 6. Create the main service provider class, which derives from the DataService<T> base class (under System.Data.Services namespace) and implements the IServiceProvider 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;
    }
    
  7. 7. 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 the DirectoryFileDataContext 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.

  8. 8. 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.

Note

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