How to support a nested open complex type in the OData C# driver?

偶尔善良 提交于 2021-02-04 14:06:42

问题


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

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!