OData序列化

11

我一直在尝试为过去2个月从OData控制器返回的实体自定义序列化找到解决方案!请帮帮我!!!

用例非常简单,我已经进一步简化了它以达到问题点。我的模型中有一些附加到某些实体的虚拟字段,例如:

public class Customer
{
    public int CustomerId { get; set; }
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public string VirtualField1 { get; set; }
    public string VirtualField2 { get; set; }
    public string VirtualField3 { get; set; }
}

现在,假设客户端已将VirtualField1配置为"CompanyName"。
我想要做的就是创建一个自定义的JSON序列化器和反序列化器,以便:

  1. 任何获取客户(和当然是IQueryable<>中的客户)的GET请求都将通过此序列化器进行处理,在每个客户集合中用"CompanyName"替换字段"VirtualField1"的名称。
  2. 任何POST请求都将经过相反的替换 - 也就是用"VirtualField1"替换"CompanyName"。
    ** 实际的替换逻辑有点复杂,但思路是相同的。

我已经阅读了谷歌能够找到的一切内容,但没有发现任何可行的示例。 以下是一些链接:
https://aspnetwebstack.codeplex.com/wikipage?title=OData%20formatter%20extensibility
** 当前的OData API有点不同了,但我认为原则是相同的。
customizing odata output from asp.net web api
Using OData in webapi for properties known only at runtime

所有链接(以及我找到的任何信息)的共同点是我必须继承DefaultODataSerializerProvider并将其添加到我的格式化程序中:
在WebApiConfig.cs中:

       var customFormatters = ODataMediaTypeFormatters.Create(new CustomODataSerilizerProvider(), new CustomODataDeSerilizerProvider());
       config.Formatters.InsertRange(0, customFormatters);

以及实际的提供程序和序列化程序:

 public class CustomODataSerilizerProvider : DefaultODataSerializerProvider
 {
    public override ODataEdmTypeSerializer GetEdmTypeSerializer(IEdmTypeReference edmType)
    {
        if (edmType.IsEntity())
        {
            return new CustomODataEntityTypeSerializer(edmType.AsEntity(), this);
        }

        return base.GetEdmTypeSerializer(edmType);
    }
 }

edmType.IsEntity()对于IQueryable结果永远不会为真,因此它永远不会创建具体的序列化程序。 如果我强制创建它,它仍然不会在CreateEntity上中断(或任何其他create方法)。

  public class CustomODataEntityTypeSerializer : ODataEntityTypeSerializer
  {
    public CustomODataEntityTypeSerializer(IEdmEntityTypeReference entityType, ODataSerializerProvider serializerProvider)
        : base(serializerProvider)
    {
    }

    public override ODataEntry CreateEntry(SelectExpandNode selectExpandNode, EntityInstanceContext entityInstanceContext)
    {
        var oDataEntry = base.CreateEntry(selectExpandNode, entityInstanceContext);
        return oDataEntry;
    }
 }

如果我将具体的序列化器更改为继承自ODataCollectionSerializer:
  public override ODataEdmTypeSerializer GetEdmTypeSerializer(IEdmTypeReference edmType)
    {

        if (edmType.IsCollection())
        {
            return new CollectionSerilizer(this);
        }

        return base.GetEdmTypeSerializer(edmType);
    }

并且。
public class CollectionSerilizer : ODataCollectionSerializer
{
    public CollectionSerilizer(ODataSerializerProvider serializerProvider) : base(serializerProvider)
    {
    }

    public override ODataCollectionValue CreateODataCollectionValue(IEnumerable enumerable, IEdmTypeReference elementType,
        ODataSerializerContext writeContext)
    {
        var oDataCollectionValue = base.CreateODataCollectionValue(enumerable, elementType, writeContext);
        return oDataCollectionValue;
    }

    public override void WriteObject(object graph, Type type, ODataMessageWriter messageWriter, ODataSerializerContext writeContext)
    {
        base.WriteObject(graph, type, messageWriter, writeContext);
    }
}

它会在WriteObject断点处停止,但是不起作用,基础部分抛出以下错误:

"Models.Customer"指定为集合项类型的类型不是基元或复杂类型。 ODataCollectionWriter只能写入基元或复杂值的集合。


另一个有趣的事情是,即使我插入了所谓的默认提供程序:

var customFormatters = ODataMediaTypeFormatters.Create(new DefaultODataSerializerProvider(), new DefaultODataDeserializerProvider());
        config.Formatters.InsertRange(0, customFormatters);

无论他们的位置是在开头还是其他地方:

config.Formatters.InsertRange(0, customFormatters);

或者在结尾处:

config.Formatters.AddRange(customFormatters);
OData功能,例如$expand(如/odata/Customers?$expand=Images)完全消失且无法工作(以下是响应):
 [{"Images":[],"CustomerId":1,"FirstName":"Bla","LastName":"Bla", "VirtualField1":null]

在此实例中,图像没有被扩展,尽管未添加自定义格式化程序时会扩展。
有任何想法、思路或方向吗?

你有没有找到这个问题的答案?我有一个带有自定义集合的类型,我使用自定义JsonConverter,这对于普通的WebAPI可以正常工作,但是一旦我将OData引入其中,从API返回的对象就无法在我的客户端中反序列化。我发现很少有关如何使其正常工作的信息。如果你找到了解决方案,能否分享一下? - John
很不幸,我并没有找到解决方法,据我所知,使用OData是无法实现的。原因在于OData的序列化机制与模型紧密耦合,因此修改模型会破坏序列化。我认为唯一可能的方法是在序列化之后添加一些过滤器。祝你好运,如果你找到了解决方法,请添加一些信息。 - Tomer
我放弃了这种方法。我决定移除 OData 包并坚持使用原始的 WebAPI,因为创建和处理可跟踪实体对我来说更重要。 - John
祝你好运!使用API非常棒,但是在发送循环依赖对象时要小心,因为JSON序列化器在处理此类情况时表现不佳(如果A包含B包含A,则会导致无限响应,除非进行处理,当然,使用OData可以避免这种情况,因为您必须使用$expand来获取对象访问器)。 - Tomer
作为附言:WebAPI现在使用Json.NET,它非常擅长处理引用循环。谢谢。 - John
我的回答或许可以帮到你:https://dev59.com/1GIj5IYBdhLWcg3wEhUA#36976343 - user326608
1个回答

4
可能来得有点晚,但我注意到在Microsoft的OData工具包中,对于OData V1-3和OData V4之间的序列化,在可用性方面存在显着差异。
我认为你正在使用V4,因为我也曾尝试使用DefaultODataSerializerProvider,并且没有成功。然后,我开始了一个新的Web Api项目,添加了所有NuGet OData V1-3的包,添加了一个Web Api OData控制器,立即就能够成功地进行任何形式的序列化自定义。这是因为OData V1-3与自定义MediaTypeFormatters一起工作,就像WebApi一样。之后变得非常容易。
我不会插入关于此的代码,因为使用MediaTypeFormatter的示例很多,例如:http://www.asp.net/web-api/overview/formats-and-model-binding/media-formatters
好的,确实会失去一些V4的功能: http://www.asp.net/web-api/overview/releases/whats-new-in-aspnet-web-api-22#OData。但是我相信您可以忍受严格的V4 Oasis实现带来的麻烦,而微软则以自己的方式跟随它(可能是因为这是他们自己的东西)。
V4中最重要的特性:
支持在OData模型中别名属性
谁在乎。
在ODataConventionModelBuilder中支持ComplexTypeAttribute,AssociationAttribute,TimesTampAttribute和ConcurrencyCheckAttribute 你不需要它们。
提供了为操作提供友好标题的功能
谁在乎。
与ODL UriParser集成
我不明白。
支持枚举、包含和单例
你不需要那个。
支持原始类型的转换
你不需要那个。
添加了OData函数支持
这很不幸,但可以通过在OData之外使用普通的ApiController轻松解决。
支持函数调用的参数别名
你不需要那个,它很混乱。
模型中支持驼峰命名约定
有支持很好,但我建议不要这样做。
在$filter中支持cast()
不需要它。
支持开放式复杂类型
是啊,怎么了?你有自定义序列化。你可以序列化任何类型。

移除了EntitySetController和AsyncEntitySetController。

  • 好事。

将$link改为$ref。

  • 好的。

添加了属性路由支持。

  • 不幸的是,所以你必须坚持在WebApiConfig中定义的路由。没什么大不了的。

问问自己,你使用OData是为了什么? 我显然不能代表你的情况,但我会尝试猜测一下你的用例: - 你正在尝试允许通过http向数据源提出复杂问题,因为你不想每次客户端N想到一个新问题时都更改接口。 - 你正在尝试允许客户端请求他/她返回的实际数据格式。Csv?Xml?Json?vCard?甚至odt?pdf?

如果这两个目标是你真正想要实现的,那就坚持使用V3吧。这样你就可以节省大量时间来理解你的域模型。

以上是我的0.02美元建议。


和你一样,我也发现这个问题有点晚了...在不使用 OData V4 的情况下如何绕过筛选子集合的过滤器...甚至在 V3 规范中都没有,而我的一些查询太大了,不能筛选。 - War

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接