Why is the receiving API not getting content in the parameter? - c#

I am trying to get one API (reports API) to talk to another API (stored procedure runner API) which is a solution we are sort of forced to adopt given that we created the first API in .NET Core 2.2 and the Sap.Data.SQLAnywhere.v4.5 drivers only play nice with the .NET 4.7.2 framework. So we segregated them to compensate. All the answers I have seen over the last 4 or 5 hours leads me to believe I am doing this correctly but it still doesn't work.
I can hit the stored procedure runner from Postman with a json/text body just fine and get results from the database. However, when I try to hit this from C# I was first getting Unsupported Media Type which I think I fixed but now I get 500 errors and when debugging through to the stored procedure runner from the reportsAPI I notice that I don't get a parameter passed from the body.
[HttpPost]
[Route("api/Reports/GetStuff")]
[ResponseType(typeof(ResponseObject))]
public ResponseObject GetStuff([FromBody]string report)
{
var response = new ResponseObject();
try
{
response = new ReportService().RunStuff(report);
}
catch (Exception e)
{
throw;
}
return response;
}
The above is the SprocRunnerAPI and I get as far as
response = new ReportService().RunStuff(report);
before it fails out because it has nothing in "report".
public class ApiService
{
public static HttpClient ApiClient { get; set; }
public static void InitializeClient()
{
ApiClient = new HttpClient();
//ApiClient.BaseAddress = new Uri("");
ApiClient.DefaultRequestHeaders.Accept.Clear();
ApiClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
}
}
This is where I initialize everything then use it in the following class method:
public ResponseObject RunReportToCSV(string report)
{
var rep = new ResponseObject();
ApiClient.ApiService.InitializeClient();
string url = #"http://localhost/EnterpriseReportRunner/api/reports/GetStuff";
var httpContent = new StringContent(report, Encoding.UTF8, "application/json");
//ServicePointManager.Expect100Continue = false; I honestly don't know where this goes... or if it is needed.
var response = ApiClient.ApiService.ApiClient.PostAsync(url , httpContent).Result;
if (response.IsSuccessStatusCode)
{
rep = response.Content.ReadAsAsync<ResponseObject>().Result;
}
else
{
throw new Exception(response.ReasonPhrase);
}
return rep;
}
I get as far as
var response = ApiClient.ApiService.ApiClient.PostAsync(url, httpContent).Result;
when it calls the aforementioned api method externally and it fails out. I had noticed no differences between the bodies in what I put in C# and what I put in Postman nor did I notice a difference looking through Fiddler on the Headers when making either call. I am seriously confused as to why what appears to be what I have seen everywhere used, that this is not working.
Thank you.
Adding Stack Trace from the SprocRunnerAPI… this isn't much help because I know exactly why this happens. By this point I expect a JsonSerialized object and I don't have it because it wasn't passed from the first API to the second.
at Newtonsoft.Json.JsonConvert.DeserializeObject(String value, Type type, JsonSerializerSettings settings)
at Newtonsoft.Json.JsonConvert.DeserializeObject[T](String value, JsonSerializerSettings settings)
at EnterpriseReportRunner.Service.ReportService.RunStuff(String report) in C:\Workspace\Solution.NET\LOB\InternalReportsAdminConsole Solution\EnterpriseReportRunner\EnterpriseReportRunner\Service\ReportService.cs:line 27
at EnterpriseReportRunner.Controllers.ReportsController.GetStuff(String report) in C:\Workspace\Solution.NET\LOB\InternalReportsAdminConsole Solution\EnterpriseReportRunner\EnterpriseReportRunner\Controllers\ReportsController.cs:line 67
To be clear this is the stack from the SprocRunnerAPI not the ReportsAPI that calls it. I am trying to find the body inside the HttpContext to see the difference between the Postman call and the C# call but can't seem to find it, is it really buried in the InputStream?

The issue seems to be one that I thought would be redundant. The bottom line is that even if you expect a serialized JSON object you need to serialize it again, otherwise when you pass the StringContent via the HttpClient the body contents don't show in the receiving controller's parameter for consumption.
var httpContent = new StringContent(JsonConvert.SerializeObject(report), Encoding.UTF8, "application/json");
Even though "report" is already a string... really weird to me. But that solves it.
Found out that the strings that come into a controller are also wrapped inside another object so .Root seems to be your friend when deserializing. At least I learned something from the experience.
Thank you for all your input. Might want to link this to this other question.

Related

"Pass through" controller action (gets and returns JSON) in .NET Core 3.1

Someone's probably done this before but I can't seem to formulate the question properly to find results. I want to make AJAX calls from a view, but I can't directly call the external API from javascript because there's a key that I can't expose. My idea is to have another controller action that I call from the page that calls the actual external REST API I want to get data from and just passes it on as a JSON. I see lots of examples of getting a JSON through C# and deserializing it but not many where you get a JSON and then return it and consume it from the view. Any help appreciated.
public JsonResult GetStuff()
{
HttpClient client = new HttpClient();
client.BaseAddress = new Uri(URL);
client.DefaultRequestHeaders.Accept.Add(
new MediaTypeWithQualityHeaderValue("application/json"));
HttpResponseMessage response = client.GetAsync("Stuff/?Id=" + id).Result;
*code to take response and pass it on as a JSON that I can consume from Javascript
}
Here is what I recommend.
[HttpGet("myapi/{id}");
public async Task MyApi(int id) {
// Replace these lines as needed to make your API call properly.
using HttpClient client = new() {
BaseAddress = REMOTE_SERVER_BASE
}
// Make sure to properly encode url parameters if needed
using HttpResponseMessage response = await client.GetAsync($"myapi/{id}");
this.HttpContext.Response.StatusCode = (int)response.StatusCode;
foreach (KeyValuePair<string, IEnumerable<string>> header in response.Headers) {
this.HttpContext.Response.Headers[header.Key] = new StringValues(header.Value.ToArray());
}
await response.Content.CopyToAsync(this.HttpContext.Response.Body);
}
This will copy all the common response fields such as status code, headers, and body content, over to your response.
This code isn't tested so you might have to tweak it a bit but it should be enough to get you started.

HTTP Post Request from C# to Flask REST API won't attach any parameters?

I have been attempting to do a simple POST request to a basic Flask API, and no matter what I try it simply will do http://localhost:5000/ rather than the http://localhost/?test_key=test_value that I want it to do. C# is not my strongest language, but I have tried a bunch of different methods that I found online. I don't think it has to do with my method being bad at this point, I more so think that I'm missing a major piece entirely.
Here is the most recent attempt I tried (keep in mind this is a snippet of a far larger project, but it doesn't involve any other pieces of it):
class DisputeResponse
{
public int id;
public string res;
public string test_key;
}
[HttpPost]
public async Task<JsonResult> TestResponse(int id, string res)
{
HttpClient client = new HttpClient();
client.BaseAddress = new Uri("http://localhost:5000/");
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
try
{
var dispRes = new DisputeResponse();
dispRes.test_key = "test_value";
var result = await client.PostAsync("http://localhost:5000/",
new StringContent(JsonConvert.SerializeObject(dispRes), Encoding.UTF8, "application/json"
));
result.EnsureSuccessStatusCode();
}
catch (Exception e)
{
System.Diagnostics.Trace.TraceInformation(e.Message);
}
return null;
}
Here is the output when I use Postman (works perfectly):
127.0.0.1 - - [15/Apr/2019 16:26:28] "POST /?test_key=test_value HTTP/1.1" 200 -
And here is when I try to use C# code:
127.0.0.1 - - [15/Apr/2019 16:36:54] "POST / HTTP/1.1" 500 -
EDIT: Sorry I had some extraneous lines that I removed.
The HTTPClient is putting the parameters inside the body of the request. If you would like to add it to the URI you can do so by modifying the URI string. If you are not posting anything to the body, I would modify the FLASK API to receive a GET instead of a POST.
Typically I would only use query string parameters in the URI string for HTTPGet requests, even though there is no rule stopping you from doing this with a HTTPPost. Additionally, I would only include my parameters for an HTTPPost request within the body. Again, no rule for this but just what I do.
Here are a few examples to help you along with the HTTPClient post async method:
https://csharp.hotexamples.com/examples/-/HttpClient/PostAsync/php-httpclient-postasync-method-examples.html

My webservice wont work because HttpContentHeaders does not implement Add(System.Object)

I'm new to this one so please be gentle.
I am adding a method to a webservice so that it can recieve POSTs from another.
The POST contains a JSON object in the body with parameters I need in the receiving solution.
I am using HttpClient's PostAsync method to send the data (as FormUrlEncodedContent) and I'm trying to recieve it as HttpRequestMessage.
When i try to compile and build I get this error in my recieving service
To be XML serializable, types which inherit from IEnumerable must have
an implementation of Add(System.Object) at all levels of their
inheritance hierarchy. System.Net.Http.Headers.HttpContentHeaders does
not implement Add(System.Object).
The odd thing is that I use these both of these in the POSTing webservice (I use ajax on a page to test it) and don't get this problem.
This is the sender:
string url = *url*;
HttpClient client = new HttpClient();
Dictionary<string, string> parameters;
parameters = new Dictionary<string, string>
{
{ "*variablename1", *string* },
{ "variablename2", *string* },
{ "variablename3", *string* },
{ "variablename4", *string* }
};
var content = new FormUrlEncodedContent(parameters);
HttpResponseMessage response = client.PostAsync(url, content).Result;
And this is what is supposed to be receiving it...
public void Receive(HttpRequestMessage request)
{
string data = request.Content.ReadAsStringAsync().Result;
*Database object* = new *Database object*();
string decodedString = HttpUtility.UrlDecode(data);
*Stuff that processes decodedString into a dictionary and puts it in database*
What I'd like is to know how I can get this to work, so an alternative to HttpRequestMessage that doesn't depend on HttpContentheaders having an add method, or another way of sending the post that doesn't require them on the recieving end.
Apologies for vague bits of code, I've only been a programmer for three months and my company works with a lot of confidential data, I'm just covering my back.

Passed Variables Null

I am building a small Web API for syncing data and pulling down the objects works great, but pushing my objects up doesn't work no matter what I have tried.
Edited to reflect some changes:
Here is my Controller:
[System.Web.Mvc.HttpPost]
public void UpdateTasks([FromBody] string s)
{
Console.WriteLine(s);
}
Here is my Client code:
HttpContent c = new StringContent("1234");
HttpClient client = new HttpClient();
c.Headers.ContentType = new MediaTypeHeaderValue("application/json");
client.BaseAddress = new Uri("http://localhost/QAQC_SyncWebService/Tasks/UpdateTasks/");
var resp = client.PostAsync(client.BaseAddress, c).Result;
I can get a value though if I put it in the URI, but the string content alone doesn't seem to work.
Try
[HttpPut]
public void UpdateTasks([FromBody]string s)
{
Console.WriteLine(s);
}
Please also note:
[FromBody] parameters must be encoded as =value
The final hurdle remaining is that Web API requires you to pass [FromBody] parameters in a particular format. That’s the reason why our value parameter was null in the previous example even after we decorated the method’s parameter with [FromBody].
Instead of the fairly standard key=value encoding that most client- and server-side frameworks expect, Web API’s model binder expects to find the [FromBody] values in the POST body without a key name at all. In other words, instead of key=value, it’s looking for =value.
This part is, by far, the most confusing part of sending primitive types into a Web API POST method. Not too bad once you understand it, but terribly unintuitive and not discoverable.
from http://encosia.com/using-jquery-to-post-frombody-parameters-to-web-api/
The line you are initializing client.BaseAddress looks a bit off.
HttpContent c = new StringContent("1234");
HttpClient client = new HttpClient();
c.Headers.ContentType = new MediaTypeHeaderValue("application/json");
client.BaseAddress = new Uri("http://localhost/QAQC_SyncWebService/Tasks/UpdateTasks");
var resp = client.PutAsync(client.BaseAddress, c).Result;
The PutAsync method is expecting the full URI, not just a method. Read more here: https://msdn.microsoft.com/en-us/library/hh138168(v=vs.118).aspx?cs-save-lang=1&cs-lang=csharp#code-snippet-1

HttpClient not downloading the entire JSON

I have an app on Windows Phone Store, it's a Feedly client and some of my users have been reporting an error for a while.
The error is a JsonReaderException: Unterminated string. Expected delimiter: ". Path 'items[0].summary.content', line 1, position 702
Looking at the error, it seems that the HttpClient didn't download the entire Json, since the position is the end of the response and the response seems incomplete.
Here is one of the responses:
{
"id":"user/{userIdOmmited}/category/global.all",
"updated":1417324466038,
"continuation":"149ebfc5c13:c446de6:113fbbc6",
"items": [{
"id":"HBKNOlrSqigutJYKcZCnF5drtVL1uLeqMvamlHXyreE=_149ff1f0f76:213a17:34628bd3",
"fingerprint":"eb0dc432",
"originId":"https://medium.com/p/7948bfedb1bc",
"updated":1417324463000,
"title":"Iran’s Stealth Drone Claims Are Total BS",
"published":1417324463000,"crawled":1417324466038,
"alternate":[{
"href":"https://medium.com/war-is-boring/irans-stealth-drone-claims-are-total-bs-7948bfedb1bc",
"type":"text/html"
}],
"summary":{
"content":"<div><p><a href=\"https://medium.com/war-is-boring/irans-stealth-drone-claims-are-total-bs-7948bfedb1bc\"><img height=\"200
This is the entire Json of one of the responses, as you can see it ends suddenly at the summary.content, that's why Json.Net can't deserialize it.
My Get method looks like this:
protected async Task<T> GetRequest<T>(string url)
{
var handler = new HttpClientHandler();
if (handler.SupportsAutomaticDecompression)
handler.AutomaticDecompression = System.Net.DecompressionMethods.GZip | System.Net.DecompressionMethods.Deflate;
using (var client = new HttpClient(handler))
{
var request = new HttpRequestMessage(HttpMethod.Get, url);
request.Headers.AcceptEncoding.Add(new StringWithQualityHeaderValue("gzip"));
if (authentication != null)
request.Headers.Authorization = authentication;
var result = await client.SendAsync(request);
result.EnsureSuccessStatusCode();
var data = await result.Content.ReadAsStringAsync();
return JsonConvert.DeserializeObject<T>(data.EscapeJson());
}
}
I pass the response DTO as a generics parameter to the method and it deserializes the Json.
The EscapeJson method in the return looks like this:
public static string EscapeJson(this string stringToEscape)
{
return Regex.Replace(stringToEscape, #"(?<!\\)\\(?!"")(?!n)(?!\\)", #"\\", RegexOptions.IgnorePatternWhitespace);
}
I've added this to try to solve the problem because I thought the problem was with the back slashes, but it wasn't (before I found out the json wasn't being downloaded completely).
I've been searching for a solution for this problem for a few weeks, and I couldn't come up with an answer.
In my research I found out that there is a parameter in the SendAsync that is the completionOption, which is an enum, HttpCompletionOption, that has two options: ResponseContentRead and ResponseHeadersRead.
The problem is that I don't know which one is the default and I don't know if changing this will solve the problem since I can't reproduce the problem myself, so I can't test it.
Does anyone has an idea of what might be the problem here?
Could it be a Timeout of sorts or this HttpCompletionOption?
I've been seeing the error for a while, searching for an answer and I have no clue on what might be going on.
Thanks in advance for the help!
Make one HttpClient for the app to use. Do not dispose it. HttpClient is supposed to be reused and not disposed pr request. That is most likely the error.

Categories