【问题标题】:Web Api 2 with OData v4 - Bound function returning complex object带有 OData v4 的 Web Api 2 - 返回复杂对象的绑定函数
【发布时间】:2016-09-25 17:18:10
【问题描述】:

在这个简单的示例中,我试图从 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 函数的返回参数来正确序列化复杂对象?

谢谢!

【问题讨论】:

  • 我不认为 odata 旨在与这样的匿名类型一起使用,但我没有足够的信心将其发布为答案!我认为您需要定义一个类型,将其添加到您的模型构建器(并将 Returns() 从“object”更改为您新定义的类型),然后在您当前创建匿名的地方创建该类型的实例类型...在我看来,odata 正在返回您告诉它的“对象”,但除了“这里有一个对象”之外,它还没有定义要说。
  • 我忘了提 - 即使我声明了一些类型然后 .Returns(),结果是一样的:\

标签: json asp.net-web-api2 odata asp.net-web-api-odata odata-v4


【解决方案1】:

正如 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

【讨论】:

  • 我会提到 TestObject 类定义中的 getter 和 setter 似乎不是可选的。
【解决方案2】:

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

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

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

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

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

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

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

     return this.JsonResult(res);
    
    • 此响应不在通常的 OData 响应 包装器中,因此它不包括标准的 @odata 属性,如 @odata.context
    • 如果您的响应对象与 OData 模型中的 Action/Function 定义中指定的类型不匹配,则对该端点的调用将导致在 HTTP 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;
    

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2023-03-17
    • 2012-02-02
    • 2014-10-08
    • 2015-02-11
    • 1970-01-01
    相关资源
    最近更新 更多