I am working on a library for the Destiny 2 API and I am running into a problem at this spot. I wrote an extension for the HttpClient.GetAsync so that I can do HttpClient.GetAsync<T> and I am finding that in this extension I am not getting a success message because I get a
307 Temporary Redirect
I have run this same API request into Postman using my same api key and same path request and Postman doesn't get an error.
Extension Code:
public async static Task<T> GetAsync<T>(this HttpClient Web, String Path)
{
var result = await Web.GetAsync(Path);
if (result.IsSuccessStatusCode)
{
var body = await result.Content.ReadAsStringAsync();
return JsonConvert.DeserializeObject<T>(body);
}
else
{
return default(T);
}
}
HttpClient Creation at creation of root ApiClass:
private HttpClient _Web { get; set; } = new HttpClient(new HttpClientHandler()
{
AllowAutoRedirect = true
});
So after beating my head for awhile I found that I was passing this string string path = $"User/SearchUsers?q={q}"; when it wanted this string string path = $"User/SearchUsers/?q={q}"; notice the slash after SearchUsers... Their API I think is just misconfigured and that was very annoying..
Related
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.
I'm using this request code from C# and getting a Bad Request result:
public class GoogleDriveManager
{
private static readonly HttpClient Client = new HttpClient();
private const string Access = "ACCESS";
public GoogleDriveManager()
{
Client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", Access);
}
public async Task<HttpResponseMessage> GetFilesAsync(string driveId)
{
var url =
$"https://www.googleapis.com/drive/v3/files?driveId={driveId}&includeItemsFromAllDrives=true&corpora=true&supportsAllDrives=true";
var result = await Client.GetAsync(url);
return result;
}
}
But when I make the same sort of request from Postman, it works great:
GET https://www.googleapis.com/drive/v3/files?driveId=DRIVEID&includeItemsFromAllDrives=true&corpora=drive&supportsAllDrives=true
(Authorization Bearer Token, with token value the same as the one used above)
What am I doing wrong on the C# side?
According to API documentation supported values for corpora are:
user
domain
drive
allDrives
Your Postman example has corpora=drive but your C# example shows corpora=true.
I have ASP.NET Core 2.1 application hosted on Azure web app. I am sending photos base64 string over WebSockets and then by HttpClient to Azure Face API.
After some 150-250 requests HttpClient stops responding and I can't use HttpClient class in any part of my application.
In my localhost it works properly and I never get this problem.
public class FaceApiHttpClient
{
private HttpClient _client;
public FaceApiHttpClient(HttpClient client)
{
_client = client;
}
public async Task<string> GetStringAsync(byte[] byteData,string uri)
{
using (ByteArrayContent content = new ByteArrayContent(byteData))
{
content.Headers.ContentType = new MediaTypeHeaderValue("application/octet-stream");
HttpResponseMessage response = await _client.PostAsync(uri, content).ConfigureAwait(false);
return await response.Content.ReadAsStringAsync().ConfigureAwait(false);
}
}
}
DI:
services.AddHttpClient<FaceApiHttpClient>(
client => {
client.BaseAddress = new Uri("xxx");
client.DefaultRequestHeaders.Add("Ocp-Apim-Subscription-Key", "xxx");
});
The method from FaceApiClient is invoke in a Scoped Service:
public interface IFaceAPIService
{
Task<DataServiceResult<List<Face>>> GetFacesDataFromImage(byte[] byteArray);
}
public class FaceAPIService: ServiceBase, IFaceAPIService
{
private readonly IServerLogger _serverLogger;
private FaceApiHttpClient _httpClient;
//Consts
public const string _APIKey = "xxx";
public const string _BaseURL = "xxx";
public FaceAPIService(IServerLogger serverLogger, FaceApiHttpClient client)
{
_serverLogger = serverLogger;
_httpClient = client;
}
public async Task<DataServiceResult<List<Face>>> GetFacesDataFromImage(byte[] byteData)
{
try
{
// Request parameters. A third optional parameter is "details".
string requestParameters = "returnFaceId=true&returnFaceLandmarks=false&returnFaceAttributes=age,gender,headPose,smile,facialHair,glasses,emotion,hair,makeup,occlusion,accessories,blur,exposure,noise";
// Assemble the URI for the REST API Call.
string uri = _BaseURL + "/detect" + "?" + requestParameters;
var result = await _httpClient.GetStringAsync(byteData, uri).ConfigureAwait(false);
List<Face> faces = JsonConvert.DeserializeObject<List<Face>>(result);
return Success(faces);
}
catch (Exception ex)
{
_serverLogger.LogExceptionFromService(ex);
return DataServiceResult.ErrorResult<List<Face>>(ex.Message);
}
}
}
a) on localhost enviroment it works. I run 11 simulators with many request per seconds and it never broke (10 hours of simulators, over 20k requests).
b) HttpClient stops working in any part of application not only in one class.
How to fix this?
Consider changing up the design a bit.
Using a typed client the assumption is that its configuration is something that will not change frequently and that it should be added once when registering the typed client.
services.AddHttpClient<FaceApiHttpClient>(_ => {
_.BaseAddress = new Uri(Configuration["OcpApimBaseURL"]);
var apiKey = Configuration["OcpApimSubscriptionKey"];
_.DefaultRequestHeaders.Add("Ocp-Apim-Subscription-Key", apiKey);
_.Timeout = new TimeSpan(0, 0, 10);
});
Which would allow the typed client to no have to add the key for every call
public class FaceApiHttpClient {
private readonly HttpClient client;
public FaceApiHttpClient(HttpClient client) {
this.client = client;
}
public async Task<string> GetStringAsync(byte[] byteData, string uri) {
using (var content = new ByteArrayContent(byteData)) {
// This example uses content type "application/octet-stream".
// The other content types you can use are "application/json" and "multipart/form-data".
content.Headers.ContentType = new MediaTypeHeaderValue("application/octet-stream");
// Execute the REST API call.
HttpResponseMessage response; response = await _client.PostAsync(uri, content).ConfigureAwait(false);
// Get the JSON response.
return await response.Content.ReadAsStringAsync().ConfigureAwait(false);
}
}
}
It should be noted from ASP.NET Core 2.1-preview1: Introducing HTTPClient factory
A typed client is, effectively, a transient service, meaning that a new instance is created each time one is needed and it will receive a new HttpClient instance each time it is constructed. This means that the configuration func, in this case retrieving the URI from configuration, will run every time something needs a FaceApiHttpClient.
Based on previous documentations, having that many clients created can pose problems, but the assumption here is that the developers of this new feature took that into consideration when designing it.
I say this because the issues you described are similar to previous problem with the same cause.
After release version of ASP .NET CORE 2.1 RC1 the problem is fixed. I updated project to new version and now there is no problem with deadlock.
The problem with deadlock was only in ASP .NET CORE 2.1 Preview 2 version.
I have created a web API in visual studio 2015 using a MySQL database. The API is working perfect.
So I decided to make a console client application in which I can consume my web-service (web API). The client code is based on HttpClient, and in the API I have used HttpResponse. Now when I run my console application code, I get nothing. Below is my code:
Class
class meters_info_dev
{
public int id { get; set; }
public string meter_msn { get; set; }
public string meter_kwh { get; set; }
}
This class is same as in my web API model class:
Model in web API
namespace WebServiceMySQL.Models
{
using System;
using System.Collections.Generic;
public partial class meters_info_dev
{
public int id { get; set; }
public string meter_msn { get; set; }
public string meter_kwh { get; set; }
}
Console application code
static HttpClient client = new HttpClient();
static void ShowAllProducts(meters_info_dev mi)
{
Console.WriteLine($"Meter Serial Number:{mi.meter_msn}\t Meter_kwh: {mi.meter_kwh}", "\n");
}
static async Task<List<meters_info_dev>> GetAllRecordsAsync(string path)
{
List<meters_info_dev> mID = new List<meters_info_dev>();
HttpResponseMessage response = await client.GetAsync(path);
if (response.IsSuccessStatusCode)
{
mID = await response.Content.ReadAsAsync<List<meters_info_dev>>();
}
else
{
Console.WriteLine("No Record Found");
}
return mID;
}
static void Main()
{
RunAsync().Wait();
}
static async Task RunAsync()
{
client.BaseAddress = new Uri("http://localhost:2813/");
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
var m = await GetAllRecordsAsync("api/metersinfo/");
foreach(var b in m)
{
ShowAllProducts(b);
}
}
In my API I have 3 GET methods under a single controller, so I have created different routes for them. Also the URL for them is different.
http://localhost:2813/api/metersinfo/ will return all records
While debugging the code, I found that List<meters_info_dev> mID = new List<meters_info_dev>(); is empty:
While the response is 302 Found, the URL is also correct:
Update 1
After a suggestion I have done the following:
using (var client = new HttpClient())
{
List<meters_info_dev> mID = new List<meters_info_dev>();
HttpResponseMessage response = await client.GetAsync(path);
if (response.IsSuccessStatusCode)
{
mID = await response.Content.ReadAsAsync<List<meters_info_dev>>();
}
else
{
Console.WriteLine("No Record Found");
}
return mID;
}
When I run the application, I get the exception "An invalid request URI was provided. The request URI must either be an absolute URI or BaseAddress must be set."
Update 2
I have added a new piece of code:
using (var cl = new HttpClient())
{
var res = await cl.GetAsync("http://localhost:2813/api/metersinfo");
var resp = await res.Content.ReadAsStringAsync();
}
And in the response I am getting all the records:
I don't know why it's not working with the other logic and what the problem is. I have also read the questions Httpclient consume web api via console app C# and Consuming Api in Console Application.
Any help would be highly appreciated.
The code needs quite a bit of work.
The line you highlighted will always be empty because that's where you initialise the variable. What you want is run thorugh the code until you get the result back form the call.
First, make sure your api actually works, you can call the GET method you want in the browser and you see results.
using (var client = new HttpClient())
{
var result = await client.GetAsync("bla");
return await result.Content.ReadAsStringAsync();
}
that's an example of course, so replace that with your particular data and methods.
now, when you check the results just because your response.IsSuccessStatusCode is false that doesn't mean there are no records. What it means is that the call failed completely. Success result with an empty list is not the same thing as complete failure.
If you want to see what you get back you can alter your code a little bit:
if(response.IsSuccessStatusCode)
{
var responseData = await response.Content.ReadAsStringAsync();
//more stuff
}
put a breakpoint on this line and see what you actually get back, then you worry about casting the result to your list of objects. Just make sure you get back the same thing you get when you test the call in the browser.
<------------------------------->
More details after edit.
Why don't you simplify your code a little bit.
for example just set the URL of the request in one go :
using (var client = new HttpClient())
{
var result = await client.GetAsync("http://localhost:2813/api/metersinfo");
var response = await result.Content.ReadAsStringAsync();
//set debug point here and check to see if you get the correct data in the response object
}
Your first order of the day is to see if you can hit the url and get the data.
You can worry about the base address once you get a correct response. Start simple and work your way up from there, once you have a working sample.
<----------------- new edit ---------------->
Ok, now that you are getting a response back, you can serialise the string back to the list of objects using something like Newtonsoft.Json. This is a NuGet package, you might either have it already installed, if not just add it.
Add a using statement at the top of the file.
using Newtonsoft.Json;
then your code becomes something like :
using (var client = new HttpClient())
{
var result = await client.GetAsync("bla");
var response = await result.Content.ReadAsStringAsync();
var mID = JsonConvert.DeserializeObject<List<meters_info_dev>>(response);
}
At this point you should have your list of objects and you can do whatever else you need.
When testing my web API with Postman my API get executes fine!
When it comes to running the code with HttpClient in my client application the code executes without error but without the expected result on the server.
What could be happening?
From my client application:
private string GetResponseFromURI(Uri u)
{
var response = "";
HttpResponseMessage result;
using (var client = new HttpClient())
{
Task task = Task.Run(async () =>
{
result = await client.GetAsync(u);
if (result.IsSuccessStatusCode)
{
response = await result.Content.ReadAsStringAsync();
}
});
task.Wait();
}
return response;
}
Here is the API controller:
[Route("api/[controller]")]
public class CartsController : Controller
{
private readonly ICartRepository _cartRepo;
public CartsController(ICartRepository cartRepo)
{
_cartRepo = cartRepo;
}
[HttpGet]
public string GetTodays()
{
return _cartRepo.GetTodaysCarts();
}
[HttpGet]
[Route("Add")]
public string GetIncrement()
{
var cart = new CountedCarts();
_cartRepo.Add(cart);
return _cartRepo.GetTodaysCarts();
}
[HttpGet]
[Route("Remove")]
public string GetDecrement()
{
_cartRepo.RemoveLast();
return _cartRepo.GetTodaysCarts();
}
}
Note these API calls work as expected when called from Postman.
You shouldn't use await with client.GetAsync, It's managed by .Net platform, because you can only send one request at the time.
just use it like this
var response = client.GetAsync("URL").Result; // Blocking call!
if (response.IsSuccessStatusCode)
{
// Parse the response body. Blocking!
var dataObjects = response.Content.ReadAsAsync<object>().Result;
}
else
{
var result = $"{(int)response.StatusCode} ({response.ReasonPhrase})";
// logger.WriteEntry(result, EventLogEntryType.Error, 40);
}
You are doing fire-and-forget approach. In your case, you need to wait for the result.
For example,
static async Task<string> GetResponseFromURI(Uri u)
{
var response = "";
using (var client = new HttpClient())
{
HttpResponseMessage result = await client.GetAsync(u);
if (result.IsSuccessStatusCode)
{
response = await result.Content.ReadAsStringAsync();
}
}
return response;
}
static void Main(string[] args)
{
var t = Task.Run(() => GetResponseFromURI(new Uri("http://www.google.com")));
t.Wait();
Console.WriteLine(t.Result);
Console.ReadLine();
}
Simple sample used to get page data.
public string GetPage(string url)
{
HttpResponseMessage response = client.GetAsync(url).Result;
if (response.IsSuccessStatusCode)
{
string page = response.Content.ReadAsStringAsync().Result;
return "Successfully load page";
}
else
{
return "Invalid Page url requested";
}
}
I've had a problem with chace control when using httpclient.
HttpBaseProtocalFilter^ filter = ref new HttpBaseProtocolFilter();
filter->CacheControl->ReadBehavior = Windows::Web::Http::Filters::HttpCacheReadBehavior::MostRecent;
HttpClient^ httpClient = ref new HttpClient(filter);
I'm not really sure what the expected results are or what results your getting at all so this is really just a guessing game right now.
When I POST something using HttpClient I found adding headers by hand seemed to work more often than using default headers.
auto httpClient = ref new HttpClient();
Windows::Web::Http::Headers::HttpMediaTypeHeaderValue^ type = ref new Windows::Web::http::Headers::HttpMediaTypeHeaderValue("application/json");
content->Headers->ContentType = type;
If I don't do these 2 things I found, for me anyways, that half the time my web requests were either not actually being sent or the headers were all messed up and the other half of the time it worked perfectly.
I just read a comment where you said it would only fire once, that makes me think it is the cachecontrol. I think what happens is something (Windows?) sees 2 requests being sent that are the exact same, so to speed things up it just assumes the same answer and never actually sends the request a 2nd time