Webapi: Null value in the controller after 'put'/'post' request.

Created on 12 Jan 2018  Â·  29Comments  Â·  Source: OData/WebApi

When sending of request on update the entity, controller have value is null in model. The model include sub type, wich have property of type DateTimeOffset. This property at updating have the value "7/3/2017 01:33 AM -07:00", (example when value in controller is null). But if send the request with "2017-03-07T01:33:52.433+07:00" value, then value in controller is correct.

Assemblies affected

Asp Net Core 2.0.1
nuget 7.0.0-Nightly201802131328

How this problem can workaround ?

For example was created simple the WebApi project with OData 6 version. On 6 version OData 'put' request on update is working - value in controller is correct.
Even if property have value "7/3/2017 01:33 AM -07:00" or null.

For example was created simple the WebApi project after update OData to last version with OData 7 version. If the value of property is "7/3/2017 01:33 AM -07:00", then get incorrect result - value in controller is null.

P2 bug featurnetcore in-progress

All 29 comments

When sending put request with format type of DateTimeOffest "7/3/2017 01:33 AM -07:00", and after debugging of source WebApi OData 7.0.0-Nightly201801121325 was found the exception in Microsoft.AspNet.OData.Shared\Formatter\Deserialization\ODataReaderExtensions.cs:line 31

We think PATCH command is also effected. I havent narrowed the problem yet, but all of our PATCH tests using JsonPatchDocument are failing.

We think PATCH command is also effected. I havent narrowed the problem yet, but all of our PATCH tests using JsonPatchDocument are failing.

This is exception was after sending of request with body:

    {
      "ID": "1",
      "name": "Tom",
      "email": "[email protected]",
      "actionTime.created":"2011-05-08T10:19:33.583+07:00",
      "actionTime.updated":"2011-05-08T10:19:33.583+07:00",
      "actionTime": {
        "created": "2011-05-08T10:19:33.583+07:00",
        "updated": "2011-05-08T10:19:33.583+07:00",
      }
    }

The exception:
when reading 'actionTime.created' from the JSON reader, In OData, Instance annotation name must start with @.
Yep, this duplicate properties is bad practice, but in the Core OData v6 work fine.
However, after removes the actionTime.created & actionTime.updated from body, the put/post request with date '10 Apr 2017 17:14' format doesn`t work.

I finds error with array objects in body request. If sending of request with body:

[
{"id":"1", "title":"Some text"},{"id":"2", "title":"Some text #2"},{"id":"3", "title":"Some text#3"}
]

then get exception:

An unexpected 'StartArray' node was found when reading from the JSON reader. A 'StartObject' node was expected.

This is exception again in ODataReaderExtensions.cs line: 31

When you work with JsonPatchDocument you follow RFC6902, the server expects something like:

[
    { "op": "add", "path": "/foo", "value": "bar"},
    { "op": "replace", "path": "/baz", "value": "boo" }
]

You need to use Delta <...> from OData.

After testing I found interesting moment: if using another class in controller(as argument), then controller take array of objects. This is class don`t registered in edm model. For example, I was reproduced this situation in the simple project.

I can add another variation of this problem (as I think it's connected). Post json data in Delta T deserializies only in string class fields of T. As I think json converter sees this fields as objects without type info. For example while debugging TrySetPropertyValue() in DeltaOfTStructuralType.cs I see value of type int64 (default type for integers) from json converter and in model propertyType is int32. Guid -> null and so on...

@voltage220 - Looks like a few (possibly related) issues here:

1.) DateTimeOffset encoding works differently in WebApi 5.x vs WebApi 7.x (due to a change in ODL 6.x vs 7.x I think).

2.) A problem with instance annotations due to a change from ODL 6.x. Is thi sissue reporesented in one of your samples? I did not see a model that matched the payload.

3.) A third problem related to this project: can you tell me what input URI you are using to repo the issue? I looked at the controllers in the sample project and could not understand how the issue is reproduced.

@HaroonSaid - Did @ErliSoares advice help resolve your issue?

@muhanov-apps - Can you provide a sample to reproduce the issue?

@robward-ms - I made project
there is one method in controller with Delta
if you make POST request with json like
{"testString":"foo", "testInt":"123", "testGuid":"7b862f57-c1d1-4dd0-be54-18fe6f865cfb"}
then in controller only testString will have value, other will remain default values

@robward-ms - thanks what was paid attention to the problem.
1) the problem with DateTimeOffset partially of resolved - using of ISO format date
2017-03-07T01:33:52.433+07:00. I right see, what this is behavior(use of ISO format need always) will always ?
2) I take the configure of EDM model as is from gide. About the problem with post request - if using class wich was added in configuration EDM
builder.EntitySet<Product>("Products");
then list will empty. But if using another class ProductDTO in the action public IActionResult Post([FromBody] List<ProductDTO> dtos), then dtos will full.
3) I was added some description in readme for reproducing problems. And then new problem was added - not recognized of input parameter in action(GET/POST request).

@muhanov-apps - I looked at your project, it seems to have no OData related code in it. You have linked against the OData library but have not configured any OData routes; looks the problem you are seeing it with ASP.NET MVC.

@robward-ms I thought as this method uses Delta class from Odata so the problem might be in Odata. This code was copy-paste from my previous classic .net mvc project with odata 6. And there it worked fine. When I ported it to AspNetCore - I get this behavior. And it's strange that strings are parsed fine but other types not... if it doesn’t work completely - I might suppose it’s something else then odata. But when it half works… ))

@muhanov-apps - Got it. I traced the code and the set property occurs in the Delta class and fails. I'm not sure if this is due to a platform, .Net Core vs .Net framework, or if it's an Delta<> bug yet. The failure occurs here, specifically:

!propertyType.IsAssignableFrom(value.GetType())

the code checks to see if int/guid is assignable from string, seems to be returning false here in these cases, hence the only property to be set is string.

From an OData persepctive, this appears to be the only OData class involved.

OK, there is a fair amount going on in this thread, I'll try to address them one at a time.

@voltage220 - For the DateTime issue, I think you have discovered this comes down to the serializable format of DateTime. There is a stack overflow issue on this topic here.

@HaroonSaid - Take a look at this post. Note that [FromBody] was optional in ASP.NET but is required in ASP.NET Core. Could this be the source of the issue?

@muhanov-apps - I was able to repro the issue you reported using your project and was able to make it work using @voltage220's project. The issue comes down to this: when using an OData route, the OData deserializer is used and it's able to convert "" into a Guid. When using a non-OData route, the Delta class is deserialized by the ASP.ENT deserializer and the "" string directly and is unable to convert it to the Guid. Any chance your older project was using the OData formatters? In ASP.NET Core, the OData formatters are only used on OData routes.

@voltage220 - I loaded up your sample and here is what I found:
1.) The methods of your controller don’t match the expected routing conventions for OData. If you look at this, you see that post is used for adding an entity, i.e. Post(Product item). Put is used to update a particular item, i.e. Put(int key, Product item). I modified the controller (see below) to match the expected OData routing.

2.) Once I ensured that Content-Type was specified and that I used lower case property names, due to builder.EnableLowerCamelCase();, the request was routed property to the Put() and Post() methods as expected.

Put URL: http://localhost:49473/odata/Products(100)
Post URL: http://localhost:49473/odata/Products
Headers (put & post):
User-Agent: Fiddler
Host: localhost:49473
Content-Type: application/json
Content-Length: 27

Payload (put & post):
{"id":"100", "name":"name"}

My version of the controller:

    [EnableQuery]
    public class ProductsController : ODataController
    {
        ...
        public IActionResult Post([FromBody]Product item)
        {
            if (item == null) return BadRequest();

            return Ok(item);
        }

        public IActionResult Put(int key, [FromBody]Product item)
        {
            if (item == null) return BadRequest();

            return Ok(item);
        }
        ...
    }

@robward-ms you idea with deserializer was right. I looked into my old odata6 project and found out, that I used custom deserializer from Json.Net. I copied it to core project and now it works too... though I will experiment with native OData formatters. Thank you for your time.

@robward-ms - thanks for you example of controller. But I talk about action(put/post) wich accepting list of items Products:
public IActionResult Put(int key, [FromBody] List<Product> items)
This list items will is empty. But if use another class(ProductDTO), which does not registered in EDM, the items will have values:
public IActionResult Put(int key, [FromBody] List<ProductDTO> items)

@robward-ms - situation, when the post action takes the collection of data on input and this collection is null. This case is bug or wrong the configuration the edm and the controller?
The test project was updated to latest version night build WebApi Odata.

@muhanov-apps Could you tell me how you replaced the deserializer with one from Json.NET?

I'm running into a similar issue where my model is null in an ODataController if I have "bad data" (example: upload a number instead of a string) but the same model works fine in a standard ASP.NET Controller.

For example, this fails on ODataController but works fine on an ASP.NET Controller:

{ "MemberNumber": 5 }

as MemberNumber is defined as public virtual string MemberNumber { get; set; }. This works fine on both:

{ "MemberNumber": "5" }

While I should always be sending the correct values, getting a null model with no error/explanation to why is rather frustration and takes a good bit of trail/error to figure out the root problem (especially before we realized it was a datatype issue)

@techniq Use this class
and in Startup.cs / ConfigureServices(IServiceCollection services)

services.AddMvc().AddJsonOptions(options => { options.SerializerSettings.ContractResolver = new DeltaContractResolver(); });

@muhanov-apps thanks for the quick reply and I'll give it a shot and let you know

@muhanov-apps sadly still null :(

image

Thanks for the help though

@techniq
try direct json.net class
options.SerializerSettings.ContractResolver = new DefaultContractResolver();

@muhanov-apps also null

@techniq maybe it's something else... this code solves my problem...

Here is a simple test case (might require a separate issue report at this point, I just wanted to capture it for now):

```c#
public class TestController : ODataController
{
[HttpPost]
public virtual IActionResult Post([FromBody] Model model)
{
return Ok();
}
}

public class Model
{
public int Id { get; set; }
public string MemberNumber { get; set; }
}


Doesn't work in OData controller (null `model`) but works in regular MVC controller:

curl -i 'http://localhost:5000/odata/Test' -H 'Content-Type: application/json' --data-binary '{"MemberNumber":6}'

Works in both

curl -i 'http://localhost:5000/odata/Test' -H 'Content-Type: application/json' --data-binary '{"MemberNumber":"6"}'
```

I read through the whole comments in this issue, and I think @robward-ms gave us a detail summary about the above multiple problems coming from the mis-use of OData and ASP.NET MVC/Web API.

The document at http://odata.github.io/ can help both of you to understand what's OData and how to use it in your project.

For this issue itself, i don't think any fix required. So, i close it.

Please file a new issue if you have other questions/concerns. Thanks your input.

Same problem but, in my case is working on PUT but not POST

Is there anyway to raise an error when model resolution fails descripting the reason of failure instead of getting a null model?

Was this page helpful?
0 / 5 - 0 ratings