-
Notifications
You must be signed in to change notification settings - Fork 153
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
Some parts of the API cause ambiguity when components implement multiple interfaces #348
Comments
Dear Simple Injector users, would you please tell us whether you use |
I haven't used it |
No, only with E.g. |
container.RegisterConditional(
typeof(IConnection),
Lifestyle.Singleton.CreateRegistration(() => new CarDatabaseConnection(), container),
c => c.Consumer != null && c.Consumer.ServiceType == typeof(IDatabase<Car>));
container.Register(typeof(IDatabase<>), typeof(InMemoryDatabase<>), Lifestyle.Singleton); |
I just went over a few repositories on GitHub and found https://github.com/maca88/PowerArhitecture/blob/master/Source/PowerArhitecture.DataAccess/Extensions/SimpleInjectorExtensions.cs#L121 from @maca88: container.RegisterConditional(typeof(ISession), registration, context =>
{
var defRepoType = context.Consumer?.ServiceType?.GetGenericType(typeof(IRepository<,>));
...
var modelType = defRepoType.GetGenericArguments()[0];
} |
@janhartmann, that's an interesting example, because it shows how the if (context.Consumer.ImplementationType.IsClosedTypeOf(typeof(IRepository<,>)) {
Type defRepoType =
context.Consumer.ImplementationType.GetClosedTypesOf(typeof(IRepository<,>)).Single();
...
Type modelType = defRepoType.GetGenericArguments()[0];
} Note the |
I guess this is almost the same as @janhartmann but nevertheless: public static void RegisterFallBackAuthorizedRepository(
Container container, Type[] knownTypes, Lifestyle scope)
{
container.RegisterConditional(typeof(IAuthorizedRepository<>),
typeof(AuthorizedRepository<>), scope,
context =>
{
if (!context.Handled)
{
var type = context.ServiceType.GetGenericArguments().Single();
if (!knownTypes.Contains(type))
{
throw new NotSupportedException("some message");
}
}
return !context.Handled;
});
} What I do with this code is preventing somebody in my team to use the default implementation of an AuthorizedRepo, which does no authorization at all. We have this implementations for models which include data available for all users in the system. So it prevents somebody to use the fallback directly and hopefully the user will rethink what he was doing and may add a type to the knowntypes list or must create an implementation which will actually check if the current user has the correct permissions. Although I think I also could get the generic type argument also via the implementationtype in this case. |
I moved the creation of these common extension methods on |
Using the @Fresa's example: container.RegisterConditional(
typeof(IConnection),
Lifestyle.Singleton.CreateRegistration(() => new CarDatabaseConnection(), container),
c => typeof(IDatabase<Car>).IsAssignableFrom(c.Consumer.ImplementationType)); NOTE: Examples updated to use @maca88's example: container.RegisterConditional(typeof(ISession), registration, context =>
{
var defRepoType =
context.Consumer.ImplementationType.GetClosedTypeOf(typeof(IRepository<,>);
...
var modelType = defRepoType.GetGenericArguments()[0];
} @TheBigRic's example: var serviceType =
context.ImplementationType.GetClosedTypeOf(typeof(IAuthorizedRepository<>));
var type = serviceType.GetGenericArguments().Single(); |
As |
Seems like nice solutions! |
There's a similar API that can cause the same problems as with |
Container.RegisterInitializer(Action<InstanceInitializationData>, Predicate<InitializationContext>) replaced with RegisterInitializer(Action<InstanceInitializationData>, Predicate<InitializerContext>) method. Related to #348
There are multiple design flaws in v3 that can cause ambiguity when components implement multiple interfaces.
The following parts of the API are affected:
PredicateContext.Consumer.ServiceType
ExpressionBuilding.RegisteredServiceType
Type
argument inIPropertySelectionBehavior.SelectProperty(Type, PropertyInfo)
.1. Conditional registrations can cause ambiguity when Consumer.ServiceType is used
There is a design flaw in v3 concerning
RegisterConditional
that we overlooked while designing v3.The predicate supplied to a
RegisterConditional
method does not allow looking up to the consumer's consumer, because this would easily lead to ambiguity problems, when types are registered using a lifestyle other thanTransient
.This ambiguity problem however still exists when a conditional registration is selected based on the service type of its consumer, while:
While using the following code structure:
The following unit test demonstrates the problem:
The previous test fails because the returned instance of
IY
does not contain theYLogger
but theXLogger
. This is caused by theGetInstance<IX>()
call. That call causesA
to be created (using the context ofIX
) and that instance is cached. During the time thatIY
is requested, the instance ofA
is already created and will be returned immediately.The previous case is something that can't safely be expressed; it is technically impossible to do this, and instead of giving the user the illusion that he can differentiate based on the
ServiceType
, Simple Injector should prevent the user falling into this trap.There are two possible solutions to this problem:
Consumer.ServiceType
property from thePredicateContext
, since this data is causing ambiguity. Downside is that quite a lot of users are using this property, so this is a severe breaking change.Not only is the detection of invalid use of
ServiceType
hard to implement, it still doesn't guide users into the right path. This means that Removing theServiceType
property is the best solution.2.
ExpressionBuilding.RegisteredServiceType
For
ExpressionBuilding.RegisteredServiceType
partly the same problems exist.ExpressionBuilding
is called during the process of building the expression tree insideRegistration
, but sinceRegistration
can be reused by multipleInstanceProducer
instances (that control the actual service type), ambiguity emerges when bothInstanceProducer
instances have a different service type.3. The
Type
argument inIPropertySelectionBehavior.SelectProperty(Type, PropertyInfo)
.For
IPropertySelectionBehavior.SelectProperty
the problem is identical toExpressionBuilding.RegisteredServiceType
.SelectProperty
is called during the process of building the expression tree insideRegistration
, but sinceRegistration
can be reused by multipleInstanceProducer
instances (that control the actual service type), ambiguity emerges when bothInstanceProducer
instances have a different service type.4. InstanceInitializationData.Context.Producer
Similar to the previous problems, during the building of the expression tree of a
Registration
, the registration wraps the expression with the available initializers, registered throughContainer.RegisterInitializer
(if any). In case there is an applicable initializer, theRegistration
class will call ensure that that initializer is called where it passes in anInstanceInitializationData
instance. AnInstanceInitializationData
wraps both the created instance and anInitializationContext
class. TheInitializationContext
contains both theRegistration
and theInstanceProducer
. Obviously, thisInstanceProducer
again is the problem, because theRegistration
can belong to multipleInstanceProducer
instances, while the expression tree will be built just once. This again causes ambiguity. The producer that gets built first wins.To solve this problem, the
InstanceInitializationData
should not wrap anInitializationContext
instance, but simply theRegistration
class.The text was updated successfully, but these errors were encountered: