1

So, here's the situation: I have a complex object that's coming from a database. And I have a second object (a list of objects, actually), that I want to APPEAR to the user to be something they can expand to, but are actually just runtime objects I have in the application..

So, what I'm trying to do is create a custom ODataResourceSerializer to append a model to data that's being serialized. I'm guessing I need to override AppendDynamicProperties, CreateStructuralProperty, CreateResource, or CreateSelectExpandNode, but I just don't know which one and how.

enter image description here

So in the image above, the normal EF/OData setup returns the left side when expanding to "RelatedPosts" b/c there aren't any in the DB, but I want to basically insert data there at serialization time.

Possible?

For some history, in overriding AppendDynamicProperties, I've been able to add a brand new property, as you can see in the screenshot above as "MyCustomProp". But I haven't figured out how to add an entire object. Here's the code for my class extending the ODataResourceSerializer

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.OData.Formatter.Serialization;
using Microsoft.OData;
using System.Web.OData;
using Microsoft.OData.Edm;

namespace Boomerang.OData
{
    public class CustomODataResourceSerializer : ODataResourceSerializer
    {
        public CustomODataResourceSerializer(ODataSerializerProvider serializerProvider) : base(serializerProvider)
        {

        }

        public override Microsoft.OData.ODataProperty CreateStructuralProperty(IEdmStructuralProperty structuralProperty, ResourceContext resourceContext)
        {
            Microsoft.OData.ODataProperty property = base.CreateStructuralProperty(structuralProperty, resourceContext);
            return property;
        }

        public override void AppendDynamicProperties(ODataResource resource, SelectExpandNode selectExpandNode, ResourceContext resourceContext)
        {
            // add property
            var list = resource.Properties.ToList();
            list.Add(new ODataProperty() { Name = "MyCustomProp", Value = "This Thing" });
            resource.Properties = list.AsEnumerable();

            base.AppendDynamicProperties(resource, selectExpandNode, resourceContext);
        }
        public override ODataResource CreateResource(SelectExpandNode selectExpandNode, ResourceContext resourceContext)
        {
            return base.CreateResource(selectExpandNode, resourceContext);
        }
        public override SelectExpandNode CreateSelectExpandNode(ResourceContext resourceContext)
        {
            return base.CreateSelectExpandNode(resourceContext);
        }
        public override void WriteObject(object graph, Type type, ODataMessageWriter messageWriter, ODataSerializerContext writeContext)
        {
            base.WriteObject(graph, type, messageWriter, writeContext);
        }
    }
}
rockyourteeth
  • 113
  • 1
  • 8
  • 1
    Possible, provide your code that you have tried – Aria Nov 12 '17 at 07:34
  • Okay, adding it above... – rockyourteeth Nov 12 '17 at 18:58
  • instead of an `ODataProperty` with a value of `"This Thing"` try to build a (collection of) `ODataComplexValue` and add that – user326608 Feb 23 '18 at 05:52
  • rockyourteeth, did you find how to do it ? i want to implement the Display Vocabulary but there is no OData property display attribute. [my problem](https://github.com/OData/WebApi/issues/1573) @user326608 thanks, do you have an example? – flieks Aug 21 '18 at 06:25
  • @flieks see the 'CreateComplexValue' helper method in the utils class at the bottom of https://stackoverflow.com/a/36976343 – user326608 Aug 21 '18 at 06:36
  • @user326608 does that work for .NET Core also because the odata serialisation is different there. So that allows you to expand a navigation prop even if not expanded in the URL ? – flieks Oct 05 '18 at 14:18
  • @flieks i think its the same, ODataLib does the work. that code is for building the feed queryable, whether they are expanded depends on the operators applied. the code will always run even if the objects aren't expanded unfortunately – user326608 Oct 08 '18 at 05:24
  • @user326608 we want to expand it even if no operators (in URL) are applied. Seems very hard or not possible. Thanks – flieks Oct 16 '18 at 15:34
  • @flieks i have an odata sample proj at https://github.com/kevin-osborne/ODataSample. if you combine that with the code from https://stackoverflow.com/a/36976343 you should get what you want – user326608 Oct 16 '18 at 23:53
  • @flieks, no i was not able to accomplish what I wanted. I have since moved onto another project so unfortunately I don't have a nice conclusion for you. Good luck! – rockyourteeth Oct 17 '18 at 17:17
  • @user326608 your sample is using 1 entity (without navigations) but ok, we don't need it anymore. It was better to use another way of getting the data – flieks Oct 19 '18 at 07:40

1 Answers1

0

Thanks to user326608 for pointing us to this answer on SO regarding ODataMediaTypeFormatter: https://stackoverflow.com/a/36976343

My issue is a bit different to OP, however it shows an extension point where you can effectively construct your own output, regardless of the structures that OData knows about in the metadata.

The following solution ignores the SelectExpandNode for the complex type serialization altogether, you could probably construct the SelectExpandNode to include the necessary metadata but it's less effort to just force the values into the output.

In your ODataResourceSerializer just override CreateResource:

public override ODataResource CreateResource(SelectExpandNode selectExpandNode, ResourceContext resourceContext)
{
    var resource = base.CreateResource(selectExpandNode, resourceContext);
    // Check for conditions where you want to Augment the output
    // for me (CS) I don't want to allow partial complex types.
    if (resourceContext.StructuredType.TypeKind == EdmTypeKind.Complex)
    {
        // I want ALL the properties from the complex type, if there are NONE, then generate them all!
        // This shows how to add properties to the existing output, 
        // The data I want is already available to me in the data query (ResourceInstance) that we are serializing, but you could go get it now if you need to.
        // the only caveate for manipulating existing resources is that you need to include any existing properties if you want them
        if (!resource.Properties.Any())
        {
            var type = resourceContext.ResourceInstance.GetType();
            PropertyInfo[] complex_properties = type.GetProperties();

            List<ODataProperty> child_properties = new List<ODataProperty>();
            foreach (PropertyInfo property in complex_properties)
            {
                ODataProperty child_property = CreateProperty(property.Name, property.GetValue(resourceContext.ResourceInstance));
                child_properties.Add(child_property);
            }
            resource.Properties = child_properties;
        }
    }
    return resource;
}

public static ODataResource CreateComplexValue(Type type, object value)
{
    ODataResource complex_value = new ODataResource();
    // Not sure this name will work, we probably need to lookup the name of this type from the OData metadata,
    // but I'm not sure that we can have complex value properties on a complex value anyway.
    complex_value.TypeName = type.ToString();
    PropertyInfo[] complex_properties = type.GetProperties();
    List<ODataProperty> child_properties = new List<ODataProperty>();
    foreach (PropertyInfo property in complex_properties)
    {
        ODataProperty child_property = CreateProperty(property.Name, property.GetValue(value));
        if(child_property != null)
            child_properties.Add(child_property);
    }
    complex_value.Properties = child_properties;
    return complex_value;
}

public static bool IsPrimitiveType(Type t)
{
    if (!t.IsPrimitive && t != typeof(Decimal) && t != typeof(String) && t != typeof(Guid) && t != typeof(DateTime)) // todo
    {
        return false;
    }
    return true;
}

public static ODataProperty CreateProperty(string name, object value)
{
    object property_value = value;
    if (value != null)
    {
        Type t = value.GetType();
        if (!IsPrimitiveType(t))
        {
            property_value = CreateComplexValue(t, value);
        }
        else if (t == typeof(DateTime) || t == typeof(DateTime?))
        {
            DateTime dt = (DateTime)value;
            dt = DateTime.SpecifyKind(dt, DateTimeKind.Utc);
            DateTimeOffset dto = dt;
            property_value = dto;
        }
    }
    ODataProperty new_property = new ODataProperty()
    {
        Name = name,
        Value = property_value
    };
    return new_property;
}
Chris Schaller
  • 13,704
  • 3
  • 43
  • 81