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

时间:2014-07-21 10:39:34

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

我要求完全忽略响应中的空值字段。 我可以通过修改正常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; }
}

ValuesController:

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”不存在于第二个项目中(因为它的值为null)。我需要在我的响应中实现它,我不希望用户只查询非空值。

3 个答案:

答案 0 :(得分:2)

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

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());
    }
}

格式化属性将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;
    }
}

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

答案 1 :(得分:0)

所有方法都相同,我对webapiconfig进行了更改

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

这有助于我解决结果

答案 2 :(得分:0)

ODataLib v7中,由于依赖注入(DI)的影响,这些类型的自定义发生了巨大变化

  

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

     

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

我们可以全局重写DefaultODataSerializer,以便使用以下步骤从所有Entity和Complex值序列化输出中省略空值:

  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. 定义将确定何时使用我们的自定义序列化程序的提供程序

      

    继承自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 < / p>

    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定义

Service                     Default Implementation      Lifetime    Prototype?
--------------------------  --------------------------  ----------  ---------
IJsonReaderFactory          DefaultJsonReaderFactory    Singleton   N
IJsonWriterFactory          DefaultJsonWriterFactory    Singleton   N
ODataMediaTypeResolver      ODataMediaTypeResolver      Singleton   N
ODataMessageReaderSettings  ODataMessageReaderSettings  Scoped      Y
ODataMessageWriterSettings  ODataMessageWriterSettings  Scoped      Y
ODataPayloadValueConverter  ODataPayloadValueConverter  Singleton   N
IEdmModel                   EdmCoreModel.Instance       Singleton   N
ODataUriResolver            ODataUriResolver            Singleton   N
UriPathParser               UriPathParser               Scoped      N
ODataSimplifiedOptions      ODataSimplifiedOptions      Scoped      Y