Creating a Data Service Provider – Part 3 – IDataServiceMetadataProvider


UPDATE: I’ve made a few updates to the code / write-up to reflect refactors I’ve made as I’ve implemented more of the interfaces.

This is Part 3 of my ongoing series on Creating a Data Service Provider, and in this post we’ll look at how to implement IDataServiceMetadataProvider

IDataServiceMetadataProvider is responsible for describing the shape of the Resources and ResourceSets available from your DataSource (see Part 2).

And what is available from your DataSource is really up to you:

  • It could be completely static. For example if you are exposing data from an off the shelve software package or something where the model is fixed – perhaps Twitter, Stackoverflow or something similar.
  • It could be strongly typed. For example your implementation might reflect over the Properties of the DataSource itself and create ResourceSets for each property that returns IQueryable<T> and ResourceTypes for all Ts. This is essentially the way the built-in Reflection and Entity Framework providers work.
  • It could be completely dynamic. For example you could go off to some database somewhere and read a catalog.

Your options are almost endless.

However whichever option you chose the first step is to create a class that implements the interface.

public class DSPMetadata: IDataServiceMetadataProvider
{

The interface has 6 methods and 5 properties, which sounds terribly complicated until you actually try to implement it. I got my first example working in less than half an hour, and I’m not even a developer anymore!

Here is how I’ve hooked it together:

public abstract class DSPDataService<T>: DataService<T>

   IDataServiceMetadataProvider _metadata;
   
   public DSPMetadata()
   {
      _metadata = GetMetadataProvider(typeof(T));
   }
   
   public object GetService(Type serviceType)
   {
      if (serviceType == typeof(IDataServiceMetadataProvider))
      {
         return _metadata;
      }
      …    
   } 

   public abstract IDataServiceMetadataProvider GetMetadataProvider(Type dataSourceType);
}

As you can see this code has an abstract method to get the actual metadata, which subclasses must implement.

We are almost ready to implement the interface, but first lets learn about…

Creating Metadata

If you look at the methods on the interface you will see ResourceTypes, ResourceProperties, ServiceOperations, ResourceSets and ResourceAssociationSets.

These are the public classes in the System.Data.Services assembly that you can construct and manipulate yourself to describe your model.

Essentially these methods just walk over this metadata, exposing different information about the model.

Imagine if you have this CLR class:

public class Product
{
   public int ProdKey {get;set;}
   public string Name {get;set;}
   public Decimal Price {get;set;}
   public Decimal Cost {get;set;}

}

And you want to create a ResourceType for Product, this is how you would do it:

var productType = new ResourceType(
    typeof(Product), // CLR type backing this Resource
    ResourceTypeKind.EntityType, // Entity, ComplexType etc
    null, // BaseType
    “Namespace”, // Namespace
    “Product”, // Name
    false // Abstract?
);
var prodKey = new ResourceProperty(
   “ProdKey”,
   ResourcePropertyKind.Key |
   ResourcePropertyKind.Primitive,
   ResourceType.GetPrimitiveResourceType(typeof(int))
);
var prodName = new ResourceProperty(
   “Name”, 
   ResourcePropertyKind.Primitive,
   ResourceType.GetPrimitiveResourceType(typeof(string))
);
var prodPrice = new ResourceProperty(
   “Price”, 
   ResourcePropertyKind.Primitive,
   ResourceType.GetPrimitiveResourceType(typeof(Decimal))
);
productType.AddProperty(prodKey);
productType.AddProperty(prodName);
productType.AddProperty(prodPrice);

Notice that we aren’t exposing the Cost property, because that is ‘sensitive’ information.

Next to create a ResourceSet called Products to expose our ResourceType, you simply do this:

var productsSet = new ResourceSet(“Products”, productType);

As you can see building metadata is not hard.

Freezing your metadata

The final step before you can use the Metadata is to freeze it:

productsSet.SetReadonly();
productType.SetReadonly();

This means it can no longer be modified, at least not for the current Request.

NOTE: Data Services will ask for a IDataServiceMetadataProvider implementation for every request so even though you need to Freeze your metadata per-request, it is completely possible to reconstruct the metadata for each request if necessary.

Exposing Metadata via IDataServiceMetadataProvider

Once you’ve built your metadata you just need to expose it via your implementation.

Here is what your implementation might look like if you didn’t support inheritance, relationships or service operations.

public class DSPMetadataProvider

    private Dictionary<string, ResourceType> _resourceTypes 
       = new Dictionary<string, ResourceType>();
    private Dictionary<string, ResourceSet> _resourceSets
       = new Dictionary<string, ResourceSet>();

    public DSPMetadataProvider(){} 

    public void AddResourceType(ResourceType type)
    {
       type.SetReadOnly();
       _resourceTypes.Add(type.FullName, type);
    }

    public void AddResourceSet(ResourceSet set)
    {
       set.SetReadOnly();
       _resourceSets.Add(set.Name, set);
    }

    public string ContainerName
    {
        get { return “Container”; }
    } 

    public string ContainerNamespace
    {
        get { return “Namespace”; }
    }

    public IEnumerable<ResourceType> GetDerivedTypes(
        ResourceType resourceType
    )
    {
        // We don’t support type inheritance yet
        yield break;
    } 

    public ResourceAssociationSet GetResourceAssociationSet(
        ResourceSet resourceSet,
        ResourceType resourceType,
        ResourceProperty resourceProperty)
    {
        throw new NotImplementedException(“No relationships.”);
    } 

    public bool HasDerivedTypes(ResourceType resourceType)
    {
        // We don’t support inheritance yet
        return false;
    }

    public IEnumerable<ResourceSet> ResourceSets
    {
        get { return this.resourceSets.Values; }
    } 

    public IEnumerable<ServiceOperation> ServiceOperations
    {
        // No service operations yet
        get { yield break; }
    } 

    public bool TryResolveResourceSet(
        string name,
        out ResourceSet resourceSet)
    {
        return resourceSets.TryGetValue(name, out resourceSet);
    } 

    public bool TryResolveResourceType(
        string name, 
        out ResourceType resourceType)
    {
        return resourceTypes.TryGetValue(name, out resourceType);
    }

    public bool TryResolveServiceOperation(
        string name, 
        out ServiceOperation serviceOperation)
    {
        // No service operations are supported yet
        serviceOperation = null;
        return false;
    } 

    public IEnumerable<ResourceType> Types
    {
        get { return this.resourceTypes.Values; }
    }
}

The idea here is that your implementation of GetMetadataProvider you do something like this:

public override IDataServiceMetadataProvider GetMetadataProvider(Type dataSourceType)
{
    DSPMetadataProvider metadata = new DSPMetadataProvider();
    var productType = new ResourceType(
        typeof(Product), // CLR type backing this Resource
        ResourceTypeKind.EntityType, // Entity, ComplexType etc
        null, // BaseType
        “Namespace”, // Namespace
        “Product”, // Name
        false // Abstract?
    );
    var prodKey = new ResourceProperty(
       “ProdKey”,
       ResourcePropertyKind.Key |
       ResourcePropertyKind.Primitive,
       ResourceType.GetPrimitiveResourceType(typeof(int))
    );
    var prodName = new ResourceProperty(
       “Name”,
       ResourcePropertyKind.Primitive,
       ResourceType.GetPrimitiveResourceType(typeof(string))
    );
    var prodPrice = new ResourceProperty(
       “Price”,
       ResourcePropertyKind.Primitive,
       ResourceType.GetPrimitiveResourceType(typeof(Decimal))
    );
    productType.AddProperty(prodKey);
    productType.AddProperty(prodName);
    productType.AddProperty(prodPrice);

    metadata.AddResourceType(productType);
    metadata.AddResourceSet(
       new ResourceSet(“Products”, productType)
    );
    return metadata;
}

Notice that as you add ResourceTypes and ResourceSets to the metadata they are indexed and frozen (SetReadOnly) for you.

Of course as you add support for inheritance, relationships and service operations, things get a little bit more complicated. But you can handle each of these methods using LINQ to Objects pretty easily.

Performance Considerations

You might have noticed that there are two kinds of functions/properties on this interface, those that return enumerations and those that try to find a specific instance.

This is for performance reasons.

Most requests to the server use the TryResolveXXX instance methods. This means that even if you have a lot of metadata you can often avoid creating most of it.

The other methods are called much more sparingly: for example when someone gets $metadata or the root service document.

Next time

Now we’ve implemented hooked up our DataService to our Custom DSP, and we’ve implemented IDataServiceMetadataProvider, all we need to do to finish our custom read-only DSP is implement IDataServiceQueryProvider.

We’ll start to cover that in Part 4.

Comments (10)

  1. Anonymous says:

    Thank you for the series!

    The ResourceType/Property/Set/AssociationSet etc. essentially represent [the implementation of] the metamodel for Data Services. This is obviously a critically important piece. Is there a good description of this metamodel anywhere? Does it have a name? What exactly is the relationship of this metamodel to EDM? Differences if any? odata.org talks about metadata in terms of CSDL/EDMX …

  2. Alex D James says:

    Astoria / Data Services expose an EDM model. So one way to think about things is that when you are using these classes you are using a DSL to construct an CSDL/EDMX file.

    There are a few differences but these are minor.

  3. Anonymous says:

    how to implement dynamic datasource?

  4. Anonymous says:

    can you give me a demo!thank you very much!

  5. Anonymous says:

    Hi, awesome post, I have tried this and really liked it. Can you control/extend the way in which the metadata is published? Say I want to add a FAQ link to each ResourseSet in the metadata for my apps to consume, for instance, what are the ways to include further metadata to the resourceSets, etc?

  6. Anonymous says:

    Nice Post.

    How can I define Enum in metadata?

  7. Anonymous says:

    Nice Post. I want to create a resourceType which contains an Enum. Can you please guide me on this?

  8. FrankXP says:

    is there a way to add feed customization to the metadata provider?

    Thanks

    Frank

  9. David Burg says:

    In the sample code:

    public abstract class DSPDataService<T>: DataService<T>

    {  

      IDataServiceMetadataProvider _metadata;

      public DSPMetadata()

      {

         _metadata = GetMetadataProvider(typeof(T));

      }

      public object GetService(Type serviceType)

      {

         if (serviceType == typeof(IDataServiceMetadataProvider))

         {

            return _metadata;

         }

         …    

      }  

      public abstract IDataServiceMetadataProvider GetMetadataProvider(Type dataSourceType);

    }

    Isn't public DSPMetadata() supposed to be the cstor for DSPDataService? The cstor method name should be DSPDataService if that is correct. If that is not correct, then the method DSPMetadata needs to return a type or void.