Search code examples
c#asp.netodata

OData Collection Parameter bindings ... do they actually work?


According to the docs here ...

http://www.asp.net/web-api/overview/odata-support-in-aspnet-web-api/odata-v3/odata-actions

...

Binding an Action to an Entity Set

In the previous example, the action is bound to a single entity: The client rates a single product. You can also bind an action to a collection of entities. Just make the following changes:

In the EDM, add the action to the entity's Collection property.

var rateAllProducts = builder.Entity().Collection.Action("RateAllProducts"); In the controller method, omit the key parameter.

[HttpPost] public int RateAllProducts(ODataActionParameters parameters) { // .... }

Why when I do this does it not work ...

Invoice Ref:

public class InvoiceReference
{
    public string InvoiceNumber { get; set; }
    public int SupplierId { get; set; }
}

Action setup:

var getByRefs = Builder.EntityType<SIHead>().Collection.Action("ByRefs");
getByRefs.CollectionParameter<InvoiceReference>("refs");
getByRefs.ReturnsCollectionFromEntitySet<SIHead>("SIHead");

Action method in the controller:

[HttpPost]
[EnableQuery]
[ODataRoute("ByRefs")]
public async Task<IHttpActionResult> ByRefs(ODataActionParameters p)
{
   var refs = p["refs"] as InvoiceReference[];
   // exception p is null
}

Example json content posted:

[
  {
    "InvoiceNumber": "5100011759|9800006622",
    "SupplierId": 2
  },
  {
    "InvoiceNumber": "5100012624|9800006635",
    "SupplierId": 2
  },
  {
    "InvoiceNumber": "5100012625|9800006636",
    "SupplierId": 2
  }
]

Seems to me that either I missed something or OData is broken.


Solution

  • After gettting some feedback from github (thanks Sam) I came to the conclusion that the way OData works means we must always post an object and never a collection directly ...

    I missed some subtle / implied rules here ...

    I have to provide an object (as a container) and not just the array I want to post. I can not bind directly to an ICollection, IList, List or Array only IEnumerable Out of curiosity: Why is this different to normal webAPI? The underlying binding framework in WebAPI's binding is awesome.

    I'm not sure this "oddity" was / is well documented, it looks like no matter what I am posting I should always provide an object and never a collection directly in the body.

    to post an array I therefore have to do ...

    { "foos": [1,2,3,4] }
    

    .. instead of doing ...

    [1,2,3,4]
    

    ... and then in the action always treat the posted collection as an Enumerable ...

    Task PostStuff(ODataActionParameters p)
    {
       var foos = p["foos"] as IEnumerable<Foo>;
       ...
    }
    

    ... I'm pretty sure this example is given somewhere but i'm pretty sure this requirement that the body always contain an object is not (i could be wrong). I guess this is to encourage people to build strongly typed request bodies (feels like a good call IMO).