Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

JSON.NET as a WebAPI 2 OData serializer vs ODataMediaTypeFormatter

I'm trying to use JSON.NET as a default serializer in WebAPI 2 stack. I've implemented JsonMediaTypeFormatter, in which I've used JSON.NET serializer for serialize/deserialize data and created JsonContentNegotiator for using this media type formatter. All works fine except OData querying - if I add [Queryable] metadata ot action method, then response object doesn't contains any metadata information, only list of entities.

Small example. My action method:

[Queryable]
public async Task<PageResult<RuleType>> GetRuleType(ODataQueryOptions<RuleType> options)
{
    var ret = await _service.ListRuleTypesAsync(options);
    return new PageResult<RuleType>(
        ret,
        Request.GetNextPageLink(),
        Request.GetInlineCount());
}

If I use default OData serialize and call some query by Rule type (for example - .../odata/RuleType?$inlinecount=allpages&$skip=0&$top=1), I receive classic OData response with metadata info and count property:

odata.metadata ".../odata/$metadata#RuleType" 
odata.count    "2" 
value
        0    {
                 Id: 1
             Name: "General"
             Code: "General"
             Notes: null
             }

(some fields skipped, but I have Notes property with null value) But if I add my JsonContentNegotiator with JsonMediaTypeFormatter as a serializer - I receive only list of entities:

[
  {
    "Id": 1,
    "Name": "General",
    "Code": "General"
  }
]

(no Notes field here because of NullValueHandling.Ignore) Even more. If I remove [Queryable] attribute in action method - I receive another result:

{
  "Items": [
    {
      "Id": 1,
      "Name": "General",
      "Code": "General"
    }
  ],
  "Count": 2
}

In this case I've received Count, but still no metadata here. And also odata response property names completely differs from default.

My mind is blowing up. I just want to use JSON.NET as my serializer in any part of my web app (because of some strong restrictions). How can I do this?

like image 317
Dmytro Rudenko Avatar asked Dec 18 '13 10:12

Dmytro Rudenko


1 Answers

I've already figured out my problem and found the solution. OData uses separate media type formatters, inherited from ODataMediaTypeFormatter. Also OData uses different formatters for serialization and deserialization. For replacing this behavior we have to implement descendants of ODataDeserializerProvider and/or ODataSerializerProvider classes and add those classes to the HttpConfiguration.Formatters collections by

var odataFormatters = ODataMediaTypeFormatters
    .Create(new MyODataSerializerProvider(), new MuODataDeserializerProvider());
config.Formatters.AddRange(odataFormatters);

Small deserialization provider example:

public class JsonODataDeserializerProvider : ODataDeserializerProvider
{
    public override ODataEdmTypeDeserializer GetEdmTypeDeserializer(IEdmTypeReference edmType)
    {
        var kind = GetODataPayloadKind(edmType);

        return new JsonODataEdmTypeDeserializer(kind, this);
    }

    private static ODataPayloadKind GetODataPayloadKind(IEdmTypeReference edmType)
    {
        switch (edmType.TypeKind())
        {
            case EdmTypeKind.Entity:
                return ODataPayloadKind.Entry;
            case EdmTypeKind.Primitive:
            case EdmTypeKind.Complex:
                return ODataPayloadKind.Property;
            case EdmTypeKind.Collection:
                IEdmCollectionTypeReference collectionType = edmType.AsCollection();
                return collectionType.ElementType().IsEntity() ? ODataPayloadKind.Feed : ODataPayloadKind.Collection;
            default:
                return ODataPayloadKind.Entry;
        }
    }

    public override ODataDeserializer GetODataDeserializer(IEdmModel model, Type type, HttpRequestMessage request)
    {
        var edmType = model.GetEdmTypeReference(type);

        return edmType == null ? null : GetEdmTypeDeserializer(edmType);
    }
}

ODataDeserializer:

public class JsonODataEdmTypeDeserializer : ODataEdmTypeDeserializer
{
    public JsonODataEdmTypeDeserializer(ODataPayloadKind payloadKind) : base(payloadKind)
    {
    }

    public JsonODataEdmTypeDeserializer(ODataPayloadKind payloadKind, ODataDeserializerProvider deserializerProvider) : base(payloadKind, deserializerProvider)
    {
    }

    public override object Read(ODataMessageReader messageReader, Type type, ODataDeserializerContext readContext)
    {
        var data = readContext.Request.Content.ReadAsStringAsync().Result;

        return JsonConvert.DeserializeObject(data, type);
    }
}

And I also have added EdmLibsHelper class from WebAPI OData source code in my project with GetEdmTypeReference() and GetEdmType() methods because this class is internal.

like image 72
Dmytro Rudenko Avatar answered Oct 05 '22 19:10

Dmytro Rudenko