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() }));
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());
});
});
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 would like to unit test a class that uses HttpClient. We injected the HttpClient object in the class constructor.
public class ClassA : IClassA
{
private readonly HttpClient _httpClient;
public ClassA(HttpClient httpClient)
{
_httpClient = httpClient;
}
public async Task<HttpResponseMessage> SendRequest(SomeObject someObject)
{
//Do some stuff
var request = new HttpRequestMessage(HttpMethod.Post, "http://some-domain.in");
//Build the request
var response = await _httpClient.SendAsync(request);
return response;
}
}
Now we would like to unit test the ClassA.SendRequest method. We are using Ms Test for unit testing framework and Moq for mocking.
When we tried to mock the HttpClient, it throws NotSupportedException.
[TestMethod]
public async Task SendRequestAsync_Test()
{
var mockHttpClient = new Mock<HttpClient>();
mockHttpClient.Setup(
m => m.SendAsync(It.IsAny<HttpRequestMessage>()))
.Returns(() => Task.FromResult(new HttpResponseMessage(HttpStatusCode.OK)));
}
How can we solve this issue?
That particular overload method is not virtual so is unable to be overridden by Moq.
public Task<HttpResponseMessage> SendAsync(HttpRequestMessage request);
Which is why it throws NotSupportedException
The virtual method you are looking for is this method
public virtual Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken);
However mocking HttpClient is not as simple as it seems with its internal message handler.
I suggest using a concrete client with a custom message handler stub that will allow for more flexibility when faking the request.
Here is an example of a delegating handler stub.
public class DelegatingHandlerStub : DelegatingHandler {
private readonly Func<HttpRequestMessage, CancellationToken, Task<HttpResponseMessage>> _handlerFunc;
public DelegatingHandlerStub() {
_handlerFunc = (request, cancellationToken) => Task.FromResult(request.CreateResponse(HttpStatusCode.OK));
}
public DelegatingHandlerStub(Func<HttpRequestMessage, CancellationToken, Task<HttpResponseMessage>> handlerFunc) {
_handlerFunc = handlerFunc;
}
protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) {
return _handlerFunc(request, cancellationToken);
}
}
Note the default constructor is doing basically what you were trying to mock before. It also allows for more custom scenarios with a delegate for the request.
With the stub, the test can be refactored to something like
public async Task _SendRequestAsync_Test() {
//Arrange
var handlerStub = new DelegatingHandlerStub();
var client = new HttpClient(handlerStub);
var sut = new ClassA(client);
var obj = new SomeObject() {
//Populate
};
//Act
var response = await sut.SendRequest(obj);
//Assert
Assert.IsNotNull(response);
Assert.IsTrue(response.IsSuccessStatusCode);
}
Moq can mock out protected methods, such as SendAsync on the HttpMessageHandler that you can provide to HttpClient in its constructor.
var mockHttpMessageHandler = new Mock<HttpMessageHandler>();
mockHttpMessageHandler.Protected()
.Setup<Task<HttpResponseMessage>>("SendAsync", ItExpr.IsAny<HttpRequestMessage>(), ItExpr.IsAny<CancellationToken>())
.ReturnsAsync(new HttpResponseMessage
{
StatusCode = HttpStatusCode.OK
});
var client = new HttpClient(mockHttpMessageHandler.Object);
Copied from https://www.thecodebuzz.com/unit-test-mock-httpclientfactory-moq-net-core/
Propper mocking with HttpClient is hard work as it was written before most people did unit testing in dotnet. Sometimes I setup a stub HTTP server that returns canned responses based on pattern matching the request url, meaning you test real HTTP requests not mocks but to a localhost server. Using WireMock.net makes this really easy and runs fast enough to satisfy most of my unit testing needs.
So instead of http://some-domain.in use a localhost server setup on some port, and then:
var server = FluentMockServer.Start(/*server and port can be setup here*/);
server.Given(
Request.Create()
.WithPath("/").UsingPost()
)
.RespondWith(
Response.Create()
.WithStatusCode(200)
.WithHeader("Content-Type", "application/json")
.WithBody("{'attr':'value'}")
);
You can find a more details and guidance on using wiremock in tests here.
I recently had to mock HttpClient, and I used Moq.Contrib.HttpClient. It was what I needed, and simple to use, so I thought I'd throw it out there.
Here is an example of general usage:
// All requests made with HttpClient go through its handler's SendAsync() which we mock
var handler = new Mock<HttpMessageHandler>();
var client = handler.CreateClient();
// A simple example that returns 404 for any request
handler.SetupAnyRequest()
.ReturnsResponse(HttpStatusCode.NotFound);
// Match GET requests to an endpoint that returns json (defaults to 200 OK)
handler.SetupRequest(HttpMethod.Get, "https://example.com/api/stuff")
.ReturnsResponse(JsonConvert.SerializeObject(model), "application/json");
// Setting additional headers on the response using the optional configure action
handler.SetupRequest("https://example.com/api/stuff")
.ReturnsResponse(bytes, configure: response =>
{
response.Content.Headers.LastModified = new DateTime(2018, 3, 9);
})
.Verifiable(); // Naturally we can use Moq methods as well
// Verify methods are provided matching the setup helpers
handler.VerifyAnyRequest(Times.Exactly(3));
For more info, check out author's blog post here.
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;
}