I'm writing an OData V4 service with Web API 2 using the currently available OData NuGet packages.
I have an Entity Set of class Foo
like so:
class Foo {
string SomePropertyUnrelatedToThePost {get; set;}
...
IList<Bar> TheImportantPropertyList {get; set;}
}
Bar
doesn't have too much going on:
class Bar {
string Name {get; set;}
int? Group {get; set;}
object Value {get; set;}
}
In use, Bar#Value is never assigned anything other than basic values, but some are primitives and some are not: bool, byte, char, short, int, long, string, Decimal, DateTime...
I am registering the Foo set as the docs instruct, using an ODataConventionModelBuilder like so:
...
builder.EntitySet<Foo>("Foos");
and registering my Bar
as a complex type with builder.ComplexType<Bar>();
does not seem to change the outcome here.
The problem is that when I return a Foo object in my ODataController, the JSON response does not include Bar#Value.
{
...
"SomePropertyUnrelatedToThePost": "Foo was here",
...
"TheImportantPropertyList": [
{
"Name": "TheAnswer",
"Group": null
},
{
"Name": "TheQuestion",
"Group": null
}
]
}
Adding to my confusion is the fact that I can manually serialize a Foo in my controller method like so:
var settings = GlobalConfiguration.Configuration.Formatters.JsonFormatter.SerializerSettings;//.CreateJsonSerializer();
var s = JsonSerializer.Create(settings);
...
var json = Encoding.Default.GetString(...);
to produce a properly serialized result:
{
"SomePropertyUnrelatedToThePost": "Foo was here",
...
"TheImportantPropertyList": [
{
"Name": "TheAnswer",
"Value": 42,
"Group": null
},
{
"Name": "TheQuestion",
"Value": "What is the airspeed velocity of an unladen swallow?",
"Group": null
}
]
}
Am I configuring OData incorrectly? Do I have some other core misunderstanding? As I wrote this question it occurred to me that if I changed my model to include the System.Type of the assigned Value property, I could write a custom serializer, but it seems like it shouldn't have to come to that.
Edit: When I'm manually serializing my Foo
, I'm not using the default OData serializer, I'm using a new Newtonsoft JsonSerializer. The default OData serializer and deserializers simply do not like properties of type Object
.
I got this going. This post helped. Being new to OData, it took a while to get through the documentation, as most of it is out of date.
In my WebApiConfig.cs
, I used the new method of injecting an ODataSerializerProvider
into OData:
config.MapODataServiceRoute("odata", "api/v1", b =>
b.AddService(ServiceLifetime.Singleton, sp => builder.GetEdmModel())
.AddService<ODataSerializerProvider>(ServiceLifetime.Singleton, sp => new MySerializerProvider(sp)));
MySerializerProvider:
internal sealed class MySerializerProvider : DefaultODataSerializerProvider
{
private MySerializer _mySerializer;
public MySerializerProvider(IServiceProvider sp) : base(sp)
{
_mySerializer = new MySerializer(this);
}
public override ODataEdmTypeSerializer GetEdmTypeSerializer(IEdmTypeReference edmType)
{
var fullName = edmType.FullName();
if (fullName == "Namespace.Bar")
return _mySerializer;
else
return base.GetEdmTypeSerializer(edmType);
}
}
In my custom serializer, I noted OData will not automatically convert a DateTime
to a DateTimeOffset
. MySerializer:
internal sealed class MySerializer : ODataResourceSerializer
{
public MySerializer(ODataSerializerProvider sp) : base(sp) { }
public override ODataResource CreateResource(SelectExpandNode selectExpandNode, ResourceContext resourceContext)
{
ODataResource resource = base.CreateResource(selectExpandNode, resourceContext);
if (resource != null && resourceContext.ResourceInstance is Bar b)
resource = BarToResource(b);
return resource;
}
private ODataResource BarToResource(Bar b)
{
var odr = new ODataResource
{
Properties = new List<ODataProperty>
{
new ODataProperty
{
Name = "Name",
Value = b.Name
},
new ODataProperty
{
Name = "Value",
Value = b.Value is DateTime dt ? new DateTimeOffset(dt) : b.Value
},
new ODataProperty
{
Name = "Group",
Value = b.Group
},
}
};
return odr;
}
}
I realize this is a pretty specific question and answer but I hope someone finds it useful.