-
Notifications
You must be signed in to change notification settings - Fork 1.6k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
#1518 Create building the IServiceCollection
#1986
#1518 Create building the IServiceCollection
#1986
Conversation
IServiceCollection
Hi Adrien! I've added PR to March'24 milestone. So, it will be released during first days of April. No objections? |
Hey Raman, As said on the issue, I'm not involved anymore in development of Ocelot-based software. It is a mere hotfix regarding any aspnet 6+ compatibility, to prevent some apps to crash. |
@ArwynFr |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for English text typo corrections in XML docs! 😄
var descriptor = services.Where(descriptor => descriptor.ServiceType == typeof(IConfiguration)).FirstOrDefault(); | ||
var provider = new ServiceCollection().Add(descriptor).BuildServiceProvider(); | ||
var configuration = provider.GetRequiredService<IConfiguration>(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why not to define a private helper to search for IConfiguration
object?
Also, the XML description need to point out to the developer that the interface object is derived from DI and must be registered in DI container anyway!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is my fault when I reviewed #1655 and didn't pay my attention to the fact we add overloaded version without the interface object 👇
Ocelot/src/Ocelot/DependencyInjection/ServiceCollectionExtensions.cs
Lines 48 to 52 in 5a3c55e
public static IOcelotBuilder AddOcelotUsingBuilder(this IServiceCollection services, Func<IMvcCoreBuilder, Assembly, IMvcCoreBuilder> customBuilder) | |
{ | |
var configuration = services.BuildServiceProvider().GetRequiredService<IConfiguration>(); | |
return new OcelotBuilder(services, configuration, customBuilder); | |
} |
So, 😮 But Ocelot Builder has required 2nd argument:
public OcelotBuilder(IServiceCollection services, IConfiguration configurationRoot, Func<IMvcCoreBuilder, Assembly, IMvcCoreBuilder> customBuilder = null) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The fact that OcelotBuilder
requires an IConfiguration
is actually the problem in the first place.
This builder should only focus on registering service descriptors with their lifetime managers.
Services would access configuration by injection of IOptionsMonitor<T>
rather than an IConfiguration reference.
Overloads with IConfiguration parameters would then be the ones to deprecate.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I wasn't sure deduplication of 3 lines of code was worth writing a separate helper.
Also in my mind this code is expected to go away once the IConfiguration references are gone.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@raman-m I've added some default behavior in case the IConfiguration wasn't registered.
Note that Tom's code reads the configuration from current working path, which may be different from the executable's path.
@@ -17,15 +18,17 @@ public static class ServiceCollectionExtensions | |||
/// <returns>An <see cref="IOcelotBuilder"/> object.</returns> | |||
public static IOcelotBuilder AddOcelot(this IServiceCollection services) | |||
{ | |||
var configuration = services.BuildServiceProvider().GetRequiredService<IConfiguration>(); | |||
var descriptor = services.Where(descriptor => descriptor.ServiceType == typeof(IConfiguration)).FirstOrDefault(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not an issue, but my questions:
- How many
IConfiguration
objects can be registered in DI? If more than one instance, how to detect exactlyocelot.json
one? - Could not the
IConfiguration
be registered at all? Seems not, because ofappsettings.json
...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
IConfiguration
is a singleton. When adding multiple configuration sources, these are mixed together as a single configuration context, where the latest source overrides former ones in case of configuration key duplication.
Technically you could provoke an error if you provide a new ServiceCollection()
, but these methods are designed to be used with WebApplicationBuilder
which does formally creates an IConfiguration instance.
Perhpas this could cause problems with slimmer versions of the builder introduced in dotnet 8.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
By flattening multiple sources in a single IConfiguration context, the framework allows to easily implement "configuration overriding" features. For instance the default WebApplicationBuilder registers appsettings.json, then user secret file, and then environment variables as configuration sources. This allows secret files to override appsettings.json and environment variables to override both.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also using Builder without an IConfiguration allows to register configuration sources AFTER the registration of the services. This in turns allow to do some fancy stuff like configuration bootstraping (read configuration from file, create services to access a remote storage using this config, then provide the rest of the app with config values from that storage).
Which is exacly the kind of problems Ocelot had tried to solve with Consul/Eureka.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If you want to isloate ocelot.json
, you need to have a separate IConfiguration instance, using a separate ConfigurationBuilder for instance and not have it injected in the DI container. This is NOT what currently happens and NEITHER is what is described in the documentation.
Currently all configuration (ocelot routes and aspnet config) are mixed together in a single IConfiguration
See discussion in #1518 Applications built using a dotnet 6+ with WebApplicationBuilder instead of Host/Startup, and using AddOcelot() override without the IConfiguration parameter will likely fail to start or crash. This is because the method builds the service provider which is expected not to happen with AddXXX methods. This in turns puts the provider in a weird state where resolving services could fail, throw an uncaught exception and provoke failure to start. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok, we should keep in mind it's a quick fix, but yes, it will work. As of the issue with the Eureka Service Discovery, it's probably related to the Steeltoe extension methods: https://github.com/ggnaegi/Ocelot/blob/42ac3ca51829006bbe2e63bc684ab3a3caaccf1e/src/Ocelot.Provider.Eureka/OcelotBuilderExtensions.cs#L12
As we can read in their latest commit here:
SteeltoeOSS/Steeltoe@e934355
@ArwynFr Maybe it would make sense that you include the package update (3.6) in your PR, do you agree?
@ggnaegi Steeltoe is facing the same problem as Ocelot, but they have chosen to enforce the |
@ArwynFr Yes, but they were, at least until version 3.5 using the same var configuration = services.BuildServiceProvider().GetRequiredService<IConfiguration>(); as ocelot did. |
Since it's my first PR to Ocelot, I'd rather have the lib dependency update separated please. |
Ok, fine |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok, thanks @ArwynFr !
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
My suggestions below. And I worry about this
@raman-m will look into it this weekend |
…uck! Hunting for `new FileInfo("")` bug... Done!
Coverage 100% →Report headerPrivate methods: coverage is goodI like these pictures! And you, Adrien? 😉 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ready for delivery! ✅
- Code review ✔️
- Team approvals ✔️
- Unit testing ✔️
- Acceptance tests ❔ It would be crazy to try to emulate a basic application crash scenario by trying to juggle startup configurations 🤣
- Updated docs ❔ not required for system updates 🆗
@ArwynFr
Because this PR is like a hotfix: it contains pretty system updates then I believe we can go without docs updating.
My input
- Defined new
ServiceCollectionExtensionsTests
class which contains exactly unit testing for private methods and public too - Added unit tests to
OcelotBuilderTests
with new functionality coverage - Switched off annoying reminder about
Obsolete
definitions aka CS0618 Warn , but for test projects only - The ultimate goal is 100% coverage of
ServiceCollectionExtensions
class achieved. - Fixed ugly a little duck duck bug in the
GetMergedOcelotJson
method ofConfigurationBuilderExtensions
class. So,null
value of theIWebHostEnvironment env
argument led to empty string passing to the cstor in line of this code:new FileInfo(environmentFile)
. Well... It was my design gap. 😢 And especially these unit testing challenge helped me to uncover this Duck bug 🦆 🐛
That's why I love unit testing 😍
Yahoo! We can merge, @ArwynFr, right? 😋
We haven't made any breaking changes, right? 😳😁
@ArwynFr Where are you? |
@ArwynFr I have no time to wait for a simple action to do. I don't want to delay release too. |
@ArwynFr Thank you for the contribution! |
Fixes #1518
Proposed Changes
ServiceCollection
by a newServiceProvider
by copying theServiceDescriptor
Beware that this does not fix the problem of Steeltoe discovery client that might still build the service provider.
This is just a hotfix, there is still room for a larger refactor of removing the
IConfiguration
from theOcelotBuilder
.