Jamie
Jamie

Reputation: 1679

400 Bad Request in MVC C# Web App but not Postman

I have the following method that I've been working on for hours now and am not able to PUT my updates to an external Ellucian Ethos API.

public async Task<string> UpdatePersonH11()
{
    var token = await GetAccessTokenAsync("TokenApi", "value");
    var guid = await GetPersonGUID(token);
    Uri personsURI = new Uri(string.Format("https://URLtoAPI" + guid));

    H11Model h11Data = new H11Model
    {
        h11 = new h11
        {
            extendedPersonUser2 = "2021/FA",
            extendedPersonUser3 = "OUT",
            extendedPersonUser4 = DateTime.Now.ToShortDateString() 
        }
    };

    using (var client = new HttpClient())
    {
        client.BaseAddress = personsURI;
        client.DefaultRequestHeaders.Accept.Clear();
        client.DefaultRequestHeaders.Add("Accept", "application/vnd.hedtech.integration.v12+json");
        client.DefaultRequestHeaders.Add("Accept-Encoding", "gzip, deflate, br");
        client.DefaultRequestHeaders.Add("Connection", "keep-alive");
        client.DefaultRequestHeaders.Add("Authorization", $"Bearer {token}");
      
        var responseString = "";
        var content = JsonSerializer.Serialize(h11Data);

        **using (HttpResponseMessage response = await client.PutAsJsonAsync(personsURI, content))** Debugger stops here and gives me a 400 Bad Request
        {
            if (response.IsSuccessStatusCode)
            {
                responseString = await response.Content.ReadAsStringAsync();                   
            }
            else
            {
                log.Debug("Error in response." + response);
                return "";
            }
            return responseString;
        }
    }
}

This is the json that Json is serializing for the variable content to send through PutAsJsonAsync.

{
    "h11":  
    {
        "extendedPersonUser2":"2021/FA",
        "extendedPersonUser3":"OUT",
        "extendedPersonUser4":"8/5/2021",
    }
}

H11 Model

public class H11Model
{        
    [JsonProperty("h11")]
    public h11 h11 { get; set; }
}

public class h11
{
    [JsonProperty("extendedPersonUser2")]
    public string extendedPersonUser2 { get; set; }

    [JsonProperty("extendedPersonUser3")]
    public string extendedPersonUser3 { get; set; }

    [JsonProperty("extendedPersonUser4")]
    public string extendedPersonUser4 { get; set; }

    [JsonProperty("extendedPersonUser5")]
    public string extendedPersonUser5 { get; set; }

    [JsonProperty("extendedPersonUser6")]
    public string extendedPersonUser6 { get; set; }

    [JsonProperty("extendedPersonUser7")]
    public string extendedPersonUser7 { get; set; }

    [JsonProperty("extendedPersonUser8")]
    public string extendedPersonUser8 { get; set; }

    [JsonProperty("extendedPersonUser9")]
    public string extendedPersonUser9 { get; set; }

    [JsonProperty("extendedPersonUser10")]
    public string extendedPersonUser10 { get; set; }
}

I've read that PutAsJsonAsync does not need the content serialized first, but when I comment out var content = JsonSerializer.Serialize(h11Data); I get a 406 Not Acceptable error, that leads me to believe that I do need to serialize the content first.

This is the request message I get back from the debugger:

{
    Method: PUT, 
    RequestUri: 'API URI', 
    Version: 1.1, 
    Content: System.Net.Http.ObjectContent`1[[System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=token]],
    Headers:
    {
        Authorization: Bearer 'token is here'
        Accept: application/vnd.hedtech.integration.v12+json
        Content-Type: application/json; charset=utf-8
        Content-Length: 293
    }
}

This is the response message:

{
    StatusCode: 400, ReasonPhrase: 'Bad Request', Version: 1.1, Content: System.Net.Http.StreamContent, Headers:
    {
        Connection: keep-alive
        pragma: no-cache
        hedtech-ethos-integration-application-id: GUID
        hedtech-ethos-integration-application-name: Colleague
        vary: origin
        access-control-expose-headers: x-max-page-size,x-media-type,x-total-count,x-content-restricted,hedtech-ethos-integration-application-id,hedtech-ethos-integration-application-name,hedtech-ethos-integration-proxy-generated
        Cache-Control: no-cache
        Date: Fri, 06 Aug 2021 13:41:56 GMT
        Server: Microsoft-IIS/10.0
        Content-Length: 447
        Content-Type: text/plain; charset=utf-8
        Expires: -1
    }
}

If anyone can help point me in the right direction, I would really appreciate it. I haven't used APIs this way before, so this is new territory for me and I am stumped on this.

EDIT: Final code (snippet) that works:

H11Model h11Data = new H11Model
        {
            h11 = new h11
            {
                extendedPersonUser5 = "OUT", // fall term
                extendedPersonUser6 = DateTime.Now.ToShortDateString()
            }
        };

        using (var client = new HttpClient())
        {
            client.BaseAddress = personsURI;
            client.DefaultRequestHeaders.Accept.Clear();
            client.DefaultRequestHeaders.Add("Accept", "application/vnd.hedtech.integration.v12+json");
            client.DefaultRequestHeaders.Add("Accept-Encoding", "gzip, deflate, br");
            client.DefaultRequestHeaders.Add("Connection", "keep-alive");
            client.DefaultRequestHeaders.Add("Authorization", $"Bearer {token}");                                       

            var responseString = "";
            //var content = JsonSerializer.Serialize(h11Data);

            using (HttpResponseMessage response = await client.PutAsJsonAsync(personsURI, h11Data))
            {
                if (response.IsSuccessStatusCode)
                {
                    try
                    {
                        responseString = await response.Content.ReadAsStringAsync();
                    }
                    catch (NotSupportedException ex) // When content type is not valid
                    {
                        log.Debug("The content type is not supported.", ex);
                    }
                    catch (JsonException ex) // Invalid JSON
                    {
                        log.Debug("Invalid JSON.", ex);
                    }
                }
                else
                {
                    log.Debug("Error in response." + response);
                    return "";
                }
                return responseString;
            }
        }
    

Upvotes: 1

Views: 2422

Answers (1)

Botan
Botan

Reputation: 806

If the service does not override the meaning of the HTTP codes and uses them as described in RFC then

406 Not Acceptable

The 406 (Not Acceptable) status code indicates that the target resource does not have a current representation that would be acceptable to the user agent, according to the proactive negotiation header fields received in the request (Section 5.3), and the server is unwilling to supply a default representation.

Simply put, the value of Accept, Accept-Encoding, Accept-Charset or Accept-Language HTTP header is invalid or not defined as service expects.

In the described case, there are at least two such headers set through HttpClient.DefaultRequestHeaders collection. Their values need to be compared with the service documentation. And it is important to note that in the general case the Accept-Encoding header should be set via HttpClientHandler.AutomaticDecompression property instead of DefaultRequestHeaders collection, otherwise it will be ignored.

400 Bad Request

The 400 (Bad Request) status code indicates that the server cannot or will not process the request due to something that is perceived to be a client error (e.g., malformed request syntax, invalid request message framing, or deceptive request routing).

For example, service expects { "h11": { "extendedPersonUser2": ... JSON object; but instead of it, service receives "{ \"h11\": { \"extendedPersonUser2\": ... and cannot deserialize it.

As it was already mentioned in comments, there is a difference between PutAsJsonAsync extension method and HttpClient.PutAsync method with StringContent. The first one sends a PUT request to the specified Uri containing the value serialized as JSON in the request body. The PutAsync method sends raw content as it is defined by content argument.

So, the code from example

   string content = JsonSerializer.Serialize(h11Data);
   ... = await client.PutAsJsonAsync(personsURI, content);

uses PutAsJsonAsync and sends JSON string additionally serialized to JSON. As result service receives the following content "{ \"h11\": { \"extendedPersonUser2\": ....

Usually, when PutAsJsonAsync extension method is used, the additional JSON serialization is not needed. Content object can be passed directly to PutAsJsonAsync extension method.

   ... = await client.PutAsJsonAsync(personsURI, h11Data);

In such case the request will be sent as { "h11": { "extendedPersonUser2": ....

There are multiple PutAsJsonAsync extension method overloads that also accept JsonSerializerOptions to customize JSON serialization. And it's important to note that PutAsJsonAsync internally use HttpClient.PutAsync method with StringContent to send the request.

Upvotes: 2

Related Questions