Tuesday 21 February 2012

WCF Data Services Reflection Provider with Derived Types

Recently while doing some POC investigative development work, on the potential use of WCF Data Services aka the Microsoft implementation of OData feeds, I discovered some behaviour while testing out the Reflection Provider that I was unable to explain or find an answer too online. So I decided to do some more digging to figure out what was causing this unexpected behaviour.

Background


WCF Data Services supports multiple Data Provider models for exposing data as a Open Data Protocol (OData) feed, these are:
  • Entity Framework Provider
  • Reflection Provider
  • Custom Data Service Provider
I was specifically interested in delving into the capabilities of the Reflection Provider. The reason being the data that I wanted to expose, as an OData feed, a data model based on an existing set of data classes, this rules out the use of the Entity Framework provider as a mechanism to expose the data. This left the oprions of the Reflection Provider or rolling my own Custom Data Service Provider as options for exposing the data.

My used case is actually more complicated than simply exposing a set of POCO as an OData feed. So I was prepared for my investigation to lead me to the conclusion that I would have to roll my own Custom Data Service Provider. Something instinct was telling me I would need to do anyway. But it was worth the effort to spend some time up front figuring out if the Reflection Provider would be a suitable candidate implementation. As it would mean less LOC for the final implementation.

The Behaviour


The behaviour that I discovered while building and testing the POC was in relation to exposing types that are derived from a base type which is a key requirement for the code that will need to be implemented. Specifically the “problem” behaviour is the Reflection Provider is unable to expose the metadata and data for a types derived from a base type,where the derived type is not located in the same assembly as the base type. To clarify this point further I have put together a simple demo application that demonstrates the “problem”.

Simple Object Graph


SimpleHierarchy

The above class diagram shows a simple hierarchy of types. BaseEntityTypeA and BaseEntityTypeB are identical in terms of definition as are the derived types of DerivedTypeA and DerivedTypeB. What is not shown is that DerivedTypeB is defined in a different assembly from that of the rest of the types.

Simple Data Service

The types are exposed from the following simple data service.
[System.ServiceModel.ServiceBehavior(IncludeExceptionDetailInFaults = true)]
public class EntityDataservice : DataService<EntityDataItems>
{
    // This method is called only once to initialize
    //service-wide policies.
    public static void InitializeService(IDataServiceConfiguration config)
    {
        config.SetEntitySetAccessRule("*", EntitySetRights.All);
        config.UseVerboseErrors = true;
    }
}

public class EntityDataItems
{
    private static List<DerivedTypeA> _derivedA;
    private static List<DerivedTypeB> _derivedB;

    static EntityDataItems()
    {
        _derivedA = new List<DerivedTypeA>();
        _derivedB = new List<DerivedTypeB>();

        for (int i = 0; i < 10; i++)
        {
            _derivedA.Add(new DerivedTypeA() { Id = i, Name = i.ToString() });
            _derivedB.Add(new DerivedTypeB() { Id = i, Name = i.ToString() });
        }
    }

    public IQueryable<BaseEntityTypeA> DerivedA
    {
        get { return _derivedA.AsQueryable<DerivedTypeA>(); }
    }

    public IQueryable<BaseEntityTypeB> DerivedB
    {
        get { return _derivedB.AsQueryable<DerivedTypeB>(); }
    }
}

Query Endpoint

When the service is fired up and we browse to the base address of the service, http://localhost:8080/EntityDataservice, we see the following:

Atom

Query Metadata


So far so good, so lets query the metadata from the service http://localhost:8080/EntityDataservice/$metadata

metadata

So its at this point that we notice that there is something not quite right with the service. The definition of DerivedTypeA is exposed in the metadata feed but there is no definition of DerivedTypeB exposed from the service.

<EntityType Name="DerivedTypeA" BaseType="BaseEntityTypes.BaseEntityTypeA">
    <Property Name="Name" Type="Edm.String" Nullable="true" />
</EntityType>

Query DerivedTypeA Data


When we query the DerivedTypeA data feed from http://localhost:8080/EntityDataservice/DerivedA we see the following snippet of data:

<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<feed xml:base="http://localhost:8080/EntityDataservice/" xmlns:d="http://schemas.microsoft.com/ado/2007/08/dataservices" xmlns:m="http://schemas.microsoft.com/ado/2007/08/dataservices/metadata" xmlns="http://www.w3.org/2005/Atom">
  <title type="text">DerivedA</title>
  <id>http://localhost:8080/EntityDataservice/DerivedA</id>
  <updated>2012-02-20T15:39:43Z</updated>
  <link rel="self" title="DerivedA" href="DerivedA" />
  <entry>
    <id>http://localhost:8080/EntityDataservice/DerivedA(0)</id>
    <title type="text"></title>
    <updated>2012-02-20T15:39:43Z</updated>
    <author>
      <name />
    </author>
    <link rel="edit" title="BaseEntityTypeA" href="DerivedA(0)" />
    <category term="BaseEntityTypes.DerivedTypeA" scheme="http://schemas.microsoft.com/ado/2007/08/dataservices/scheme" />
    <content type="application/xml">
      <m:properties>
        <d:Id m:type="Edm.Int32">0</d:Id>
        <d:Name>0</d:Name>
      </m:properties>
    </content>
  </entry>
  <entry>
    <id>http://localhost:8080/EntityDataservice/DerivedA(1)</id>
    <title type="text"></title>
    <updated>2012-02-20T15:39:43Z</updated>
    <author>
      <name />
    </author>
    <link rel="edit" title="BaseEntityTypeA" href="DerivedA(1)" />
    <category term="BaseEntityTypes.DerivedTypeA" scheme="http://schemas.microsoft.com/ado/2007/08/dataservices/scheme" />
    <content type="application/xml">
      <m:properties>
        <d:Id m:type="Edm.Int32">1</d:Id>
        <d:Name>1</d:Name>
      </m:properties>
    </content>
  </entry>
</feed>

Query DerivedTypeB Data

When we query the DerivedTypeA data feed from http://localhost:8080/EntityDataservice/DerivedB we see the following in the browser:

Dead

The response data from the service shows the following
<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<feed xml:base="http://localhost:8080/EntityDataservice/" xmlns:d="http://schemas.microsoft.com/ado/2007/08/dataservices" xmlns:m="http://schemas.microsoft.com/ado/2007/08/dataservices/metadata" xmlns="http://www.w3.org/2005/Atom">
  <title type="text">DerivedB</title>
  <id>http://localhost:8080/EntityDataservice/DerivedB</id>
  <updated>2012-02-20T15:40:51Z</updated>
  <link rel="self" title="DerivedB" href="DerivedB" />
  <m:error>
    <m:code></m:code>
    <m:message xml:lang="en-IE">Internal Server Error. The type 'DerivedEntityTypes.DerivedTypeB' is not a complex type or an entity type.</m:message>
  </m:error>

Debugging

So what specifically the problem with the Reflection Provider, to answer this question its time to step into the .net framework. From reflecting on the DataService<T> code using ILSpy I found the method CreateProvider() which conditionally instantiates the ReflectionServiceProvider.

CreateProvider

The method that we are interested in is highlighted in the above image dataProviderInstance.PopulateMetadata(); It is this method of the ReflectionServiceProvider instance that generates the metadata for the types exposed by the Data Service.

Drilling down to into this method we find a call to PopulateMetadataForDerivedTypes it is this method that populates the metadata for the types derived from the base entity types.

Debugging a little further we see that the ReflectionServiceProvider.PopulateMetadataForDerivedTypes iterates over the collection of known resource types, in the test case this is BaseEntityTypeA and BaseEntityTypeB

knowntypes


For each known resource type the ReflectionServiceProvider.PopulateMetadataForDerivedTypes get the assembly of the known type being evaluated

Assembly

For each type in the assembly the method checks if the type can be assigned to the root known type. If so it is added to the collection of derived types that are further processed to generate the metadata.

FindDerivedInAssembly

You can clearly see that this does not support having derived types in a different assembly.

Conclusion


In my opinion this seems like a reasonable strategy to employ as to do other wise would require reflecting over every non CLR or .net framework in the current app domain. It would be nice to see this behaviour of the ReflectionServiceProvider documented.

In general this confirmed my initial suspicion that the ReflectionServiceProvider  would have some restrictive limitations for what I intend on implementing. I think it is better for most non EF4 based implementations of a WCF Data Service to skip the ReflectionServiceProvider and jump straight to a Custom Data Service Provider. Hopefully my findings will save someone a bit of time when using the ReflectionServiceProvider in the future.


The code is available here: GitHub ReflectionProviderTest

No comments:

Post a Comment