-
Notifications
You must be signed in to change notification settings - Fork 317
Contextual information when defining creation of dependencies #498
Comments
I've asked for a similar, but slightly different feature before, to support decoration of open generic types; get access to the requested service type in the factory delegate. Example: services.AddScoped(typeof(IService<>), typeof(Service<>));
// the call below is basically a way to do decoration with MS.Ext.DI...
services.AddScoped(typeof(IService<>), (provider, context) =>
{
var typeArgument = context.RequestedType.GetGenericArguments().First();
var decoratorType = typeof(DecoratedService<>).MakeGenericType(typeArgument);
var decoratedInstance = /* get the decorated instance from the service provider */
return ActivatorUtilities.CreateInstance(provider, decoratorType, decoratedInstance);
}); Where I think the main issue here, is that this would bring in more "requirements" for MS.Ext.DI adapters. Suddenly, they all have to be able to provide this information. |
Maybe this could be used as a ground for further discussion. LightInject provides the RegisterConstructorDependency/RegisterPropertyDependency methods that provides "context" related to the dependency being injected. The following example shows the example with creating a logger that is tied to the type that requests the logger |
Huh. I stumbled here due to this little conversation, which stemmed from a StackOverflow question I posted some time ago with the same general concern here. I'd like to know where @khellang asked for this feature before. Is it another issue? In case it's helpful, my question on StackOverflow is clear about what I'm asking for, but not why this functionality would be helpful. The rationale becomes clear when you use composition to break down behaviors into wrappers. Since specificity is the soul of narrative, let me give an example of how that can be used: Let's say I have an public interface IRepository<TModel> where TModel: Model {
public TModel GetById(Guid id);
public TModel Create(TModel toCreate);
}
public class FreeSpirit : Model {
public Guid Id { get; }
public String Name { get; }
public FreeSpirit(Guid id, String name) {
this.Id = id;
this.Name = name;
}
}
public class Curmudgeon : Model {
public Guid Id { get; }
public DateTime EventTimestamp { get; }
public Curmudgeon(Guid id, DateTime eventTimestamp) {
this.Id = id;
this.EventTimestamp = eventTimestamp;
}
} Assuming a base persistence implementation of services.AddScoped(typeof(IRepository<>), typeof(StandardRepository<>)); Now, let's say that every time someone updates a [Audited]
public class Curmudgeon : Model {
public Guid Id { get; }
public DateTime EventTimestamp { get; }
public Curmudgeon(Guid id, DateTime eventTimestamp) {
this.Id = id;
this.EventTimestamp = eventTimestamp;
}
}
public class AuditingRepositoryWrapper<TModel> : IRepository<TModel> {
private IRepository<TModel> inner { get; }
private IAuditingService auditService { get; }
public AuditingRepositoryWrapper(IRepository<TModel> inner, IAuditingService auditService) {
this.inner = inner;
this.auditService = auditService;
}
public TModel GetById(Guid id) {
return this.inner.GetById(id);
}
public TModel Create(TModel toCreate) {
var result = this.inner.Create(toCreate);
this.auditService.AuditChanges(toCreate, result);
return result;
}
} Now registration is tricky. I need to wrap my Generic, desired result: services.AddScoped(typeof(StandardRepository<>));
services.AddScoped(typeof(IAuditingService), typeof(AuditingService));
services.AddScoped(typeof(IService<>), (provider, context) => {
var typeArgument = context.RequestedType.GetGenericArguments().First();
var isAudited =
typeArgument
.GetTypeInfo()
.CustomAttributes
.Any(attr => attr.AttributeType == typeof(AuditedAttribute));
// Would need to solve the lack of closing type for the repository variable at this point.
// (I think the best way to solve this is with a factory at this point that takes in the 'context'.)
// See: https://github.com/invio/Invio.Extensions.DependencyInjection
IRepository<?> repository = provider.GetRequiredService(typeof(StandardRepository<?>), typeArgument);
if (isAudited) {
repository = new AuditingRepositoryWrapper<?>(
provider.GetRequiredService(repository, typeof(IAuditingService));
);
}
return repository;
}); My team gets around this today by doing something that is similar to the following: services.AddScoped(typeof(StandardRepository<>));
services.AddScoped(typeof(IAuditingService), typeof(AuditingService));
services.AddRepository<FreeSpirit>();
services.AddRepository<Curmudgeon>();
[ ... ]
public static IServiceCollection AddRepository<TModel>(this IServiceCollection services) {
var isAudited =
typeof(TModel)
.GetTypeInfo()
.CustomAttributes
.Any(attr => attr.AttributeType == typeof(AuditedAttribute));
if (isAudited) {
services.AddScoped(
typeof(IRepository<TModel>),
(provider) => {
new AuditingRepositoryWrapper<TModel>(
provider.GetRequiredService<IAuditingService>(),
provider.GetRequiredService<StandardRepository<TModel>()
);
}
);
} else {
services.AddScoped(typeof(IRepository<TModel>), typeof(StandardRepository<TModel>));
}
return services;
}); The key to this workaround is not using open generics. They are closed when they are registered as services in the services collection. This only works when you know how you want to register the service on boot. If you want to register the service differently based upon some kind of user-level configuration, it's possible, but it gets more complicated. I would love to know what Microsoft's stance on this feature is. If they never want to support it, that's fine, I'll just use other DI frameworks. I've banged my head against it already with my own library. |
It' been mentioned in #474 as well. I can't remember if it was an issue, in chat or in person, but it's definitely been discussed. |
Thanks, @khellang. For what it's worth, StructureMap solves the problems with Microsoft's DI around composition. We've internally decided to go that way the next time we have to invest into our DI logic. |
@khellang Seem to remember you played around with decorator support at one time? Anyways, thought I just wanted to show you how this could be solved in LightInject
|
@seesharper Yes, it's actually published as a NuGet package and the source is here: https://github.com/khellang/Scrutor The only issue left is to be able to decorate open generics. The problem is that, unlike in LightInject, the MS.Ext.DI factory doesn't give you access to |
We are closing this issue because no further action is planned for this issue. If you still have any issues or questions, please log a new issue with any additional details that you have. |
This is a feature request, so please forgive me if this is the wrong format / wrong spot.
I ran into an issue when using aspnet/Logging in that I was trying to use the non-generic interface
ILogger
.The problem is that when creating an instance of
ILogger
the logger requires type information to determine the message-scope of the logger.An example of the current behavior is:
Where my desired constructor would look like:
The problem here is that the generic definition is redundant, as the actual interface does nothing with
T
at all. I get suggestions from ReSharper telling me to use the non-generic interface.Using the non-generic interface fails, however, as the it's not possible to tell aspnet/DependencyInjection how to create an instance of Logger.
The feature I am requesting is to have contextual information of the type the dependency is going to be injected into. This would be helpful not only for my particular use case, but for other use cases like when using decorators.
I syntax I am proposing would be:
Where
ctx.DestinationType
would be the type that the given instance is being injected into.The text was updated successfully, but these errors were encountered: