Is it possible to set custom DNS resolver in C#'s HttpClient - c#

what exactly I want :
public static CustomDnsResolver : Dns
{
.....
}
public static void Main(string[] args)
{
var httpClient = new HttpClient();
httpClient.Dns(new CustomDnsResolver());
}
basically I just want to use my custom DNS resolver in HttpClient instead of System default, Is there any way to achieve it?

The use case you have is exactly why Microsoft build the HttpClient stack. It allow you to put your business logic in layered class with the help of HttpMessageHandler class. You can find some sample in ms docs or visualstudiomagazine
void Main()
{
var dnsHandler = new DnsHandler(new CustomDnsResolver());
var client = new HttpClient(dnsHandler);
var html = client.GetStringAsync("http://google.com").Result;
}
public class DnsHandler : HttpClientHandler
{
private readonly CustomDnsResolver _dnsResolver;
public DnsHandler(CustomDnsResolver dnsResolver)
{
_dnsResolver = dnsResolver;
}
protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
var host = request.RequestUri.Host;
var ip = _dnsResolver.Resolve(host);
var builder = new UriBuilder(request.RequestUri);
builder.Host = ip;
request.RequestUri = builder.Uri;
return base.SendAsync(request, cancellationToken);
}
}
public class CustomDnsResolver
{
public string Resolve(string host)
{
return "127.0.0.1";
}
}

Not sure if this works for all cases, but it worked brilliantly for my case, even when using HTTPS. So what you do is you replace the host in the URL with the actual IP-address that your custom resolver has resolved, and then you simply add a "host" header with the host name. Like this:
var requestUri = new Uri("https://123.123.123.123/some/path");
using var request = new HttpRequestMessage(HttpMethod.Get, requestUri);
request.Headers.TryAddWithoutValidation("host", "www.host-name.com");
using var response = await httpClient.SendAsync(request);
I hope this helps as this is far by the first time I've run into this issue and I've never been able to solve it before now.

Consider using SocketsHttpHandler.ConnectCallback.

Related

Azure DevOps API creating work item returns 404 error

I am attempting to add a work task using Azure Apis but I keep getting a 404 error. I attempted to do a get all projects and this works (so my authentication token is working fine). I even granted all Azure Permissions to the token and it still returns a 404 error.
public class Main
{
public static void Main(string[] args)
{
AzureClient ac = new AzureClient();
var task = ac.AddTask();
}
}
public class AzureClient
{
private readonly HttpClient _client;
public AzureClient()
{
_client = new HttpClient()
{
Timeout = TimeSpan.FromSeconds(30)
};
_client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
// ADDED PAT HERE TO CLIENT
}
public async Task AddTask()
{
List<object> task = new List<object>
{
new { op = "add", path = "/fields/System.Title", value = "Test"}
};
string jsonTask = JsonConvert.SerializeObject(task);
string baseUri = "some base uri";
string uri = $"{baseUri}/_apis/wit/workitems/$Task?api-version=5.0";
// RESPONSE HERE RETURNS 404
var response = _client.PostAsync(uri, new StringContent(jsonTask, Encoding.UTF8, "application/json-patch+json")).Result;
}
}
Please use the 'application/json-patch+json' instead of the 'application/json' in your code:
_client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
If you use the Postman to test the Api, you will find the error:
Valid content types for this method are: application/json-patch+json.
That's way we need to use the application/json-patch+json
Hope this will help.

Mirror of gRPC calls

I want to be able to mirror all gRPC calls for some services to another set of services with same interface, in C#.
I think about writing extension method for GrpcClientFactory.CreateClient, to return class which would do two calls in parallel. Result from mirror is not needed, so I would return result from first call.
Is it best aproach for the task, or it is possible to make it better?
Pretty much what you described, in your case you can make use of IHttpClientFactory for creating http clients
public class GrpResponse
{
public string client { get; set; }
public HttpResponseMessage response { get; set; }
}
private async void ExecuteGrpRequest()
{
var payLoad = new HttpRequestMessage();
var grpTasks = new List<Task<GrpResponse>>
{
SendAsyc("real", payLoad),
SendAsyc("mock", payLoad)
};
var responses = await Task.WhenAll(grpTasks);
}
private async Task<GrpResponse> SendAsyc(string client, HttpRequestMessage message )
{
// IHttpClientFactory is _httpClientFacotry; injected from ctor
var httpClient = _httpClientFacotry.CreateClient(client);
return new GrpResponse
{
client = client,
response = await httpClient.SendAsync(new HttpRequestMessage())
};
}

Add multiple tokens into DefaultRequestHeaders.Authorization in HttpClient

The API I'm calling from my ASP.NET Web API app requires two tokens i.e. accessToken and userToken.
The following code is not working because it takes only the second token, not both. Looks like the second line is over-writing the first one.
How do I add multiple tokens to my request header?
_client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("APIAccessToken", "token1");
_client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("UserToken", "token2");
UPDATE:
Here's the way I set this up and it's not working. Basically, my API calls seem to go nowhere. I get no errors. Just no response.
First, I have the HttpClientAccessor that looks like this:
public static class HttpClientAccessor
{
private static Lazy<HttpClient> client = new Lazy<HttpClient>(() => new HttpClient());
public static HttpClient HttpClient
{
get
{
client.Value.BaseAddress = new Uri("https://api.someurl.com");
client.Value.DefaultRequestHeaders.Accept.Clear();
client.Value.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
client.Value.DefaultRequestHeaders.TryAddWithoutValidation("APIAccessToken", "token1");
client.Value.DefaultRequestHeaders.TryAddWithoutValidation("UserToken", "token2");
return client.Value;
}
}
}
I then have my ApiClient that will perform my API calls which looks like this:
public class MyApiClient
{
HttpClient _client;
public MyApiClient()
{
_client = HttpClientAccessor.HttpClient;
}
public async Task Get()
{
try
{
HttpResponseMessage response = await _client.GetAsync("/myendpoint"); // This is where it gets lost
var data = await response.Content.ReadAsStringAsync();
}
catch(Exception e)
{
var error = e.Message;
}
}
}
This is my controller action:
public class MyController : Controller
{
private readonly MyApiClient _client;
public MyController()
{
_client = new MyApiClient();
}
public IActionResult SomeAction()
{
_client.Get().Wait();
}
}
You are confusing the standard authorization header with custom headers
According to the linked documentation
Request Header
Add the generated tokens to the request headers "APIAccessToken" and "UserToken"
Example Request
APIAccessToken: zjhVgRIvcZItU8sCNjLn+0V56bJR8UOKOTDYeLTa43eQX9eynX90QntWtINDjLaRjAyOPgrWdrGK12xPaOdDZQ==
UserToken: 5sb8Wf94B0g3n4RGOqkBdPfX+wr2pmBTegIK73S3h7uL8EzU6cjsnJ0+B6vt5iqn0q+jkZgN+gMRU4Y5+2AaXw==
To get headers like above, add them to the client like below
_client.DefaultRequestHeaders.TryAddWithoutValidation("APIAccessToken", "token1");
_client.DefaultRequestHeaders.TryAddWithoutValidation("UserToken", "token2");
Based on shown update, the client is adding the headers every time the client is called. This should be in the value factory of the lazy client.
public static class HttpClientAccessor {
public static Func<HttpClient> ValueFactory = () => {
var client = new HttpClient();
client.BaseAddress = new Uri("https://someApiUrl");
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
client.DefaultRequestHeaders.TryAddWithoutValidation("APIAccessToken", "token1");
client.DefaultRequestHeaders.TryAddWithoutValidation("UserToken", "token2");
return client;
};
private static Lazy<HttpClient> client = new Lazy<HttpClient>(ValueFactory);
public static HttpClient HttpClient {
get {
return client.Value;
}
}
}
The controller action also needs to be refactored to avoid deadlocks because of the mixing of async and blocking calls like .Wait() or .Result.
public class MyController : Controller {
private readonly MyApiClient _client;
public MyController() {
_client = new MyApiClient();
}
public async Task<IActionResult> SomeAction() {
await _client.Get();
//... code removed for brevity
}
}

Setting cookies to httpClient of Asp.Net Core TestServer

I,m testing ASP.NET Core app with TestServer, and there are controllers that require cookie auth. I've created test server instance like this:
_testServer = new TestServer(new WebHostBuilder()
.UseEnvironment(CustomEnvironments.Test)
.UseContentRoot(currentDirectory)
.UseStartup<Web.Startup>()
.UseUrls("http://localhost/"));
ApiClient = _testServer.CreateClient();
and now I have to add auth cookie, but it is ignored by server. If the client could be created directly I could pass HttpClientHandler to constractor and set UseCookies to false, and it works, but I can't access the handler when I get client from test server. Is there a way to add auth cookies to test client?
I've found the solution. TestServer has method CreateRequest(string path), it returns RequestBuilder, which allows to insert cookies to header
Using #AlexK's answer for inspiration, combined with information from a blog post (as an aside, this post goes into a lot of other useful details when dealing with other issues when sending requests to the test server), here is one way of getting cookies to work with the TestServer using CreateRequest(string path) based on what I used for my own project.
public class TestWebEnvironment : IDisposable
{
private TestServer Server { get; }
private CookieContainer CookieContainer { get; }
public TestWebEnvironment()
{
var builder = new WebHostBuilder()
.UseEnvironment("Test")
.UseStartup<TestWebStartup>();
Server = new TestServer(builder);
CookieContainer = new CookieContainer();
}
private RequestBuilder BuildRequest(string url)
{
var uri = new Uri(Server.BaseAddress, url);
var builder = Server.CreateRequest(url);
var cookieHeader = CookieContainer.GetCookieHeader(uri);
if (!string.IsNullOrWhiteSpace(cookieHeader))
{
builder.AddHeader(HeaderNames.Cookie, cookieHeader);
}
return builder;
}
private void UpdateCookies(string url, HttpResponseMessage response)
{
if (response.Headers.Contains(HeaderNames.SetCookie))
{
var uri = new Uri(Server.BaseAddress, url);
var cookies = response.Headers.GetValues(HeaderNames.SetCookie);
foreach (var cookie in cookies)
{
CookieContainer.SetCookies(uri, cookie);
}
}
}
public async Task<string> GetAsync(string url)
{
using (var response = await BuildRequest(url).GetAsync())
{
UpdateCookies(url, response);
return await response.Content.ReadAsStringAsync();
}
}
public async Task<string> PostAsync(string url, HttpContent content)
{
var builder = BuildRequest(url);
builder.And(request => request.Content = content);
using (var response = await builder.PostAsync())
{
UpdateCookies(url, response);
return await response.Content.ReadAsStringAsync();
}
}
public void Dispose()
{
Server.Dispose();
}
}

HttpClient vs HttpWebRequest for better performance, security and less connections

I discovered that a single HttpClient could be shared by multiple requests. If shared, and the requests are to the same destination, multiple requests could reuse the connections. WebRequest needs to recreate the connection for each request.
I also looked up some documentation on other ways to use HttpClient in examples.
The following article summarizes the high-speed NTLM-authenticated connection sharing: HttpWebRequest.UnsafeAuthenticatedConnectionSharing
 
Possible implementations that I tried out are shown below
A)
private WebRequestHandler GetWebRequestHandler()
{
CredentialCache credentialCache = new CredentialCache();
credentialCache.Add(ResourceUriCanBeAnyUri, "NTLM", CredentialCache.DefaultNetworkCredentials);
WebRequestHandler handler = new WebRequestHandler
{
UnsafeAuthenticatedConnectionSharing = true,
Credentials = credentialCache
};
return handler;
}
using (HttpClient client = new HttpClient(GetWebRequestHandler(), false))
{
}
B)
using (HttpClient client = new HttpClient)
{
}
C)
HttpWebRequest req = (HttpWebRequest)WebRequest.Create("some uri string")
I would appreciate any help in making me understand which approach I should take so as to achieve max performance, minimizing connections and making sure security is not impacted.
If you use either of them with async it should be good for the performance point of view as it will not block the resources waiting for the response and you will get good throughput.
HttpClient is preferred over HttpWebRequest due to async methods available out of the box and you would not have to worry about writing begin/end methods.
Basically when you use async call (using either of the class), it will not block the resources waiting for the response and any other request would utilise the resources to make further calls.
Another thing to keep in mind that you should not be using HttpClient in the 'using' block to allow reuse of same resources again and again for other web requests.
See following thread for more information
Do HttpClient and HttpClientHandler have to be disposed?
This is my ApiClient which creates the HttpClient for only once. Register this object as singleton to your dependency injection library. It's safe to reuse because it's stateless. Do NOT recreate HTTPClient for each request. Reuse Httpclient as much as possible
using System;
using System.Net;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Text;
using System.Threading.Tasks;
//You need to install package Newtonsoft.Json > https://www.nuget.org/packages/Newtonsoft.Json/
using Newtonsoft.Json;
using Newtonsoft.Json.Serialization;
public class MyApiClient : IDisposable
{
private readonly TimeSpan _timeout;
private HttpClient _httpClient;
private HttpClientHandler _httpClientHandler;
private readonly string _baseUrl;
private const string ClientUserAgent = "my-api-client-v1";
private const string MediaTypeJson = "application/json";
public MyApiClient(string baseUrl, TimeSpan? timeout = null)
{
_baseUrl = NormalizeBaseUrl(baseUrl);
_timeout = timeout ?? TimeSpan.FromSeconds(90);
}
public async Task<string> PostAsync(string url, object input)
{
EnsureHttpClientCreated();
using (var requestContent = new StringContent(ConvertToJsonString(input), Encoding.UTF8, MediaTypeJson))
{
using (var response = await _httpClient.PostAsync(url, requestContent))
{
response.EnsureSuccessStatusCode();
return await response.Content.ReadAsStringAsync();
}
}
}
public async Task<TResult> PostAsync<TResult>(string url, object input) where TResult : class, new()
{
var strResponse = await PostAsync(url, input);
return JsonConvert.DeserializeObject<TResult>(strResponse, new JsonSerializerSettings
{
ContractResolver = new CamelCasePropertyNamesContractResolver()
});
}
public async Task<TResult> GetAsync<TResult>(string url) where TResult : class, new()
{
var strResponse = await GetAsync(url);
return JsonConvert.DeserializeObject<TResult>(strResponse, new JsonSerializerSettings
{
ContractResolver = new CamelCasePropertyNamesContractResolver()
});
}
public async Task<string> GetAsync(string url)
{
EnsureHttpClientCreated();
using (var response = await _httpClient.GetAsync(url))
{
response.EnsureSuccessStatusCode();
return await response.Content.ReadAsStringAsync();
}
}
public async Task<string> PutAsync(string url, object input)
{
return await PutAsync(url, new StringContent(JsonConvert.SerializeObject(input), Encoding.UTF8, MediaTypeJson));
}
public async Task<string> PutAsync(string url, HttpContent content)
{
EnsureHttpClientCreated();
using (var response = await _httpClient.PutAsync(url, content))
{
response.EnsureSuccessStatusCode();
return await response.Content.ReadAsStringAsync();
}
}
public async Task<string> DeleteAsync(string url)
{
EnsureHttpClientCreated();
using (var response = await _httpClient.DeleteAsync(url))
{
response.EnsureSuccessStatusCode();
return await response.Content.ReadAsStringAsync();
}
}
public void Dispose()
{
_httpClientHandler?.Dispose();
_httpClient?.Dispose();
}
private void CreateHttpClient()
{
_httpClientHandler = new HttpClientHandler
{
AutomaticDecompression = DecompressionMethods.Deflate | DecompressionMethods.GZip
};
_httpClient = new HttpClient(_httpClientHandler, false)
{
Timeout = _timeout
};
_httpClient.DefaultRequestHeaders.UserAgent.ParseAdd(ClientUserAgent);
if (!string.IsNullOrWhiteSpace(_baseUrl))
{
_httpClient.BaseAddress = new Uri(_baseUrl);
}
_httpClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue(MediaTypeJson));
}
private void EnsureHttpClientCreated()
{
if (_httpClient == null)
{
CreateHttpClient();
}
}
private static string ConvertToJsonString(object obj)
{
if (obj == null)
{
return string.Empty;
}
return JsonConvert.SerializeObject(obj, new JsonSerializerSettings
{
ContractResolver = new CamelCasePropertyNamesContractResolver()
});
}
private static string NormalizeBaseUrl(string url)
{
return url.EndsWith("/") ? url : url + "/";
}
}
The usage;
using ( var client = new MyApiClient("http://localhost:8080"))
{
var response = client.GetAsync("api/users/findByUsername?username=alper").Result;
var userResponse = client.GetAsync<MyUser>("api/users/findByUsername?username=alper").Result;
}
There is a problem in your implementation 'A'. The lifetime of the instance returned from GetWebRequestHandler() is shortlived (maybe just for the sake of the example?). If this was done on purpose, it negates the passing of false for the 2nd param of HttpClient constructor. The value of false tells the HttpClient to not dispose the underlying HttpMessageHandler (which helps with scaling since it does not close the port for the request). This is, of course, assuming that the lifetime of the HttpMessageHandler is long enough for you to take advantage of the benefit of not opening/closing ports (which is a big impact on the scalability of your server). Thus, I have a recommendation below of option 'D'.
There is also an option 'D' that you do not list above - to make the Httpclient instance static and re-used across all api calls. This is much more efficient from a memory allocation and GC perspective - as well as the opening of ports on the client. You don't have the overhead of allocating memory for and creating HttpClient instances (and all of its underlying objects) and thus avoid the cleanup via GC for them too.
Please refer to my answer provided about a similar question - What is the overhead of creating a new HttpClient per call in a WebAPI client?

Categories

Resources