-
Notifications
You must be signed in to change notification settings - Fork 37
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
Register service type + implementation type in Jab modules #158
Comments
I agree that supporting multiple implementations with the same service is a more elegant solution, but unfortunately, the generator internals are pretty tied to the idea of one service having one implementation; it might take a while to untangle. Depending on your target framework you can use static interface members to add factories to modules, but I think allowing |
Yes, the fixed one-to-one relationship is probably the most serious shortcoming (for my purpose) in an otherwise awesome library :) I thought a little about static interface members and default implementation. However, I don't see how you could use static members for this particular problem. Yes, it allows you to implement a factory on the interface, but you still don't have a place to store the singleton instance. Do you have an example, how you would imagine this to work? Alternatively, I came up with the following design. I realized that the Jab source generator actually implements the following (known) interfaces: [ServiceProviderModule]
[Scoped(typeof(ITypeA), Factory=nameof(TypeAFactory))]
[Scoped(typeof(ITypeB), Factory=nameof(TypeBFactory))]
[Scoped(typeof(MyClass))]
public interface IModuleA : IServiceProvider
{
ITypeA TypeAFactory() => (ITypeA) GetService(typeof(MyClass));
ITypeB TypeBFactory() => (ITypeB) GetService(typeof(MyClass));
} Unfortunatly, the source generator fails to implement the factory methods in the generated/embedded |
Ok, I figured it out. You have to use a static factory method, which takes an [ServiceProviderModule]
[Singleton(typeof(ITypeA), Factory = nameof(TypeAFactory))]
[Singleton(typeof(ITypeB), Factory = nameof(TypeBFactory))]
[Singleton(typeof(MyClass))]
public interface IModuleA : IServiceProvider
{
public static ITypeA TypeAFactory(IServiceProvider sp) => (ITypeA) sp.GetService(typeof(MyClass));
public static ITypeB TypeBFactory(IServiceProvider sp) => (ITypeB) sp.GetService(typeof(MyClass));
} Interface inheritance is optional, but IMO improves readability/traceability of the hierarchy. (full example in Program2.txt) |
You can simplify it even more by using factory parameter injection: [ServiceProviderModule]
[Singleton(typeof(ServiceDefinedInAModule))]
[Singleton(typeof(IInterface1), Factory = "Create1")]
[Singleton(typeof(IInterface2), Factory = "Create2")]
public interface IModule
{
public static IInterface1 Create1(ServiceDefinedInAModule s) => s;
public static IInterface2 Create2(ServiceDefinedInAModule s) => s;
} |
@pakrym: Thanks, that's obviously even better! Seems that "factory parameter injection" is an undocumented feature (not mentioned here: https://github.com/pakrym/jab#factories), which greatly simplifies a couple of scenarios for me. "Redirecting" alternative registrations being the most important. |
I'm having issues with modules in combination with registering both service type and implementation type. In #97 a very similar issue is raised (proposed solution is to change Jab module interfaces to be classes). In #133 it is suggested to change attributes to allow both service+implementation type registration.
But let me try to explain the scenario step by step
For some implementations I would like to register a service type (mostly an interface) and additionally the concrete (implementation) type with the container. This is currently not possible, the only option you have is to put multiple attributes (one for each type or interface to be registered) like this:
However, in that case the singleton does not apply anymore (same for scoped). Because both registrations are singletons, you end up with two instances in the end. This can be solved by using
Instance=
orFactory=
parameters with some custom logic to ensure that only a single instance is ever created. This case is also discussed in #97.This is not only tedious to write repeatedly, but the custom logic requires a class, because you either need a field to store the instance for
Factory=
or access toGetService<>()
which is only available on the container class. You could (as one example) have something like this:Here,
MyImpl
is created by the container andIMyService
is "redirected" to point to the same instance.However, you can't do the same in a Jab module, because Jab modules are interfaces (and you can't implement the required logic).
It seems that there are two potential solutions:
I think the registration attribute could be extended to provide:
This might look like this:
Since all "elements" are registered together in that case, the source generator would "know" to produce a proper singleton for
MyClass
which gets exposed (resolvable via the container) asITypeA
andITypeB
(and ifregisterInstance=true
also asMyClass
itself).From my POV this would solve both issues quite elegantly. What do you think?
I'm open to suggestions on how to combine Jab modules and multiple registrations for a single implementation (even if it requires a little more code), because I don't think that Jab can be used for the outlined scenario above at the moment.
The text was updated successfully, but these errors were encountered: