Web Api 2 with OData v4 - 返回复杂对象的绑定函数

Web Api 2 with OData v4 - Bound function returning complex object

在这个简单的示例中,我试图从 Web Api 2 + OData v4 服务中获取序列化为 JSON 的对象。控制器具有绑定函数 Test,它是 returning annon 数组。对象。

public class ProductsController : ODataController
{
    [HttpGet]
    public IHttpActionResult Test(int key)
    {
        var res = new[]
        {
            new { Name = "a", Value = new[] { 1, 2, 3 } },
            new { Name = "b", Value = new[] { 2, 4, 5 } }

            // this also produces same result
            // new { Name = "a", Value = "c" },
            // new { Name = "b", Value = "c" }
        };

        return this.Ok(res);
    }
}

Edm 是用这段代码构建的:

ODataModelBuilder builder = new ODataConventionModelBuilder();

builder.EntitySet<Product>("Products");
var productType = builder.EntityType<Product>();

var f = productType.Function("Test").Returns<object>();

当我向服务发出请求时(例如 http://localhost:9010/odata/Products(33)/Default.Test),我收到一个奇怪的响应 - 一个包含两个空对象的数组,如下所示:

{
  "@odata.context": "http://localhost:9010/odata/$metadata#Collection(System.Object)",
  "value": [
    {},
    {}
  ]
}

在我的真实应用程序中,我使用 Newtonsoft 的 Json 转换器将对象 return 序列化为 JSON 字符串 - 工作正常,但这个问题仍然困扰着我。我怀疑它与 OData 的默认序列化器有关,但我不清楚如何配置它。

那么,是否可以配置 edm 函数的 return 参数,以便我可以正确序列化复杂对象?

谢谢!

正如 lukkea 所说,OData 并非设计用于匿名类型。
旁注,在你的 WebApiConfig 中,如果你正在 return 收集,你应该将 "Returns" 更改为 "ReturnsCollection"。

无论如何,假设您编写了以下内容。

return this.Ok(Newtonsoft.Json.JsonConvert.SerializeObject(res));

var f = productType.Function("Test").Returns<string>();

你会得到以下信息:

{
    "@odata.context": "http://localhost/Test/odata/$metadata#Edm.String",
    "value": 
        "[
            {\"Name\":\"a\",\"Value\":[1,2,3]},
            {\"Name\":\"b\",\"Value\":[2,4,5]}
        ]"
}

请注意,数组中仍有 2 个项目,但这次它们不是空的。
由于 OData 不知道您前面示例中的 return 类型,因此它 return 2 个没有值的对象。

你有2个选项。

  1. Return 将匿名类型返回为序列化的 json 字符串,然后在客户端反序列化 json。
  2. 创建一个 class 和 return 类型。

选项 1

// ON SERVER
return this.Ok(Newtonsoft.Json.JsonConvert.SerializeObject(res));

var f = productType.Function("Test").Returns<string>();

// ON CLIENT
string jsonString = odataContext.Products.ByKey(33).Test().GetValue();  
var objectList = Newtonsoft.Json.JsonConvert.DeserializeObject<dynamic>(jsonString);  

string firstObjectName = objectList[0].Name;

选项 2

// ON SERVER  
public class TestObject
{
    public string Name { get; set; }
    public List<int> Integers { get; set; }
}

var res = new List<TestObject>
{
     new TestObject { Name = "a", Integers = new List<int> { 1, 2, 3 } },
     new TestObject { Name = "b", Integers = new List<int> { 2, 4, 5 } }
};

return this.Ok(res);  

var f = productType.Function("Test").ReturnsCollection<TestObject>();

如果你想 return 一个人有一个额外的 属性 不是强类型那么你想要 ODataOpenType

虽然使用动态响应确实很棘手,但这并不难,您当然不需要通过字符串编码求助于 returning 您的 object。

关键是 动态 响应意味着我们不能使用标准 EnableQueryAttribute 对方法响应应用特定的投影或过滤,我们可以't return OkNegotiatedContentResult 因为此响应 object 旨在使运行时能够操纵响应 object 如何序列化为 HTTP 响应。

ApiController.Ok(T content);
Creates an System.Web.Http.Results.OkNegotiatedContentResult with the specified values.
content: The content value to negotiate and format in the entity body.

Content Negotiation
Content Negotiation is basically a mechansim to encapsulate the process to determine how your method response should be transmitted over http as well as the heavy lifting to physically encode the response.

By using Content Negotiation, your method only needs to return a query or raw c# object, even if the caller specified in the request that the output should be XML (instead of the standard JSON). The concept of dealing with the physical serialization and the logic to interpret the caller's intent is abstracted away so you do not need to worry about it at all.

您可以使用 2 个选项来自定义输出:

  1. ApiController.JsonResult(T内容);
    这允许您指定要序列化的 object 图,这不会响应 EnableQueryAttributeContent Negotiation.

     return this.JsonResult(res);
    
    • 此响应不在通常的 OData 响应 包装器中,因此它不包含标准 @odata 属性,如 @odata.context.
    • 如果您的响应 object 与 OData 模型中 Action/Function 定义中指定的类型不匹配,则调用此端点将导致与 OData 模型中指定的类型不匹配,这将导致 HTTP 406 Not Acceptable,因此请确保将响应注册为 object 或您的响应 object 继承或实现的另一种类型的接口。
  2. HttpResponseMessage
    一起绕过 OData 响应管理,并直接从您的方法 return HttpResponseMessage 。通过这种方式,您负责序列化响应内容以及响应 headers.

    然而,这会绕过所有 OData 机制,包括响应验证和格式化,这意味着您可以 return 任何您想要的。

    var result = new HttpResponseMessage(HttpStatusCode.OK)
    {
        Content = new StringContent(Newtonsoft.Json.JsonConvert.SerializeObject(res))
    };
    result.Content.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue("application/json");
    return result;