9
votes

I'm having a really hard time migrating from WCF data service to web api odata v4. I'm stuck on the following issue:

odata web api doesn't seem to support @odata.bind.

I've found the following link: https://github.com/OData/WebApi/issues/158 There someone suggested to implement an own ODataEntityDeserializer derived class like so:

public class ExtendedODataEntityDeserializer : ODataEntityDeserializer
{
    public ExtendedODataEntityDeserializer(ODataDeserializerProvider deserializerProvider) : base(deserializerProvider)
    {
    }

    public override void ApplyNavigationProperty(
        object entityResource, 
        ODataNavigationLinkWithItems navigationLinkWrapper, 
        IEdmEntityTypeReference entityType, 
        ODataDeserializerContext readContext)
    {
        base.ApplyNavigationProperty(entityResource, navigationLinkWrapper, entityType, readContext);

        foreach (var childItem in navigationLinkWrapper.NestedItems)
        {
            var entityReferenceLink = childItem as ODataEntityReferenceLinkBase;

            if (entityReferenceLink != null)
            {
                var navigationPropertyName = navigationLinkWrapper.NavigationLink.Name;
                Uri referencedEntityUrl = entityReferenceLink.EntityReferenceLink.Url;

                if (!referencedEntityUrl.IsAbsoluteUri)
                {
                    referencedEntityUrl = new Uri(readContext.Request.RequestUri, referencedEntityUrl);
                }

                var linkedEntities = (Model.LinkedEntityCollection)entityResource;
                linkedEntities.Add(navigationPropertyName, referencedEntityUrl);
            }
        }
    }
}

LinkedEntityCollection is a base class for my entity classes that serves as dictionary (MyEntity : LinkedEntityCollection implementation is trivial).

public class ExtendedODataDeserializerProvider : ODataDeserializerProvider
{
    private static ExtendedODataDeserializerProvider _instance = null;

    private ExtendedODataDeserializerProvider()
    {
        _instance = this;
    }

    public static ExtendedODataDeserializerProvider Instance
    {
        get { return _instance ?? new ExtendedODataDeserializerProvider(); }
    }

    public override ODataEdmTypeDeserializer GetEdmTypeDeserializer(Microsoft.OData.Edm.IEdmTypeReference edmType)
    {
        return DefaultODataDeserializerProvider.Instance.GetEdmTypeDeserializer(edmType);
    }

    public override ODataDeserializer GetODataDeserializer(Microsoft.OData.Edm.IEdmModel model, Type type, System.Net.Http.HttpRequestMessage request)
    {
        return new ExtendedODataEntityDeserializer(DefaultODataDeserializerProvider.Instance);
    }
}

Registering in the http configuration:

public static void Register(HttpConfiguration config)
{
    ODataModelBuilder builder = new ODataConventionModelBuilder();

    // registering entities: builder.EntitySet<T>("EntityName") ...

    ODataBatchHandler batchHandler = new DefaultODataBatchHandler(new HttpServer(config));
    batchHandler.MessageQuotas.MaxOperationsPerChangeset = 10;
    batchHandler.MessageQuotas.MaxPartsPerBatch = 10;

    var odataFormatters = ODataMediaTypeFormatters.Create(DefaultODataSerializerProvider.Instance, ExtendedODataDeserializerProvider.Instance);

    config.Formatters.Clear();
    config.Formatters.AddRange(odataFormatters);

    config.MapODataServiceRoute(routeName: "Central", routePrefix: "Odata", model: builder.GetEdmModel(), batchHandler: batchHandler);
}

However if you do use this hack you loose the capability of creating links between enities like described in this aritcle: http://www.asp.net/web-api/overview/odata-support-in-aspnet-web-api/odata-v4/entity-relations-in-odata-v4

The "@odata.id" doesn't get parsed, it is assumed that it's an entity in the function ODataEntityDeserializer.Read. Maybe I'm missing somthing fundamentally here, since I'm fairly new to web api (config.Formatters.Clear() seems a bit harsh). Or is there any other workaround to support @odata.bind?

Is there any chance that this feature gets implemented in the near future?

Update

I got my hack working. The Problem was that I should have derrived ExtendedODataDeserializerProvider from DefaultODataDeserializerProvider instead of ODataDeserializerProvider and return the deserializers accordingly:

public override ODataDeserializer GetODataDeserializer(
     Microsoft.OData.Edm.IEdmModel model, 
     Type type,     
     System.Net.Http.HttpRequestMessage request)
{
    if (type == typeof(ODataActionParameters) || 
        type == typeof(ODataUntypedActionParameters) || 
        type == typeof(Uri))
    {
        return base.GetODataDeserializer(model, type, request);
    }

    return new ExtendedODataEntityDeserializer(DefaultODataDeserializerProvider.Instance);
 }

So the question that remains: when do we get @odata.bind support in asp.net webapi odata?

1
I'm in a situation where I have my custom serialier and want to register it. I am using config.Formatters.InsertRange(0,mycustomformatter). However, it doesn't work if I don't Clear() before that. My question to you: are you still clearing before you add your formatter or did you find an alternative way?Randa Sbeity
I'm still using it as described above (clear and then add formatters). The only change I've made since then is that I'm adding also the JsonMediaTypeFormatter and XmlMediaTypeFormatter to the Formatter collection. Since this works for me, I've stopped looking for an alternative solution.Jeldrik

1 Answers

1
votes

Trying to answer

So the question that remains: when do we get @odata.bind support in asp.net webapi odata?

There seems to be a feature relating to this in the vnext repo. However, the issue is marked as having low impact. https://github.com/OData/WebApi/milestones/vNext and hence there is no way to know when this feature will be implemented.