odata.net icon indicating copy to clipboard operation
odata.net copied to clipboard

JSON Body deserialization doesn't error when a non-primitive collection contains a primitive

Open DavidSimner opened this issue 4 years ago • 3 comments

When deserializing the JSON Body of a HTTP POST request, and when the model contains a collection of a non-primitive type, if the POST Body specifies a primitive type as one of the entities in this collection then no error is observed.

For example, if the model for ProductFamily is:

    public class ProductFamily
    {
        public int Id { get; set; }

        public string Name { get; set; }

        public string Description { get; set; }

        public virtual Supplier Supplier { get; set; }

        public virtual ICollection<Product> Products { get; set; } = new List<Product>();
    }

Then a HTTP POST request to create a ProductFamily that demonstrates the bug has this POST Body:

{
  "Id": 9,
  "Products": ["Hello"]
}

The corresponding response should be a 400 Bad Request (and this is the behaviour in version 7.1.0), because in the JSON, Products is an array with 1 item -- "Hello" -- however that JSON array containing a string can't be deserialized into ICollection<Product> because string is not the same type as Product.

Whereas in version 7.2.1 and later, what happens is that the string "Hello" is dropped on the floor, and the controller method is called with an empty list, which means that it will return a 201 Created.

Assemblies affected

This bug was introduced in the NuGet package Microsoft.AspNetCore.OData version 7.2.1 (version 7.1.0 works correctly).

I think that the root cause is code in Microsoft.AspNet.OData.Shared and specifically in the class Microsoft.AspNet.OData.Formatter.Deserialization.ODataReaderExtensions

Reproduce steps

  • Load the sample https://github.com/DavidSimner/ODataSamples/tree/patch-1/WebApiCore/ODataServiceSample (this is a fork of the ODataSamples repo, the only change is https://github.com/OData/ODataSamples/pull/134)
  • Start the project ODataService.Web
  • Make a HTTP POST request to /odata/ProductFamilies with the POST Body:
{
  "Id": 9,
  "Products": ["Hello"]
}

Expected result

The HTTP request should fail with a 400 Bad Request -- this is the behaviour observed in version 7.1.0

Actual result

The HTTP request succeeds with a 201 Created -- this is the behaviour observed in version 7.2.1 and later.

Additional detail

When deserializing the example above, a Contract.Assert detects the issue, see: https://github.com/OData/WebApi/blob/1b6c9bf59bf605b4462234db147cf6c2797025a6/src/Microsoft.AspNet.OData.Shared/Formatter/Deserialization/ODataReaderExtensions.cs#L134

DavidSimner avatar Jul 09 '20 08:07 DavidSimner

@DavidSimner you can get an error message in the ModelState from the controller.

ElizabethOkerio avatar Jul 14 '20 16:07 ElizabethOkerio

@ElizabethOkerio I am also facing this exactly same issue. Also ModelState does not contain any error message.

naansa avatar Apr 12 '21 12:04 naansa

@ElizabethOkerio We are currently on Microsoft.AspNetCore.OData 8.0.10 and still seeing this issue.

KK911 avatar Jul 01 '22 07:07 KK911

Fixed by #2072 Fell free to create a new issue if it still persists

KenitoInc avatar May 05 '23 08:05 KenitoInc