Reputation: 2831
The OData questions keep coming :)
I have an Entity with a composite key, like this one:
public class Entity
{
public virtual Int32 FirstId { get; set; }
public virtual Guid SecondId { get; set; }
public virtual First First { get; set; }
public virtual Second Second { get; set; }
}
I created a CompositeKeyRoutingConvention that handles the composite keys for ODataController
s. Everything is working, except Navigation Links like this one:
http://localhost:51590/odata/Entities(FirstId=1,SecondId=guid'...')/First
I get the following error message in Firefox:
<?xml version="1.0" encoding="utf-8"?>
<m:error xmlns:m="http://schemas.microsoft.com/ado/2007/08/dataservices/metadata">
<m:code />
<m:message xml:lang="en-US">No HTTP resource was found that matches the request URI 'http://localhost:51950/odata/Entities(FirstId=1,SecondId=guid'a344b92f-55dc-45aa-b92f-271d74643493')/First'.</m:message>
<m:innererror>
<m:message>No action was found on the controller 'Entities' that matches the request.</m:message>
<m:type></m:type>
<m:stacktrace></m:stacktrace>
</m:innererror>
</m:error>
I traced the error message in the ASP.NET source code to the FindMatchingActions method in the ApiControllerActionSelector returning an empty list, but my knowledge of ASP.NET ends there.
For reference, this is the implementation of the navigation link action method (in an ODataController
):
public First GetFirst(
[FromODataUri(Name = "FirstId")] Int32 firstId,
[FromODataUri(Name = "SecondId")] Guid secondId)
{
var entity = repo.Find(firstId, secondId);
if (entity == null) throw new HttpResponseException(HttpStatusCode.NotFound);
return entity.First;
}
I tried not setting a name at the FromODataUri
attribute, setting a lowercase name, everything sensible I could think of. The only thing I noticed is when using a regular EntitySetController
is that the arguments for the key value have to be named key
(or the FromODataUri
attribute has to have the Name property set to key
), otherwise it won't work. I wonder if something like this is the case here as well...
Upvotes: 1
Views: 1682
Reputation: 2831
I found what was missing:
In addition to a custom EntityRoutingConvention
, you will need a custom NavigationRoutingConvention
.
type CompositeKeyNavigationRoutingConvention () =
inherit NavigationRoutingConvention ()
override this.SelectAction (odataPath, controllerContext, actionMap) =
match base.SelectAction (odataPath, controllerContext, actionMap) with
| null -> null
| action ->
let routeValues = controllerContext.RouteData.Values
match routeValues.TryGetValue ODataRouteConstants.Key with
| true, (:? String as keyRaw) ->
keyRaw.Split ','
|> Seq.iter (fun compoundKeyPair ->
match compoundKeyPair.Split ([| '=' |], 2) with
| [| keyName; keyValue |] ->
routeValues.Add (keyName.Trim (), keyValue.Trim ())
| _ -> ()
)
| _ -> ()
action
And just add this to the front of the conventions like the custom EntityRoutingConvention
. Done :)
Update for the comment below:
You have to implement your own NavigationRoutingConvention
that overrides the SelectAction
method and splits the composite keys inside the controller context into key and values. Then you have to add them to the route values yourself.
Finally, in the configuration, where you call MapODDataRoute
with your custom EntityRoutingConvention
already, add the new NavigationRoutingConvention
to the list of conventions.
NavigationRoutingConvention in C#:
public class CompositeKeyNavigationRoutingConvention : NavigationRoutingConvention
{
public override String SelectAction(System.Web.OData.Routing.ODataPath odataPath, HttpControllerContext controllerContext, ILookup<String, HttpActionDescriptor> actionMap)
{
String action = base.SelectAction(odataPath, controllerContext, actionMap);
// Only look for a composite key if an action could be selected.
if (action != null)
{
var routeValues = controllerContext.RouteData.Values;
// Try getting the OData key from the route values (looks like: "key1=value1,key2=value2,...").
Object keyRaw;
if (routeValues.TryGetValue(ODataRouteConstants.Key, out keyRaw))
{
// Split the composite key into key/value pairs (like: "key=value").
foreach (var compoundKeyPair in ((String)keyRaw).Split(','))
{
// Split the key/value pair into its components.
var compoundKeyArray = compoundKeyPair.Split(new[] { '=' }, 2);
if (compoundKeyArray.Length == 2)
// Add the key and value of the composite key to the route values.
routeValues.Add(compoundKeyArray[0].Trim(), compoundKeyArray[1].Trim());
}
}
}
return action;
}
}
Finally, you have to add it to the OData route (presumably in App_Start/WebApiConfig.cs
), where you already added the EntityRoutingConvention
.
Upvotes: 2