In ODataLib v7由于依赖注入(DI),围绕这些定制的事情发生了巨大的变化
此建议适用于已升级到 ODataLib v7 的任何人,他们可能已经实现了之前接受的答案。
如果您有微软.OData.Corenuget 软件包 v7 或更高版本则这适用于您:)。如果您仍在使用旧版本,请使用 @stas-natalenko 提供的代码,但请不要停止从 ODataController 继承...
我们可以全局重写 DefaultODataSerializer,以便使用以下步骤从所有实体和复杂值序列化输出中省略空值:
- 定义自定义序列化程序,它将省略具有空值的属性
继承自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;
}
}
-
定义一个 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);
}
}
-
现在我们需要将其注入到您的容器生成器中。
具体细节将根据您的 .Net 版本而有所不同,对于许多较旧的项目,这将是您映射 ODataServiceRoute 的位置,通常位于您的startup.cs
or WebApiConfig.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 服务的数据输入应用程序的数据消耗
注意:此时,必须使用此技术来覆盖任何这些默认服务:(如此处定义OData.Net - 依赖注入支持
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
更新:如何处理列表或复杂类型
另一种常见的情况是,如果复杂类型的所有属性均为 null,则从输出中排除复杂类型,尤其是现在我们不包含 null 属性。我们可以重写WriteObjectInline
方法中的IngoreNullEntityPropertiesSerializer
为了这:
public override void WriteObjectInline(object graph, IEdmTypeReference expectedType, Microsoft.OData.ODataWriter writer, ODataSerializerContext writeContext)
{
if (graph != null)
{
// special case, nullable Complex Types, just skip them if there is no value to write
if (expectedType.IsComplex() && graph.GetType().GetProperty("Instance")?.GetValue(graph) == null
&& (bool?)graph.GetType().GetProperty("UseInstanceForProperties")?.GetValue(graph) == true)
{
// skip properties that are null, especially if they are wrapped in generic types or explicitly requested by an expander
}
else
{
base.WriteObjectInline(graph, expectedType, writer, writeContext);
}
}
}
Q:如果我们还需要省略空列表属性呢?
如果您想使用相同的逻辑来排除所有列表,如果它们为空,那么您可以删除expectedType.IsComplex()
clause:
// special case, nullable Complex Types, just skip them if there is no value to write
if (graph.GetType().GetProperty("Instance")?.GetValue(graph) == null
&& (bool?)graph.GetType().GetProperty("UseInstanceForProperties")?.GetValue(graph) == true)
{
// skip properties that are null, especially if they are wrapped in generic types or explicitly requested by an expander
}
我不建议对导航属性列表使用此方法,只有在明确请求导航属性时,导航属性才会包含在输出中。$expand
子句,或者通过其他基于约定的逻辑,您可能会做同样的事情。输出中的空或 null 数组对于某些客户端逻辑可能很重要,可以确认所请求的属性数据已加载但没有数据可返回。