Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Simplified usage of BindingContext resolved command handlers#1358

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 ourterms of service andprivacy statement. We’ll occasionally send you account related emails.

Already on GitHub?Sign in to your account

Open
fredrikhr wants to merge6 commits intodotnet:main
base:main
Choose a base branch
Loading
fromfredrikhr:servicecollection

Conversation

@fredrikhr
Copy link
Contributor

This PR adds convenience helper methods to simplify the usage of services resolved by the BindingContext.

  • Add static helperFromBindingContext toCommandHandler.
    Creates anICommandHandler where the specified command handler type is filled in at runtime by the BindingContext. This requires that the BindingContext knows how to instantiate the handler type (requires service registration).
  • Add aConfigureBindingContext middleware.
    Middleware simply takes anAction<BindingContext> parameter. In this callback the developer can callAddModelBinder andAddService. This simplifies service-registration on the BindingContext.
  • Add no-parameterAddService instance method onBindingContext.
    Simplified service registration for trivial types. Uses aModelBinder to auto-construct the service.

inspired by discussion in#1344.

/cc@leonardochaia

leonardochaia reacted with hooray emoji
@fredrikhr
Copy link
ContributorAuthor

I added2f2796b following thesuggestion from@leonardochaia

leonardochaia reacted with hooray emoji

@leonardochaia
Copy link

Awesome! It looks like I would be able to remove my DI workaround with this changes.

This allows to override injected dependencies for testing purposes!

Can we please add this last test case? :)

varparser=newCommandLineBuilder(command).ConfigureBindingContext(context=>context.AddService<IGreeter,GreeterImpl>()).ConfigureBindingContext(context=>context.AddService<IGreeter>(sp=>newGreeterMock()))// would use Moq or something here.Build();

IGreeter should be of typeGreeterMock
I think this should work since the IServiceProvider impl has a Dictionary with a Type for the key as far as I remember.

@fredrikhr
Copy link
ContributorAuthor

@leonardochaia I don't understand what you mean? The first one will be done by this PR, the second one you could always do already?

@leonardochaia
Copy link

Sorry, should have been clearer.

I'm just trying to be sure that the secondConfigureBindingContext will override the first one (and don't throw, or silently ignore it)

That's why I would assert that

IGreeter should be of type GreeterMock

@fredrikhr
Copy link
ContributorAuthor

The second one thows??? Really? What exception do you get? 🤔

@leonardochaia
Copy link

leonardochaia commentedJul 21, 2021
edited
Loading

I'm sorry (again). Didn't mean to say it is throwing right now. My point is that, if the expected API is that multiple calls toConfigureBindingContext should be supported, and the latter call should be able to override things configured on previous calls, we should have a test that makes sure that that behavior is not gonna be broken in the future by someone i.e changing the inner code to throw when a service is already registered or something like that.

So that's why I propose we add a test that represents the actual use case of overriding an already registered service.

Something likeRegistered_services_can_be_overriden_by_subsequent_calls_to_ConfigurreBindingContext

EDIT: I'm not saying we should test theAddService methods work. We know they do by the previous tests as you mentioned. I'm interested in testing that subsequent calls toConfigureBindingContext will allow to override services registered on earlier calls, since that's the API I think we need for testing purposes

fredrikhr reacted with thumbs up emoji

@leonardochaia
Copy link

This looks awesome! 🎉

@jonsequitur
Copy link
Contributor

The newAddService overloads increase the scope of the DI features in ways that might be misleading and will likely lead to additional issues being opened when people misunderstand the goal. The pre-existingAddService method accepted aFunc so that you can know when it's called and control things like lifetime management, singleton versus transient instances, and recursive resolution, none of which System.CommandLine's service provider currently does for external types.

leonardochaia reacted with thumbs up emoji

Func<T1,T2,T3,T4,T5,T6,T7,T8,T9,T10,T11,T12,T13,T14,T15,T16,Task<int>>action)=>
HandlerDescriptor.FromDelegate(action).GetCommandHandler();

publicstaticICommandHandlerFromBindingContext<THandler>()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

It might be helpful for the naming to reflect that the actual handler will be created during binding. One not great idea to open up discussion:DuringBindingCreateHandlerOfType<T>

Copy link
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

CreateLazyBoundHandler (to match with the existingCreate)?

})
.ConfigureBindingContext(context=>context.AddService(_=>action))
.ConfigureBindingContext(context=>context.AddService<IBindingContextCommandHandlerInterface,BindingContextCommandHandler1>())
.ConfigureBindingContext(context=>context.AddService<IBindingContextCommandHandlerInterface,BindingContextCommandHandler2>())
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

One concern I have with this approach is that all handler registrations for all subcommands will happen regardless of which subcommand was used, when only one subcommand is ever used. This can be a bit wasteful and is part of why we've generally steered away from the common pattern for longer-lived apps where DI is configured up front but the cost is amortized over a longer lifetime. This is very often not the case for command-line apps.

Copy link
ContributorAuthor

@fredrikhrfredrikhrAug 8, 2021
edited
Loading

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

@jonsequitur In this case (using the lazily bound registration) only the registration is added, no instance ofBindingContextCommandHandler1 norBindingContextCommandHandler2 is actually created at this point. And neither does the call toCommandHandler.FromBindingContext. The actual instance is first created when the command handler for the choses command is invoked.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

Understood. At invocation time though, all of these registrations happen on theBindingContext, and coupled with the addition of non-lazyAddService calls, this encourages the pattern I mentioned above.

@fredrikhr
Copy link
ContributorAuthor

@jonsequitur

The new AddService overloads increase the scope of the DI features in ways that might be misleading and will likely lead to additional issues being opened when people misunderstand the goal.

Yeah, I can see that. Basically, all service registrations are technically Transient services (they all have a factory registration that is called each time the service is resolved), but of course the factory for the basic services returns singleton instances. However, the no-func overloads that I added really are Transient. Maybe add theTransient suffix? Would that help?

@jonsequitur
Copy link
Contributor

I'd prefer to avoid increasing the scope of the existing DI implementation at all, which originally lacked a publicAddService method of any kind.

A safer approach might be to allow people to bring their ownIServiceProvider implementation and therefore be able to completely control object lifetimes.

@fredrikhr
Copy link
ContributorAuthor

@jonsequitur Hmm, okay, so we could just remove all the overloads toAddService. So, what aboutCommandHandler.CreateLazyFromModelBinder? If I get a ModelBinder that would be able to create an instance regardless of whether it was added to the BindingContext, no? So we don't actually need to add Handlers as BindingContext services? (Provided paramless-ctor)

@jonsequitur
Copy link
Contributor

The way we create command handlers is undergoing some rethinking so that we can eventually remove all of the name-based matching and a lot of model binding complexity. If you're interested in the source generator approach we're looking at, take a look at this PR:#1362.

Sign up for freeto join this conversation on GitHub. Already have an account?Sign in to comment

Reviewers

@jonsequiturjonsequiturjonsequitur left review comments

At least 1 approving review is required to merge this pull request.

Assignees

No one assigned

Labels

None yet

Projects

None yet

Milestone

No milestone

Development

Successfully merging this pull request may close these issues.

3 participants

@fredrikhr@leonardochaia@jonsequitur

[8]ページ先頭

©2009-2025 Movatter.jp