如何在 ASP.NET Web API 实现中将数组传递给 OData 函数?

Posted

技术标签:

【中文标题】如何在 ASP.NET Web API 实现中将数组传递给 OData 函数?【英文标题】:How to pass Array to OData function in ASP.NET Web API implementation? 【发布时间】:2014-12-25 09:58:51 【问题描述】:

OData V4 的规范声明它必须是可能的: https://issues.oasis-open.org/browse/ODATA-636.

"复杂类型和数组只能通过 参数别名”

当我尝试传递带有 OData 参数别名的数组时,会发生异常。

/TestEntities/NS.TestFunction(ArrayHere=@p)?@p=[1,2,3]

结果:

无法将“EdmValidCoreModelPrimitiveType”类型的对象转换为类型 'Microsoft.OData.Edm.IEdmStructuredType

有趣的是,元数据文档在这种情况下是正确组成的:

<Function Name="TestFunction" IsBound="true">
  <Parameter Name="bindingParameter" Type="Collection(NS.TestEntity)"/>
  <Parameter Name="ArrayHere" Type="System.Int32[]"/>
  <ReturnType Type="Collection(NS.TestEntity)"/>
</Function>

ASP.NET MVC Web API 2 OData 是否可以在查询字符串中将数组传递给 OData 函数?

更新:

这是构建 EDM 模型和控制器的代码。

 var builder = new ODataConventionModelBuilder();

 builder.Namespace = "NS";

 builder.EntitySet<TestEntity>("TestEntities");

 builder.EntityType<TestEntity>().Collection
    .Function("TestFunction")
    .ReturnsCollectionFromEntitySet<TestEntity>("TestEntities")
    .Parameter<int[]>("ArrayHere");

控制器:

public class TestEntitiesController : ODataController

    public IEnumerable<TestEntity> TestFunction(int[] arrayHere)
    
        throw new NotImplementedException();
    

[FromODataUri]标记参数不能解决问题。

更新 2:

这是堆栈跟踪:

at Microsoft.OData.Core.UriParser.TypePromotionUtils.CanConvertTo(SingleValueNode sourceNodeOrNull, IEdmTypeReference sourceReference, IEdmTypeReference targetReference)
at Microsoft.OData.Core.UriParser.Parsers.MetadataBindingUtils.ConvertToTypeIfNeeded(SingleValueNode source, IEdmTypeReference targetTypeReference)
at Microsoft.OData.Core.UriParser.Parsers.FunctionCallBinder.BindSegmentParameters(ODataUriParserConfiguration configuration, IEdmOperation functionOrOpertion, ICollection`1 segmentParameterTokens)
at Microsoft.OData.Core.UriParser.Parsers.ODataPathParser.TryBindingParametersAndMatchingOperation(String identifier, String parenthesisExpression, IEdmType bindingType, ODataUriParserConfiguration configuration, ICollection`1& boundParameters, IEdmOperation& matchingOperation)
at Microsoft.OData.Core.UriParser.Parsers.ODataPathParser.TryCreateSegmentForOperation(ODataPathSegment previousSegment, String identifier, String parenthesisExpression)
at Microsoft.OData.Core.UriParser.Parsers.ODataPathParser.CreateNextSegment(String text)
at Microsoft.OData.Core.UriParser.Parsers.ODataPathParser.ParsePath(ICollection`1 segments)
at Microsoft.OData.Core.UriParser.Parsers.ODataPathFactory.BindPath(ICollection`1 segments, ODataUriParserConfiguration configuration)
at Microsoft.OData.Core.UriParser.ODataUriParser.ParsePathImplementation()
at Microsoft.OData.Core.UriParser.ODataUriParser.Initialize()
at System.Web.OData.Routing.DefaultODataPathHandler.Parse(IEdmModel model, String serviceRoot, String odataPath, Boolean enableUriTemplateParsing)
at System.Web.OData.Routing.DefaultODataPathHandler.Parse(IEdmModel model, String serviceRoot, String odataPath)
at System.Web.OData.Routing.ODataPathRouteConstraint.Match(HttpRequestMessage request, IHttpRoute route, String parameterName, IDictionary`2 values, HttpRouteDirection routeDirection)
at System.Web.Http.Routing.HttpRoute.ProcessConstraint(HttpRequestMessage request, Object constraint, String parameterName, HttpRouteValueDictionary values, HttpRouteDirection routeDirection)
at System.Web.Http.Routing.HttpRoute.ProcessConstraints(HttpRequestMessage request, HttpRouteValueDictionary values, HttpRouteDirection routeDirection)
at System.Web.Http.Routing.HttpRoute.GetRouteData(String virtualPathRoot, HttpRequestMessage request)
at System.Web.Http.WebHost.Routing.HttpWebRoute.GetRouteData(HttpContextBase httpContext)

【问题讨论】:

你能贴出抛出异常的代码吗? 有趣的问题,我真的很高兴看到您用于将函数添加到 $metadata 和函数实现的代码。 @gdoron,请查看更新后的问题 @PseudoNym01,请查看更新后的问题 我有类似的代码,但使用 CollectionParameter() 而不是 Parameter()。它不会抛出异常,但传递给控制器​​中自定义函数的值始终为 null(无论我将其定义为 int[] 还是 IEnumerable 还是 ICollection 等)。有趣的是,Microsoft 的 OData 客户端构建器创建的代码将 JSON 作为参数值而不是使用参数别名。但是,OData 路由代码无法找到这样格式的函数调用的控制器。 【参考方案1】:

假设您使用的是 OData V4,您需要在注册函数时使用 CollectionParameter,并且您缺少 arrayHere 参数上的 [FromODataUri]。另外,请尝试使用 IEnumerable&lt;int&gt; 而不是数组。

var builder = new ODataConventionModelBuilder();

 builder.Namespace = "NS";

 builder.EntitySet<TestEntity>("TestEntities");

 builder.EntityType<TestEntity>().Collection
    .Function("TestFunction")
    .ReturnsCollectionFromEntitySet<TestEntity>("TestEntities")
    .CollectionParameter<int>("ArrayHere");

像这样在控制器中使用函数...

[HttpGet]
public async Task<IHttpActionResult> TestFunction([FromODataUri] IEnumerable<int> ArrayHere)

    // Do stuff

现在您可以提出诸如...之类的请求

http://yourRestService/API/TestEntities/NS.TestFunction(ArrayHere=[1,2,3])

请注意,您也可以接受复杂类型的数组。您必须对数组的 json 进行 url 编码并使用参数别名,这样您最终会得到这样的结果...

 builder.EntityType<TestEntity>().Collection
    .Function("TestFunction2")
    .ReturnsCollectionFromEntitySet<TestEntity>("TestEntities")
    .CollectionParameter<person>("ArrayHere");

[HttpGet]
public async Task<IHttpActionResult> TestFunction2([FromODataUri] IEnumerable<person> ArrayHere)

    // Do stuff

http://yourRestService/API/TestEntities/NS.TestFunction2(ArrayHere=@ArrayData)?@ArrayData=%5B%7B%22FirstName%22%3A%22Bob%22%2C+%22LastName%22%3A%22Dole%22%7D%2C%7B%22FirstName%22%3A%22Bill%22%2C+%22LastName%22%3A%22Clinton%22%7D%5D

【讨论】:

太棒了!集合参数!我试图使用 Parameter()。我想澄清一下,这些参数的值包含在 URL 中的括号中:NS.TestFunction9(IntArrayHere=[11,12,13,14,15]) 我投了赞成票,真的很有用。但是使用正确的命名空间一定很重要。因此,例如,如果您的 OData 控制器具有命名空间“CK.Common.Planogram.Controllers”,则必须在 WebApi.config 中使用“CK.Common.Planogram”作为命名空间。没有正确命名空间的函数将返回 404/406。小心点。 +1 深入显示函数的使用情况,其中其他热门响应仅集中在操作上。编码后的 HTTP 很难阅读,也不是完全有用,因为我们倾向于在请求会为我们正确编码的环境中编码,所以这可能就是你投反对票的原因。也许为这样的功能展示 swagger doc 也可能会有所帮助 检查here,ODataConventionModelBuilder没有EntityType方法,那怎么用?是否有明确的教程将集合发布到 Odata 操作? 我收到:HTTP Error 404.0 - Not Found The resource you are looking for has been removed, had its name changed, or is temporarily unavailable.【参考方案2】:

我不知道您当前使用的是哪个版本的 OData,但这是我实施的适用于 v4 并将简单数组传递给 OData Action 方法的解决方案

在您的 WebConfig.cs 中

ODataModelBuilder builder = new ODataConventionModelBuilder();
builder.EntitySet<TestEntity>("TestEntities");

builder.Namespace = "NS";

builder.EntityType<TestEntity>()
        .Action("TestAction")
    .CollectionParameter<string>("ArrayHere");    

这里是你的控制器方法

[HttpPost]
public async Task<IHttpActionResult> TestAction([FromODataUri] int key, ODataActionParameters parameters)

    // Do your thing here..
    var invites = parameters["ArrayHere"] as IEnumerable<string>;

生成的 API 端点请求应如下所示:

POST http://localhost/odata/TestEntities(1)/NS.TestAction HTTP/1.1
Content-type: application/json
Host: localhost:49255
Content-Length: 37

"ArrayHere":["hello", "world"]    

我多次遇到这个特殊问题,但无法使其适用于 OData 函数。我挖得更深一点,发现CollectionParameter&lt;&gt;() 方法可以解决所有问题。我没有回去尝试Function,因为我并没有真正看到需要。希望对你有所帮助

【讨论】:

感谢您的回答,但问题是关于将数组传递给 OData 函数(作为查询字符串的一部分),而不是对我也有用的操作(作为请求有效负载)。

以上是关于如何在 ASP.NET Web API 实现中将数组传递给 OData 函数?的主要内容,如果未能解决你的问题,请参考以下文章

在 Asp.Net Web API 中将 JSON 反序列化为派生类型

ASP.NET 的 web api - 如何构建对象流

如何通过js跨域调用ASP.NET Web API (请问如何实现在javascript中通过http get的方式跨域调用ASP.NET Web API?)

如何使用 Web Api Asp.Net Core 实现基于声明的授权?

如何使用 AddMvcCore() 实现“纯”ASP.NET Core Web API

ASP.NET Core Web API 身份验证