-
Notifications
You must be signed in to change notification settings - Fork 72
Creating a scoped service with an HttpClientFactory #134
Comments
I found that I can do the following at it starts working: var type = typeof(ITypedHttpClientFactory<>).Assembly.DefinedTypes.Single(t => t.Name.Contains("DefaultTypedHttpClientFactory"));
services.AddScoped(typeof(ITypedHttpClientFactory<>), type); Is this ok to do? |
At least for message handlers, transient is required for things to work properly. Have you tried that instead? |
So this? var type = typeof(ITypedHttpClientFactory<>).Assembly.DefinedTypes.Single(t => t.Name.Contains("DefaultTypedHttpClientFactory"));
services.AddTransient(typeof(ITypedHttpClientFactory<>), type); |
What I meant was to register Test2 as a transient, rather than scoped, dependency. Alternatively it may be related to being a nested type. I’m not at a computer at the moment so I don’t have an IDE to hand to go digging. |
That allows it to be resolved, but changes the semantics of the lifetime of |
Injecting handlers is however subject to a memory leak (see other issue opened on that topic). The dependency container will keep a reference to the handler because the handler implements IDisposable. That prevents the handler from being released for garbage collection. A way around it is using a container that allows you to disable IDisposable tracking (for example ExternallyOwned () on autofac). In general, you always need to keep on mind how IDisposable is handled by the container. The .net core container will keep references, but that was not actually immediately clear from the documentation. The cache defines its own lifetime independent of the dependency scopes. If you want the scopes to control lifetime of the handler, you can just register your own (not using httpclientfactory). |
Assigning myself to take a look at this. |
So after some discussion here, I think the right thing to do is for us to create a scope for each handler chain created by the factory. The reason is that each handler chain is its own lifetime which is independent of other lifetimes in the application (per request in a web app). This will allow things like transients to work as expected when they outlive the request (since they are resolved from the scope). This will also avoid piling up a bunch of transient disposables in the root scope. The change here will be that you can no longer use scoped services to share state outside of the handler chain - since we're creating a new scope to build the handler chain. I don't have major concerns about this because as folks have pointed out, there are lots of problems right now with mixing delegating handlers + DI right now. Another change that isn't as easy to reason about is that when using typed clients, the typed client will be resolved from a different DI scope than the handler chain. So if you're using scoped services as a way to pass data from a typed client to a delegating handler, that would no longer be supported. I don't have a big concern about this because If you're familiar with this area please read and digest the above changes and let me know if you have any concerns. /cc @theezak @twsouthwick @martincostello @glennc @davidfowl |
@twsouthwick - reading over your post again - what does the consuming code look like? Where are you resolving
This implies that you're resolving from the root provider - ie not from a scope. I'm curious about if you found some clever way to break the world. |
From a quick digest of the suggested change, the one thing I'd wonder about is for some monitoring code we have in some internal applications. We use a singleton class to pool sockets to send metrics to StatsD over UDP as we found this gave us a lot better throughput on a metrics and overall CPU load on the application. We wrap this singleton into a delegating handler to send metrics for out HTTP traffic to and from the applications' dependencies. If I've understood the proposal correctly and if the scoping is changing so that these singletons are only scoped to the handler chain's lifetime (with a 2 minute default lifetime?), then we'd be losing some of the gains we've made here as we'll have a pool per handler chain instead, with them being regularly cycled. While not a world-ending change, it would be a trade-off compared to some features we're enjoying today with 2.1. |
@rynowak I don't have the original scenario (it was some customer's code) but I don't remember doing anything weird. I think it was just some scoped service being resolved within a controller. I'll see if I can repro it again. |
The proposed solution reflects perfectly the fact that these handlers have their own lifetime. I think there is no impact on singletons. For transients and scoped you have to acknowledge that they are part of the handler pipeline and thus have a different lifetime already. If there is a breaking change here for you, then the current code is already broken. |
Singleton means singleton - key word being 'single' - scopes inherit singletons from the root. This change should have no negative impact on this scenario. I'm going to be adding tests for this kind of thing so that I make sure I'm not missing anything. Lots of stuff to grok here, but resolving a singleton always resolves it in the root scope. @twsouthwick - I'm going to include this sort of thing in my integration tests so that we know we understand the behavior. I'll update you if I figure something out, but as you described it, I would expect that to work.
Thanks, that's the same conclusion I came to as well. |
I have a similar issue -- I have a message handler registered with Point is that if you have all this new http factory stuff in DI and ancillary services also in DI, then people will naturally want to depend on other services in DI (scoped or otherwise). So either the lower level bits in the http factory could be more forgiving with respect to the DI system, or everyone else's services need to be aware that they're in essence singletons and need to manually resolve stuff from DI if they need scoped stuff. |
@twsouthwick the issue you reported was the same as the one reported here: #134 (comment) The |
@brockallen - if you just want to use a Let me know if you have suggestions or need more details |
This is a rework of the http client factory interacts with dependency injection when creating a message handler. Since the http client factory manages the lifetimes of message handlers, it's not appropriate for a message handler and related services to share lifetimes with the application or 'current request' scope. Now the factory creates a scope each time it creates a handler, and then disposes the scope when the handler is disposed. This allows the handler building process to resolve scoped services and also to prevent allocating lots and lots of disposable transients in the application global scope. The fix for #134 also required a rework of some details of `DefaultTypedClientFactory<>`.
@rynowak I’ve not really used the scoping stuff on the service provider before, so I thought that it would create a “parallel world”, rather than inherit any singletons already created in the root. In that case, given I’ve just misunderstood the container scoping, the proposed solution sounds fine. |
Ran into this problem with AutoMapper https://github.com/AutoMapper/AutoMapper.Extensions.Microsoft.DependencyInjection Specifically, IMapper is registered as a scoped instance, so...
fails as noted in the OP |
This is a rework of the http client factory interacts with dependency injection when creating a message handler. Since the http client factory manages the lifetimes of message handlers, it's not appropriate for a message handler and related services to share lifetimes with the application or 'current request' scope. Now the factory creates a scope each time it creates a handler, and then disposes the scope when the handler is disposed. This allows the handler building process to resolve scoped services and also to prevent allocating lots and lots of disposable transients in the application global scope. The fix for #134 also required a rework of some details of `DefaultTypedClientFactory<>`.
This is a rework of the http client factory interacts with dependency injection when creating a message handler. Since the http client factory manages the lifetimes of message handlers, it's not appropriate for a message handler and related services to share lifetimes with the application or 'current request' scope. Now the factory creates a scope each time it creates a handler, and then disposes the scope when the handler is disposed. This allows the handler building process to resolve scoped services and also to prevent allocating lots and lots of disposable transients in the application global scope. The fix for #134 also required a rework of some details of `DefaultTypedClientFactory<>`.
Fixed for 2.2.0-preview2 |
@rynowak any hot fixes for 2.1 will be released? |
No, this is not currently planned to be patched for 2.1.X. This is a much bigger change than the sort of thing we usually include in a patch. |
@rynowak sad, but I understand that. |
Soon? 😁 preview2 is out now |
preview it's great, but need more stable version |
@rynowak Do you mind explaining why the delegating handlers in the I understand why the primary |
The real answer, because this is how we intended for it to work and how we coded it up. We didn't intend for message handlers to be stateful or share state with the 'current request'. It works when used with a typed client, and doesn't work if you call the factory directly. You could imagine a more complicated design where some handlers were managed by the factory and some not, and then you run into some really practical problems:
So you run into some limitations right away.
These aren't big problems that cannot be solved. We'd have to introduce yet another interface/abstraction, because the This isn't necessarily the final word on this. The changes we're doing in 2.2 solve a set of real problems that are totally blocking. We could consider a more involved design that creates handlers from the current scope in the future if it's really really important - but I want to understand the problems that people have right now. I'm really more interested at this point in knowing more about what your handlers are, and what problems you use to solve them. We think that there's a common set of features that everyone wants, and we're interested in providing them. Anytime someone asks me a question like "why can't I use this thing in this way", what I want to know is "what problem are you trying to solve in the first place". |
There was a similar discussion here: #166 with a suggestion for a workaround. |
Thanks for responding @rynowak. In terms of the problem we are trying to solve, we have a suite of internal http api's that all take a standard set of headers for cross cutting concerns like authentication, logging/monitoring, etc. Our preference is that when we are coding our clients, these cross cutting concerns are taken care of transparently without needing to worry about them in every place that we make an http call. Some of the values of these headers are based on data specific to the current request. A simple example is that we have a X-Correlation-Id header that we want to forward to all of our service calls. Its value comes from the current request (and is logged with the current request). We have always used a factory for our You have suggested a couple options that could address my concerns (like providing a
|
Getting access to the underlying HttpMessageHandler should be enabled by the changes coming in 2.2.0 from #118. |
Thanks @martincostello. I was just looking through |
@andyalm HttpClient has a property that controls timeout of the underlying connection. If the only value you see in the factory is the ability to periodically recycle a connection then you can set that and call life good. But I would've thought that there's more value than that given that you are presumably going to write a bunch of code similar to the factory once you start using that, to stitch together your handlers and maybe do something like a typed client. Do you see value in typed clients? |
@glennc What property of HttpClient are you referring to? While I have certainly written some code similar to the factory, its not all that complex or interesting (just newing up some objects in a chain), so its not very burdensome and it solves my problem of needing request scoped As far as typed clients go, yes, I absolutely see value in typed clients and think they were a great addition. While the typed clients themselves can be request scoped, which is great, the Having the |
I really like the idea of HttpClientFactory, but have found issues that the convenience methods assume you have singleton instances of the services required. For instance, the following fails:
This results in the following:
Can there be a way to set the lifetime of the scope? In this example, I want to set
Test1
as a singleton.The text was updated successfully, but these errors were encountered: