GithubHelp home page GithubHelp logo

jonpsmith / netcore.autoregisterdi Goto Github PK

View Code? Open in Web Editor NEW
231.0 231.0 32.0 100 KB

Extension method to find/register classes in an assembly into the Microsoft DI provider

Home Page: https://www.thereformedprogrammer.net/asp-net-core-fast-and-automatic-dependency-injection-setup/

License: MIT License

C# 100.00%

netcore.autoregisterdi's Introduction

NetCore.AutoRegisterDi

I have written a simple version of AutoFac's RegisterAssemblyTypes method that works directly with Microsoft's DI provider, i.e this library to scan an assemby (or assemblies) on your application and register all the public normal classes (i.e not generic classes) that have an interface into the Microsoft NET's Dependency injection provider (DI for short).

The NetCore.AutoRegisterDi is available on NuGet as EfCore.SchemaCompare and is an open-source library under the MIT license. The documenation is found in the README file and the ReleaseNotes contains the details of each release.

Why have I written this extension?

There are two reasons:

  1. I really hate having to hand-code each registering of my services - this extension method scans assembles and finds/registers classes with interfaces for you.
  2. I used to use AutoFac's assembly scanning feature, but I then saw a tweet by @davidfowl about Dependency Injection container benchmark which showed the Microsoft's DI provider was much faster than AutoFac. I therefore implemented a similar (but not exactly the same) feature for the Microsoft.Extensions.DependencyInjection library.

Documentation

NOTE: There is an article about this library which gives you an overview of this library. Useful if you haven't used this library before.

Two, simple examples

Example 1 - scan the calling assembly

This example scans the assembly where you call the AutoRegisterDi's RegisterAssemblyPublicNonGenericClasses method for all the classes which has one or more public interfaces and the Class's name ends with "Service" are registered with .NET's

public void ConfigureServices(IServiceCollection services)
{
   //... other configure code removed

   service.RegisterAssemblyPublicNonGenericClasses()
     .Where(c => c.Name.EndsWith("Service"))
     .AsPublicImplementedInterfaces();

Example 2 - scanning multiple assemblies

This example scans the three assemblies and registers all the classes that have one or more public interfaces. That's because I have commented out the .Where(c => c.Name.EndsWith("Service")) method.

public void ConfigureServices(IServiceCollection services)
{
   //... other configure code removed

   var assembliesToScan = new [] 
   {
        Assembly.GetExecutingAssembly(),
        Assembly.GetAssembly(typeof(MyServiceInAssembly1)),
        Assembly.GetAssembly(typeof(MyServiceInAssembly2))
   };   

   service.RegisterAssemblyPublicNonGenericClasses(assembliesToScan)
     //commenting the line below means it will scan all public classes
     //.Where(c => c.Name.EndsWith("Service"))  
     .AsPublicImplementedInterfaces(); 

Detailed information

There are four parts:

  1. RegisterAssemblyPublicNonGenericClasses, which finds all the public classes that:
    • Aren't abstract
    • Aren't a generic type, e.g. MyClass<AnotherClass>
    • Isn't nested. e.g. It won't look at classes defined inside other classes
  2. An optional Where method, which allows you to filter the classes to be considered.
  3. The AsPublicImplementedInterfaces method which finds ant interfaces on a class and registers those interfaces as pointing to the class.
  4. There are two methods to allow you to say that a specific interface should be ignored:
    • The IgnoreThisInterface<TInterface>() method to add an interface to the the ignored list.
    • The IgnoreThisGenericInterface(Type interfaceType) method to add an generic interface to the the ignored list.
  5. Various attributes that you can add to your classes to tell NetCore.AutoRegisterDi what to do:
    • Set the ServiceLifetime of your class, e.g. [RegisterAsSingleton] to apply a Singleton lifetime to your class.
    • A [DoNotAutoRegister] attribute to stop library your class from being registered with the DI.

1. The RegisterAssemblyPublicNonGenericClasses method

The RegisterAssemblyPublicNonGenericClasses method will find all the classes in

  1. If no assemblies are provided then it scans the assembly that called this method.
  2. You can provide one or more assemblies to be scanned. The easiest way to reference an assembly is to use something like this Assembly.GetAssembly(typeof(MyService)), which gets the assembly that MyService was defined in.

I only consider classes which match ALL of the criteria below:

  • Public access
  • Not nested, e.g. It won't look at classes defined inside other classes
  • Not Generic, e.g. MyClass<T>
  • Not Abstract

2. The Where method

Pretty straightforward - you are provided with the Type of each class and you can filter by any of the Type properties etc. This allows you to do things like only registering certain classes, e.g Where(c => c.Name.EndsWith("Service")).

NOTE: Useful also if you want to register some classes with a different time scope - See next section.

3. The AsPublicImplementedInterfaces method

The AsPublicImplementedInterfaces method finds any public, non-nested interfaces (apart from IDisposable and ISerializable) that each class implements and registers each interface, known as service type, against the class, known as the implementation type. This means if you use an interface in a constructor (or other DI-enabled places) then the Microsoft DI resolver will provide an instance of the class that interface was linked to. See Microsoft DI Docs for more on this.

By default it will register the classes as having a lifetime of ServiceLifetime.Transient, but the AsPublicImplementedInterfaces method has an optional parameter called lifetime which you can change to another lifetime. Note that all the classes will have the same lifetime, but you can use AutoRegisterDi's attributes to set different lifetime to a class (see section 5).

See this useful article on what lifetime (and other terms) means.

4. Ignore Interfaces

Some classes have interfaces that we don't really want to be registered to the DI provider - for instance IDisposable and ISerializable. Therefore AutoRegisterDi has two methods which allow you to define a interface that shouldn't be registered on any classes. They are:

The IgnoreThisInterface<TInterface> method

This method allows you to add a interface to to a list of interfaces that you don't register to the DI provider. The example below adds the IMyInterface in the list of interfaces to not register.

service.RegisterAssemblyPublicNonGenericClasses()
   .IgnoreThisInterface<IMyInterface>()
   .AsPublicImplementedInterfaces();

NOTES

  • The list of interfaces to ignore has already got the IDisposable and ISerializable interfaces.
  • You can ignore many interfaces by including have many IgnoreThisInterface<TInterface> calls in the setup.

The IgnoreThisGenericInterface(Type interfaceType) method

This method was asked for Lajos Marton (GitHub @martonx). He was using records and found that each record has a IEquatable<RecordType> and he didn't wanted the DI provider be up with interfaces that aren't used.

You could use the IgnoreThisInterface<IEquatable<RecordType>>, but you would need to do that for every record. The other solution is to say that ALL IEquatable<T> are ignored. The code below will do that.

service.RegisterAssemblyPublicNonGenericClasses()
   .IgnoreThisGenericInterface(typeof(IEquatable<>))
   .AsPublicImplementedInterfaces();

NOTES:

  • I haven't IEquatable<> to the ignore interface list as there may be a valid use to register a IEquatable<SomeClass> sometimes. In that case you would need to use IgnoreThisGenericInterface to define all your records separably.
  • The method works for any generic interface type as long that the generic interface has no arguments are filled in, e.g IDictionary<,> is fine, but IDictionary<,string> won't work (you get an exception).

5. The attributes

Fedor Zhekov, (GitHub @ZFi88) added attributes to allow you to define the ServiceLifetime of your class, and also exclude your class from being registered with the DI.

Here are the attributes that sets the ServiceLifetime to be used when NetCore.AutoRegisterDi registers your class with the DI.

  1. [RegisterAsSingleton] - Singleton lifetime.
  2. [RegisterAsTransient] - Transient lifetime.
  3. [RegisterAsScoped] - Scoped lifetime.

The last attribute is [DoNotAutoRegister], which stops NetCore.AutoRegisterDi registered that class with the DI.

netcore.autoregisterdi's People

Contributors

chrisk00 avatar jonpsmith avatar skiptirengu avatar zfi88 avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar

netcore.autoregisterdi's Issues

Don't try to auto register records

Hi,

As records has this syntax:

public record CardMessage(int CardId, string Upc, string Amount, string LocalTransactionTime, string LocalTransactionDate, string RetrievalReferenceNumber)

NetCore.AutoRegisterDi tries to resolve all of the record constructor parameters as DI dependencies.

Intended behavior:
AutoRegisterDI should skip all of the records in the registering process.

GetService based on Class Type instead of Interface

Hello, I'm not sure if I'm doing something wrong or if it is how AutoRegisterDi should work, but I have the following issue:

ServiceLocator.ServiceContainer.GetService<T>(Type type);

The method above brings me the services I have registered in my service container, basically, it is a proxy for IHttpContextAccessor.

In my project I have some classes like this one (code trimmed):

public class GetAllUseCase : UseCase<IGetAllOutput>
{
    protected override async Task Execute()
    {
        // voodoo
    }
}

public class ForecastUseCase : UseCase<IForecastOutput>
{
    protected override async Task Execute()
    {
        // voodoo
    }
}

Basically I have a bunch of classes with UseCase suffix. The way I register them now is services.AddScoped(typeof(GetAllUseCase));, that is a) in order to avoid long generic interfaces and b) when I inject the class into the needed class it goes like so:

public class Weather : ApiController
{
    private readonly ForecastUseCase _useCase;

    public Weather([FromServices] ForecastUseCase useCase) => _useCase = useCase;

So far so good, what I expected with AutoRegisterDi was something like this:

services.RegisterAssemblyPublicNonGenericClasses(typeof(ForecastUseCase).Assembly)
    .Where(c => c.Name.EndsWith("UseCase"));

I tried also with .AsPublicImplementedInterfaces(), but it didn't work.

To simplify things I have the following:

public interface IFoobar<T>
{ }

public class Foobar : IFoobar<string>
{
    public string Foo { get; } = "Foo";
    
    public string Bar { get; } = "Bar";
}

Obviously the generic type here has no practical reason, it is just for depo purposes

and I saw that if I try to call by interface like so:

var foobar = ServiceLocator.ServiceContainer.GetService<IFoobar<string>>(
                typeof(IFoobar<string>));

it works, but if I call it by class it doesn't:

var foobar = ServiceLocator.ServiceContainer.GetService<Foobar>(
                typeof(Foobar));

Could you suggest me a way to make it so it will work by calling either the interface or the class directly if that is possible of course?

Thanks!

Service registration

Hello!
What do you think about using some custom attributes for marking classes, which needs to be registred in container, maybe [Service]?
Also we can using attributes for marking classes life time scope([Transient], [Scoped], [Singleton])).
By that change we can register dependencies in one call...
I can create pull request for you.

Add the possibility to ignore generic interfaces

It would be great if the IgnoreThisInterface method has an overload that accepts a type, and supports generic interface.

Example:

services.RegisterAssemblyPublicNonGenericClasses()
    .IgnoreThisInterface(typeof(INotificationHandler<>))
    .AsPublicImplementedInterfaces();

Currently I am using this code to acheive this:

services.RegisterAssemblyPublicNonGenericClasses()
    .Where(c => !c.IsAssignableToGenericType(typeof(INotificationHandler<>)))
    .AsPublicImplementedInterfaces();

// Implementation of the extension method
public static bool IsAssignableToGenericType(this Type type, Type genericType)
{
    var interfaceTypes = type.GetInterfaces();

    foreach (var it in interfaceTypes)
    {
        if (it.IsGenericType && it.GetGenericTypeDefinition() == genericType)
            return true;
    }

    if (type.IsGenericType && type.GetGenericTypeDefinition() == genericType)
        return true;

    var baseType = type.BaseType;
    if (baseType is null) return false;

    return baseType.IsAssignableToGenericType(genericType);
}

Extensions.AssertExtensions as a package?

Extensions.AssertExtensions is very useful set of xunit extensions. Perhaps you should make this a separate package for all xunit users to use?

Looks very useful to me.

Classes without interface

As far as I can tell, the tool does not register classes that have no interface?

I can imagine that in most codebases, and for DI to be used to its fullest, you would define interfaces for most of the classes you want to inject. But it might be useful to also support (perhaps trough a setting) adding classes without interfaces to the DI chain?

Is this something you would consider or are there reasons not to support this?

How to use without Interface?

Hello and thank you for this package
If I inheritance UserService from IUserService this code in Program.cs inject UserService into UserController correctly:

builder.Services.RegisterAssemblyPublicNonGenericClasses()
  .Where(c => c.Name.EndsWith("Service"))
  .AsPublicImplementedInterfaces(ServiceLifetime.Scoped);

public class UserService : IUserService

private readonly IUserService _userService;
public UserController(IUserService userService)
{
    _userService = userService;
}

But I want to inject UserService to UserController (not IUserService)

public class UserService //: IUserService
{
    private readonly AppDbContext _db;

    public UserService(AppDbContext appDbContext)
    {
        _db = appDbContext;
    }
public class UserController : ControllerBase
{
    private readonly UserService _userService;
    public UserController(UserService userService)
    {
        _userService = userService;
    }

Then I get this error in swagger :
"Unable to resolve service for type 'WebApi.Services.UserService.UserService' while attempting to activate 'WebApi.Controllers.UserController.UserController'."

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.