如何在序列化 OData 响应时忽略空值

Pav*_*van 5 c# serialization json odata asp.net-web-api

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

config.Formatters.JsonFormatter.SerializationSettings
      .NullValueHandling = NullValueHandling.Ignore;
Run Code Online (Sandbox Code Playgroud)

但是一旦我切换到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());
}
Run Code Online (Sandbox Code Playgroud)

商品型号:

public class Item
{
    public int Id { get; set; }
    public string Name { get; set; }
    public string OptionalField { get; set; }
}
Run Code Online (Sandbox Code Playgroud)

值控制器:

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);
    }
}
Run Code Online (Sandbox Code Playgroud)

这是我得到的 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
   }
  ]
}
Run Code Online (Sandbox Code Playgroud)

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

Chr*_*ler 7

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;
        }
    }
    
    Run Code Online (Sandbox Code Playgroud)
  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);
        }
    }
    
    Run Code Online (Sandbox Code Playgroud)
  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));
    
    Run Code Online (Sandbox Code Playgroud)

你有它,重新执行你的查询,你应该得到以下信息:

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

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

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

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


Sta*_*nko 2

我知道它看起来不符合逻辑,但只需将 DefaultODataSerializerProvider 和 DefaultODataDeserializerProvider 添加到格式化程序列表中就可以解决我的问题:

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);
Run Code Online (Sandbox Code Playgroud)

更新

由于全局格式化程序修改对我来说无法正常工作,因此我选择了另一种方式。首先,我放弃了 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());
    }
}
Run Code Online (Sandbox Code Playgroud)

格式化属性将 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);
    }
}
Run Code Online (Sandbox Code Playgroud)

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

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;
    }
}
Run Code Online (Sandbox Code Playgroud)

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