Giter VIP home page Giter VIP logo

dotnurseinjector's Introduction

DotNurseInjector

.Nurse Injector

Simple, lightweight & useful Dependency Injector for dotnet.

.NET Core Nuget CodeFactor Gitmoji


Getting Started

  • Install Nuget package from here.

  • Go to your Startup.cs, remove all your manual injections and use AddServicesFrom() method with namespace.

    • If you have following pattern:
    - services.AddTransient<IBookRepository, BookRepository>();
    - services.AddTransient<IAuthorRepository, AuthorRepository>();
    - services.AddTransient<IPublisherRepository, PublisherRepository>();
    • Replace them with following:
    services.AddServicesFrom("MyCompany.ProjectName.Repositories.Concrete"); // <-- Your implementations namespace.
  • โœ… That's it! DotNurse will scan your entire assembly and referenced assemblies to find types with the given namespace then register them to ServiceCollection.

Managing in Objects

You can even define lifetimes and expose types from objects via using [RegisterAs] attribute.

  • Firstly, you should add following method for registering by attributes.
    services.AddServicesByAttributes();
  • Then you're ready to decorate your objects with [RegisterAs] attribute.
    [RegisterAs(typeof(IBookRepository))]
    public class BookRepository : IBookRepository, IAnotherInterface, IYetAnothetInterface
    {
    }

Property/Field Injection

This section is optional. You can still use default Microsoft Dependency Injection and skip this step.

You can use attribute injection instead of constructor injection. Using [InjectService] attribute for properties or fields is enough to inject them from IoC.

Setting Up

You must replace your Service Provider with .Nurse Injecor to use Attribute Injection.

  • Go your Program.cs and add UseDotNurseInjector() method to IHostBuilder.
 public static IHostBuilder CreateHostBuilder(string[] args) =>
            Host.CreateDefaultBuilder(args)
                .UseDotNurseInjector() // <-- Adding this one is enough!
                .ConfigureWebHostDefaults(webBuilder =>
                {
                    webBuilder.UseStartup<Startup>();
                });

Usage

[InjectService] public IBookRepository BookRepository { get; private set; }
[InjectService] protected IBookRepository bookRepository;

Refactor your old codes

You can remove long constructor and place attribute to your fields or properties.

  • Old code before DotNurseInjector
public class BookService 
{
  public IBookRepository BookRepository { get; private set; }
  private readonly BookManager _bookManager;
  // ...
  
  public BookService(
    IBookRepository bookRepository,
    BookManager bookManager,
    // ... 
    )
  {
     BookRepository = bookRepository;
     _bookManager = bookManager;
     // ...
  }
}
  • New code after DotNutseInjector
public class BookService 
{
  [InjectService] private IBookRepository BookRepository { get; set; }
  [InjectService] private readonly BookManager _bookManager;
  // ...
}

dotnurse-injector-social-preview


Customizations

DotNurse meets your custom requirements such as defining lifetime, injecting into different interfaces, etc.


Managing from Startup

.Nurse provides fluent API to manage your injections from a single point.

Service Lifetime

services.AddServicesFrom("MyCompany.ProjectName.Services", ServiceLifetime.Scoped);

Interface Selector

You can define a pattern to choose interface from multiple interfaces.

services.AddServicesFrom("ProjectNamespace.Services", ServiceLifetime.Scoped, opts =>
{
    opts.SelectInterface = interfaces => interfaces.FirstOrDefault(x => x.Name.EndsWith("Repository"));
});

Implementation Selector

You can define a pattern to choose which objects will be injected into IoC. For example you have a base type in same namespace and you don't want to add it into service collection. You can use this feature:

  • ProjectNamespace.Services
    • BookService
    • BaseService <- You want to ignore this
    • AuthorService
    • ...
services.AddServicesFrom("ProjectNamespace.Services", ServiceLifetime.Scoped, opts =>
{
    opts.SelectImplementtion = i => !i.Name.StartsWith("Base");
});

Implementation Base

Think about same scenario like previous, you can choose a base type to inject all classes which is inhetired from.

services.AddServicesFrom("ProjectNamespace.Services", ServiceLifetime.Scoped, opts =>
{
    opts.ImplementationBase = typeof(BaseRepository);
});

Custom Registration Rule

You can use lambda expression to define your custom rules for registration.

In example, you can use it for registering types in a namespace recursively.

// Following code will register all types under Services namespace and sub-namespaces too.
services.AddServicesFrom(
    x => x.Namespace != null && (x.Namespace.StartsWith("MyProject.Services"));

Managing from Objects

You can manage your injections for class by class.

Service Lifetime Attribute

[ServiceLifeTime(ServiceLifetime.Singleton)] // <-- Only this object will be Singleton.
public class MyRepository : IMyRepository
{
    // ...
}

Ignoring Registration by Attribute

You can ignore some of your class from injector.

[DontRegister] // <-- This object will not be added into services
public class MyRepository : IMyRepository
{
    // ...
}

Register As Attribute

You can manage your service types to add into. This attribute can be used multiple times at once.

/* 
 * Following object will be added into given types.
 */
[RegisterAs(typeof(IBookRepository))]
[RegisterAs(typeof(IBaseRepository<Book>), ServiceLifetime.Scoped)]
[RegisterAs(typeof(BookRepository), ServiceLifetime.Singleton)]
public class BookRepository : IBookRepository
{
    // ...
}

This injection will do following code:

services.AddTransient<IBookRepository, BookRepository>();
services.AddScoped<IBaseRepository<Book>>, BookRepository>();
services.AddSingleton<BookRepository>();

dotnurseinjector's People

Contributors

enisn avatar gregoryagu 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

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

dotnurseinjector's Issues

Auto Registration from Attributes

Summary

One more extension method is required to register from attributes instead of registering from namespace.

It might looks like:

services.AddFromAttributes();

And all attribute used classes should be registered.

[RegisterAs(typeof(IMyAwesomeService), ServiceLifetime.Singleton)]
public class MyAwesomeService : ServiceBase, IMyAwesomeService
{
    //...
}

Multiple Service Type for RegisterAsAttribute

More than one service type should be passed to [RegisterAs] as a parameter.

In this way, multiple interfaces can be exposed from an implementation.


A short example:

public interface IFoo
{
    void DoFoo();
}

public interface IBar
{
    void DoBar();
}

Both interfaces can be exposed from a single implementation like below:

[RegisterAs(typeof(IFoo), typeof(IBar))] // <-- It'll be registered as IFoo and IBar at the same time.
public class FooBarImplementation : IFoo, IBar
{
    public void DoFoo(){}
    public void DoBar(){}
}

Add option to Attribute Injection for required services

[InjectService] attribute should have IsRequired parameter to decide that dependency resolution is optional or not.

Sample usage should be like below.

public class MyService
{
    // Following definition will throw an exception if that service wasn't registered to service collection.
    [InjectService(IsRequired = true)]
    public IMustExistService { get; private set; }

    // Following property might be null if that service wasn't registered to service collection.
    [InjectService(IsRequired = false)]
    public IOptionalService { get; private set; }
}

Support Recursive Assembly Registration

Currently, registration works only Assembly & Dependencies of Assembly.
Let me explain with an example. Let say there is an assembly tree below;

  • Assembly Main
    • Assembly A
      • Assembly A.1
      • Assembly A.2
    • Assembly B
      • Assembly B.1
      • Assembly B.2

In the current state, when you call registration in Assembly Main, all types from Assembly Main, Assembly A, and Assembly B will be registered.

  • Assembly Main
    • Assembly A
      • Assembly A.1
      • Assembly A.2
    • Assembly B
      • Assembly B.1
      • Assembly B.2

There should be an option to register the entire dependency tree which discovers all assemblies. It's not useful at the moment, each assembly should register its own services but TypeExplorer should have that discovery option.

Problem with injecting .NET IOptions<TOptions>

I'm registering IOptions<DataAccess> this way:

services.Configure<DataAccess>(configuration.GetSection(nameof(DataAccess)));

After that I'm getting exception:

System.ArgumentException: 'Open generic service type 'Microsoft.Extensions.Options.IOptions`1[TOptions]' requires registering an open generic implementation type. (Parameter 'descriptors')'

I saw that u tried to apply fix in related issue: #6. However, it still doesn't work.

Performance Improvement for AssemblyLoading

Currently, assemblies are loaded and cached. It's good for calling twice or more. Because it'll return the same assembly collection after the first call. But that collection has never been released or cleared.

The main required action is, clearing that collection after ServiceCollection is built.

Source code of that section:

private static IList<Assembly> AssembliesToSearchFor
{
get
{
if (assemblies == null)
{
lock (lockingObj)
{
var _assemblies = Assembly
.GetEntryAssembly()
.GetReferencedAssemblies()
.Select(s => Assembly.Load(s.ToString()))
.Concat(new[] { Assembly.GetEntryAssembly() });
if (AppDomain.CurrentDomain.FriendlyName == "testhost") // Test host is not referenced directly .dll
{
_assemblies = _assemblies
.Concat(AppDomain.CurrentDomain.GetAssemblies());
}
assemblies = _assemblies.Distinct().ToList();
}
}
return assemblies;

Recursive register namespace

I've a project with structure like a this.

image

Can i resolve all below root.Handlers and will be recursively register all Childs?

so, I just call with code services.AddServicesFrom("root.Handlers", ServiceLifetime.Scoped);

Unhandled exception. System.ArgumentException: Open generic service type 'Microsoft.Extensions.Options.IOptions`1[TOptions]' requires registering an open generic implementation type. (Parameter 'descriptors')

I am trying to add the dot nurse injector to my project but it fails when I try to start the server and has this error.

Unhandled exception. System.ArgumentException: Open generic service type 'Microsoft.Extensions.Options.IOptions`1[TOptions]' requires registering an open generic implementation type.
 (Parameter 'descriptors')
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteFactory.Populate()
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteFactory..ctor(IEnumerable`1 descriptors)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.ServiceProviderEngine..ctor(IEnumerable`1 serviceDescriptors)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CompiledServiceProviderEngine..ctor(IEnumerable`1 serviceDescriptors)
   at Microsoft.Extensions.DependencyInjection.ServiceCollectionContainerBuilderExtensions.BuildServiceProvider(IServiceCollection services, ServiceProviderOptions options)
   at DotNurse.Injector.DotNurseServiceProvider..ctor(IServiceCollection defaultServiceCollection)
   at DotNurse.Injector.DotNurseServiceProviderFactory.CreateBuilder(IServiceCollection services)
   at Microsoft.Extensions.Hosting.Internal.ServiceFactoryAdapter`1.CreateBuilder(IServiceCollection services)
   at Microsoft.Extensions.Hosting.HostBuilder.CreateServiceProvider()
   at Microsoft.Extensions.Hosting.HostBuilder.Build()
   at backend.Program.Main(String[] args) in C:\Users\Alex\source\repos\cloudpc\backend\backend\Program.cs:line 16

DotNurseServiceProvider can't resolve in an attached process

If the DLL is loaded at runtime and attached to another process, DotNurseServiceProvider can't resolve any requested types.

Here is a sample code:

 var configuration = new ConfigurationBuilder()
        .SetBasePath(Path.Combine(Path.GetDirectoryName(typeof(Main).Assembly.Location)))
        .AddJsonFile("appsettings.json")
        .Build();

        var services = new ServiceCollection().AddOptions().AddSingleton<IConfigurationRoot>(configuration).AddSingleton<IAttributeInjector, DotNurseAttributeInjector>();

        services.AddServicesByAttributes();

        services.Configure<MongoDbOptions>(x => x.ConnectionString = configuration.GetConnectionString("MongoDB"));

        services.AddTransient(typeof(IRepository<>), typeof(MongoDbRepository<>));

        //ServiceProvider = services.BuildServiceProvider(); // <-- microsoft's one is working properly.

        ServiceProvider = new DotNurseServiceProvider(services);
        Console.WriteLine(">>>>>>>>>>>>>>>>>>ServiceProvider is built!");

        Console.WriteLine("Started resolving!");
        var service = ServiceProvider.GetService<IAttributeInjector>(); // Stucked here, nothing happens and block main thread of application.
        Console.WriteLine("The service has been resolved!"); // never been written

Property injection only works on services that are directly resolved from the IServiceProvider

In order to inject properties, first you resolve the service from an internal default service provider and then you try to inject its properties. This way, if the service has any dependencies, and those dependencies happen to have some properties to be injected, those properties will not be affected by your algorithm -since they are resolved by THE DEFAULT SERVICE PROVIDER NOT YOURS.
Consider this modified version of your Service.cs file in the test project:

public interface IDataProvider<T>
{
    T Get();
}

public interface IMessageDataProvider : IDataProvider<string>
{
}

public class MessageDataProvider : IMessageDataProvider
{
    public string Get()
    {
        return "Hello World!";
    }
}

public interface IDataService<T>
{
    T Retrieve();
}

public interface IMessageDataService : IDataService<string>
{
    IEncryptionService EncryptionService { get; }
}

public class MessageDataService : IMessageDataService
{
    [InjectService]
    public IMessageDataProvider MessageDataProvider { get; set; }

    public IEncryptionService EncryptionService { get; }

    public MessageDataService(IEncryptionService encryptionService)
    {
        this.EncryptionService = encryptionService;
    }

    public string Retrieve()
    {
        return MessageDataProvider.Get();
    }
}

public interface IEncryptionService
{
    public IEncryptionAlgorithm EncryptionAlgorithm { get; set; }
}

public class EncryptionService : IEncryptionService
{
    [InjectService]
    public IEncryptionAlgorithm EncryptionAlgorithm { get; set; }
}

public interface IEncryptionAlgorithm
{
}

public class EncryptionAlgorithm : IEncryptionAlgorithm
{
}

And create a service provider like you did in the test class:

var services = new ServiceCollection();
services.AddDotNurseInjector();
services.AddServicesFrom("DotNurse.Injector.Tests.Environment.NamespaceInjectService");

IServiceProvider serviceProvider = new DotNurseServiceProvider(services);

Now if I resolve an IEncryptionService directly from the service provider, it works as expected and the EncryptionAlgorithm property will be injected:

var encryptionService = serviceProvider.GetService<IEncryptionService>();
Assert.NotNull(encryptionService.EncryptionAlgorithm);

But if the IEncryptionService gets resolved as a dependency of an IMessageDataService, the EncryptionAlgorithm property will be null:

var service = serviceProvider.GetService<IMessageDataService>();
Assert.NotNull(service.EncryptionService.EncryptionAlgorithm);

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.