Injecting state into your HttpRequest when using IHttpClientFactory is achievable by populating HttpRequestMessage.Properties see Using DelegatingHandler with custom data on HttpClient
Now if I have third party extensions on HttpClient (such as IdentityModel), how would I intercept these http requests using custom state?
public async Task DoEnquiry(IHttpClientFactory factory)
{
var id = Database.InsertEnquiry();
var httpClient = factory.CreateClient();
// GetDiscoveryDocumentAsync is a third party extension method on HttpClient
// I therefore cannot inject or alter the request message to be handled by the InterceptorHandler
var discovery = await httpClient.GetDiscoveryDocumentAsync();
// I want id to be associated with any request / response GetDiscoveryDocumentAsync is making
}
The only plausible solution I currently have is to override HttpClient.
public class InspectorHttpClient: HttpClient
{
private readonly HttpClient _internal;
private readonly int _id;
public const string Key = "insepctor";
public InspectorHttpClient(HttpClient #internal, int id)
{
_internal = #internal;
_id = id;
}
public override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
// attach data into HttpRequestMessage for the delegate handler
request.Properties.Add(Key, _id);
return _internal.SendAsync(request, cancellationToken);
}
// override all methods forwarding to _internal
}
A then I'm able to intercept these requests.
public async Task DoEnquiry(IHttpClientFactory factory)
{
var id = Database.InsertEnquiry();
var httpClient = new InspectorHttpClient(factory.CreateClient(), id);
var discovery = await httpClient.GetDiscoveryDocumentAsync();
}
Is that a plausible solution? Something tell me now not to override HttpClient. Quoting from https://learn.microsoft.com/en-us/dotnet/api/system.net.http.httpclient?view=net-5.0
The HttpClient also acts as a base class for more specific HTTP clients. An example would be a FacebookHttpClient providing additional methods specific to a Facebook web service (a GetFriends method, for instance). Derived classes should not override the virtual methods on the class. Instead, use a constructor overload that accepts HttpMessageHandler to configure any pre- or post-request processing instead.
I almost included this in my other answer as an alternative solution, but I figured it was too long already. :)
The technique is practically the same, but instead of HttpRequestMessage.Properties, use AsyncLocal<T>. "Async local" is kind of like thread-local storage but for a specific asynchronous code block.
There are a few caveats to using AsyncLocal<T> that aren't particularly well-documented:
Use an immutable nullable type for T.
When setting the async local value, return an IDisposable that resets it.
If you don't do this, then only set the async local value from an async method.
You don't have to follow these guidelines, but they will make your life much easier.
With that out of the way, the solution is similar to the last one, except it just uses AsyncLocal<T> instead. Starting with the helper methods:
public static class AmbientContext
{
public static IDisposable SetId(int id)
{
var oldValue = AmbientId.Value;
AmbientId.Value = id;
// The following line uses Nito.Disposables; feel free to write your own.
return Disposable.Create(() => AmbientId.Value = oldValue);
}
public static int? TryGetId() => AmbientId.Value;
private static readonly AsyncLocal<int?> AmbientId = new AsyncLocal<int?>();
}
Then the calling code is updated to set the ambient value:
public async Task DoEnquiry(IHttpClientFactory factory)
{
var id = Database.InsertEnquiry();
using (AmbientContext.SetId(id))
{
var httpClient = factory.CreateClient();
var discovery = await httpClient.GetDiscoveryDocumentAsync();
}
}
Note that there is an explicit scope for that ambient id value. Any code within that scope can get the id by calling AmbientContext.TryGetId. Using this pattern ensures that this is true for any code: synchronous, async, ConfigureAwait(false), whatever - all code within that scope can get the id value. Including your custom handler:
public class HttpClientInterceptor : DelegatingHandler
{
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
var id = AmbientContext.TryGetId();
if (id == null)
throw new InvalidOperationException("The caller must set an ambient id.");
// associate the id with this request
Database.InsertEnquiry(id.Value, request);
return await base.SendAsync(request, cancellationToken);
}
}
Followup readings:
Blog post on "async local" - written before AsyncLocal<T> existed, but has details on how it works. This answers the questions "why should T be immutable?" and "if I don't use IDisposable, why do I have to set the value from an async method?".
Related
I would like to gather your advice on the following problem:
Task
There are two microservices running A and B. At some time microservice_A will create a request message (or array of RequestMessage[]) which is an example "RequestMessage" and send it to microservice_B.
public class RequestMessage
{
public Guid guid;
public string result;
public DateTime expirationDateTime;
public RequestMessage()
{
guid = Guid.NewGuid();
result = "no_result";
}
}
The way of getting a response is built in the way that there is a service class that implements the method, which will be called at some time after the request was sent. The call (of the function ResolveRequestedMessage()) will be performed under the hood and only function implementation lay on the developer.
public class RequestMessageResolver : IRequestMessageResolver
{
public bool TResolveRequestedMessage(Guid Id, string result)
{
// find the Request e.g in Request[] by id, Implementation lay on the developer side and can be various.
RequestCollection.Get(id).result = "resolved";
}
}
Problem
The RequestMessage should be awaitable, however the interface of the RequestMessage is defined and cant be changed.
Microservise_A should not proceed to any further action (within the call scope, however still be valid for some other requests e.g status/error/etc) until it id not get the resolution of the requested message.
My idea and thoughts
First I tried to create a wrapper class that will have TaskCompletionSource and can be set from outside (example 1). This works but required a lot of extra wrappers to achieve the desired results.
Another idea is to modify the wrapper to implement INotifyCompletion instead of having TaskCompletionSource, but not sure if this will bring big overhead and make the solution complex for no reason - did not try yet.
Code Example 1:
public class RequestMessageWrapper
{
public TaskCompletionSource<bool> completionSource;
public RequestMessage requestMessage;
public RequestMessageWrapper(RequestMessage requestMessage)
{
completionSource = new TaskCompletionSource<bool>();
this.requestMessage = requestMessage;
}
public async Task GetResponseAsync()
{
// also need to be cancelled somehow if (DateTime.Now > requestMessage.expirationDateTime)
await completionSource.Task;
}
}
I'm trying to store token I get from external api on session.
code snippet concerning this;
[HttpPost]
public async void Post()
{
if (HttpContext.Session.GetValue<User>("Token") == null)
{
HttpContext.Session.SetValue("Token", "test");
var res = await _loginBusiness.GetToken();
HttpContext.Session.SetValue("Token", res);
}
}
HttpContext.Session.SetValue("Token", "test");
in this part, it doesn't occur any error but second the same code line give an error after GetToken().
related error
System.ObjectDisposedException: 'IFeatureCollection has been disposed.
Object name: 'Collection'.'
Also GetToken():
public async Task<User> GetToken()
{
String url = "login/login";
var client = httpClientFactory.CreateClient("VoiceScope");
var postRes = await client.PostAsync<User>(new UserLogin(), url);
return postRes;
}
The problem is that you are using async void. These promises can't be observed and their semantics end up a lot different from a normal Task. Your disposal is happening early because the infrastructure just assumes your Post method has completed (it has no way to tell otherwise).
Change the signature of Post to be:
public async Task Post()
Please note that async void should be limited to event handlers.
I am not sure about using HttpContext. You have IHttpContextAccessor in asp.net core.
I think for store token you can use this
public class UserContext
{
public UserContext(IHttpContextAccessor context)
{
Token = GetAccessToken(context);
}
private static string GetAccessToken(IHttpContextAccessor contextAccessor)
{
var identity = (ClaimsIdentity)contextAccessor?.HttpContext?.User?.Identity;
return identity?.Claims.FirstOrDefault(x => x.Type == "token")?.Value;
}
public string Token { get; }
}
And then, add this staff in your DI like scope object and use it in controllers via ServiceProvider.
I am using AzureFunctions.Autofac to inject into my Azure Functions web api. An example of the config:
public class DIConfig
{
public DIConfig()
{
DependencyInjection.Initialize(builder =>
{
// DAL
builder.Register<IDbContext>(c => new SecretCompanyContext()).InstancePerLifetimeScope();
builder.RegisterType<SecretCompanyContext>().InstancePerLifetimeScope();
builder.RegisterType<SecretCompanyContext>().As<ICartContext>().InstancePerLifetimeScope();
builder.RegisterGeneric(typeof(Repository<>)).As(typeof(IRepository<>)).InstancePerLifetimeScope();
// Services
builder.RegisterType<InventoryServices>().As<IInventoryServices>().InstancePerLifetimeScope();
// Controllers ported from ASP.NET MVC Web API
builder.RegisterType<InventoryController>().InstancePerLifetimeScope();
});
}
Then my Azure functions, I have one class that defines all methods in the API
[DependencyInjectionConfig(typeof(DIConfig))]
public class InventoryFunctions : FunctionsApi
{
[FunctionName("GetProductsByCategory")]
// /inventory/categories/{id}/products
public static async Task<HttpResponseMessage> GetProductsByCategory(
[HttpTrigger(AuthorizationLevel.Function, "get", Route = "inventory/categories/{id}/products")]
HttpRequestMessage req,
TraceWriter log,
int id,
[Inject] InventoryController controller)
{
// do stuff
var result = await controller.GetProductsByCategory(id);
return JsonResponse(result, HttpStatusCode.OK);
}
[FunctionName("GetInventoryBySku")]
// /inventory/skus?sku=ASDF&sku=ASDG&sku=ASDH
public static async Task<HttpResponseMessage> GetInventoryBySku(
[HttpTrigger(AuthorizationLevel.Function, "get", Route = "inventory")]
HttpRequestMessage req,
TraceWriter log,
[Inject] InventoryController controller)
{
// do stuff
var result = await controller.QueryInventoryBySkuList(skuList);
return JsonResponse(result, HttpStatusCode.OK);
}
[FunctionName("UpdateProductsQuantity")]
// /inventory
// Post
public static async Task<HttpResponseMessage> UpdateProductsQuantity(
[HttpTrigger(AuthorizationLevel.Function, "put", Route = "inventory")]
HttpRequestMessage req,
TraceWriter log,
[Inject] InventoryController controller)
{
// do stuff
var inventoryProducts = await req.Content.ReadAsAsync<List<InvProductOperation>>();
var result = await controller.UpdateAvailableProductsQuantity(inventoryProducts);
return JsonResponse(result, HttpStatusCode.OK);
}
But I keep getting this error:
A second operation started on this context before a previous
asynchronous operation completed. Use 'await' to ensure that
any asynchronous operations have completed before calling
another method on this context. Any instance members are not
guaranteed to be thread safe.
I have verified that async and await are used properly, so following the error message's recommendation isn't fixing it. What appears to be the issue is that IDbContext is not honoring the InstancePerLifetimeScope as expected. Is this happening because I have more than one method in my InventoryFunctions class? Or is AzureFunctions.Autofac not threadsafe?
Change the registration of the DbContext to this:
builder.Register<IDbContext>(c => new SecretCompanyContext()).InstancePerDependency();
You can find a deeper explanation of mine for why this is happening here.
I was going by this SO answer: Autofac - InstancePerHttpRequest vs InstancePerLifetimeScope which said that InstancePerLifetimeScope was the non-ASP.NET equivalent of InstancePerRequest.
I spoke to the developers and they said the truth is that getting one DbContext per HttpRequest was the default behavior when you simply register using builder.RegisterType<SecretCompanyContext>.As<IDbContext>() so there's some misinformation out there.
So the solution is, instead of using
builder.Register<IDbContext>(c => new SecretCompanyContext()).InstancePerDependency();
or
builder.RegisterType<SecretCompanyContext>().As<IDbContext>().InstancePerLifetimeScope();
one should just use
builder.RegisterType<SecretCompanyContext>().As<IDbContext>();
if the goal is one instance per HTTP request.
I'm using a client library for accessing a 3rd party API. The library was generated by NSwagStudio from Swagger documentation.
The app I'm working on is entirely synchronous in all its calls and updating it to be async is out of scope of what I'm working on.
When I test the client library from a unit test, it works fine. When I try to call it from within an ASP.Net app, I get the following error:
The CancellationTokenSource has been disposed.
I've distilled the client library down to the essentials for demonstrating the problem, I selected an option to provide sync methods as well as async:
public class ClientApi
{
private readonly HttpClient _httpClient;
public ClientApi(HttpClient httpClient)
{
_httpClient = httpClient;
}
public string BaseUrl { get; set; }
public object Get()
{
return Task.Run(async () => await GetAsync(CancellationToken.None)).GetAwaiter().GetResult();
}
/// <returns>OK</returns>
/// <param name="cancellationToken">
/// A cancellation token that can be used by other objects or threads to receive notice of
/// cancellation.
/// </param>
public async Task<string> GetAsync(CancellationToken cancellationToken)
{
var client_ = _httpClient;
try
{
using (var request_ = new HttpRequestMessage())
{
request_.Method = new HttpMethod("GET");
request_.RequestUri = new System.Uri(BaseUrl, System.UriKind.RelativeOrAbsolute);
var response_ = await client_.SendAsync(
request_,
HttpCompletionOption.ResponseHeadersRead,
cancellationToken
).ConfigureAwait(false);
try
{
// Exception occurs on following line
var responseData_ = response_.Content == null
? null
: await response_.Content.ReadAsStringAsync().ConfigureAwait(false);
return responseData_;
}
finally
{
response_?.Dispose();
}
}
}
finally { }
}
}
Here's the code that calls it:
protected void OnClick(object sender, EventArgs e)
{
var httpClient = new HttpClient();
var client = new ClientApi(httpClient)
{
BaseUrl = "https://www.google.com"
};
var html = client.Get();
}
The code calling this is just an asp.net page with a button, and the button events runs the same code as the unit test that passes.
When I compare the runs in a debugger: from a unit test, the response_.Content object does not have a cancellation token, however when run from asp.net it does. In fact they almost seem to be different objects, despite the fact GetType() reports them both as being System.Net.Http.StreamContent. From decompiling the class, this doesn't have a _cancellationtoken property, so where is the debugger getting it from?
I'm guessing that the http request to my asp.net web app has it's own token and source, that is somehow getting used by the HttpClient. However, the client is awaiting all the async calls to get the result synchronously, so I don't understand how the underlying CTS could be disposed as we haven't returned from the call the client library yet.
Can anyone understand what's happening and is there a resolution?
First of, you should really rethink of rewriting your client app so you can implement async all the way.
“Async all the way” means that you shouldn’t mix synchronous and
asynchronous code without carefully considering the consequences. In
particular, it’s usually a bad idea to block on async code by calling
Task.Wait or Task.Result.
Taken from this great guide.
Basicaly, by running async code sync you will allways do things wrong.
But if you really need one solution, start by wrapping your disposable objects in using statements instead of manually disposing them.
Here's a simplified solutions of your ClientApi class which does what you need(But it can deadlock). The code is basically the same as in this answer.
public class ClientApi
{
public object Get(string url)
{
using (var client = new HttpClient())
{
var response = client.GetAsync(url).Result;
if (response.IsSuccessStatusCode)
{
var responseContent = response.Content;
return responseContent.ReadAsStringAsync().Result;
}
}
}
}
Read more about deadlock here
I am using HttpClient (aka Web API client) to consume RESTfull services.
Services require session to be established (via login) and then destroyed (via logout) upon each operation. So the call to consume service A looks something like this (pseudocode)
// setup
create auth dictionary authDict
create authenticationContent using FormUrlEndodeContent(authDict)
create cookieContainer
create HttpClientHandler...
create HttpClient
// login
await httpClient.PostAsync(LoginUrl, authenticationContent);
do error checking
// perform Operation A
await httpClient.....post...or...get...
extract data, process it, tranform it, get a cup of coffee, etc, etc
populate OperationAResult
// logout
await httpClient.GetAsync(LogoutUrl);
// return result
return OperationAResult
My question is, how can I easily reuse setup, login, and logout for different operations?
Should I be creating some method that will take in Action<> and if so how do I make sure that operations occur in order?
Probably the easiest way is to just write a wrapper class.
public class MyHttpClient
{
private HttpClient _client = new HttpClient();
private MyHttpClientSetup _setup;
public MyHttpClient(MyHttpClientSetup setup)
{
this._setup = setup;
}
private void HttpLogin()
{
// .. custom login stuff that uses this._setup
}
private void HttpLogout()
{
// .. custom logout stuff that uses this._setup
}
public void Reset()
{
this._client = new HttpClient();
}
// Wrapped Properties from the private HttpClient (1 example)
public Uri BaseAddress
{
get{ return this._client.BaseAddress;}
set{ this._client.BaseAddress = value;}
}
// Wrapped HttpMethods (1 example)
// Extremely poorly written, should be delegated properly
// This is just a bad example not using Task properly
public Task<HttpResponseMessage> DeleteAsync(string requestUri)
{
this.HttpLogin();
Task<HttpResponseMessage> result = this._client.DeleteAsync(requestUri);
this.HttpLogout();
return result;
}
public class MyHttpClientSetup
{
// Properties required for setup;
}
}
You may be able to create a new MessageHandler to handle this stuff for you transparently.
public class ConnectionHandler : DelegatingHandler {
public HttpClient HttpClient {get;set;}
public TestHandler(HttpMessageHandler handler) {
this.InnerHandler = handler;
}
protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, System.Threading.CancellationToken cancellationToken)
{
// Do your login stuff here
return base.SendAsync(request, cancellationToken) // Make your actual request
.ContinueWith(t => {
// Do your logout stuff here
}
}
}
Then you can just use a single instance of a HttpClient to do all your requests. To add your handler to the request/response pipeline you just need to create a regular HttpClientHandler, assign it to the InnerHandler property of your DelegatingHandler and then pass your new handler into the constructor of the HttpClient. From that point on, all requests made via the HttpClient will be routed through your ConnnectionHandler.
var connectionHandler = new ConnectionHandler(new HttpClientHandler());
var client = new HttpClient(connectionHandler);
connectionHandler.HttpClient = client;
var response = client.GetAsync("http://example.org/request").Result;
The advantage of using a single HttpClient instance is that you don't have to keep re-specifying the DefaultRequestHeaders. Also, disposing the HttpClient will kill the TCP Connection so the next request will have to re-open it.