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

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;
}
}

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.

Related

C# RestSharp AddJsonBody() doesn't add API call Body name

I am working with RestSharp and an API I believe is set up with Azure. I making a POST request to the API, but have noted that the request only works when the body is named, "agentGetRequest" in Postman.
Bad Body:
{
"supplierKey": "XXXX-XXXX-XXXX-XXXX",
"inputDate": "2021-01-17T00:00:00.000Z"
}
Good Body:
{
"agentGetRequest": {
"supplierKey": "XXXX-XXXX-XXXX-XXXX",
"inputDate": "2021-01-17T00:00:00.000Z"
}
}
So when I run my code
void Post(AgentRequest agentRequest, Credentials creds )
{
Uri baseUrl = new Uri(creds.baseURL);
IRestClient client = new RestClient(baseUrl);
IRestRequest request = new RestRequest(ENDPOINT, Method.POST);
request.AddHeader("subscriptionKey", creds.subscriptionKey);
// Option 1:
request.AddJsonBody(agentRequest);
// Option 2:
request.AddJsonBody(agentRequest, "agentGetRequest")
IRestResponse<GetAgentResponse> response = client.Execute<GetAgentResponse>(request);
if (response.IsSuccessful)
{
Console.WriteLine(JsonConvert.SerializeObject(response.Content));
}
else
{
Console.WriteLine(response.ErrorMessage);
}
}
// Body request object
public class AgentRequest
{
public string inputDate { get; set; }
public string supplierKey { get; set; }
}
It throws a bad request error because it can't find the body, "agentRequest."
I have found a workaround where I feed it a string as the body - but this would hate to have to do this for every endpoint:
// Instead of request.AddJsonBody()...
string body = "{\"agentGetRequest\": {\"supplierKey\": \"XXXX-XXXX-XXXX-XXXX\",\"inputDate\": \"2021-0117T00:00:00.000Z\"}}";
request.AddParameter("application/json", body, ParameterType.RequestBody);
How can I correctly format the body when adding it via AddJsonBody to include the name (and maybe the encapsulating brackets)?
Recording to the accepted answer of RestSharp Post a JSON Object, you could add JSON body like below:
request.AddJsonBody(
new
{
agentGetRequest = agentRequest
}); // AddJsonBody serializes the object automatically

API POST call from Console Application

How to do the REST API POST Call from the console Application ?
I want to pass the class from the Console application to the REST API. My below code is working if I have to do the GET call but not for the POST. It is hitting the API but in the Parameter it is not passing anything.
API
public class Student
{
public int Id { get; set; }
public string Name { get; set; }
}
public class ValuesController : ApiController
{
// GET api/values
public IEnumerable<string> Get()
{
return new string[] { "value1", "value2" };
}
// GET api/values/5
public string Get(int id)
{
return "value";
}
// POST api/values
//public void Post([FromBody]string value)
//{
//}
public void Post([FromBody]Student value)
{
}
}
Console Application
static async Task CallWebAPIAsync()
{
var student = new Student() { Id = 1, Name = "Steve" };
using (var client = new HttpClient())
{
//Send HTTP requests from here.
client.BaseAddress = new Uri("http://localhost:58847/");
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
HttpResponseMessage response = await client.PostAsJsonAsync("api/values", student);
if (response.IsSuccessStatusCode)
{
}
else
{
Console.WriteLine("Internal server Error");
}
}
}
The Same is working if I call from fiddler.
User-Agent: Fiddler
Content-Length: 31
Host: localhost:58847
Content-Type: application/json
Request Body:
{
"Id":"1",
"Name":"Rohit"
}
This is working for me.
public async Task CallWebAPIAsync()
{
var student = "{'Id':'1','Name':'Steve'}";
HttpClient client = new HttpClient();
client.BaseAddress = new Uri("http://localhost:58847/");
var response = await client.PostAsync("api/values", new StringContent(student, Encoding.UTF8, "application/json"));
if (response != null)
{
Console.WriteLine(response.ToString());
}
}
You are not serializing the student object.
You can try to send a StringContent
StringContent sc = new StringContent(Student)
HttpResponseMessage response = await client.PostAsJsonAsync("api/values", sc);
if this doesn't work (a long time I used StringContent).
Use NewtonSoft sterilizer
string output = JsonConvert.SerializeObject(product);
HttpResponseMessage response = await client.PostAsJsonAsync("api/values", output);
To be honest I don't know. It seems like your StringContent did not sterilize it to UTF8 which your restful API is trying to do by default. However, your console application should also do that by default.
The issue seemed to be that the restful API could not bind the byte data and therefor not assign the data to your class Student in the restful API.
What you can try to do is add following code before you make your post to API:
var encoding = System.Text.Encoding.Default;
It will tell you what is your default encoding type. It could be that UTF8 is not the default encoding for some reason.

aspnet webapi 2 response payload not displayed for HttpStatusCode.Unauthorized

I have an ActionFilterAttribute that overrides the OnActionExecuting. If the user isn't authenticated I want to return and 401 Unauthorized status and a JSON object of type Response with a custom message and other properties
public class Response
{
public Boolean Error { get; set; }
public IList<string> Messages { get; set; }
public object Data { get; set; }
}
That's what I did:
public override void OnActionExecuting(HttpActionContext actionContext)
{
//some code here
var response = new Response();
response.AddMessage(true, Util.Localization.Authentication.Validation_UserNotAuthenticated);
actionContext.Response = new HttpResponseMessage(HttpStatusCode.Unauthorized)
{
Content = new ObjectContent(typeof(Response), response, new JsonMediaTypeFormatter())
};
}
When the client makes a request, that's the Response Header (from google chrome developer tools - network):
HTTP/1.1 401 Unauthorized
Cache-Control: no-cache
Pragma: no-cache
Content-Type: application/json; charset=utf-8
Expires: -1
Server: Microsoft-IIS/8.5
X-AspNet-Version: 4.0.30319
X-Powered-By: ASP.NET
Date: Thu, 18 Dec 2014 13:19:12 GMT
Content-Length: 83
Well, the JSON with the Response object isn't displayed to the client.
If I only change theHttpStatusCode to OK, the JSON is displayed:
actionContext.Response = new HttpResponseMessage(HttpStatusCode.OK)
{
Content = new ObjectContent(typeof(Response), response, new JsonMediaTypeFormatter())
};
Also, if I keep theHttpStatusCode as Unauthorized, but change the Type to string, the text is displayed normally to the client:
actionContext.Response = new HttpResponseMessage(HttpStatusCode.Unauthorized)
{
Content = new ObjectContent(typeof(string), "test string", new JsonMediaTypeFormatter())
};
How can I send a custom JSON object when I get a Unauthorized Http status?
Thnaks
I assume you are using the built-in AuthorizeAttribute on your controller to secure your api. I think the reason it's not working is because the AuthorizationFilters (like AuthorizeAttribute) happen earlier in the WebApi pipeline than ActionFilters. See here for details:
https://damienbod.wordpress.com/2014/01/04/web-api-2-using-actionfilterattribute-overrideactionfiltersattribute-and-ioc-injection/
So your code never executes because the AuthorizeAttribute already failed and returned its default response (401 message with no body).
The easiest way to do what you want is to inherit a custom authorization attribute, ex MyAuthorizeAttribute, inheriting from AuthorizeAttribute and changing the way it handles errors. Then just decorate your controllers with [MyAuthorize] instead of [Authorize].
public class MyAuthorizeAttribute : AuthorizeAttribute
{
protected override void HandleUnauthorizedRequest(HttpActionContext actionContext)
{
var jsonFormatter = new JsonMediaTypeFormatter();
var response = new Response();
response.AddMessage(true, Util.Localization.Authentication.Validation_UserNotAuthenticated);
actionContext.Response = new HttpResponseMessage(HttpStatusCode.Unauthorized)
{
ReasonPhrase = "Unauthorized",
Content = new ObjectContent(typeof(Response), response, new JsonMediaTypeFormatter())
};
}
}

HttpResponseMessage content is not getting serialized for BadRequest responses

I have ASP.NET application, with some MVC and WebAPI controllers in there. MVC 5.1 and WebAPI 2.1. There is no tricky configuration at all, the only thing is getting configured is removed XML formatter:
public static class WebApi
{
public static void Configure(HttpConfiguration config)
{
var formatters = config.Formatters;
formatters.Remove(formatters.XmlFormatter);
}
}
There is simple DTO to put data returned from controller action together:
public class TokenResponse
{
public string token { get; set; }
public string token_type { get; set; }
public int expires_in { get; set; }
public string error { get; set; }
}
And the following code:
[NoHttpResponseCaching]
public class TokenController : ApiController
{
public async Task<HttpResponseMessage> PostAsync(HttpRequestMessage request, TokenRequest tokenRequest)
{
try
{
// do some stuff
var response = new TokenResponse { ... };
return request.CreateResponse(HttpStatusCode.OK, response);
}
catch (TokenRequestValidationException ex)
{
_logger.WriteError(ex);
var response = new TokenResponse { error = ex.ErrorToDisplay };
return request.CreateResponse(HttpStatusCode.BadRequest, response);
}
}
}
The problem is that if everything fine, I have my TokenResponse serialized to JSON, as expected, but if there is exception occurring, execution flow is coming into catch block and response body is equal to "Bad Request", this is the RAW dump of response from Fiddler:
HTTP/1.1 400 Bad Request
Cache-Control: no-store
Pragma: no-cache
Content-Type: text/html
Server: Microsoft-IIS/8.5
X-Powered-By: ASP.NET
Date: Wed, 18 Jun 2014 08:25:53 GMT
Content-Length: 11
Bad Request
Tried to return anonymous object having some random named properties instead of using TokenResponse, getting same response:
return request.CreateResponse(HttpStatusCode.BadRequest, new {klhjaoiubf = "kjhaflkjh"});
And I'm stuck at this point trying to understand why I'm not getting my object serialized in response body and how to change it. Anyone, any ideas, why my object is getting ignored when response code is BadRequest?

send Http request with Content Type application Json on C#

Am trying to send HTTP Get request with Content Type application/json via C#. but I don't find how to create this request.
My HTTP request it's like this:
POST /messaging/registrations/(REGISTRATION_ID_FOR_DESTINATION_APP_INSTANCE)/messages HTTP/1.1
Host: api.amazon.com
Authorization: Bearer (MY_ACCESS_TOKEN)
Content-Type: application/json
X-Amzn-Type-Version: com.amazon.device.messaging.ADMMessage#1.0
Accept: application/json
X-Amzn-Accept-Type: com.amazon.device.messaging.ADMSendResult#1.0
{
"data":{"key1":"value1","key2":"value2"},
"consolidationKey":"Some Key",
"expiresAfter":86400
}
Someone can help me, please.
thinks for all.
Assuming you have a class like this to represent the payload,
class Payload
{
public Dictionary<string, string> data { get; set; }
public string consolidationKey { get; set;}
public long expiresAfter { get; set; }
}
you can use HttpClient, like this.
string url = "http://api.amazon.com/messaging/registrations/1234/messages";
var client = new HttpClient();
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue(
"Bearer", "token");
client.DefaultRequestHeaders.Accept.Add(
new MediaTypeWithQualityHeaderValue("application/json"));
client.DefaultRequestHeaders.Add("X-Amzn-Type-Version",
"com.amazon.device.messaging.ADMMessage#1.0");
client.DefaultRequestHeaders.Add("X-Amzn-Accept-Type",
"com.amazon.device.messaging.ADMSendResult#1.0");
var kvp = new Dictionary<string, string>();
kvp.Add("key1", "value1");
kvp.Add("key2", "value2");
var payload = new Payload()
{
consolidationKey = "Some Key", expiresAfter = 86400, data = kvp
};
var result = client.PostAsJsonAsync<Payload>(url, payload).Result;

Categories