【问题标题】:How to Ignore Null values while serializing OData response如何在序列化 OData 响应时忽略 Null 值
【发布时间】:2014-09-11 20:11:14
【问题描述】:

我需要从响应中完全省略空值字段。 我可以通过为正常的 webapi 响应修改 JsonFormatter 序列化设置来做到这一点。

config.Formatters.JsonFormatter.SerializationSettings
      .NullValueHandling = NullValueHandling.Ignore;

但是一旦我切换到OData,这似乎不起作用。

这是我的文件: WebApi.config:

public static void Register(HttpConfiguration config)
{
    var builder = new ODataConventionModelBuilder();
    var workerEntitySet = builder.EntitySet<Item>("Values");
    config.Routes.MapODataRoute("Default", "api", builder.GetEdmModel());
}

物品型号:

public class Item
{
    public int Id { get; set; }
    public string Name { get; set; }
    public string OptionalField { get; set; }
}

值控制器:

public class ValuesController : EntitySetController<Item, int>
{
    public static List<Item> items = new List<Item>() 
    {
        new Item { Id = 1, Name = "name1", OptionalField = "Value Present" }, 
        new Item { Id = 3, Name = "name2" } 
    };
    [Queryable(AllowedQueryOptions = AllowedQueryOptions.All)]
    public override IQueryable<Item> Get()
    {
        return items.AsQueryable();
    }
    [Queryable]
    protected override Item GetEntityByKey(int  id)
    {
        return items.Single(i => i.Id == id);
    }
}

这是我得到的 GET 响应:api/Values。

{
 "odata.metadata":"http://localhost:28776/api/$metadata#Values",
 "value":[
   {
     "Id":1,
     "Name":"name1",
     "OptionalField":"Value Present"
   },
   {
     "Id":3,
     "Name":"name2",
     "OptionalField":null
   }
  ]
}

但我不需要响应中存在空值的元素 - 在下面的响应中,我需要第二项中不存在“OptionalField”(因为它的值为空)。我需要在我的回复中实现它,我不希望用户只查询非空值。

【问题讨论】:

    标签: c# json serialization asp.net-web-api odata


    【解决方案1】:

    ODataLib v7 中,由于依赖注入 (DI),围绕这些自定义的事情发生了巨大变化

    此建议适用于已升级到 ODataLib v7 且可能已实施先前接受的答案的任何人。

    如果您有Microsoft.OData.Core nuget 包 v7 或更高版本,那么这适用于您:)。如果您仍在使用旧版本,请使用@stas-natalenko 提供的代码,但请不要停止从 ODataController 继承...

    我们可以全局覆盖 DefaultODataSerializer,以便使用以下步骤从所有实体和复杂值序列化输出中省略空值:

    1. 定义您的自定义序列化器,该序列化器将省略具有空值的属性

      Microsoft.AspNet.OData.Formatter.Serialization.ODataResourceSerializer继承

      /// <summary>
      /// OData Entity Serilizer that omits null properties from the response
      /// </summary>
      public class IngoreNullEntityPropertiesSerializer : ODataResourceSerializer
      {
          public IngoreNullEntityPropertiesSerializer(ODataSerializerProvider provider)
              : base(provider) { }
      
          /// <summary>
          /// Only return properties that are not null
          /// </summary>
          /// <param name="structuralProperty">The EDM structural property being written.</param>
          /// <param name="resourceContext">The context for the entity instance being written.</param>
          /// <returns>The property be written by the serilizer, a null response will effectively skip this property.</returns>
          public override Microsoft.OData.ODataProperty CreateStructuralProperty(Microsoft.OData.Edm.IEdmStructuralProperty structuralProperty, ResourceContext resourceContext)
          {
              var property = base.CreateStructuralProperty(structuralProperty, resourceContext);
              return property.Value != null ? property : null;
          }
      }
      
    2. 定义一个 Provider 来决定何时使用我们的自定义序列化器

      Microsoft.AspNet.OData.Formatter.Serialization.DefaultODataSerializerProvider继承

      /// <summary>
      /// Provider that selects the IngoreNullEntityPropertiesSerializer that omits null properties on resources from the response
      /// </summary>
      public class IngoreNullEntityPropertiesSerializerProvider : DefaultODataSerializerProvider
      {
          private readonly IngoreNullEntityPropertiesSerializer _entityTypeSerializer;
      
          public IngoreNullEntityPropertiesSerializerProvider(IServiceProvider rootContainer)
              : base(rootContainer) {
              _entityTypeSerializer = new IngoreNullEntityPropertiesSerializer(this);
          }
      
          public override ODataEdmTypeSerializer GetEdmTypeSerializer(Microsoft.OData.Edm.IEdmTypeReference edmType)
          {
              // Support for Entity types AND Complex types
              if (edmType.Definition.TypeKind == EdmTypeKind.Entity || edmType.Definition.TypeKind == EdmTypeKind.Complex)
                  return _entityTypeSerializer;
              else
                  return base.GetEdmTypeSerializer(edmType);
          }
      }
      
    3. 现在我们需要将它注入到您的 Container Builder 中。

      具体情况取决于您的 .Net 版本,对于许多较旧的项目,这将是您映射 ODataServiceRoute 的位置,通常位于您的 startup.csWebApiConfig.cs

      builder => builder
          .AddService(ServiceLifetime.Singleton, sp => model)
          // Injected our custom serializer to override the current ODataSerializerProvider
          // .AddService<{Type of service to Override}>({service lifetime}, sp => {return your custom implementation})
          .AddService<Microsoft.AspNet.OData.Formatter.Serialization.ODataSerializerProvider>(ServiceLifetime.Singleton, sp => new IngoreNullEntityPropertiesSerializerProvider(sp));
      

    你有了它,重新执行你的查询,你应该得到以下结果:

    {
     "odata.metadata":"http://localhost:28776/api/$metadata#Values",
     "value":[
       {
         "Id":1,
         "Name":"name1",
         "OptionalField":"Value Present"
       },
       {
         "Id":3,
         "Name":"name2"
       }
      ]
    }
    

    这是一个非常方便的解决方案,可以显着减少许多基于 OData Services 的数据录入应用程序的数据消耗

    注意:此时,必须使用此技术覆盖任何这些默认服务:(如此处定义的 OData.Net - Dependency Injection Support

    服务默认实现生命周期原型? -------------------------- ------------------------ -- ---------- --------- IJsonReaderFactory DefaultJsonReaderFactory Singleton N IJsonWriterFactory DefaultJsonWriterFactory Singleton N ODataMediaTypeResolver ODataMediaTypeResolver Singleton N ODataMessageReaderSettings ODataMessageReaderSettings Scoped Y ODataMessageWriterSettings ODataMessageWriterSettings 范围 Y ODataPayloadValueConverter ODataPayloadValueConverter Singleton N IEdmModel EdmCoreModel.Instance Singleton N ODataUriResolver ODataUriResolver Singleton N UriPathParser UriPathParser Scoped N ODataSimplifiedOptions ODataSimplifiedOptions 范围 Y

    【讨论】:

    • 您能否在第 3 步中展示您的完整注册方法示例?
    • 知道为什么像在响应中不显示空值这样微不足道的事情需要如此可怕的实现吗?
    • 试图在我的 ASP.NET 3.1 OData 示例项目中实现这一点,但没有成功...请您提供更完整的注册示例吗?将不胜感激,并会收到我的支持...
    • 比这更完整? @JAVIzcaino 此解决方案是为 ASP.Net 和 NOT ASP.Net Core 提供的,.Net Core 中的解决方案可能会有所不同,这是针对已升级到 v7 的 ASP.Net 代码库的特定指南作为 v7 的 OData Lib 与之前的版本相比有重大的重大变化。
    • 我认为这是 ASP.Net Core 的实现。如果您对我的要求感到失望,我很抱歉。
    【解决方案2】:

    我知道它看起来不合逻辑,但只需将 DefaultODataSerializerProvider 和 DefaultODataDeserializerProvider 添加到 Formatter 列表就可以了:

    public static class WebApiConfig
    {
        public static void Register(HttpConfiguration config)
        {
            //... 
    
            var odataFormatters = System.Web.OData.Formatter.ODataMediaTypeFormatters.Create(
                System.Web.OData.Formatter.Serialization.DefaultODataSerializerProvider.Instance, 
                System.Web.OData.Formatter.Deserialization.DefaultODataDeserializerProvider.Instance);
            config.Formatters.AddRange(odataFormatters);
    

    更新

    由于全局格式化程序修改对我来说不能正常工作,我选择了不同的方式。 首先,我离开了 ODataController,并使用自定义 ODataFormatting 属性从 ApiController 继承了我的控制器:

    [ODataRouting]
    [CustomODataFormatting]
    public class MyController : ApiController
    {
        ...
    }
    
    public class CustomODataFormattingAttribute : ODataFormattingAttribute
    {
        public override IList<System.Web.OData.Formatter.ODataMediaTypeFormatter> CreateODataFormatters()
        {
            return System.Web.OData.Formatter.ODataMediaTypeFormatters.Create(
                new CustomODataSerializerProvider(),
                new System.Web.OData.Formatter.Deserialization.DefaultODataDeserializerProvider());
        }
    }
    

    formatting 属性将 DefaultODataSerializerProvider 替换为修改后的属性:

    public class CustomODataSerializerProvider : DefaultODataSerializerProvider
    {
        public override ODataEdmTypeSerializer GetEdmTypeSerializer(IEdmTypeReference edmType)
        {
            if(edmType.Definition.TypeKind == EdmTypeKind.Entity)
                return new CustomODataEntityTypeSerializer(this);
            else 
                return base.GetEdmTypeSerializer(edmType);
        }
    }
    

    最后,自定义序列化程序过滤具有空值的结构属性:

    public class CustomODataEntityTypeSerializer : System.Web.OData.Formatter.Serialization.ODataEntityTypeSerializer
    {
        public CustomODataEntityTypeSerializer(ODataSerializerProvider provider)
            : base(provider) { }
    
        public override ODataProperty CreateStructuralProperty(IEdmStructuralProperty structuralProperty, EntityInstanceContext entityInstanceContext)
        {
            var property = base.CreateStructuralProperty(structuralProperty, entityInstanceContext);
            return property.Value != null ? property : null;
        }
    }
    

    在我看来,这不是最好的解决方案,但这是我找到的。

    【讨论】:

    • 这个答案不是要走的路。不知道如何,但您已经设法关闭 OData 序列化程序并切换回 JSON.Net,这不太可能是您想要的 OData。
    • Gareth,谢谢你的评论,你是对的。我使用了不同的方法——只是用它更新了我的答案。
    【解决方案3】:

    所有方法都一样,我对 webapiconfig 进行了更改

    var odataFormatters = ODataMediaTypeFormatters.Create(new CustomODataSerializerProvider(), new DefaultODataDeserializerProvider());
                config.Formatters.InsertRange(0, odataFormatters);
    

    这有助于我解决结果

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2013-04-11
      • 2016-01-22
      • 2021-01-20
      • 2020-10-19
      • 2018-06-06
      • 1970-01-01
      • 2015-11-15
      • 1970-01-01
      相关资源
      最近更新 更多