【问题标题】:ASP.NET Web API OData: Navigation Links when using Composite KeysASP.NET Web API OData:使用复合键时的导航链接
【发布时间】:2014-11-14 19:34:00
【问题描述】:

OData 问题不断出现 :)

我有一个带有复合键的实体,比如这个:

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; }
}

我创建了一个CompositeKeyRoutingConvention 来处理ODataControllers 的复合键。一切正常,除了像这样的导航链接:

http://localhost:51590/odata/Entities(FirstId=1,SecondId=guid'...')/First

我在 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>

我将 ASP.NET 源代码中的错误消息追踪到 the FindMatchingActions method in the ApiControllerActionSelector 返回一个空列表,但我对 ASP.NET 的了解到此为止。

作为参考,这是导航链接动作方法的实现(在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;
}

我尝试不在FromODataUri 属性上设置名称,设置一个小写名称,我能想到的一切都是合理的。我唯一注意到的是,在使用常规 EntitySetController 时,键值的参数必须命名为 key(或 FromODataUri 属性必须将 Name 属性设置为 key),否则不会工作。我想知道这里是否也有类似的情况......

【问题讨论】:

    标签: asp.net-mvc asp.net-web-api odata


    【解决方案1】:

    我找到了丢失的东西:

    除了自定义EntityRoutingConvention,您还需要自定义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
    

    只需将其添加到自定义 EntityRoutingConvention 等约定的前面。完成:)


    以下评论更新:

    您必须实现自己的NavigationRoutingConvention,它会覆盖SelectAction 方法并将控制器上下文中的复合键拆分为键和值。然后你必须自己将它们添加到路由值中。

    最后,在配置中,您已经使用自定义EntityRoutingConvention 调用MapODDataRoute,将新的NavigationRoutingConvention 添加到约定列表中。

    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;
        }
    }
    

    最后,您必须将其添加到 OData 路由中(大概在 App_Start/WebApiConfig.cs 中),您已经在其中添加了 EntityRoutingConvention

    【讨论】:

    • 我遇到了完全相同的问题,但我不明白你的意思。
    • @Eduardo 抱歉,这太令人困惑了。我添加了一个可能已经有所帮助的简短段落,并且在我下班回家后将添加一个完整的代码示例(这次是 C#)。
    • @NikontheThird 您好,您的帖子真的很有帮助。谢谢!我有类似的情况,我需要访问具有 Id1=integer 数据类型和另一个 Id2=Guid 数据类型的复合键实体。我有点理解你的回答,但是为什么我们需要 entitydataroute 和 navigation route?我不明白需要吗?前者应该不够吗?每个路由实现什么?如果有机会请解释一下。再次感谢。
    猜你喜欢
    • 2015-01-08
    • 2013-03-07
    • 2015-02-16
    • 2013-01-12
    • 2020-12-17
    • 1970-01-01
    • 2014-03-31
    • 1970-01-01
    • 2013-03-23
    相关资源
    最近更新 更多