Singleton HttpClient handling - dotnet-httpclient

I am using a singleton instance of HttpClient to make my calls. I was wondering is there a way to test if the HttpClient is valid? Can this instance of HttpClient go bad during the life of the application where there would be a need to create a new instance. My code is as follows
public class HttpClientWrapper : IHttpClientWrapper
{
private Uri _baseAddress;
private int _connectionTimeout;
private HttpClient _httpClientInstance;
private HttpClient HttpClientInstance
{
get
{
if (_httpClientInstance == null)
_httpClientInstance = CreateHttpClient();
return _httpClientInstance;
}
}
public HttpClientWrapper(Uri baseAddress, int connectionTimeout)
{
_baseAddress = baseAddress;
_connectionTimeout = connectionTimeout;
}
public Task<HttpResponseMessage> PostAsJsonAsync<T>(string requestUri, T value)
{
//Test the instance before using it. What should be the test??
return HttpClientInstance.PostAsJsonAsync<T>(requestUri, value);
}
private HttpClient CreateHttpClient()
{
HttpClientHandler handler = new HttpClientHandler()
{
UseDefaultCredentials = true
};
HttpClient client = new HttpClient(handler);
client.Timeout = TimeSpan.FromMilliseconds(_connectionTimeout);
client.BaseAddress = _baseAddress;
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
return client;
}
}

HttpClient is designed to be reused. It is not like the old days with WCF and ChannelFactory where the channel could become in a faulted state and had to be recreated.
HttpClient is intended to be instantiated once and re-used throughout the life of an application. Especially in server applications, creating a new HttpClient instance for every request will exhaust the number of sockets available under heavy loads. This will result in SocketException errors.
Ref: https://www.asp.net/web-api/overview/advanced/calling-a-web-api-from-a-net-client

Related

Connect to Office365 via backend service using OAuth2 in NON interactive way (bar initial setup)

I have a background service which reads & sends from a mailbox. It is created in a web ui, but after the schedule is created and mailbox set, it should run automatically, without further user prompt.
I have used the various combinations of the MSAL and both public and confidential clients (either would be acceptable as the server can maintain the client secret.
I have used the EWS client and got that working, but there is a note that the client_credentials flow won't work for IMAP/POP/SMTP.
I have a small console app working, but each time it runs, it needs to login interactively, and so long as I don't restart the application, it will keep authenticating, and I can call the AquireTokenSilently.
The Question
How can I make the MSAL save the tokens/data such that when it next runs, I can authenticate without user interaction again? I can store whatever is needed to make this work when the user authenticates, but I don't know what that should be nor how to reinstate it to make a new request, if the console app is restarted.
The Code
internal async Task<string> Test()
{
PublicClientApplication =
PublicClientApplicationBuilder.Create( "5896de31-e251-460c-9dc2-xxxxxxxxxxxx" )
.WithRedirectUri( "https://login.microsoftonline.com/common/oauth2/nativeclient" )
.WithAuthority( AzureCloudInstance.AzurePublic, ConfigurationManager.AppSettings["tenantId"] )
.Build();
//var scopes = new string[] { "email", "offline_access", "profile", "User.Read", "Mail.Read" };
var scopes = new string[] { "https://outlook.office.com/IMAP.AccessAsUser.All" };
var accounts = await PublicClientApplication.GetAccountsAsync();
var firstAccount = accounts.FirstOrDefault();
AuthenticationResult authResult;
if (firstAccount == null )
{
authResult = await PublicClientApplication.AcquireTokenInteractive( scopes ).ExecuteAsync();
}
else
{
//The firstAccount is null when the console app is run again
authResult = await PublicClientApplication.AcquireTokenSilent( scopes, firstAccount ).ExecuteAsync();
}
if(authResult == null)
{
authResult = await PublicClientApplication.AcquireTokenInteractive( scopes ).ExecuteAsync();
}
MailBee.Global.LicenseKey = "MN120-569E9E8D9E5B9E8D9EC8C4BC83D3-D428"; // (demo licence only)
MailBee.ImapMail.Imap imap = new MailBee.ImapMail.Imap();
var xOAuthkey = MailBee.OAuth2.GetXOAuthKeyStatic( authResult.Account.Username, authResult.AccessToken );
imap.Connect( "imap.outlook.com", 993 );
imap.Login( null, xOAuthkey, AuthenticationMethods.SaslOAuth2, AuthenticationOptions.None, null );
imap.SelectFolder( "INBOX" );
var count = imap.MessageCount.ToString();
return authResult.AccessToken;
}
It feels very much like a step missed, which can store the information to make subsequent requests and I would love a pointer in the right direction please.
When you create your PublicClientApplication, it provides you with the UserTokenCache.
UserTokenCache implements interface ITokenCache, which defines events to subscribe to token cache serialization requests as well as methods to serialize or de-serialize the cache at various formats.
You should create your own TokenCacheBuilder, which can store the tokens in file/memory/database etc.. and then use the events to subscribe to to token cache request.
An example of a FileTokenCacheProvider:
public abstract class MsalTokenCacheProviderBase
{
private Microsoft.Identity.Client.ITokenCache cache;
private bool initialized = false;
public MsalTokenCacheProviderBase()
{
}
public void InitializeCache(Microsoft.Identity.Client.ITokenCache tokenCache)
{
if (initialized)
return;
cache = tokenCache;
cache.SetBeforeAccessAsync(OnBeforeAccessAsync);
cache.SetAfterAccessAsync(OnAfterAccessAsync);
initialized = true;
}
private async Task OnAfterAccessAsync(TokenCacheNotificationArgs args)
{
if (args.HasStateChanged)
{
if (args.HasTokens)
{
await StoreAsync(args.Account.HomeAccountId.Identifier,
args.TokenCache.SerializeMsalV3()).ConfigureAwait(false);
}
else
{
// No token in the cache. we can remove the cache entry
await DeleteAsync<bool>(args.SuggestedCacheKey).ConfigureAwait(false);
}
}
}
private async Task OnBeforeAccessAsync(TokenCacheNotificationArgs args)
{
if (!string.IsNullOrEmpty(args.SuggestedCacheKey))
{
byte[] tokenCacheBytes = await GetAsync<byte[]>(args.SuggestedCacheKey).ConfigureAwait(false);
args.TokenCache.DeserializeMsalV3(tokenCacheBytes, shouldClearExistingCache: true);
}
}
protected virtual Task OnBeforeWriteAsync(TokenCacheNotificationArgs args)
{
return Task.CompletedTask;
}
public abstract Task StoreAsync<T>(string key, T value);
public abstract Task DeleteAsync<T>(string key);
public abstract Task<T> GetAsync<T>(string key);
public abstract Task ClearAsync();
}
And the MsalFileTokenCacheProvider:
public sealed class MsalFileTokenCacheProvider : MsalTokenCacheProviderBase
{
private string basePath;
public MsalFileTokenCacheProvider(string basePath)
{
this.basePath = basePath;
}
public override Task ClearAsync()
{
throw new NotImplementedException();
}
public override Task DeleteAsync<T>(string key)
{
if (string.IsNullOrEmpty(key))
{
throw new ArgumentException("Key MUST have a value");
}
string path = Path.Combine(basePath, key + ".json");
if (File.Exists(path))
File.Delete(path);
return Task.FromResult(true);
}
public override Task<T> GetAsync<T>(string key)
{
if (string.IsNullOrEmpty(key))
{
throw new ArgumentException("Key MUST have a value");
}
string path = Path.Combine(basePath, key + ".json");
if (File.Exists(path))
{
T value = JsonConvert.DeserializeObject<T>(File.ReadAllText(path));
return Task.FromResult(value);
}
else
return Task.FromResult(default(T));
}
public override Task StoreAsync<T>(string key, T value)
{
string contents = JsonConvert.SerializeObject(value);
string path = Path.Combine(basePath, key + ".json");
File.WriteAllText(path, contents);
return Task.FromResult(value);
}
}
So based on your code you will have:
PublicClientApplication =
PublicClientApplicationBuilder.Create( "5896de31-e251-460c-9dc2-xxxxxxxxxxxx" )
.WithRedirectUri( "https://login.microsoftonline.com/common/oauth2/nativeclient" )
.WithAuthority( AzureCloudInstance.AzurePublic, ConfigurationManager.AppSettings["tenantId"] )
.Build();
MsalFileTokenCacheProvider cacheProvider = new MsalFileTokenCacheProvider("TokensFolder");
cacheProvider.InitializeCache(PublicClientApplication.UserTokenCache);
//var scopes = new string[] { "email", "offline_access", "profile", "User.Read", "Mail.Read" };
var scopes = new string[] { "https://outlook.office.com/IMAP.AccessAsUser.All" };
// when you call the below code, the PublicClientApplication will use your token cache
//provider in order to get the required Account. You should also use the
//PublicClientApplication.GetAccountAsync(key) which will use the token cache provider for
//the specific account that you want to get the token. If there is an account you could
//just call the AcquireTokenSilent method. The acquireTokenSilent method will take care of the token expiration and will refresh if needed.
//Please bare in mind that in some circumstances the AcquireTokenSilent method will fail and you will have to use the AcquireTokenInteractive method again. //Example of this would be when the user changes password, or has removed the access to your Application via their Account.
var accounts = await PublicClientApplication.GetAccountsAsync();
var firstAccount = accounts.FirstOrDefault();
Please refer to the following documentation from Microsoft.
https://learn.microsoft.com/en-us/azure/active-directory/develop/msal-net-token-cache-serialization

How can I convert an Object to Json in a Rabbit reply?

I have two applications communicating with each other using rabbit.
I need to send (from app1) an object to a listener (in app2) and after some process (on listener) it answer me with another object, now I am receiving this error:
ClassNotFound
I am using this config for rabbit in both applications:
#Configuration
public class RabbitConfiguration {
public final static String EXCHANGE_NAME = "paymentExchange";
public final static String EVENT_ROUTING_KEY = "eventRoute";
public final static String PAYEMNT_ROUTING_KEY = "paymentRoute";
public final static String QUEUE_EVENT = EXCHANGE_NAME + "." + "event";
public final static String QUEUE_PAYMENT = EXCHANGE_NAME + "." + "payment";
public final static String QUEUE_CAPTURE = EXCHANGE_NAME + "." + "capture";
#Bean
public List<Declarable> ds() {
return queues(QUEUE_EVENT, QUEUE_PAYMENT);
}
#Autowired
private ConnectionFactory rabbitConnectionFactory;
#Bean
public AmqpAdmin amqpAdmin() {
return new RabbitAdmin(rabbitConnectionFactory);
}
#Bean
public DirectExchange exchange() {
return new DirectExchange(EXCHANGE_NAME);
}
#Bean
public RabbitTemplate rabbitTemplate() {
RabbitTemplate r = new RabbitTemplate(rabbitConnectionFactory);
r.setExchange(EXCHANGE_NAME);
r.setChannelTransacted(false);
r.setConnectionFactory(rabbitConnectionFactory);
r.setMessageConverter(jsonMessageConverter());
return r;
}
#Bean
public MessageConverter jsonMessageConverter() {
return new Jackson2JsonMessageConverter();
}
private List<Declarable> queues(String... nomes) {
List<Declarable> result = new ArrayList<>();
for (int i = 0; i < nomes.length; i++) {
result.add(newQueue(nomes[i]));
if (nomes[i].equals(QUEUE_EVENT))
result.add(makeBindingToQueue(nomes[i], EVENT_ROUTING_KEY));
else
result.add(makeBindingToQueue(nomes[i], PAYEMNT_ROUTING_KEY));
}
return result;
}
private static Binding makeBindingToQueue(String queueName, String route) {
return new Binding(queueName, DestinationType.QUEUE, EXCHANGE_NAME, route, null);
}
private static Queue newQueue(String nome) {
return new Queue(nome);
}
}
I send the message using this:
String response = (String) rabbitTemplate.convertSendAndReceive(RabbitConfiguration.EXCHANGE_NAME,
RabbitConfiguration.PAYEMNT_ROUTING_KEY, domainEvent);
And await for a response using a cast to the object.
This communication is between two different applications using the same rabbit server.
How can I solve this?
I expected rabbit convert the message to a json in the send operation and the same in the reply, so I've created the object to correspond to a json of reply.
Show, please, the configuration for the listener. You should be sure that ListenerContainer there is supplied with the Jackson2JsonMessageConverter as well to carry __TypeId__ header back with the reply.
Also see Spring AMQP JSON sample for some help.

MVC app stops after SendAsync to an API - The same code runs fine in Console App though

I built a simple Console Application to test the connection to an API. Calling the connection method from Console App Main works fine. I get a response with an access-token.
I though that I just could implement the same method/code to an MVC-project and add the method within the HomeController, then call the method from any ActionResult, getting the access-token and then put it in a ViewBag to display it in a view (just for testing). But it doesn't work in the MVC-project.
If I run the debugger, it seems like the app hangs when SendAsync is executed in the method. The console gives this output:
Application Insights Telemetry (unconfigured): {"name":"Microsoft.ApplicationInsights.Dev.RemoteDependency","time":"2017-04-08T09:26:32.4945663Z","tags":{"ai.internal.sdkVersion":"rddf:2.2.0-738","ai.internal.nodeName":"XXXXXX","ai.cloud.roleInstance":"XXXXXXXX"},"data":{"baseType":"RemoteDependencyData","baseData":{"ver":2,"name":"/token","id":"XXXXXXXXX=","data":"https://api.vasttrafik.se/token","duration":"00:00:00.2810000","resultCode":"200","success":true,"type":"Http","target":"api.vasttrafik.se","properties":{"DeveloperMode":"true"}}}}
The thread 0x1f68 has exited with code 0 (0x0).
What can I do to make the API-call / response work in the MVC-application?
My knowledge in the area is ridiculously low. But I really want to understand whats going on here.
Thanks!
Best
J
MVC project
public class HomeController : Controller
{
public ActionResult Index()
{
string token = PostRequest().Result;
ViewBag.Token = token;
return View();
}
async static Task<string> PostRequest()
{
var client = new HttpClient();
client.BaseAddress = new Uri("https://api.vasttrafik.se");
var request = new HttpRequestMessage(HttpMethod.Post, "/token");
// Key // Secret
string credentials = "xxxxxxxxxoVS5xDrcO6qZsAp0a" + ":" + "xxxxxxxxhn0STj1w4asDwixdMa";
var plainTextBytes = Encoding.UTF8.GetBytes(credentials);
//Key and secret encoded
string encodedCrentedials = Convert.ToBase64String(plainTextBytes);
//Console.WriteLine(encodedCrentedials);
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic", encodedCrentedials);
var formData = new List<KeyValuePair<string, string>>();
formData.Add(new KeyValuePair<string, string>("grant_type", "client_credentials"));
formData.Add(new KeyValuePair<string, string>("scope", "xxxxxxxxw0oVS5xDrcO6qZsAp0a"));
request.Content = new FormUrlEncodedContent(formData);
// This is where the app hangs....
var response = await client.SendAsync(request);
var mycontentres = await response.Content.ReadAsByteArrayAsync();
var responseBody = Encoding.Default.GetString(mycontentres);
//Console.WriteLine(responseBody);
JavaScriptSerializer seri = new JavaScriptSerializer();
dynamic data = JObject.Parse(responseBody);
string tok = data.access_token;
return tok;
}
}
Don't block on async code:
public async Task<ActionResult> Index()
{
string token = await PostRequest();
ViewBag.Token = token;
return View();
}

Calling API (within same solution) from MVC Controller fails when true ASYNC, but works when SYNC

Can someone help me on this - in the code below the option 3 works fine for me, but that is a SYNC call, the Option 4 never returns which is true ASYNC, I need option 4 to work (the main difference is using AWAIT syntax).
// CODE from Controller within MVC APP
IRBVer01AdminSite.Data.ApiClient apic = new Data.ApiClient();
IRBVer01AdminSite.Models.Producer prd = new IRBVer01AdminSite.Models.Producer();
// opt 3 - Make a HTTP call to API DLL - BUT this one is Syncronous
source = null;
source = apic.RunAsync("http://localhost:56099/", string.Concat("api/producers/", id.ToString().Trim())).Result;
producer = null;
destination = null;
destination = Mapper.Map<IRBVer01CodeFirst.IRBVer01Domain.Producer>(source);
producer = destination;
if (producer == null)
return HttpNotFound();
//
// opt 4 - Make Http call to API DLL - Syncronous method (FAILS SO FAR)
source = null;
source = apic.GetAsyncData("http://localhost:56099/", string.Concat("api/producers/", id.ToString().Trim())).Result;
producer = null;
destination = null;
destination = Mapper.Map<IRBVer01CodeFirst.IRBVer01Domain.Producer>(source);
producer = destination;
if (producer == null)
return HttpNotFound();
//
// CODE FROM ApiClient CLASS within MVC APP
using System;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Threading.Tasks;
using Newtonsoft.Json;
// called on OPT 3
public async Task<IRBVer01Api.Models.Producer> RunAsync(string urlBase, string urlPath)
{
IRBVer01Api.Models.Producer producer = null;
using (var client = new HttpClient())
{
client.BaseAddress = new Uri(urlBase);
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
var response = client.GetStringAsync(urlPath).Result; // No.1 - This is not SYNC, there is no AWAIT syntax used
producer = Task.Factory.StartNew(() => JsonConvert.DeserializeObject<IRBVer01Api.Models.Producer>(response)).Result;
}
return producer;
}
// called on OPT 4
public async Task<IRBVer01Api.Models.Producer> GetAsyncData(string urlBase, string urlPath)
{
IRBVer01Api.Models.Producer producer = null;
using (var client = new HttpClient())
{
client.BaseAddress = new Uri(urlBase);
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
var response = await client.GetStringAsync(urlPath); // No.1 - This is ASYNC Version but comipler never comes back
//No.1 - for a moment forget about whats happening on the next syntax, the last line is not coming back... EVER
//producer = Task.Factory.StartNew(() => JsonConvert.DeserializeObject<IRBVer01Api.Models.Producer>(response)).Result;
}
return producer;
}
//

Windows 8 Httpclient basic authentcation

I am new to this forum.
I am trying to do Basic authentication using Httclient for my Windows app.
var handler2 = new HttpClientHandler
{
Credentials = new NetworkCredential(username, password)
};
var httpClient2 = new HttpClient(handler2);
httpClient2.DefaultRequestHeaders.Add("user-Agent", "authentication.cs");
var response2 = httpClient.GetAsync(uri);
I have 2 questions:
I need to add header content type and user-agent. Dont know how to add them. Could someone help me out.
In response i am getting null values. Any idea why?
Regards,
TM
You can add the user agent header by doing
client.DefaultRequestHeaders.UserAgent.Add(new ProductInfoHeaderValue("authentication.cs"));
You can't add Content-Type to the default request headers because you can only set Content-Type when you are sending some Content using a PUT or a POST.
I'm guessing you want to set the Accept header like this:
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("text/html"));
Update: Without my own account, this is as far as I can go.
public sealed partial class MainPage : Page
{
private readonly HttpClient _httpClient = new HttpClient();
public MainPage()
{
this.InitializeComponent();
InitHttpClient();
}
private void InitHttpClient() {
var username = "youremail#somewhere.com";
var password = "yourharvestpassword";
String authparam = System.Convert.ToBase64String(System.Text.Encoding.UTF8.GetBytes(username + ":" + password));
_httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic", authparam);
_httpClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/xml"));
_httpClient.DefaultRequestHeaders.UserAgent.Add(new ProductInfoHeaderValue("MyHarvestClient", "1.0"));
}
/// <summary>
/// Invoked when this page is about to be displayed in a Frame.
/// </summary>
/// <param name="e">Event data that describes how this page was reached. The Parameter
/// property is typically used to configure the page.</param>
protected override void OnNavigatedTo(NavigationEventArgs e) {
_httpClient.GetAsync("https://yoursubdomain.harvestapp.com/projects")
.ContinueWith(t => HandleResponse(t.Result));
}
private void HandleResponse(HttpResponseMessage response) {
response.EnsureSuccessStatusCode();
var contentString = response.Content.ReadAsStringAsync().Result;
var contentXML = XDocument.Parse(contentString);
}
}

Resources