HttpResponseMessage.Content is null - c#

I've been working on both the backend and the frontend of a mobile application and am having trouble getting my post requests to work properly. I've been using Xamarin.Forms for the frontend and .Net for the backend.
The code on the client side:
var res = await App.Client.PostAsync("url", args);
var s =await res.Content.ReadAsStringAsync();
await DisplayAlert("Content", s, "OK");
I have checked and I am receiving an HttpResponseMessage, but when I try to read it the Content Stream is always null. I suspect it is something I am doing wrong on the server side though.
Server side code:
[MobileAppController,Authorize,RoutePrefix("api/SomeController")]
public class someController : ApController{
[HttpPost,Route("theRoute/{id}"),AllowAnonymous]
public HttpResponseMessage someFunction(args)
{
return new HttpResponseMessage(System.Net.HttpStatusCode.OK)
{
Content = new StringContent("Hello");
}
}
}
If I instead display the response with res.ToString(), I get a response message with
StatusCode:200
ReasonPhrase:'OK'
Version:1.1
Content: System.Net.HttpStreamContent
Headers:{
Server: Microsoft-IIS/10.0
X-Powered-By:ASP.NET,
Date: Tue,20,Feb 2018 17:08:42 GMT,
Content-Length:4
Content-Type: application/json; charset=utf-8
}
I've been trying to figure out why Content is null but I can't figure out why.
Edit 1: App.Client is the HttpClient used by the entire mobile application.
In my app.xaml.cs file:
static HttpClient client;
public static HttpClient Client
{
get
{
if (client == null)
{
client = new HttpClient();
}
return client;
}
}

Try to set the client DefaultRequestHeaders on the application response type. For example:
client.DefaultRequestHeaders.Add("Accept", "application/json");

Related

HttpClient PostAsJsonAsync results in null on the API and PostAsync works fine

For some reason PostAsJsonAsync doesn't work:
Blazor Client (.Net Core 3.1):
var item = new TodoItem() { Name = "1111111" };
var client1 = new HttpClient();
await client1.PostAsJsonAsync("http://localhost:64400/api/test/", item);
var client2 = new HttpClient();
var content = new StringContent(JsonConvert.SerializeObject(item), Encoding.UTF8, "application/json");
await client2.PostAsync("http://localhost:64400/api/test/", content);
API (.NET 4.8)
public IHttpActionResult Post(TodoItem model)
{
return Ok();
}
client1 call results in model being null and for client1 call a proper model can be seen on the API. When I run Fiddle, both calls are identical and repeating client1 call actually results in a proper model seen on the API.
When I examine the raw request of client1 in Fiddler, I do see some weird characters
POST http://localhost:64400/api/test/ HTTP/1.1
Host: localhost:64400
Request-Id: |4bde91df-41c09eb19a740271.
Transfer-Encoding: chunked
Content-Type: application/json; charset=utf-8
2C
{"Id":0,"Name":"1111111","IsComplete":false}
0
2C and 0 don't seem like they should be there? What is going on?
Edit: the culprit is Transfer-Encoding: chunked and for some reason client1.DefaultRequestHeaders.TransferEncodingChunked = false; doesn't work

Body not sent when using Bearer Authentication

I have a winform client that consumes data from a Python Flask API using Zalando's Connexion and OpenAPI 3.
The client uses Net Framework 4.8. When I send a POST request with Authorization header, the body doesn't get sent, so I get an error 400 from the server. I've inspected received data on API side, also I created a blank project with Flask only that just outputs what it receives as requests, and the body is not there. Inspecting the content on Visual Studio shows the body, but it never reaches the API server.
If I don't put the Authorization header it works ok. It also works ok for GET, WITH the header.
This is how I set the token on client side:
public void SetToken(string token) {
Client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token);
}
This is my default constructor for the client:
public class RestClient{
private readonly HttpClient Client;
public RestClient {
Client = new HttpClient();
Client.DefaultRequestHeaders.Accept.Clear();
Client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
}
}
I've searched a lot before asking here for someone with my same problem but couldn't find any post.
I also see that almost all examples use form/urlencoded for POST instead of application/json but I guess that's simple a choice for the format, doesn't seem as a requirement when using authentication.
I'm using:
Visual Studio 2019
Net Framework 4.8 (tried with 4.7 too)
Python 3.7.2
Flask 1.1.1
Connexion 2.3.0
Also tried from an API test suite with Bearer Authorization created on Python using requests library and it works ok from there...
Edit:
As requested adding my post code:
public HttpResponseMessage Post(string path, HttpContent content, int maxRetries = 0)
{
if (maxRetries < 0)
{
throw new ArgumentException("maxRetries cannot be less than 0");
}
int attemptsMade = 0;
int maxAttempts = maxRetries + 1;
bool workCompletedSuccessfully = false;
bool attemptsRemain = true;
HttpResponseMessage response = null;
while (!workCompletedSuccessfully && attemptsRemain)
{
attemptsMade++;
attemptsRemain = attemptsMade < maxAttempts;
try
{
response = Client.PostAsync(path, content).GetAwaiter().GetResult();
if (response.IsSuccessStatusCode)
{
workCompletedSuccessfully = true;
}
}
catch (Exception e)
{
if (!attemptsRemain)
{
throw e;
}
}
}
return response;
}
And this is how I call it from the service:
private const string PATH = "person";
public PersonService(RestClient rest)
{
_rest = rest;
}
public HttpResponseMessage AddNew(Person person)
{
var personJson = JsonConvert.SerializeObject(person);
using (var content = new StringContent(personJson, Encoding.UTF8, "application/json"))
{
var result = _rest.Post($"api/{PATH}", content);
return result;
}
}
Does your bearer token (the string you pass to SetToken method) contain newlines? This can cause that problem.

Asp.Net Core API response wrong json encoding using IIS

I have an API in ASP Core 3.0 and an MVC client application in ASP Core 2.2. In the client application, I use HttpClient to call API methods and ReadAsStringAsync() to read responses and it works fine in debug.
When I publish to IIS on a real server, JSON responses are not properly read using response.Content.ReadAsStringAsync(). A string is created but JSON is unreadable, probably wrongly encoded, so I'm not able to convert it to an object.
I checked the response with Fiddler and everything looks fine, header Content-Type: application/json; charset=utf-8 is present and JSON looks good. I don't know what kind of IIS specific behaviour produces this. I tried on a local instance of IIS and didn't reproduce this issue.
I've tried using Newtonsoft.Json to encode the API response content and also tried adding [Produces("application/json")] to the API controller, the problem is still there.
Here is an example of what an API method returns :
return Ok(new UserDto
{
Login = user.Login,
IdAccountUser = user.IdAccountUser,
Prenom = user.Prenom,
Nom = user.Nom,
Token = tokenString
});
And this how I read the response
HttpResponseMessage response = await _apiHttpClient.Post("users/authentication", user);
string logDetail = string.Empty;
if (response?.Content != null)
{
if (response.IsSuccessStatusCode)
{
string json = null;
try
{
json = await response.Content.ReadAsStringAsync();
// Deserialization fails here because of invalid JSON
user = JsonConvert.DeserializeObject<UserDto>(json);
bool authenticationSuccessful = await AuthenticateUser(user);
if (authenticationSuccessful)
return string.IsNullOrEmpty(model.ReturnUrl) ? await Index() : Redirect($"~{model.ReturnUrl}");
}
catch (JsonReaderException ex)
{
_logger.LogError(ex, "Erreur de lecture du JSON : {0}", json);
}
}
else
logDetail = $"Code HTTP réponse: {response.StatusCode}";
}
_apiHttpClient.Post() is a custom wrapper for HttpClient.PostAsync()
After a few tests, I understood the problem appeared only when I added this header to my HttpClient instance :
Accept-Encoding: gzip, deflate
Removing this header solved the problem. Now I have to figure out how to use compression with IIS, I'm going back to Microsoft docs ;)

gandi api call from C# returns 400. Its Working Fine from Postman

I have create gandi api code for create domain and for that i have write below code, but it show me 400 bad request error
public async System.Threading.Tasks.Task<JsonResult> InsertDomain(DomainDetails domainDetails)
{
HttpResponseMessage response = new HttpResponseMessage();
try
{
var url = "https://api.gandi.net/v5/domain/domains";
using ( var client = new HttpClient() )
{
var json = new JavaScriptSerializer().Serialize(domainDetails);
HttpContent HttpContent = new StringContent(json, Encoding.UTF8, "application/json");
var MyHttpClient = new HttpClient();
MyHttpClient.DefaultRequestHeaders.Add("authorization", GANDI_API_Key);
response = await MyHttpClient.PostAsync(url, HttpContent);
}
}
catch ( Exception ex )
{
throw;
}
return Json(new { result = response }, JsonRequestBehavior.AllowGet);
}
but when i try to pass same data using postman then it's working fine below code is my postman data
Body
{
"fqdn":"dedasdom1906.com",
"owner":
{
"city":"Paris",
"given":"Alice",
"family":"Doe",
"zip":"75001",
"country":"FR",
"streetaddr":"5 rue neuve",
"phone":"+33.123456789",
"state":"FR-J",
"type":"0",
"email":"alice#example.org"
}
}
Header
authorization : Apikey
Content-Type : application/json
I havent worked with this endpoint, but you are missing the return type.
the next thing i would try is to paste json string directly in the StringContent.
please paste the correct string content(rename the variable)
if none of this help you, please give more details.
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
For the https://api.gandi.net/v5/domain/domains endpoint, use HTTP GET (HttpClient.GetAsync) to retrieve a list of your domains. Use HTTP POST (HttpClient.PostAsync) to create a new domain.
If you're trying to POST JSON, I would use the PostAsJsonAsync method, example here:
static async Task<Uri> CreateProductAsync(Product product)
{
HttpResponseMessage response = await client.PostAsJsonAsync(
"api/products", product);
...
Also note your auth header needs to start with "apikey" though it looks like you have that working. Curl example:
curl -X GET \
https://api.gandi.net/v5/domain/domains \
-H 'authorization: Apikey your-api-key'
https://api.gandi.net/docs/domains/

.NET Core forward a local API form-data post request to remote API

I have an AJAX form which post a form-data to a local API url: /api/document. It contains a file and a custom Id. We simply want to take the exact received Request and forward it to a remote API at example.com:8000/document/upload.
Is there a simple way of achieve this "forward" (or proxy?) of the Request to a remote API using Asp.NET Core?
Below we had the idea to simply use Web API Http client to get the request and then resend it (by doing so we want to be able to for example append a private api key from the backend), but it seems not to work properly, the PostAsync doesn't accept the Request.
Raw request sent by Ajax
POST http://localhost:62640/api/document HTTP/1.1
Host: localhost:62640
Connection: keep-alive
Content-Length: 77424
Accept: application/json
Cache-Control: no-cache
User-Agent: Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.87 Safari/537.36
Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryn1BS5IFplQcUklyt
Accept-Encoding: gzip, deflate, br
Accept-Language: en-US,en;q=0.8,fr;q=0.6
------WebKitFormBoundaryn1BS5IFplQcUklyt
Content-Disposition: form-data; name="fileToUpload"; filename="test-document.pdf"
Content-Type: application/pdf
...
------WebKitFormBoundaryn1BS5IFplQcUklyt
Content-Disposition: form-data; name="id"
someid
------WebKitFormBoundaryn1BS5IFplQcUklyt--
Backend Code
Our .NET Core backend has a simple "forward to another API" purpose.
public class DocumentUploadResult
{
public int errorCode;
public string docId;
}
[Route("api/[controller]")]
public class DocumentController : Controller
{
// POST api/document
[HttpPost]
public async Task<DocumentUploadResult> Post()
{
client.BaseAddress = new Uri("http://example.com:8000");
client.DefaultRequestHeaders.Accept.Clear();
HttpResponseMessage response = await client.PostAsync("/document/upload", Request.Form);
if (response.IsSuccessStatusCode)
{
retValue = await response.Content.ReadAsAsync<DocumentUploadResult>();
}
return retValue;
}
}
We have a GET request (not reproduced here) which works just fine. As it doesn't have to fetch data from locally POSTed data.
My question
How to simply pass the incoming local HttpPost request and forwarding it to the remote API?
I searched A LOT on stackoverflow or on the web but all are old resources talking about forwarding Request.Content to the remote.
But on Asp.NET Core 1.0, we don't have access to Content. We only are able to retrieve Request.Form (nor Request.Body) which is then not accepted as an argument of PostAsync method:
Cannot convert from Microsoft.AspNetCore.Http.IformCollection to
System.Net.Http.HttpContent
I had the idea to directly pass the request to the postAsync:
Cannot convert from Microsoft.AspNetCore.Http.HttpRequest to
System.Net.Http.HttpContent
I don't know how to rebuild expected HttpContent from the local request I receive.
Expected response
For information, When we post a valid form-data with the custom Id and the uploaded file, the remote (example.com) API response is:
{
"errorCode": 0
"docId": "585846a1afe8ad12e46a4e60"
}
Ok first create a view model to hold form information. Since file upload is involved, include IFormFile in the model.
public class FormData {
public int id { get; set; }
public IFormFile fileToUpload { get; set; }
}
The model binder should pick up the types and populate the model with the incoming data.
Update controller action to accept the model and proxy the data forward by copying content to new request.
[Route("api/[controller]")]
public class DocumentController : Controller {
// POST api/document
[HttpPost]
public async Task<IActionResult> Post(FormData formData) {
if(formData != null && ModelState.IsValid) {
client.BaseAddress = new Uri("http://example.com:8000");
client.DefaultRequestHeaders.Accept.Clear();
var multiContent = new MultipartFormDataContent();
var file = formData.fileToUpload;
if(file != null) {
var fileStreamContent = new StreamContent(file.OpenReadStream());
multiContent.Add(fileStreamContent, "fileToUpload", file.FileName);
}
multiContent.Add(new StringContent(formData.id.ToString()), "id");
var response = await client.PostAsync("/document/upload", multiContent);
if (response.IsSuccessStatusCode) {
var retValue = await response.Content.ReadAsAsync<DocumentUploadResult>();
return Ok(reyValue);
}
}
//if we get this far something Failed.
return BadRequest();
}
}
You can include the necessary exception handlers as needed but this is a minimal example of how to pass the form data forward.

Categories

Resources