I'm new to SimpleInjector and working through examples using it with WebAPI. I used the SimpleInjector.Integration.WebApi.WebHost.QuickStart nu-get package, then registered a simple type for my tests, like so:
container.RegisterWebApiRequest<SimplePOCO>();
From inside an ApiController method, I am able to request an instance. So far, so good. I wanted to expand my test to earlier in pipeline, specifically a Message Handler. So I created a simple DelegatingHandler like :
protected override Task<HttpResponseMessage> SendAsync(
HttpRequestMessage request,
CancellationToken cancellationToken) {
Task<HttpResponseMessage> response;
var container = SimpleInjectorWebApiInitializer.container;
var rc = container.GetInstance<SimplePOCO>();
response = base.SendAsync(request, cancellationToken);
response.ContinueWith((responseMsg) => { });
return response;
}
Calling GetInstance<SimplePOCO>() errors with the following message:
The registered delegate for type SimplePOCO threw an exception. The
SimplePOCO is registered as 'Web API Request' lifestyle, but the
instance is requested outside the context of a Web API Request.
Am I doing something wrong? Are Message Handlers outside the lifetime of a WebAPI request? This seems odd, considering how integral they are. If message handlers are outside the lifetime is there a longer lifetime that encompasses the message handlers?
Are Message Handlers outside the lifetime of a WebAPI request?
Well, as a matter of fact, they are. Unless you trigger the creation of the IDependencyScope explicitly, the IDependencyScope gets created (by calling request.GetDependencyScope()) inside the DefaultHttpControllerActivator.Create method.
To make sure your code runs within a dependency scope, all you have to do is call request.GetDependencyScope() explicitly inside your handler:
protected override Task<HttpResponseMessage> SendAsync(
HttpRequestMessage request, CancellationToken cancellationToken) {
// trigger the creation of the scope.
request.GetDependencyScope();
Task<HttpResponseMessage> response;
var container = SimpleInjectorWebApiInitializer.container;
var rc = container.GetInstance<SimplePOCO>();
response = base.SendAsync(request, cancellationToken);
response.ContinueWith((responseMsg) => { });
return response;
}
Related
In Angular there is HttpInterceptor which make it really nice to intercept HttpClient call on the app, also handle errors of the request.
Is there a C#/.NET library that do the same thing?
HttpClientHandler
You have to override SendAsync
internal class MyHttpClientHandler : HttpClientHandler
{
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
// Do before call
var response = await base.SendAsync(request, cancellationToken).ConfigureAwait(false);
// do after call
return response;
}
}
var handler = new MyHttpClientHandler();
var client = new HttpClient(handler)
{
BaseAddress = new Uri("my-api-uri")
};
You are looking for what is called Middelware in the ASP.net world. These are code that are placed in an http pipeline that can handle incoming requests and outgoing responses.
Have a look at this article that explains how it works and what you can do.
For my application i need to make a named client for HttpRequests. I can create a named client in Startup. And to access it i inject an "IHttpClientFactory" and create a client from that. But the client needs to have an access token as an authorization header, and i cannot create the token in Startup. Therefor i need a way to create a named client outside of the Startup class. i have already tried injecting "IServiceCollection" into a controller. But this does not work.
Or is there maybe a way to edit a named client after it is already created in startup?
A similar solution to the one posted by #Ruben-J is to create a custom HttpMessageHandler which assigns an authorization header to requests made through the HttpClient at request-time.
You can create a custom HttpMessageHandler that can be assigned to a named HttpClient in Startup like so:
public class YourHttpMessageHandler : DelegatingHandler
{
private readonly IYourTokenProviderService _yourTokenProviderService;
public YourHttpMessageHandler(IYourTokenProviderService yourTokenProviderService)
: base()
{
_yourTokenProviderService = yourTokenProviderService;
}
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
var response = SendAsyncWithAuthToken(request, cancellationToken);
if (response.StatusCode == HttpStatusCode.Unauthorized)
{
await _yourTokenProviderService.RefreshTokenAsync();
response = SendAsyncWithAuthToken(request, cancellationToken);
}
return response;
}
private async Task<HttpResponseMessage> SendWithAuthTokenAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", _yourTokenProviderService.Token);
return await base.SendAsync(request, cancellationToken);
}
}
You then configure your services and named HttpClient in Startup:
public virtual void ConfigureServices(IServiceCollection services)
{
...
services.AddTransient<IYourTokenProviderService, YourTokenProviderService>();
services.AddTransient<YourHttpMessageHandler>();
services.AddHttpClient<IYourNamedHttpClient, YourNamedHttpClient>()
.AddHttpMessageHandler<YourHttpMessageHandler>();
...
}
Its worth noting that the current implementation of Polly's AddPolicyHandler is also adding its own DelegatingHandler.
For more background see the Microsoft documentation on adding DelegatingHandler's. Here is also great series of articles from Steve Gordon.
You could use Polly to add a policy handler to your client. You can then add logic if a request returns a 401 Unauthorized. So for example get your service that uses the client to refresh a bearer token and also set it for the current request. This is just a quick solution and maybe there are more elegant solutions. But this will also come in handy if your token expires. Cause then it will be refreshed automatically.
services.AddHttpClient("YourClient")
.AddPolicyHandler((provider, request) =>
{
return Policy.HandleResult<HttpResponseMessage>(r => r.StatusCode == HttpStatusCode.Unauthorized)
.RetryAsync(1, async (response, retryCount, context) =>
{
var service = provider.GetRequiredService<IYourService>();
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", await service.RefreshToken());
});
});
Well, I know this is a bit of a tricky case. I have an ASP.NET Core site backend (site) that communicates with another system (facade) via http.
But not with HttpClient but with clients that facade provides with HttpClient underneath.
Constructors of these clients look like this
public class OrdersClient : IOrdersClient
{
private HttpClient _client;
public OrdersClient(IEndpoint endpoint, HttpClient client)
{ ... }
}
And registration looks like this
services
.AddHttpClient("facade-client")
.AddHttpMessageHandler<CorrelationIdDelegatingHandler>()
.AddHttpMessageHandler<HttpErrorDelegatingHandler>()
.AddTypedClient<IOrdersClient>((endpoint, client) => new OrdersClient(endpoint, client));
And configuration contains (it's important!)
app.UseCorrelationId(new CorrelationIdOptions());
N.B. What endpoint is for in this case is not important.
Ok. So I want to test my site with brand new cool WebApplicationFactory. I create it and get a client to the site as described in the docs. For some tests I just mock clients to facade like
public TestOrdersClient: IOrdersClient
{
}
and test my logic without facade.
Getting to the point. I want to test handlers that decorates my HttpClient to facade.
How I want to test handlers: I register fake PrimaryHttpMessageHandler so it will imitate answers from facade in my WebApplicationFactory. And I added some ImitationClient that used registered HttpClient.
services
.AddHttpClient("facade-client")
.ConfigurePrimaryHttpMessageHandler(() => new HttpMessageHandlerFake())
.AddTypedClient<ImitationClient>();
And here I am in trouble.
In my test a get ImitationClient from factory and try to call some method that calls HttpClient inside.
var factory = new SiteWebApplicationFactory();
var client = factory.CreateDefaultClient();
var imitationClient =factory.Server.Host.Services.GetService<ImitationClient>();
await imitationClient.GetData();
And here I get System.NullReferenceException because of this handler:
public class CorrelationIdDelegatingHandler : DelegatingHandler
{
private readonly ICorrelationContextAccessor _correlationAccessor;
public CorrelationIdDelegatingHandler(ICorrelationContextAccessor correlationAccessor)
{
_correlationAccessor = correlationAccessor;
}
protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
request.Headers.Add(
_correlationAccessor.CorrelationContext.Header,
_correlationAccessor.CorrelationContext.CorrelationId);
return base.SendAsync(request, cancellationToken);
}
}
Correlation Id assigned to incoming request to site and then forwarded to requests from site to facade. But when I test only client to facade I don't have incoming request, don't have CorrelationContext and fail.
Finally my question is how to correctly write test for inner httpClient with WebApplicationFactory. I know my description is rather confusing, I apologize for that. I am ready to answer any questions on the code and architecture of our project. Thank you!
I am working on a WebAPI 2 project which currently uses attribute-based routing exclusively, to the point that there are no routes defined in registration, and everything is working as expected.
However, I am now adding a DelegatingHandler to provide a heartbeat that I can ping with a HEAD request:
public class HeartbeatMessagingHandler : DelegatingHandler
{
protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
if (IsHeartbeat(request))
{
if (request.Method == HttpMethod.Head)
{
var response = new HttpResponseMessage(HttpStatusCode.NoContent) { Content = new StringContent(string.Empty) };
var task = new TaskCompletionSource<HttpResponseMessage>();
task.SetResult(response);
return task.Task;
}
}
return base.SendAsync(request, cancellationToken);
}
private static bool IsHeartbeat(HttpRequestMessage request)
{
return request.RequestUri.LocalPath.Equals("/Heartbeat", StringComparison.OrdinalIgnoreCase);
}
}
However, the handler is not being invoked if I make the expected HEAD http://localhost/heartbeat request; if I call any route that does exist then the handler is invoked; and if I add an old-school routing configuration then the handler is invoked on the expected /heartbeat endpoint.
So, using attribute routing only, how can I handle a request to a "virtual" endpoint?
Update
The message handlers are registered as follows:
config.MessageHandlers.Add(new HeartbeatMessagingHandler());
config.MessageHandlers.Add(new RequestAndResponseLoggerDelegatingHandler());
config.MessageHandlers.Add(new ApplicationInsightsMessageHandler());
so it is registered before e.g. my global logging handler, and according to my understanding, message handlers receive messages in the order that they are registered.
Most likely your handler is too late in the pipeline and a handler higher in the pipeline is short-circuiting the request before it reaches your handler.
consider inserting your handler higher up so it has a better chance of intercepting the request
public static class WebApiConfig {
public static void Register(HttpConfiguration config) {
// Web API routes
config.MapHttpAttributeRoutes();
// add to the front of the pipeline
config.MessageHandlers.Insert(0, new HeartbeatMessagingHandler());
}
}
I have a web API message handler MyHandler that I want to run in OWIN pipeline as a middleware. So configuring the handler like this.
public class Startup
{
public void Configuration(IAppBuilder app)
{
app.UseHttpMessageHandler(new MyHandler());
HttpConfiguration config = new HttpConfiguration();
config.Routes.MapHttpRoute(
"DefaultWebApi",
"{controller}/{id}",
new { id = RouteParameter.Optional });
app.UseWebApi(config);
}
}
Handler is very simple and does nothing.
public class MyHandler : DelegatingHandler
{
protected override async Task<HttpResponseMessage> SendAsync(
HttpRequestMessage request, CancellationToken cancellationToken)
{ // <--- breakpoint here
var response = await base.SendAsync(request, cancellationToken);
return response;
}
}
I put a break point inside SendAsync and it does break but the following base.SendAsync bombs silently and I see A first chance exception of type 'System.InvalidOperationException' occurred in System.Net.Http.dll.
I can quite easily add MyHandler to config.MessageHandlers and it will run perfect in the Web API pipeline but that's not what I want to do. I want to run MyHandler in the OWIN pipeline. Is this possible at all? It should be. Otherwise, there is no point in having the extension method UseHttpMessageHandler, I guess. Just that I couldn't figure out a way to do what I want to do.
Yeah, this experience needs to be improved as the exception is silently ignored.
For your above scenario, you would need to derive from HttpMessageHandler instead of DelegatingHandler as the delegating handler would try to delegate the request to handlers after it.(example: The exception mentions Message=The inner handler has not been assigned)
For example, the following would work:
appBuilder.UseHttpMessageHandler(new MyNonDelegatingHandler());
public class MyNonDelegatingHandler : HttpMessageHandler
{
protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
HttpResponseMessage response = new HttpResponseMessage();
response.Content = new StringContent("Hello!");
return Task.FromResult<HttpResponseMessage>(response);
}
}
And for creating a chain of handlers, you could do the following:
appBuilder.UseHttpMessageHandler(HttpClientFactory.CreatePipeline(innerHandler: new MyNonDelegatingMessageHandler(),
handlers: new DelegatingHandler[] { new DelegatingHandlerA(), new DelegatingHandlerB() }));