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
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
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:Query Metadata
So far so good, so lets query the metadata from the service http://localhost:8080/EntityDataservice/$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: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.
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
For each known resource type the ReflectionServiceProvider.PopulateMetadataForDerivedTypes get the assembly of the known type being evaluated
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.
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