I've been busy doing my own customisations to a T4 template so that I can have strongly typed OData action invocation from a .NET client. It will probably be my first open source thing that I create :)
Anyway, I've testing and developing against the WebAPI OData sample for OData actions called "ODataActionsSample". For those who want to play along at home you can find the samples at http://aspnet.codeplex.com/SourceControl/latest#Samples/WebApi/ODataActionsSample/.
The sample has several more interesting actions. At the moment I'm trying to support the two collection-based actions since I have the others under control already. These two actions are the CheckOut
action (the override which accepts $filter queries) and the CheckOutMany
action (which accepts a collection of movie IDs).
Code from the sample...
// CheckOut action
// URI: ~/odata/Movies/CheckOut
// Shows how to bind to a collection, instead of a single entity.
// This action also accepts $filter queries. For example:
// ~/odata/Movies/CheckOut?$filter=Year eq 2005
var checkOutFromCollection = modelBuilder.Entity<Movie>().Collection.Action("CheckOut");
checkOutFromCollection.ReturnsCollectionFromEntitySet<Movie>("Movies");
// CheckOutMany action
// URI: ~/odata/Movies/CheckOutMany
// Shows an action that takes a collection parameter.
ActionConfiguration checkoutMany = modelBuilder.Entity<Movie>().Collection.Action("CheckOutMany");
checkoutMany.CollectionParameter<int>("MovieIDs");
checkoutMany.ReturnsCollectionFromEntitySet<Movie>("Movies");
The metadata generated for these is nearly identical. It is
<FunctionImport Name="CheckOut" ReturnType="Collection(ODataActionsSample.Models.Movie)" IsBindable="true" EntitySet="Movies" m:IsAlwaysBindable="true">
<Parameter Name="bindingParameter" Type="Collection(ODataActionsSample.Models.Movie)" Nullable="false" />
</FunctionImport>
<FunctionImport Name="CheckOutMany" ReturnType="Collection(ODataActionsSample.Models.Movie)" IsBindable="true" EntitySet="Movies" m:IsAlwaysBindable="true">
<Parameter Name="bindingParameter" Type="Collection(ODataActionsSample.Models.Movie)" Nullable="false" />
<Parameter Name="MovieIDs" Type="Collection(Edm.Int32)" Nullable="false" />
</FunctionImport>
As you can see in the comments from the sample, the URI to invoke each is quite different but there's not really any clues in the metadata which says that the CheckOut
action accepts what's basically an IQueryable versus the CheckOutMany
action which accepts a set of MovieIDs. Yes I see that the CheckOutMany
action has the extra parameter, but it shouldn't really have a BindingParameter entry in my opinion.
I'm thinking I could just have a heuristic to pick up this very case - where we accept a BindingParameter that's a collection but then also accept a collection that consists of primitives matching the entity of interest's single key type (in this case that's int
). Frankly that's a bit wobbly but it would at least work.
Direct questions (but comments on the above is very much appreciated!)
a) Is this the only way to go (ie, make a heuristic)?
b) Could we change the 'ActionConfiguration' setup such that the CheckOutMany
doesn't bind to the collection the same was as CheckOut
does?
c) Alternatively is this a design flaw, bug or just functionality that's not quite baked yet?
Your observation about
CheckOutMany
is correct. It doesn't make sense for CheckOutMany to bind to a an entity collection as it is already taking the movie id's it has to check out as an input.