0

I have 2 classes: Item and ItemClass and Item is referenced as a Collection in ItemClass:

public class ItemClass : DbEntityInteger, IDbInternalcodeEntity, IDbNameEntity
{
    public ItemClass()
    {
        Items = new List<Item>();
    }
    public virtual string InternalCode { get; set; }
    //[System.Text.Json.Serialization.JsonIgnore]
    public virtual IList<Item> Items { get; set; }
    public virtual string? Name { get; set; }
}

I've developed a webapi including OData Query Language so i'd like to have this behavior: Items Property must be serialized only when i use the $expand command otherwise a request to the api that return Item Classes must have this property empty.

I tried to decore Items Property with System.Text.Json.Serialization.JsonIgnore attributre but obviously the property is no longer serialized, but, if i remove the attribute the Items Collection is alwayse serialized, even without the using of $expand.

2022-04-12 edit:
According to this example and this video I only added this code in my project:
Startup:
builder.Services.AddControllers().AddOData(options => options.Select().Filter().OrderBy().Expand().Count().SetMaxTop(50).SkipToken());
Controller:
Add [EnableQuery] on method Get of my ItemClassController.

But this is not enough.
According to this other example, Controller must inherits from ODataController or implements [ODataAttributeRouting] and in Startup the EdmModel must be defined and declared in AddControllers().
Startup:

static IEdmModel GetEdmModel()
{
    ODataConventionModelBuilder builder = new();
    builder.EntitySet<ItemClass>("ItemClasses");
    return builder.GetEdmModel();
}
[...]
builder.Services.AddControllers().AddOData(opt => opt.AddRouteComponents("odata", GetEdmModel())
                                        .Select()
                                        .Filter()
                                        .OrderBy()
                                        .Expand()
                                        .Count()
                                        .SetMaxTop(50)
                                        .SkipToken()
);


ItemClassController:
public class ItemClassesController : ODataController
    {
        private readonly ILogger<ItemsController> _logger;
        private readonly IItemManager _itemManager;

        public ItemClassesController(ILogger<ItemsController> logger, IItemManager itemManager)
        {
            _logger = logger;
            _itemManager = itemManager;
        }

        [EnableQuery(PageSize = 15)]
        public ActionResult<IEnumerable<Test.Core.ItemClass>> Get()
        {
            IEnumerable<Test.Core.ItemClass> result;
            result = _itemManager.ItemClassSessionMapper.Elements;
            return new ActionResult<IEnumerable<Test.Core.ItemClass>>(result);
        }
    }

Now it works, subclasses are not expanded by default and response is encapsulated in odata context:
  "@odata.context": "http://localhost:5253/odata/$metadata#ItemClasses",
  "value": [
    {
      "InternalCode": "TST",
      "Name": "Test Class",
      "Id": 3,
      "Version": 0,
      "UniqueKey": "4b01b1a6-8042-44d2-9eb2-a1c9ef7f86cf",
      "DateTimeCreated": "2022-04-12T08:48:33.8578697+02:00",
      "DateTimeModified": null,
      "UserCreated": null,
      "UserModified": null,
      "Note": null,
      "DateTimeLoadedInSession": "2022-04-12T17:23:43.8772784+02:00"
    }
  ]
}

Unfortunately expand works with single reference property but not with collections, I suppose could depend on this version of OData which is still under development.
This is part of the exception message I get when I try to expand Items Collection:

 ---> System.ArgumentException: Argument types do not match
   at System.Linq.Expressions.Expression.Condition(Expression test, Expression ifTrue, Expression ifFalse)
   at NHibernate.Linq.ReWriters.SubQueryConditionalExpander.SubQueryFromClauseExpander.VisitConditional(ConditionalExpression node)

2022-04-14 edit:
Solved!
The issue with $Expand "Argument types do not match" was due to this reason:
in ItemClassesController I returned ItemClass Elements as IEnumerable<Test.Core.ItemClass> but, in ItemClass object the property Items was public virtual IList<Item> Items { get; set; } and it seems that OData service is unable to cast form IList to IEnumerable.
Changing Get method resolves the issue:

        [HttpGet]
        [EnableQuery(PageSize = 100)]
        public ActionResult<IList<Test.Core.ItemClass>> Get()
        {
            IList<Test.Core.ItemClass> result;
            result = _itemManager.ItemClassSessionMapper.Elements.ToList();
            return new ActionResult<IList<Test.Core.ItemClass>>(result);
        }

This is not Nhibernate Layer fault because if I deactivate OData and return an IEnumerable<Test.Core.ItemClass>, Items collection is correctly serialized.

hyppos
  • 7
  • 3
  • What is the type of Item? Is it class? – Dilshod K Apr 11 '22 at 13:35
  • Yes, Item is a class – hyppos Apr 11 '22 at 14:57
  • You have tagged your question [tag:json.net]. Assuming the presence of `$expand` is available at the time `ItemClass` is constructed, you could use [conditional property serialization](https://www.newtonsoft.com/json/help/html/ConditionalProperties.htm) to serialize `Items` only when needed by adding a method `public bool ShouldSerializeItems() => /* your logic here */;` See: [How to Ignoring Fields and Properties Conditionally During Serialization Using JSON.Net?](https://stackoverflow.com/q/34304738/3744182), which looks to be a duplicate. Agree? – dbc Apr 11 '22 at 15:38
  • I've tagged json.net because asp.net core webapi use Json.NET to serialize/deserlialize. $expand is a predicate of OData technology (https://devblogs.microsoft.com/odata/up-running-w-odata-in-asp-net-6/) that is added in the UI webapi project. My model classes (Item and ItemClass) cannot know if this technology will be used in the UI so it is neither possible nor correct to add conditional serialization logic on the model. – hyppos Apr 11 '22 at 16:18
  • If there is no directive to achieve the desired default behavior, I could probably use your suggestion by transforming the model classes into "partial classes" and extending them in the UI project or by implementing a ContractResolver. – hyppos Apr 11 '22 at 16:18
  • OK, outside my expertise then, but perhaps you could move the `Items` from an ignored property to a serialized property in a [custom `ODataEntityTypeSerializer`](https://learn.microsoft.com/en-us/odata/webapi/customize-formatter#a-custom-entity-serializer) - or maybe in a [`ODataResourceSerializer`](https://devblogs.microsoft.com/odata/build-formatter-extensions-in-asp-net-core-odata-8-and-hooks-in-odataconnectedservice/#customize-serializer-to-return-ettag-from-service) depending on your version. – dbc Apr 11 '22 at 17:04
  • Usually, if it is class it will not include it in response until you call expand. Did you mark it as property in mapping? – Dilshod K Apr 12 '22 at 03:29
  • @DilshodK what do you mean? I use NHibernate as ORM and it is mapped with "HasMany" reference, there is no other information to set: `HasMany(x => x.Items).KeyColumn("ART_CART_Id").Cascade.None();` – hyppos Apr 12 '22 at 06:25
  • OK, Can you show your response? – Dilshod K Apr 12 '22 at 06:29
  • @DilshodK you can download the response here: [link](https://r1-it.storage.cloud.it/det.stack/response_1649746242244.json?time=637853510813962885) – hyppos Apr 12 '22 at 07:05
  • aa, your request is not going to OData endpoint. Response should have @odata.context and value. Can you show your configuration and controller? – Dilshod K Apr 12 '22 at 07:23
  • I've implemented this [example](https://devblogs.microsoft.com/odata/up-running-w-odata-in-asp-net-6/) – hyppos Apr 12 '22 at 08:42
  • Adding OData: `builder.Services.AddControllers().AddOData(options => options.Select().Filter().OrderBy().Expand().Count().SetMaxTop(50).SkipToken());` – hyppos Apr 12 '22 at 08:42
  • And attribute: `[HttpGet] [EnableQuery] [Route("ItemClasses")] public ActionResult> GetItemClasses() { IEnumerable result; result = _artManager.ClasseSessionMapper.Elements; return new ActionResult>(result); }` – hyppos Apr 12 '22 at 08:43
  • Could you please update your question in a way to that your issue can reprouce easily according to your current state. – Md Farid Uddin Kiron Apr 12 '22 at 09:34
  • @MdFaridUddinKiron I've added some informations. It should now be clearer. – hyppos Apr 12 '22 at 15:50
  • Okay thanks you what's `Test.Core.ItemClass`? Did you able check what exact error or details you are getting while debugging? – Md Farid Uddin Kiron Apr 19 '22 at 09:17

0 Answers0