1

I have the following endpoint in my asp.net core 2.0 app:

[HttpPost("postself")]
public IActionResult post([FromBody]JObject email)
{
    try
    {
        var service = new EmailService();
        var emailHtml = service.GenerateEmail(email.ToString(), false);
        return Json(new { Email = emailHtml });
    }
    catch
    {
        return Json(new { Email = "error" });
    }
}

When calling the API like so:

curl -X POST \
  http://myapp/api/v1/emails/postself \
  -H 'cache-control: no-cache' \
  -H 'content-type: application/json' \
  -H 'postman-token: 85c9bbe7-2112-0746-5f41-8dc01b52ab59'

The endpoint gets hit and returns at return Jason(new { Email = "error" }); so that works. It returns error because the JObject is null but still this is expected behavior.

However, when calling the API like so (with JSON payload):

curl -X POST \
  http://myapp/api/v1/emails/postself \
  -H 'cache-control: no-cache' \
  -H 'content-type: application/json' \
  -H 'postman-token: a80c85de-8939-01d8-4a5d-c2108bf1491d' \
  -d '{
  "body": [
    {
      "image": {
        "url": "mailing_logo_slice.png",
        "alt": "This is my logo"
      }
    }
  ]
}'

...my app returns 400 Bad request.

Also, the requests work in development, but only returns 400 in production.

Any ideas?

=========

Updated code:

[HttpPost("postself")]
public IActionResult PostSameDomain([FromBody]string email)
{
    try
    {
        var service = new EmailGenerationService();
        var emailHtml = service.GenerateEmailFromJson(email, false);
        return Json(new { Email = emailHtml });
    }
    catch
    {
        return Json(new { Email = "error" });
    }
}

[HttpPost("test")]
        public IActionResult PostSameDomain([FromBody]EmailViewModel email)
        {
            try
            {
                var service = new EmailGenerationService();
                var emailHtml = service.GenerateEmailFromJson(email.Raw, false);
                return Json(new { Email = emailHtml });
            }
            catch
            {
                return Json(new { Email = "error" });
            }
        }
Corstiaan
  • 1,114
  • 15
  • 34
  • 1
    Have you debugged and looked at `ModelState.IsValid` + its various other properties? – Peter B Apr 09 '18 at 15:20
  • 1
    Also, you might want to do things 'the proper way' and use an actual Model class. See e.g. https://app.quicktype.io/ to generate example code based on your input JSON. – Peter B Apr 09 '18 at 15:26
  • 1
    For your CURL data property, does -d '{"username":"abc","password":"abc"}' work? If you don't get a 400 then I think it has to do with the structure of your current JSON object in the request. – Ben Krueger Apr 09 '18 at 15:41
  • 1
    Make sure your `FromBody` parameter can be deserialized https://stackoverflow.com/a/46196415/804385 Also use catch (Exception ex) to see what is the error details - `ex.Message` Also check in production that `Newtonsoft.Json.dll` is not missed – Dmitry Pavlov Apr 09 '18 at 16:50
  • @PeterB I checked out ModelState in development. All seems fine. Regarding your suggestion to do things the "proper way", as the format/model of the posted json differs every time I cant define a POCO. I just need the raw string or JObject. I switch from a JObject parameter to a plain string. In Development this works like before but still returns 400 in production... Any additional thoughts? – Corstiaan Apr 10 '18 at 09:30
  • @BenKrueger When I post the request with your json example I still get a 400 response unfortunately. – Corstiaan Apr 10 '18 at 09:31
  • @Corstiaan An API implements an interface between a client and a server, with a clear definition of what is accepted, and a model+code to support that. A catch-all interface is almsot never a good idea. If your JSON is actually a way to describe HTML tags, then why not post and accept a HTML string? – Peter B Apr 10 '18 at 09:39
  • @PeterB Yes, you are right of course. But for this particular use case it would make sense. However, I wrapped the request in a simple model (see edited code). Again, this works in Development but still returns 400 in Production... Any more wisdom? :-) – Corstiaan Apr 10 '18 at 10:18
  • @PeterB I should also add that regular form posts (so from an actual html form) also fail with 400 in production but work in development. – Corstiaan Apr 10 '18 at 10:28
  • If all POSTs produce 400 then you'd better look at logfiles (if you have those) or the Windows Event Log. The problem is then most likely not with this code. – Peter B Apr 10 '18 at 10:55
  • @PeterB Im on Docker/Linux. The logs are output to the console but nothing shows up regarding the 400 responses. I have set the LogLevel to Trace... – Corstiaan Apr 10 '18 at 11:40

3 Answers3

2

JObject is specific to Newtonsoft.Json and will not work. Please specify a POCO class that contains url and alt properties.

Ricardo Peres
  • 13,724
  • 5
  • 57
  • 74
2

I believe your problem here is that you're passing in an JSON Array and attempting to deserialize it into a singular JObject.

Possible fixes:

  1. Pass in a single JSON object:

    curl -X POST \
      http://myapp/api/v1/emails/postself \
      -H 'cache-control: no-cache' \
      -H 'content-type: application/json' \
      -H 'postman-token: a80c85de-8939-01d8-4a5d-c2108bf1491d' \
      -d '{
      "body": {
          "image": {
            "url": "mailing_logo_slice.png",
            "alt": "This is my logo"
          }
        }
    }'
    
  2. Switch to JArray instead of JObject:

    [HttpPost("postself")]
    public IActionResult post([FromBody]JArray email)
    {
        try
        {
            var service = new EmailService();
            var emailHtml = service.GenerateEmail(email.ToString(), false);
            return Json(new { Email = emailHtml });
        }
        catch
        {
            return Json(new { Email = "error" });
        }
    }
    
  3. Take in the data as a string and deserialize it within. This would allow you to test if you've received an array or a single object and then deserialize to a JObject or JArray as needed (this would be my personal recommendation for what its worth).

Marc LaFleur
  • 31,987
  • 4
  • 37
  • 63
  • I tried option 3. This works in Development but in Production it still returns 400. See updated question. – Corstiaan Apr 10 '18 at 09:33
  • I should also add that regular form posts (so from an actual html form) also fail with 400. – Corstiaan Apr 10 '18 at 10:27
  • An HTML form will `POST` using `application/x-www-form-urlencoded`, not `application/json`. You cannot using a JSON deserializer against `form-urlencoded` data. – Marc LaFleur Apr 10 '18 at 17:14
0

For anyone else stumbling onto this issue, this was the root of the problem.

Thanks to everybody for thinking along with my issue.

Corstiaan
  • 1,114
  • 15
  • 34