如何在 OData C# 驱动程序中支持嵌套的开放式复杂类型?
How to support a nested open complex type in the OData C# driver?
我在 .NET Web Api 项目中使用以下 C# OData 包:
Install-Package Microsoft.AspNet.OData
Install-Package Microsoft.AspNet.WebApi.OData
当遵循 Microsoft 的示例 Use Open Types in OData v4 时,一切似乎都按预期工作,只要开放类型不包含额外的嵌套开放复杂类型。
这意味着这可以正常工作:
public class WplController : ODataController
{
private List<AbstractMongoDocument> _documents = new List<AbstractMongoDocument>
{
new AbstractMongoDocument
{
Id = "2",
Meta = new MongoMeta(),
Data = new MongoData
{
Document = new Dictionary<string, object>()
{
{"root_open_type", "This works!" },
}
}
}
};
[EnableQuery]
public IQueryable<AbstractMongoDocument> Get()
{ return _documents.AsQueryable();}
}
虽然这会引发异常
public class WplController : ODataController
{
private List<AbstractMongoDocument> _documents = new List<AbstractMongoDocument>
{
new AbstractMongoDocument
{
Id = "1",
Meta = new MongoMeta(),
Data = new MongoData
{
Document = new Dictionary<string, object>()
{
{"root_open_type", "This works!" },
{"nested_open_type", new Dictionary<string, object>() //Nested dictionary throws exception!
{
{"field1", "value2" },
{"field2", "value2" }
}
}
}
}
}
};
[EnableQuery]
public IQueryable<AbstractMongoDocument> Get()
{ return _documents.AsQueryable();}
}
异常情况如下:
System.InvalidOperationException occurred
Message: The 'ObjectContent`1' type failed to serialize the response body for content type 'application/json; odata.metadata=minimal'.
Message: Exception thrown: 'System.InvalidOperationException' in System.Web.OData.dll
Additional information: The given model does not contain the type 'System.Collections.Generic.Dictionary`2[System.String,System.Object]'.
这可以通过将以下行添加到 WebApiConfig.cs
中的 ODataConventionModelBuilder
来解决:
builder.ComplexType<Dictionary<string, object>>();
但是,这会导致以下 OData 响应 JSON:
{
"@odata.context": "http://localhost:50477/odata/$metadata#wpl",
"value":
[
{
"Id": "1",
"Meta": {},
"Data":
{
"root_open_type": "This works!",
"nested_open_type":
{
"@odata.type": "#System.Collections.Generic.Dictionary_2OfString_Object",
"Keys":
[
"field1",
"field2"
]
}
}
}
]
}
如何确保 ODate 也正确序列化嵌套的开放字段? IE。我想要以下结果 OData JSON:
{
"@odata.context": "http://localhost:50477/odata/$metadata#wpl",
"value":
[
{
"Id": "1",
"Meta": {},
"Data":
{
"root_open_type": "This works!",
"nested_open_type":
{
"field1": "value1",
"field2": "value2"
}
}
}
]
}
提前感谢您提供的任何帮助!
将 Dictionary<string, object>
添加为开放式复杂类型可能会使构建器感到困惑。开放类型需要定义一个包含动态属性的 属性。在Dictionary<string, object>
的情况下,似乎认为Keys
集合是开放类型的动态属性容器。尝试创建一个类型来定义为复杂类型,如下所示:
public class OpenComplexType
{
public IDictionary<string, object> DynamicProperties { get; set; }
}
然后注册为复杂类型:
builder.ComplexType<OpenComplexType>();
最后,使用 OpenComplexType
作为类型定义您的 Document
属性:
public class MongoData
{
public OpenComplexType Document { get; set; }
}
我绝不是 WebAPI OData 库的专家,可能还有其他方法可以通过使用 Dictionary<string, object>
来解决这个问题,但这应该是一个起点。
我和你在同一条船上。我需要公开一些纯数据 JSON。这是一个使用 ODataUntypedValue
class 的有效解决方案。它序列化为您所期望的。我用你的模型和我的模型测试过它。
实施 MongoDataSerializer
class:
public class MongoDataSerializer: ODataResourceSerializer
{
public MongoDataSerializer(ODataSerializerProvider serializerProvider)
: base(serializerProvider)
{
}
/// <summary>
/// Serializes the open complex type as an <see cref="ODataUntypedValue"/>.
/// </summary>
/// <param name="graph"></param>
/// <param name="expectedType"></param>
/// <param name="writer"></param>
/// <param name="writeContext"></param>
public override void WriteObjectInline(
object graph,
IEdmTypeReference expectedType,
ODataWriter writer,
ODataSerializerContext writeContext)
{
// This cast is safe because the type is checked before using this serializer.
var mongoData = (MongoData)graph;
var properties = new List<ODataProperty>();
foreach (var item in mongoData.Document)
{
properties.Add(new ODataProperty
{
Name = item.Key,
Value = new ODataUntypedValue
{
RawValue = JsonConvert.SerializeObject(item.Value),
},
});
}
writer.WriteStart(new ODataResource
{
TypeName = expectedType.FullName(),
Properties = properties,
});
writer.WriteEnd();
}
}
实施 CustomODataSerializerProvider
class:
public class CustomODataSerializerProvider : DefaultODataSerializerProvider
{
private readonly MongoDataSerializer mongoDataSerializer;
public CustomODataSerializerProvider(
IServiceProvider odataServiceProvider)
: base(odataServiceProvider)
{
this.mongoDataSerializer = new MongoDataSerializer(this);
}
public override ODataEdmTypeSerializer GetEdmTypeSerializer(IEdmTypeReference edmType)
{
if (edmType.FullName() == typeof(MongoData).FullName)
{
return this.mongoDataSerializer;
}
return base.GetEdmTypeSerializer(edmType);
}
}
在您的 Startup.cs
中注册 CustomODataSerializerProvider
:
app.UseMvc(options =>
{
var model = builder.GetEdmModel();
options
.MapODataServiceRoute(
"odata",
"odata",
b => b
.AddService(Microsoft.OData.ServiceLifetime.Scoped, s => model)
.AddService<IEnumerable<IODataRoutingConvention>>(
Microsoft.OData.ServiceLifetime.Scoped,
s => ODataRoutingConventions.CreateDefaultWithAttributeRouting("odata", options))
.AddService<ODataSerializerProvider, CustomODataSerializerProvider>(Microsoft.OData.ServiceLifetime.Singleton));
}
这是使用您的模型的输出(注意 属性 名称以小写字母开头,因为我启用了 ODataConventionModelBuilder.EnableLowerCamelCase()
):
我在 .NET Web Api 项目中使用以下 C# OData 包:
Install-Package Microsoft.AspNet.OData
Install-Package Microsoft.AspNet.WebApi.OData
当遵循 Microsoft 的示例 Use Open Types in OData v4 时,一切似乎都按预期工作,只要开放类型不包含额外的嵌套开放复杂类型。
这意味着这可以正常工作:
public class WplController : ODataController
{
private List<AbstractMongoDocument> _documents = new List<AbstractMongoDocument>
{
new AbstractMongoDocument
{
Id = "2",
Meta = new MongoMeta(),
Data = new MongoData
{
Document = new Dictionary<string, object>()
{
{"root_open_type", "This works!" },
}
}
}
};
[EnableQuery]
public IQueryable<AbstractMongoDocument> Get()
{ return _documents.AsQueryable();}
}
虽然这会引发异常
public class WplController : ODataController
{
private List<AbstractMongoDocument> _documents = new List<AbstractMongoDocument>
{
new AbstractMongoDocument
{
Id = "1",
Meta = new MongoMeta(),
Data = new MongoData
{
Document = new Dictionary<string, object>()
{
{"root_open_type", "This works!" },
{"nested_open_type", new Dictionary<string, object>() //Nested dictionary throws exception!
{
{"field1", "value2" },
{"field2", "value2" }
}
}
}
}
}
};
[EnableQuery]
public IQueryable<AbstractMongoDocument> Get()
{ return _documents.AsQueryable();}
}
异常情况如下:
System.InvalidOperationException occurred
Message: The 'ObjectContent`1' type failed to serialize the response body for content type 'application/json; odata.metadata=minimal'.
Message: Exception thrown: 'System.InvalidOperationException' in System.Web.OData.dll
Additional information: The given model does not contain the type 'System.Collections.Generic.Dictionary`2[System.String,System.Object]'.
这可以通过将以下行添加到 WebApiConfig.cs
中的 ODataConventionModelBuilder
来解决:
builder.ComplexType<Dictionary<string, object>>();
但是,这会导致以下 OData 响应 JSON:
{
"@odata.context": "http://localhost:50477/odata/$metadata#wpl",
"value":
[
{
"Id": "1",
"Meta": {},
"Data":
{
"root_open_type": "This works!",
"nested_open_type":
{
"@odata.type": "#System.Collections.Generic.Dictionary_2OfString_Object",
"Keys":
[
"field1",
"field2"
]
}
}
}
]
}
如何确保 ODate 也正确序列化嵌套的开放字段? IE。我想要以下结果 OData JSON:
{
"@odata.context": "http://localhost:50477/odata/$metadata#wpl",
"value":
[
{
"Id": "1",
"Meta": {},
"Data":
{
"root_open_type": "This works!",
"nested_open_type":
{
"field1": "value1",
"field2": "value2"
}
}
}
]
}
提前感谢您提供的任何帮助!
将 Dictionary<string, object>
添加为开放式复杂类型可能会使构建器感到困惑。开放类型需要定义一个包含动态属性的 属性。在Dictionary<string, object>
的情况下,似乎认为Keys
集合是开放类型的动态属性容器。尝试创建一个类型来定义为复杂类型,如下所示:
public class OpenComplexType
{
public IDictionary<string, object> DynamicProperties { get; set; }
}
然后注册为复杂类型:
builder.ComplexType<OpenComplexType>();
最后,使用 OpenComplexType
作为类型定义您的 Document
属性:
public class MongoData
{
public OpenComplexType Document { get; set; }
}
我绝不是 WebAPI OData 库的专家,可能还有其他方法可以通过使用 Dictionary<string, object>
来解决这个问题,但这应该是一个起点。
我和你在同一条船上。我需要公开一些纯数据 JSON。这是一个使用 ODataUntypedValue
class 的有效解决方案。它序列化为您所期望的。我用你的模型和我的模型测试过它。
实施 MongoDataSerializer
class:
public class MongoDataSerializer: ODataResourceSerializer
{
public MongoDataSerializer(ODataSerializerProvider serializerProvider)
: base(serializerProvider)
{
}
/// <summary>
/// Serializes the open complex type as an <see cref="ODataUntypedValue"/>.
/// </summary>
/// <param name="graph"></param>
/// <param name="expectedType"></param>
/// <param name="writer"></param>
/// <param name="writeContext"></param>
public override void WriteObjectInline(
object graph,
IEdmTypeReference expectedType,
ODataWriter writer,
ODataSerializerContext writeContext)
{
// This cast is safe because the type is checked before using this serializer.
var mongoData = (MongoData)graph;
var properties = new List<ODataProperty>();
foreach (var item in mongoData.Document)
{
properties.Add(new ODataProperty
{
Name = item.Key,
Value = new ODataUntypedValue
{
RawValue = JsonConvert.SerializeObject(item.Value),
},
});
}
writer.WriteStart(new ODataResource
{
TypeName = expectedType.FullName(),
Properties = properties,
});
writer.WriteEnd();
}
}
实施 CustomODataSerializerProvider
class:
public class CustomODataSerializerProvider : DefaultODataSerializerProvider
{
private readonly MongoDataSerializer mongoDataSerializer;
public CustomODataSerializerProvider(
IServiceProvider odataServiceProvider)
: base(odataServiceProvider)
{
this.mongoDataSerializer = new MongoDataSerializer(this);
}
public override ODataEdmTypeSerializer GetEdmTypeSerializer(IEdmTypeReference edmType)
{
if (edmType.FullName() == typeof(MongoData).FullName)
{
return this.mongoDataSerializer;
}
return base.GetEdmTypeSerializer(edmType);
}
}
在您的 Startup.cs
中注册 CustomODataSerializerProvider
:
app.UseMvc(options =>
{
var model = builder.GetEdmModel();
options
.MapODataServiceRoute(
"odata",
"odata",
b => b
.AddService(Microsoft.OData.ServiceLifetime.Scoped, s => model)
.AddService<IEnumerable<IODataRoutingConvention>>(
Microsoft.OData.ServiceLifetime.Scoped,
s => ODataRoutingConventions.CreateDefaultWithAttributeRouting("odata", options))
.AddService<ODataSerializerProvider, CustomODataSerializerProvider>(Microsoft.OData.ServiceLifetime.Singleton));
}
这是使用您的模型的输出(注意 属性 名称以小写字母开头,因为我启用了 ODataConventionModelBuilder.EnableLowerCamelCase()
):