1

在这个简单的示例中,我试图从 Web Api 2 + OData v4 服务中获取序列化为 JSON 的对象。控制器具有绑定函数 Test,它返回一个 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 转换器将对象序列化为 JSON 字符串 - 效果很好,但这个问题仍然困扰着我。我怀疑这与 OData 的默认序列化程序有关,但我不清楚如何配置它。

那么,是否可以以这样的方式配置 edm 函数的返回参数,以便正确序列化复杂对象?

谢谢!

4

2 回答 2

5

正如 lukkea 所说,OData 不是为使用匿名类型而设计的。
旁注,在您的 WebApiConfig 中,如果您要返回一个集合,您应该将“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 不知道上一个示例中的返回类型,因此它返回 2 个没有值的对象。

您有 2 个选项。

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

选项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>();

如果您想返回一个具有非强类型的额外属性的人,那么您需要ODataOpenType

于 2016-10-27T11:27:36.493 回答
0

虽然使用动态响应确实很棘手,但这并不难,而且您当然不需要通过字符串编码返回您的对象。

关键是动态响应意味着我们不能使用标准 EnableQueryAttribute对方法响应应用特定的投影或过滤,并且我们不能返回OkNegotiatedContentResult,因为这个响应对象旨在使运行时能够操纵响应对象的方式序列化到 HTTP 响应中。

ApiController.Ok(T 内容);
创建具有指定值的 System.Web.Http.Results.OkNegotiatedContentResult。
content:要在实体正文中协商和格式化的内容值。

内容协商
内容协商基本上是一种机制,用于封装确定您的方法响应应如何通过 http 传输的过程,以及对响应进行物理编码的繁重工作。

通过使用内容协商,您的方法只需要返回一个查询或原始 c# 对象,即使调用者在请求中指定输出应该是 XML(而不是标准 JSON)。处理物理序列化的概念和解释调用者意图的逻辑被抽象出来,所以你根本不需要担心。

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

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

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

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

    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;
    
于 2020-08-29T13:57:09.890 回答