I am looking into using odata in the WebApi. So far so good, and i love that is more flexible then the wcf data service.
However when i try to use a virtual IQueryable property in a model i run into problems.
So for example i have these model classes:
public class MainItem
{
public int Id { get; set;}
public virtual IEnumerable<SubItem> SubItems { get; set;}
}
And my MainItemsController looks like this
public class MainItemsController : EntitySetController<MainItem, int>
{
[Queryable]
public override IQueryable<MainItem> Get()
{
return SomeMainItemIQueryable();
}
public override GetEntityByKey(int key)
{
return SingleMainItem(key);
}
[Queryable]
public IQueryable GetSubItems(int key)
{
return SomeSubItemIQueryable(SingleMainItem(key));
}
}
I get correct results on the following url's: /odata/MainItems /odata/MainItems(1) /odata/MainItems(1)/SubItems
But when i try to do /odata/MainItems(1)/SubItems(1)
I get this error This service doesn't support OData requests in the form '~/entityset/key/navigation/key'
I would love to redirect this call and also the /odata/MainItems(1)/SubItems to the SubItemsController.
I can probably do this by making a custom ODataPathHandler, but that doesn't feel like the correct way of doing this.
That's right. You do not need a custom path handler for that. It represents a valid OData URL that we do understand and can parse it into an ODataPath
. What you need would be a custom routing convention. A routing convention maps an ODataPath to a controller and an action. Be default, we ship only basic routing conventions that handle urls that WCF DS client generates. Looks like you are using an URL for which we do not have a routing convention. It is simple to write one though. Example,
public class ContainmentRoutingConvention : IODataRoutingConvention
{
public string SelectAction(ODataPath odataPath, HttpControllerContext controllerContext, ILookup<string, HttpActionDescriptor> actionMap)
{
IEdmEntitySet entitySet = odataPath.EntitySet;
if (odataPath.PathTemplate == "~/entityset/key/navigation")
{
controllerContext.RouteData.Values["key"] = (odataPath.Segments[1] as KeyValuePathSegment).Value;
return "Get" + entitySet.Name;
}
else if (odataPath.PathTemplate == "~/entityset/key/navigation/key")
{
controllerContext.RouteData.Values["key1"] = (odataPath.Segments[1] as KeyValuePathSegment).Value;
controllerContext.RouteData.Values["key2"] = (odataPath.Segments[3] as KeyValuePathSegment).Value;
return "Get" + entitySet.ElementType.Name;
}
return null;
}
public string SelectController(ODataPath odataPath, HttpRequestMessage request)
{
if (odataPath.PathTemplate == "~/entityset/key/navigation" ||
odataPath.PathTemplate == "~/entityset/key/navigation/key")
{
IEdmNavigationProperty navigationProperty = (odataPath.Segments[2] as NavigationPathSegment).NavigationProperty;
IEdmEntitySet entitySet = odataPath.EntitySet; // the target entity set, which would be 'SubItems';
return entitySet.Name;
}
return null;
}
}
This would handle only the two urls that you mentioned. It should be easy to extend to support other url's too.
The signature of the action that handles ~/entityset/key/navigation would be IEnumerable<Order> GetOrders(int key)
and for the url ~/entityset/key/navigation/key, it would be Order GetOrder(int key1, int key2)
.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With