问题
I am using the following C# OData packages, in a .NET Web Api project:
Install-Package Microsoft.AspNet.OData
Install-Package Microsoft.AspNet.WebApi.OData
When following Microsoft's example Use Open Types in OData v4, everything seems to work as expected, as long as the open type does not contain additional nested open complex types.
This means that this will work fine:
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();}
}
While this throws an exception
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();}
}
The exception is as follows:
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]'.
This can be fixed by adding the following line to the ODataConventionModelBuilder
in WebApiConfig.cs
:
builder.ComplexType<Dictionary<string, object>>();
However, this leads to the following OData response 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"
]
}
}
}
]
}
How can I make sure that ODate properly serializes the nested open fields as well? I.e. I would like the following resulting 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"
}
}
}
]
}
Thanks in advance for any potential help!
回答1:
I'm in the same boat as you. I need to expose some data as pure JSON. This is a working solution using the ODataUntypedValue
class. It serializes to what you would expect. I tested it with your models and mine.
Implement a 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();
}
}
Implement a 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);
}
}
Register the CustomODataSerializerProvider
in your Startup.cs
:
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));
}
This is the output using your models (note the property names begin with a lower case letter because I enabled ODataConventionModelBuilder.EnableLowerCamelCase()
):
回答2:
Adding Dictionary<string, object>
as an open complex type may be confusing the builder. Open types need to define a property that contains the dynamic properties. In the case of Dictionary<string, object>
, it seems to think that the Keys
collection is the dynamic property container for the open type. Try creating a type to define as the complex type, something like the following:
public class OpenComplexType
{
public IDictionary<string, object> DynamicProperties { get; set; }
}
Then register it as a complex type:
builder.ComplexType<OpenComplexType>();
Finally, define your Document
property using OpenComplexType
as the type:
public class MongoData
{
public OpenComplexType Document { get; set; }
}
I am by no means an expert with the WebAPI OData libraries, and there may be other ways to work around this by using Dictionary<string, object>
, but this should be a place to start.
来源:https://stackoverflow.com/questions/50682776/how-to-support-a-nested-open-complex-type-in-the-odata-c-sharp-driver