YairHalberstadt / stronginject

compile time dependency injection for .NET
MIT License
845 stars 24 forks source link

Consider way to support convention based registration #100

Open YairHalberstadt opened 3 years ago

YairHalberstadt commented 3 years ago

Is it possible for users to use convention based registration. For example a user might want to auto-register all types, or all types with a specific suffix, etc.

In general this relates to plugin functionality. We need to work out how it's possible for users to extend out StrongInjects functionality. https://github.com/dotnet/roslyn/discussions/48358 would allow this to be done by running a source generator before StrongInject to generate StrongInject registrations. This does make customizing things painful, but I'm not sure I can think of better alternatives.

MattMinke commented 3 years ago

I think would be a great feature. Right now convention based registration is a pretty expensive operation and being able to move this cost from runtime to compile time would be great.

YairHalberstadt commented 3 years ago

@MattMinke I definitely agree. I've been thinking through some ideas about how to do this and will check with the Roslyn team to see if they might work.

Yeah69 commented 3 years ago

Hi, I had a quick look at StrongInject and like it sofar.

I wasn't sure whether I should open a new issue. So I'll just comment here first, because I think the topic is related.

In my workflow (others may of course differ) in most cases an interface type has only one single implementation. Thus, if a dependency of the interface type is used, the container has no other option than to map it to the single implementation. So a registration for choosing an implementation shouldn't be required. What is not so unambiguous is, for example, the decision about the life time for such cases. In order to resolve that I see two option:

Either way, the user could fallback to registrations, if she/he would like to differ from the defaults in specific cases.

This is a different approach, but I think it solves the same problem which the convention-based approach is trying to solve: the user doesn't want to register every single type. With this approach the user only would need to register if there are multiple implementation types for the dependency type or if the user wants to differ from the defaults in specific cases.

YairHalberstadt commented 3 years ago

Hi @Yeah69 I'm glad you're enjoying StrongInject so far!

I'm currently working on enabling plugin functionality, but I can't offer any timeline, or even any guarantees it will work.

Once that's done I hope to release some plugin packages that provide the ability to register all types automatically. Given there's a lot of different options out there I would rather that this wasn't part of core StrongInject, so that it's easy for users to provide their own.

gleb-osokin commented 2 years ago

Looking forward to seeing this feature in StrongInject!

One other use case from me: we're currently using DryIoc container, which has a very handy feature of auto-registering classes based on attributes:

internal interface ISomeService {}

[ExportEx(typeof(ISomeService)), SingletonReuse]
internal class SomeService {}

Therefore, it would really be great to have some mechanism, that auto-converts these Export/ExportEx attributes into registrations for StrongInject module/container. Here's one possible example of convention declaration:

[RegisterByConvention(nameof(SomeAttributeConvention))]
public partial class MyContainer : IContainer<MyApp> {}

[AttributeConvention]
[RegistrationMatcher("ExportEx")]
[ScopeMatcher(
    defaultScope: Scope.InstancePerResolution, 
    instancePerResolution: "TransientReuse",
    singletonInstance: "SingletonReuse")]
// ... other convention parameters...
public class SomeAttributeConvention { }

This way it's possible to implement conventions and their functionality in a progressive manner, i.e., first support only the registrations, then add generics, then scopes, then factories, etc: construct conventions from smaller composable pieces. The drawback, of course, is that we cannot immediately see, which attributes are allowed inside the given convention, so the user will need to rely on the docs. Still, I suppose, conventions will be declared only once or few times at most per solution, so that might be tolerable.

Also, I'm wondering, what are the ideas for plugin support in StrongInject? As far as I could understand from SourceGenerators docs, it is only possible to use additional nuget packages at generation time by bundling them directly in the original generator's package. Which means it won't be super-easy to just create a separate nuget package for some custom convention. Option of having a preceding generator is probably viable, but those are not yet supported by roslyn. Another option is to load the plug-in assembly dynamically, something like:

[assembly: StrongInjectPlugin("SomePlugin.dll")]

Then, probably, it is possible to implement any kind of registrations extraction, provided there will be public extension points available.

P.S. Thanks for all the effort, this is really a great project!

YairHalberstadt commented 2 years ago

@gleb-osokin thanks for your thoughts!

In terms of plugin functionality, I've been rethinking it, and I reckon it's too early to enable that now - SourceGenerators are still a work in progress, and I don't want to tie myself yet to supporting plugins which may not work well for future generations of the SourceGenerator API. For example, a plugin API that worked well with SourceGenerators v1, might have been a very poor design for IncrementalGenerators.

As for convention based registration - for now I would like to just support something super simple, and potentially expand from there.

[RegisterAllTypesAsSelf] seems simple enough and might make a decent dent in the use cases. [RegisterAllTypesAsImplementedInterfaces] seems more useful though but comes with a huge compile time performance cost - in order to do that StrongInject would have to create a giant dictionary from all interfaces in all referenced assemblies to all implementaions in all referenced assemblies. I can't currently think of any easy way to do that.