When using WCF Data Services Client to communicate with my OData API, I occasionally need to use the AddRelatedObject method of System.Data.Services.Client.DataServiceContext
This generates a POST
to the endpoint that looks like:
http://base_url/odata/Entity(key)/NavigationProperty
The content of the http request is the serialized NavigationProperty
entity.
Since a post to this URL is unmapped in a default odata controller, I wrote a new EntitySetRoutingConvention class:
Public Class AddRelatedObjectRoutingConvention
Inherits EntitySetRoutingConvention
Public Overrides Function SelectAction(odataPath As Http.OData.Routing.ODataPath, controllerContext As Http.Controllers.HttpControllerContext, actionMap As ILookup(Of String, Http.Controllers.HttpActionDescriptor)) As String
If controllerContext.Request.Method = Net.Http.HttpMethod.Post Then
If odataPath.PathTemplate = "~/entityset/key/navigation" Then
If actionMap.Contains("AddRelation") Then
Return "AddRelation"
End If
End If
End If
Return Nothing
End Function
End Class
I added this to the default routing conventions list and passed it to the MapODATARoute
routine.
In my base controller I implemented AddRelation
, and it is called perfectly.
From this I am able to get the odatapath
, and parse it to determine the types and keys that are needed.
The problem I am having, is that once I know that my parent entity is a Tenant
with a key of 1
, and that the NavigationProperty
of Users
in the Tenant
entity is a User
entity, I can not figure out how to manually call an odatamediatypeformatter
to deserialize the http content into the User
entity so that I can then add it to the Tenants
Users
collection.
I have reviewed the OData source in an effort to find out how to the Web-API pipeline calls and deserializes the entity but have been unable to find the point at which the OData library takes the http content and turns it into an entity.
If anyone has an ideas to point me in the right direction, I will continue researching and update if I figure out more.
UPDATE 06/28/2013 Thanks to RaghuRam, I've been able to get closer. The problem I'm seeing is that the request.getconfiguration().formatters, or odatamediatypeformatters.create both seem to create type specific deserializers.
When I call ReadAsAsync I receive the error: No MediaTypeFormatter is available to read an object of type 'User' from content with media type 'application/atom+xml'.
As the post shows above, the context of the controller is a Tenant, so I believe the formatters are typed to a Tenant and so can't deserialize the User.
I tried manually creating a formatter, _childDefinition is the EDM Type Reference from the navigation property of the odatapath. In this case a User.
Dim dser As New Formatter.Deserialization.ODataEntityDeserializer(_childDefinition, New Formatter.Deserialization.DefaultODataDeserializerProvider)
Dim ser As New Formatter.Serialization.ODataEntityTypeSerializer(_childDefinition, New Formatter.Serialization.DefaultODataSerializerProvider)
Dim dprovider As New Formatter.Deserialization.DefaultODataDeserializerProvider
dprovider.SetEdmTypeDeserializer(_childDefinition, dser)
Dim sprovider As New Formatter.Serialization.DefaultODataSerializerProvider
sprovider.SetEdmTypeSerializer(_childDefinition, ser)
Dim fmt As New Formatter.ODataMediaTypeFormatter(dprovider, sprovider, New List(Of Microsoft.Data.OData.ODataPayloadKind) From {Microsoft.Data.OData.ODataPayloadKind.Entry})
fmt.SupportedEncodings.Add(System.Text.Encoding.UTF8)
fmt.SupportedEncodings.Add(System.Text.Encoding.Unicode)
fmt.SupportedMediaTypes.Add(Headers.MediaTypeHeaderValue.Parse("application/atom+xml;type=entry"))
fmt.SupportedMediaTypes.Add(New Headers.MediaTypeHeaderValue("application/atom+xml"))
I have then tried:
request.content.ReadAsAsync(of User)(new List(of odatamediatypeformatter) from {fmt})
request.content.ReadAsAsync(of User)(request.getConfiguration().formatters)
request.content.ReadAsAsync(of User)(odatamediatypeformatters.create)
All giving the same error, it seems like I'm missing something obvious.
Thanks!
Steve
Just add a parameter of type User to your AddRelation
action and you should be good. Web API would automatically invoke the OData formatter to read the User from the request body and bind it to your action parameter.
You can use this helper method to read OData request body,
private static Task<T> ReadODataContentAsAsync<T>(HttpRequestMessage request)
{
var formatters =
ODataMediaTypeFormatters.Create()
.Select(formatter => formatter.GetPerRequestFormatterInstance(typeof(T), request, request.Content.Headers.ContentType));
return request.Content.ReadAsAsync<T>(formatters);
}
like this,
Customer addedCustomer = ReadODataContentAsAsync<Customer>(Request).Result;