Add an IServiceCollection.AddAssembly extensions method #76

Closed
lodejard opened this Issue Apr 30, 2014 · 13 comments

Comments

Projects
None yet
@lodejard
Contributor

lodejard commented Apr 30, 2014

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.

@lodejard lodejard added this to the Alpha milestone Apr 30, 2014

@danroth27 danroth27 modified the milestones: CTP1, Alpha, CTP2 May 20, 2014

@davidfowl davidfowl modified the milestones: CTP2, 1.0.0-alpha4 Aug 17, 2014

@davidfowl

This comment has been minimized.

Show comment
Hide comment
@davidfowl

davidfowl Aug 17, 2014

Member

Interesting

Member

davidfowl commented Aug 17, 2014

Interesting

@davidfowl davidfowl modified the milestones: 1.0.0-alpha4, 1.0.0-beta1 Sep 15, 2014

@david-driscoll

This comment has been minimized.

Show comment
Hide comment
@david-driscoll

david-driscoll Sep 17, 2014

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 {}

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 {}
@david-driscoll

This comment has been minimized.

Show comment
Hide comment

Any interest in a PR? I have a working example at https://github.com/david-driscoll/DependencyInjection.Annotations

@lodejard

This comment has been minimized.

Show comment
Hide comment
@lodejard

lodejard Sep 23, 2014

Contributor

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.

Contributor

lodejard commented Sep 23, 2014

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.

@david-driscoll

This comment has been minimized.

Show comment
Hide comment
@david-driscoll

david-driscoll Sep 23, 2014

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.

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.

@npehrsson

This comment has been minimized.

Show comment
Hide comment
@npehrsson

npehrsson Oct 18, 2014

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.

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.

@david-driscoll

This comment has been minimized.

Show comment
Hide comment
@david-driscoll

david-driscoll Oct 18, 2014

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.

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.

@npehrsson

This comment has been minimized.

Show comment
Hide comment
@npehrsson

npehrsson Oct 20, 2014

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 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.

@Eilon Eilon modified the milestone: 1.0.0-beta1 Oct 20, 2014

@muratg muratg added this to the 1.0.0 backlog milestone Jul 14, 2015

@glennc glennc modified the milestones: Backlog, 1.0.0 backlog Jul 17, 2015

@Antaris

This comment has been minimized.

Show comment
Hide comment
@Antaris

Antaris Oct 25, 2015

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

Antaris commented Oct 25, 2015

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

@taspeotis

This comment has been minimized.

Show comment
Hide comment
@taspeotis

taspeotis Jan 15, 2016

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'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.

@khellang

This comment has been minimized.

Show comment
Hide comment
@khellang

khellang Jan 16, 2016

Contributor

I thought I already left this here, but apparently not, so here it goes... My approach; https://github.com/khellang/Scrutor

Contributor

khellang commented Jan 16, 2016

I thought I already left this here, but apparently not, so here it goes... My approach; https://github.com/khellang/Scrutor

@khellang

This comment has been minimized.

Show comment
Hide comment
@khellang

khellang Jan 16, 2016

Contributor

It doesn't look good for a built-in feature, though:

From #322 (comment):

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.

Contributor

khellang commented Jan 16, 2016

It doesn't look good for a built-in feature, though:

From #322 (comment):

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.

@davidfowl

This comment has been minimized.

Show comment
Hide comment
@davidfowl

davidfowl Feb 19, 2017

Member

Definitely have a look at https://github.com/khellang/Scrutor for features like this.

Member

davidfowl commented Feb 19, 2017

Definitely have a look at https://github.com/khellang/Scrutor for features like this.

@davidfowl davidfowl closed this Feb 19, 2017

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment