I need to connect to an API. All examples that I could find use Tokens which I can send to each transaction I desire.
Accordingly to the supplier documentation, I couldn't find anything related to tokens.
The problem is when I connect, using curl or wp_remote_post() I don't have an 'connected' object to keep doing the transactions that I need.
Bellow is how it is done in C#. I need some directions in what objects I have to use and create the same functionality in php. Thanks
Connection Class:
public class RestService: IDisposable {
private readonly HttpClient _httpClient;
private readonly string _acumaticaBaseUrl;
public RestService(
string acumaticaBaseUrl, string userName, string password, string company, string branch, string locale) {
_acumaticaBaseUrl = acumaticaBaseUrl;
_httpClient = new HttpClient(
new HttpClientHandler {
UseCookies = true,
CookieContainer = new CookieContainer()
}) {
BaseAddress = new Uri(acumaticaBaseUrl + "/entity/Default/6.00.001/"),
DefaultRequestHeaders = {
Accept = {
MediaTypeWithQualityHeaderValue.Parse("text/json")
}
}
};
//Log in to MYOB Advanced
_httpClient.PostAsJsonAsync(
acumaticaBaseUrl + "/entity/auth/login", new {
name = userName,
password = password,
company = company,
branch = branch,
locale = locale
}).Result.EnsureSuccessStatusCode();
}
void IDisposable.Dispose() {
_httpClient.PostAsync(_acumaticaBaseUrl + "/entity/auth/logout", new ByteArrayContent(new byte[0])).Wait();
_httpClient.Dispose();
}
}
////////////////
//Data submission
public string Put(string entityName, string parameters, string entity) {
var res = _httpClient.PutAsync(_acumaticaBaseUrl + "/entity/Default/6.00.001/" + entityName + "?" + parameters, new StringContent(entity, Encoding.UTF8, "application/json")).Result.EnsureSuccessStatusCode();
return res.Content.ReadAsStringAsync().Result;
}
}
Related
This question already has answers here:
What is the overhead of creating a new HttpClient per call in a WebAPI client?
(7 answers)
Closed 7 months ago.
The community reviewed whether to reopen this question 7 months ago and left it closed:
Original close reason(s) were not resolved
This is a made up scenario and I want to find the best way of implementing and learn which are the best practices.
I am writing a service that makes a get request to an api which requires an Authorization header, the problem is that I am initializing a new client for each service, I was thinking if it is the best to have a singleton client and use that anywhere in different services.
Here I have my BlogService, where I initialize the client and then add the Authorization header, username and password will be in configs but this is just an example.
namespace MyDemoProject.Services
{
public class BlogService : IBlogService
{
private readonly HttpClient client;
private string username = "myUsername";
private string password = "myPassword";
private static string url = "https://example.com/api/blogs";
public BlogService()
{
client = new HttpClient();
string encoded = System.Convert.ToBase64String(Encoding.GetEncoding("ISO-8859-1").GetBytes(username + ":" + password));
client.DefaultRequestHeaders.Add("Authorization", "Basic " + encoded);
}
public async Task<string> GetAllBlogs()
{
var request = await client.GetAsync(url);
var data = await request.Content.ReadAsStringAsync();
return data;
}
}
}
And here I have my CommentService, which is very similar, but with a different url. This is a simple example and it could be included in the same BlogService, but suppose that I want to make it granular for maintainability and scalability.
namespace MyDemoProject.Services
{
public class CommentService : ICommentService
{
private readonly HttpClient client;
private string username = "myUsername";
private string password = "myPassword";
private static string url = "https://example.com/api/comments";
public CommentService()
{
client = new HttpClient();
string encoded = System.Convert.ToBase64String(Encoding.GetEncoding("ISO-8859-1").GetBytes(username + ":" + password));
client.DefaultRequestHeaders.Add("Authorization", "Basic " + encoded);
}
public async Task<string> GetAllComments()
{
var request = await client.GetAsync(url);
var data = await request.Content.ReadAsStringAsync();
return data;
}
}
}
Is there any better way of implementing this, and are there any issues with this implementation?
The best practice in .net 6 is to use IHttpClientFactory to avoid the creation of new HttpClient each time you have to use it and to avoid code duplication: https://learn.microsoft.com/en-us/dotnet/architecture/microservices/implement-resilient-applications/use-httpclientfactory-to-implement-resilient-http-requests.
Here a simple example based on your code.
You have to register and configure IHttpClientFactory like:
_ = services.AddHttpClient("MyHttpClientKey", (serviceProvider, httpClient) =>
{
private string username = "myUsername";
private string password = "myPassword";
string encoded = System.Convert.ToBase64String(Encoding.GetEncoding("ISO-8859-1").GetBytes(username + ":" + password));
_ = httpClient.BaseAddress = new Uri("https://example.com/api/comments");
_ = httpClient.Timeout = TimeSpan.FromSeconds(30);
httpClient.DefaultRequestHeaders.Add("Authorization", "Basic " + encoded);
});
Then you can use HttpClient injecting it in your classes:
public class CommentService : ICommentService
{
private readonly HttpClient client;
public CommentService(IHttpClientFactory httpClientFactory)
{
httpClient = httpClientFactory.CreateClient("MyHttpClientKey");
}
public async Task<string> GetAllComments()
{
var request = await client.GetAsync(url);
var data = await request.Content.ReadAsStringAsync();
return data;
}
}
Hope it helps!
You should not try to manually create instance of HttpClient better avoid it as it might cause unwanted issues due to DNS cache and not recycling of TCP connections.
Use HttpClientFactory instead, it will take care of creating and maintaining the HttpClient for you
Add this dependency from nuget
Microsoft.Extensions.DependencyInjection
If you are using dependency injection add this code in startup file
In Startup.cs
public void ConfigureServices(IServiceCollection services)
{
services.AddScoped<BearerTokenHandler>();
services.AddHttpClient<ICommentService, CommentService >()
.ConfigureHttpClient(client =>
{
client.BaseAddress = new Uri("https://example.com/api");
}).AddHttpMessageHandler<BearerTokenHandler>();;
}
namespace MyDemoProject.Services
{
public class BearerTokenHandler : DelegatingHandler
{
private const string BasicAuthenticationScheme = "Basic";
private string username = "myUsername";
private string password = "myPassword";
public BearerTokenHandler()
{
}
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
string encoded = System.Convert.ToBase64String(Encoding.GetEncoding("ISO-8859-1").GetBytes(username + ":" + password));
request.Headers.Authorization = new AuthenticationHeaderValue(BasicAuthenticationScheme, token);
return await base.SendAsync(request, cancellationToken);
}
}
public class CommentService : ICommentService
{
private readonly HttpClient client;
public CommentService(HttpClient httpClient)
{
client = httpclient
}
public async Task<string> GetAllComments()
{
string response = null;
using (var request = new HttpRequestMessage( HttpMethod.GET, "comments"))
{
response = await client.SendAsync(request);
}
var data = await response.Content.ReadAsStringAsync();
return data;
}
}
}
I have a helper class to get access token from IdentityServer4. Here is the code:
public class ServerTokenHelper
{
static TokenResponse Token { get; set; }
static DateTime ExpiryTime { get; set; }
string _host;
string _clientId;
string _clientSecret;
string _clientScopes;
static object ThreadLock = new object();
static ConcurrentDictionary<string, Tuple<string, string, TokenResponse, DateTime>> userNameCache =
new ConcurrentDictionary<string, Tuple<string, string, TokenResponse, DateTime>>();
private static HttpClient _tokenClient = new HttpClient();
public ServerTokenHelper(string commAddress, string host, string clientId, string clientSecret, string clientScopes)
{
_host = host;
_clientId = clientId;
_clientSecret = clientSecret;
_clientScopes = clientScopes;
}
public async Task<TokenResponse> GetUserTokenResponseAsync(string userName, string password)
{
if (userName != null && userName.Length > 0)
{
lock (ThreadLock)
{
if (userNameCache.TryGetValue(userName, out var cacheItem))
{
// Since we always cache the result below, we should verify before reusing an entry that the IdentityToken
// isn't null because of an error getting it last time!
if (cacheItem.Item2 == password && cacheItem.Item3 != null && cacheItem.Item3.IdentityToken != null
&& cacheItem.Item4 > DateTime.UtcNow)
{
// System.Diagnostics.Debug.WriteLine($"GetUserTokenResponseAsync({userName}): returning cached value");
return cacheItem.Item3;
}
}
}
}
Trace.WriteLine($"GetUserTokenResponseAsync({userName}): new token being retrieved...");
bool blHttps = false;
if (_host.ToLower().Contains("https")) blHttps = true;
var disco = await _tokenClient.GetDiscoveryDocumentAsync(new DiscoveryDocumentRequest
{
Address = _host,
Policy = { RequireHttps = blHttps }
});
if (disco.IsError)
{
Trace.WriteLine($"GetUserTokenResponseAsync({userName}): GetDiscoveryDocumentAsync failed: {disco.Error}");
return null;
}
// request token
var tokenResponse = await _tokenClient.RequestPasswordTokenAsync(new PasswordTokenRequest
{
Address = disco.TokenEndpoint,
ClientId = _clientId,
ClientSecret = _clientSecret,
Scope = _clientScopes,
UserName = userName,
Password = password
});
if (tokenResponse.IsError)
{
Trace.WriteLine($"GetUserTokenResponseAsync({userName}): Could not retrieve token. {tokenResponse.Error} - {tokenResponse.ErrorDescription}");
}
lock (ThreadLock)
{
userNameCache[userName] = Tuple.Create(userName, password, tokenResponse,
DateTime.UtcNow.AddSeconds((tokenResponse != null) ? tokenResponse.ExpiresIn - 120 : 0));
}
return tokenResponse;
}
}
The intent of above codes is to get access token for User Resources Password flow. Recently, someone changed from
private HttpClient _tokenClient = new HttpClient();
to
private static HttpClient _tokenClient = new HttpClient();
With this change, we got some errors occasionally. The code function is in production server. There may be several thousands of api calls each hour. Here is the error message:
GetUserTokenResponseAsync: GetDiscoveryDocumentAsync failed
Can someone explain what is the issue caused by this simple change?
With HttpClient this line is the problem
private static HttpClient _tokenClient = new HttpClient();
HttpClient is not supposed to be reused/cached, instead you are supposed to dispose it after each use, because you might otherwise get various issued, like DNS or that you run out of TCP/IP ports.
But even better, why not cache the discovery document for X minutes? That document does not change that often.
See these articles:
You're using HttpClient wrong and it is destabilizing your software
Use IHttpClientFactory to implement resilient HTTP requests
I am learning as I create, that being said, I have spent quite a few hours on JUST the login/register pages in the app I am trying to make.
I have finally got to the point where I am able to make the API call to get the response back with the information I need.
I just don't know how to save the token once it comes back.
I am using SQLite for local storage, and I have a "Token" nclass to save it to, but I can't figure out how to actually save it and continue forward.
(I could be completely wrong and it doesn't work at all, but that's all part of learning, I guess.)
anyways, here is my Token class
public class Token
{
[PrimaryKey]
public int Id { get; set; }
public string accessToken { get; set; }
public string errorDescription { get; set; }
public DateTime expireDate { get; set; }
public int expireIn { get; set; }
public Token() { }
}
and here is my APIServices class (some stuff is commented because I am working with my buddy to get everything sorted on the API side)
public class ApiServices
{
public string JsonResult { get; private set; }
public async Task<bool> RegisterUserAsync(string email, string name, /*string first_name, string last_name,*/ string password)
{
var client = new HttpClient();
var model = new RegisterBindingModel
{
Email = email,
//FirstName = first_name,
//LastName = last_name,
Name = name,
Password = password,
};
var json = JsonConvert.SerializeObject(model);
HttpContent httpContent = new StringContent(json);
httpContent.Headers.ContentType = new MediaTypeHeaderValue("application/json");
var response = await client.PostAsync("https://myurl/v1/auth/register", httpContent);
if (response.IsSuccessStatusCode)
{
var result = JsonConvert.DeserializeObject(JsonResult);
return true;
}
return false;
}
public async Task<string> LoginAsync(string email, string password)
{
var keyValues = new List<KeyValuePair<string, string>>
{
new KeyValuePair<string, string>("email", email),
new KeyValuePair<string, string>("password", password),
new KeyValuePair<string, string>("grant_type", "password")
};
var request = new HttpRequestMessage(HttpMethod.Post, "https://myurl/auth/login" + "Token");
request.Content = new FormUrlEncodedContent(keyValues);
var client = new HttpClient();
var response = await client.SendAsync(request);
var content = await response.Content.ReadAsStringAsync();
JObject jwtDynamic = JsonConvert.DeserializeObject<dynamic>(content);
var accessTokenExpiration = jwtDynamic.Value<DateTime>(".expires");
var accessToken = jwtDynamic.Value<string>("access_token");
//Settings.AccessTokenExpirationDate = accessTokenExpiration;
Debug.WriteLine(accessTokenExpiration);
Debug.WriteLine(content);
return accessToken;
}
}
Lets explain it step by step:
1.Install the sqlite-net-pcl package(Nuget URL). This package will help you to work with database in an easy way.
2.After installing it successfully, You need to add the using statement of SQLite to your class:
using SQLite;
3.In order to make a request to database, you will need to first make a connection to database. to do so, you need to declare a variable for aforementioned goal:
var db = new SQLiteConnection (dbPath);
4.Once you have the database connection object, you are ready to make requests to database. for example to save an object of type Token class, just do this:
var tokenInfo= new Token();
tokenInfo.accessToken="...";//set value to other properties like this
db.Insert(tokenInfo);
These are all you need to do an Insert request to database!
You might want to read more about aforementioned package in these urls:
https://github.com/praeclarum/sqlite-net
https://learn.microsoft.com/en-us/xamarin/android/data-cloud/data-access/using-sqlite-orm
I have a simple post request using the Flurl client, and I was wondering how to make this request using a proxy using information like the IP, port, username, and password.
string result = await atc.Request(url)
.WithHeader("Accept", "application/json")
.WithHeader("Content-Type", "application/x-www-form-urlencoded")
.WithHeader("Host", "www.website.com")
.WithHeader("Origin", "http://www.website.com")
.PostUrlEncodedAsync(new { st = colorID, s = sizeID, qty = 1 })
.ReceiveString();
I was looking for a similar answer and found this:
https://github.com/tmenier/Flurl/issues/228
Here is a copy of the contents of that link. It worked for me!
You can do this with a custom factory:
using Flurl.Http.Configuration;
public class ProxyHttpClientFactory : DefaultHttpClientFactory {
private string _address;
public ProxyHttpClientFactory(string address) {
_address = address;
}
public override HttpMessageHandler CreateMessageHandler() {
return new HttpClientHandler {
Proxy = new WebProxy(_address),
UseProxy = true
};
}
}
To register it globally on startup:
FlurlHttp.Configure(settings => {
settings.HttpClientFactory = new ProxyHttpClientFactory("http://myproxyserver");
});
Im trying to convert a C# example thats posting to a API into .vb.
My programming skills are limited so I cant seem to get it to work.
One problem is that I cant use httpclient cause the project is in Framework 3.5 and thats something I cant change. Anyone?
Here is the c# code:
public class TriggerData
{
//if true, the contact will be updated with sent property data (affects performance).
public bool saveProps { get; set; }
public string originalId { get; set; }
public Dictionary<string, string> properties { get; set; }
public TriggerData()
{
properties = new Dictionary<string, string>();
}
}
public class CarmaTriggerClient
{
static void Main(string[] args)
{
//use your settings here
var host = "https://www.adress.com";
var customerId = 0;
var triggerId = 0;
var user = "";
var pass = "";
var data = new TriggerData();
//unique identifier in the list
data.originalId = "th#post.se";
///property keys are emailAddress, mobileNumber, firstName, lastName, city, zip, country, middleName, title, dateOfBirth, sex, or the id of one of your custom properties
data.properties["emailAddress"] = "th#post.se";
data.properties["4321"] = "some data";
//REST resource for trigger
var path = string.Format("/rest/{0}/triggers/{1}/messages", customerId, triggerId);
TriggerAsync(host, path, user, pass, data).Wait();
}
static async Task TriggerAsync(string host, string path, string user, string pass, TriggerData data)
{
using (var client = new HttpClient())
{
client.BaseAddress = new Uri(host);
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
var credentials = string.Format("{0}:{1}", user, pass);
//http://www.ietf.org/rfc/rfc2617.txt
var authorization = Convert.ToBase64String(System.Text.Encoding.Default.GetBytes(credentials));
client.DefaultRequestHeaders.Add("Authorization", "Basic " + authorization);
// HTTP POST
var response = await client.PostAsJsonAsync(path, data);
if (!response.IsSuccessStatusCode)
{
System.Diagnostics.Debug.WriteLine(response.Content);
}
}
}
}
}
Search for csharp vbnet online converter and you will find, for example, http://www.carlosag.net/tools/codetranslator/
I checked your code. It does translate.
Framework versions are the same regarding vbnet and csharp; which is another question.
HTH