I've been trying to find a solution for custom serializing an entity returned from an OData controller for the past 2 months! Please help!!!
The use case is pretty simple and I have simplified it even more to get to problematic point. I have some virtual fields attached to some entities in my model, for ex:
public class Customer
{
public int CustomerId { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string VirtualField1 { get; set; }
public string VirtualField2 { get; set; }
public string VirtualField3 { get; set; }
}
Now, say the client has configured VirtualField1 to be "CompanyName".
All I want to do is create a custom JSON serializer and deserializer that:
- Any GET request for a Customer (and of course Customers - that is - IQueryable<>) will go through this serializer which will replace the name of the field "VirtualField1" with "CompanyName" in case of a collection for each Customer.
- Any POST request will go through the opposite replacement - that is - replacing the "CompanyName" with "VirtualField1".
** The actual replacement logic is a bit more complex but the idea is the same.
I've read everything google could have found, but couldn't found any working example.
Here are some links:
https://aspnetwebstack.codeplex.com/wikipage?title=OData%20formatter%20extensibility
** The current OData API is a bit different now, but I figured the principles are the same.
customizing odata output from asp.net web api
Using OData in webapi for properties known only at runtime
The common for all links (and any information I've found for that matter) is I have to inherit from DefaultODataSerializerProvider and add it to my formatters:
On WebApiConfig.cs:
var customFormatters = ODataMediaTypeFormatters.Create(new CustomODataSerilizerProvider(), new CustomODataDeSerilizerProvider());
config.Formatters.InsertRange(0, customFormatters);
and the actual provider and serializer:
public class CustomODataSerilizerProvider : DefaultODataSerializerProvider
{
public override ODataEdmTypeSerializer GetEdmTypeSerializer(IEdmTypeReference edmType)
{
if (edmType.IsEntity())
{
return new CustomODataEntityTypeSerializer(edmType.AsEntity(), this);
}
return base.GetEdmTypeSerializer(edmType);
}
}
** edmType.IsEntity() is never true for IQueryable results so it never creates the concrete serializer. If I force creation it still doesn't break on CreateEntity (or any other create method for that matter).
public class CustomODataEntityTypeSerializer : ODataEntityTypeSerializer
{
public CustomODataEntityTypeSerializer(IEdmEntityTypeReference entityType, ODataSerializerProvider serializerProvider)
: base(serializerProvider)
{
}
public override ODataEntry CreateEntry(SelectExpandNode selectExpandNode, EntityInstanceContext entityInstanceContext)
{
var oDataEntry = base.CreateEntry(selectExpandNode, entityInstanceContext);
return oDataEntry;
}
}
If I change the concrete serializer to inherit from ODataCollectionSerializer:
public override ODataEdmTypeSerializer GetEdmTypeSerializer(IEdmTypeReference edmType)
{
if (edmType.IsCollection())
{
return new CollectionSerilizer(this);
}
return base.GetEdmTypeSerializer(edmType);
}
and
public class CollectionSerilizer : ODataCollectionSerializer
{
public CollectionSerilizer(ODataSerializerProvider serializerProvider) : base(serializerProvider)
{
}
public override ODataCollectionValue CreateODataCollectionValue(IEnumerable enumerable, IEdmTypeReference elementType,
ODataSerializerContext writeContext)
{
var oDataCollectionValue = base.CreateODataCollectionValue(enumerable, elementType, writeContext);
return oDataCollectionValue;
}
public override void WriteObject(object graph, Type type, ODataMessageWriter messageWriter, ODataSerializerContext writeContext)
{
base.WriteObject(graph, type, messageWriter, writeContext);
}
}
It does stop on breakpoint on WriteObject but doesn't work and the base is throwing:
"The type 'Models.Customer' specified as the collection's item type is not primitive or complex. An ODataCollectionWriter can only write collections of primitive or complex values."
Another funny thing is even if I insert the supposedly default providers:
var customFormatters = ODataMediaTypeFormatters.Create(new DefaultODataSerializerProvider(), new DefaultODataDeserializerProvider());
config.Formatters.InsertRange(0, customFormatters);
regardless of their position, that is either at the beginning:
config.Formatters.InsertRange(0, customFormatters);
or at the end:
config.Formatters.AddRange(customFormatters);
The OData functionality - that is - for example: $exapnd as in /odata/Customers?$expand=Images completely disappears and doesn't work at all (here is the response):
[{"Images":[],"CustomerId":1,"FirstName":"Bla","LastName":"Bla", "VirtualField1":null]
the images in this instance are not expanded although without adding the custom formatters they do.
Any thoughts, ideas, directions???