Closed lodejard closed 7 years ago
Interesting
We have something like this at work as well.
I'm currently doing something similar for vNext, but I'm also playing around with a preprocess compiler, that emits the appropriate Add method call at compile time, so that at run time, you don't have to iterate over all the types of an assembly.
In addition this allows Diagnostics to be created that can inform the developer that they have added an incorrect implementations, again at compile instead of app startup. In my case I'm using an attribute named ImplementationOfAttribute.
eg...
public interface IService1 {}
public interface IService2 {}
[ImplementationOf(typeof(IService1))] // Flags this as a compile time error, the interface is not implemented
class Service2 : IService2 {}
Any interest in a PR? I have a working example at https://github.com/david-driscoll/DependencyInjection.Annotations
That's genius! You know, what that actually makes me think is that we probably shouldn't bake any kind of service discovery directly into the base DI library. That seems like a perfect example of an additional package we can tell people to use if this feature is asked for.
We're also working more on preprocessing to enable an application can take a dependency on a package that has ICompileModule implementations in it. So someone wouldn't need to have code directly in their app's subfolder.
There is one more case that I need to support, that's Open Generic registrations, I think I'll end up doing that sometime today.
With preprocessing that's my understanding after talking with @davidfowl on Jabbr a few days ago. I wanted to experiment with Roslyn and "meta progamming" to see what the limits are... they're pretty limitless.
Why annotate the class, why not just add it as other DI containers do. For example Unity can resolve types even if has not been registered or having a annotation on it. This is something I miss.
You mean register the class as an implementation of the same (or base) class?
In the case of vNext anyway there are a few classes that are registered as the class (generally an abstract class, but I don't think this is always the case). HttpContext or MvcMarkerService for example.
I mean that classes that are not abstract and are having public constructors should be resolved automatically without the need of register them first. They should be resolved as Transient. If one want another behaviour one should register it. This will be convention over configuration.
I took a different approach to this. As detailed in here http://stackoverflow.com/questions/31119284/getting-interface-implementations-in-referenced-assemblies-with-roslyn.
I wanted to avoid assembly scanning and reflection so I used a compile module to search simply for an instance of an module interface and then generated a type implementation at compile time with references to those modules. Each module returns service descriptors so they own their own service registrations. An arbitrary order can be applied as part of the interface contract.
It can pick up modules defined in compilations and references and the only requirement is to drop on a compile module into the host project.
Let me know your thoughts I can push a more concrete example to Github if need be
I'd like this too, please. MEF does this well with new DirectoryCatalog("MyProject.*.dll")
which then looks for MEF attributes. I know DNX/CoreCLR has stripped down reflection capabilities (e.g. restrictions on assembly loading ... no Assembly.GetReferencedAssemblies
) so I have rolled-my-own in a slightly different way.
(I prefer some sort of control over what is added to the container. If you scan for every type that's trivially constructible through a parameter-less constructor then I think you will pollute the container with a whole bunch of MyProject.Entities.A ... B ... C
because entities are frequently POCOs.)
There is one interface and three attributes
IAddServiceAttribute
(I believe this is "best practice" to avoid a base AddServiceAttribute
class because attributes are meant to be sealed
) - has Type Interface { get; }
and Type Implementation { get; }
AddScopedAttribute
AddSingletonAttribute
AddTransientAttribute
Usage is thus:
using System;
using ...;
[assembly: AddScoped(typeof(IFooService), typeof(FooService)]
namespace MyProject {
internal sealed class FooService : IFooService {
...
}
}
And in Startup.cs
// FromAssembly<TType> resolves the assembly of TType and then calls GetCustomAttributes x 3
// AddMyProject takes care of calling options.FromAssembly with its own type
services.AddMyProject(options => options.FromAssembly<Startup>());
(Whether Assembly.GetCustomAttributes
is faster than Assembly.GetTypes().Where(...)
is an open question but it feels more direct.)
Note open generics are permitted via [AddWhatever(typeof(IRepo<>), typeof(Repo<>))]
.
I understand that ASP.NET DI is trying not to be opinionated and compatible with bring-your-own-container but some sort of auto-discovery with built-in ConventionBasedDiscovery
(given all or a list of assemblies, add constructible types) and/or AttributeBasedDiscovery
(give all or a list of assemblies, reads custom attributes).
I will state again I'm not in favour of blindly scanning all assemblies and types and adding them but it has its place.
I thought I already left this here, but apparently not, so here it goes... My approach; https://github.com/khellang/Scrutor
It doesn't look good for a built-in feature, though:
From https://github.com/aspnet/DependencyInjection/issues/322#issuecomment-157114371:
We don't have any plans to include this functionality in the default container. The default container is deliberately minimalistic. For a full featured container you can use any of the various existing containers.
Definitely have a look at https://github.com/khellang/Scrutor for features like this.
to assist in clean startup, usage optional by application of course
AddAssembly(assembly) extension method scans for public types
If the public type is creatable (class, non abstract) and a [ServiceDescriptorAttribute], add it to collection
ServiceDescriptorAttribute has a Lifecycle property, default is transient
ServiceDescriptorAttribute has a ServiceType property, default is null, null means add each interfaces.