GithubHelp home page GithubHelp logo

khellang / scrutor Goto Github PK

View Code? Open in Web Editor NEW
3.5K 64.0 234.0 374 KB

Assembly scanning and decoration extensions for Microsoft.Extensions.DependencyInjection

License: MIT License

C# 98.84% PowerShell 1.16%
scanning decoration assembly-scanning decoration-extensions asp-net-core dependency-injection conventions convention-registration hacktoberfest scrutor

scrutor's Introduction

Scrutor Build status NuGet Package

Scrutor - I search or examine thoroughly; I probe, investigate or scrutinize
From scrūta, as the original sense of the verb was to search through trash. - https://en.wiktionary.org/wiki/scrutor

Assembly scanning and decoration extensions for Microsoft.Extensions.DependencyInjection

Installation

Install the Scrutor NuGet Package.

Package Manager Console

Install-Package Scrutor

.NET Core CLI

dotnet add package Scrutor

Usage

The library adds two extension methods to IServiceCollection:

  • Scan - This is the entry point to set up your assembly scanning.
  • Decorate - This method is used to decorate already registered services.

See Examples below for usage examples.

Examples

Scanning

var collection = new ServiceCollection();

collection.Scan(scan => scan
     // We start out with all types in the assembly of ITransientService
    .FromAssemblyOf<ITransientService>()
        // AddClasses starts out with all public, non-abstract types in this assembly.
        // These types are then filtered by the delegate passed to the method.
        // In this case, we filter out only the classes that are assignable to ITransientService.
        .AddClasses(classes => classes.AssignableTo<ITransientService>())
            // We then specify what type we want to register these classes as.
            // In this case, we want to register the types as all of its implemented interfaces.
            // So if a type implements 3 interfaces; A, B, C, we'd end up with three separate registrations.
            .AsImplementedInterfaces()
            // And lastly, we specify the lifetime of these registrations.
            .WithTransientLifetime()
        // Here we start again, with a new full set of classes from the assembly above.
        // This time, filtering out only the classes assignable to IScopedService.
        .AddClasses(classes => classes.AssignableTo<IScopedService>())
            // Now, we just want to register these types as a single interface, IScopedService.
            .As<IScopedService>()
            // And again, just specify the lifetime.
            .WithScopedLifetime()
        // Generic interfaces are also supported too, e.g. public interface IOpenGeneric<T> 
        .AddClasses(classes => classes.AssignableTo(typeof(IOpenGeneric<>)))
            .AsImplementedInterfaces()
        // And you scan generics with multiple type parameters too
        // e.g. public interface IQueryHandler<TQuery, TResult>
        .AddClasses(classes => classes.AssignableTo(typeof(IQueryHandler<,>)))
            .AsImplementedInterfaces());

Decoration

var collection = new ServiceCollection();

// First, add our service to the collection.
collection.AddSingleton<IDecoratedService, Decorated>();

// Then, decorate Decorated with the Decorator type.
collection.Decorate<IDecoratedService, Decorator>();

// Finally, decorate Decorator with the OtherDecorator type.
// As you can see, OtherDecorator requires a separate service, IService. We can get that from the provider argument.
collection.Decorate<IDecoratedService>((inner, provider) => new OtherDecorator(inner, provider.GetRequiredService<IService>()));

var serviceProvider = collection.BuildServiceProvider();

// When we resolve the IDecoratedService service, we'll get the following structure:
// OtherDecorator -> Decorator -> Decorated
var instance = serviceProvider.GetRequiredService<IDecoratedService>();

scrutor's People

Contributors

adamhathcock avatar andrewlock avatar azure-pipelines[bot] avatar coxp avatar danharltey avatar dependabot-support avatar dependabot[bot] avatar dgiddins avatar geirsagberg avatar josephwoodward avatar kharos avatar khellang avatar kotlik avatar kuraiandras avatar lawrencek76 avatar mattfrear avatar maxim-kornilov avatar messerm avatar micdenny avatar mxmissile avatar nicholasham avatar prettierci-commits avatar sfmskywalker avatar starts2000 avatar thoemmi avatar twenzel avatar waynebrantley avatar wesleypetrowski-iq 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  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

scrutor's Issues

Add support for forwarding implementation

Basically, to work around the limitations in this: aspnet/DependencyInjection#360.

As far as I can tell, there's no way to use Scrutor assembly scanning and also forward multiple interface types to a single concrete service? So for example, with Scrutor code:

public class Bar : IBar, IFoo
{
} 
services.Scan(scan =>
                scan.FromAssemblyOf<Bar>()
                    .AddClasses(c => c.AssignableTo<IBar>())
                    .AsImplementedInterfaces()
                    .AsSelf()
                    .WithSingletonLifetime());

var sp = services.BuildServiceProvider();
var i1 = sp.GetService<Bar>();
var i2 = sp.GetService<IBar>();
var i3 = sp.GetService<IFoo>();

Assert.Same(i1, i2); // Fails
Assert.Same(i2, i3); // Fails

With a "raw" implementation, you can work around this issue by forwarding the request to the implementation type. It's inefficient, but works:

services.AddSingleton<Bar();
services.AddSingleton<IFoo>(x=>x.GetService<Bar>());
services.AddSingleton<IBar>(x=>x.GetService<Bar>());

var sp = services.BuildServiceProvider();
var i1 = sp.GetService<Bar>();
var i2 = sp.GetService<IBar>();
var i3 = sp.GetService<IFoo>();

Assert.Same(i1, i2); // Passes
Assert.Same(i2, i3); // Passes

It would be nice to be able to do the same with Scrutor, e.g.

services.Scan(scan =>
                scan.FromAssemblyOf<Bar>()
                    .AddClasses(c => c.AssignableTo<IBar>())
                    .AsImplementedInterfaces(forwardToImplementationtype: bool) // optional parameter, or could be a new method
                    .AsSelf()
                    .WithSingletonLifetime());

I think it would be safe to add either as an overload to AsImplementedInterfaces, or as a new method entirely. And it's a real blocker I have at the moment 🙁

What do you think? I'm happy to send a PR for it if it sounds good to you?

'IServiceCollection' does not contain a definition for 'Scan'

In a .NET Core App I'm facing this error:

Error	CS1061	'IServiceCollection' does not contain a definition for 'Scan' and no extension method 'Scan' accepting a first argument of type 'IServiceCollection' could be found (are you missing a using directive or an assembly reference?)

The code of ConfigureServices looks like this:

services.Scan(scan => scan
    .FromAssemblies(typeof(MaterialCommandHandler).GetTypeInfo().Assembly)
    .AddClasses(classes => classes.Where(x => {
        var allInterfaces = x.GetInterfaces();
        return
            allInterfaces.Any(y => y.GetTypeInfo().IsGenericType && y.GetTypeInfo().GetGenericTypeDefinition() == typeof(IHandler<>)) ||
            allInterfaces.Any(y => y.GetTypeInfo().IsGenericType && y.GetTypeInfo().GetGenericTypeDefinition() == typeof(ICancellableHandler<>));
    }))
    .AsSelf()
    .WithTransientLifetime()
);

I'm using Scrutor 2.0.0.

image

`ImplementationTypeSelector` no longer appears to support chaining calls to `AddClasses`

In the docs, the following example shows how to scan for multiple types (comments removed):

collection.Scan(scan => scan
    .FromAssemblyOf<ITransientService>()
        .AddClasses(classes => classes.AssignableTo<ITransientService>())
            .AsImplementedInterfaces()
            .WithTransientLifetime()
        .AddClasses(classes => classes.AssignableTo<IScopedService>())
            .As<IScopedService>()
            .WithScopedLifetime());

However, I've found that after version 1.10 this usage appears to be broken and must instead be written as multiple statements such as:

collection.Scan(scan => scan
    .FromAssemblyOf<ITransientService>()
        .AddClasses(classes => classes.AssignableTo<ITransientService>())
            .AsImplementedInterfaces()
            .WithTransientLifetime());

collection.Scan(scan => scan
    .FromAssemblyOf<ITransientService>()
        .AddClasses(classes => classes.AssignableTo<IScopedService>())
            .As<IScopedService>()
            .WithScopedLifetime());

While debugging, I noticed that the number of types exposed by the IImplementationTypeFilter classes parameter is always however many types matched the prior filter when chaining. So in the example, the only IScopedService types that would be registered would be those which are also assignable to ITransientService.

I don't see a unit test that performs multiple calls to AddClasses, so I can see how this may have been missed.

.As(...) forgets the previous calls to .As*()

I've noticed that if I change the order of my As* calls, I get different results.

For example: the following code works as expected:

    private static ILifetimeSelector AsSelfAndMatchingInterfacesAndDeclaredServiceTypes(this IServiceTypeSelector selector)
    {
        return selector
            .As(t =>
            {
                // some types are selected here
            })
            .AsSelf()
            .AsMatchingInterface();
    }

while this code does NOT:

    private static ILifetimeSelector AsSelfAndMatchingInterfacesAndDeclaredServiceTypes(this IServiceTypeSelector selector)
    {
        return selector
            .AsSelf()
            .AsMatchingInterface()
            .As(t =>
            {
                // some types are selected here
            });
    }

Scrutor crashes ASP.NET 5 RC1 on startup

Hello,

I am using Scrutor to configure MediatR (https://github.com/jbogard/MediatR) on an ASP.NET 5 RC1 application. On Startup I have:

  services
    .Scan(x => x.FromAssembliesOf(typeof(IMediator), typeof(MessageGetQueryHandler))
    .AddClasses()
    .AsImplementedInterfaces());

The class MessageGetQueryHandler is just a handler in the project so I can target the assembly:

  public class MessageGetQueryHandler : IRequestHandler<MessageGetQuery, MessageGetModel> {
  }

When I run the application DNX just stops and exits. The only thing I can see is a few "Cannot find or open the PDB file." messages on the Output ...

I then tried the following:

  services
    .Scan(x => x.FromAssembliesOf(typeof(IMediator), typeof(Startup))
    .AddClasses()
    .AsImplementedInterfaces());

Same problem here ... Then I tried:

  services
    .Scan(x => x.FromAssembliesOf(typeof(IMediator))
    .AddClasses()
    .AsImplementedInterfaces());

Now it runs, I am able to inject IMediator but not the Handlers since I am not scanning the project assembly ...

Any idea what might be wrong?

Thank You,
Miguel

Documentation for Decoration?

Would you mind documenting a little bit how the decoration feature works? Can you maybe provide an example of a decoration and how to get the decorating service?

From #14 I can do a lot of guessing but I think it would be still nice to have some documentation or maybe another blog on your site (like you did here).

Issues with Xamarin.iOS

I've been working on a .NET Standard 2.0 library for a while now, an using Scrutor with FromApplicationDependencies() has been working fine in my unit tests.

However, when Scrutor is used in Xamarin.iOS both FromApplicationDependencies() and FromDependencyContext(DependencyContext.Load(Assembly.GetEntryAssembly())) throw an exception:

The type initializer for 'Microsoft.Extensions.DependencyModel.DependencyContextLoader' threw an exception.

...

System.NotImplementedException: The method or operation is not implemented.

...

  at System.AppContext.GetData (System.String name) [0x00000] in /Library/Frameworks/Xamarin.iOS.framework/Versions/10.12.0.18/src/mono/mcs/class/referencesource/mscorlib/system/AppContext/AppContext.cs:46 
  at Microsoft.Extensions.DependencyModel.DependencyContextPaths.GetCurrent () [0x00000] in <8241b70107294c1cb31f55eb9c49a4a8>:0 
  at Microsoft.Extensions.DependencyModel.DependencyContextPaths..cctor () [0x0000a] in <8241b70107294c1cb31f55eb9c49a4a8>:0 

I tried getting around this by doing the following:

var entryAssembly = Assembly.GetEntryAssembly();
var entryDependencyAssemblies = entryAssembly.GetReferencedAssemblies();
List<Assembly> scanAssemblies = new List<Assembly> { entryAssembly };
foreach (var assemblyName in entryDependencyAssemblies)
{
    var assembly = Assembly.Load(assemblyName);
    scanAssemblies.Add(assembly);
}

...

.FromAssemblies(scanAssemblies)

Unfortunately Scrutor is not finding any classes when I do this. Not sure if it matters, but I specify an array of base types that I scan for, like this:

.AddClasses(classes => classes.Where(c => scanTypes.Contains(c.BaseType)))

Note that the debugger is showing that scanAssemblies does contain the correct assemblies, and I can see the types I want under DefinedTypes.

Even a hacky workaround would be very much appreciated at this point!

Allow open generics to be included in AllClasses scan

At present, open generics are excluded from an <assembly>.AddClasses scan (via ReflectionExtensions.IsNonAbstractClass, which considers a concrete open generic to be abstract). The container supports registration of open generics, so it's a little puzzling why this limitation is in place. It would be lovely to be able to include open generic registration in a scan something like the following:

services.Scan(s => s
                .FromAssembliesOf(assemblyMarkerTypes)
                .AddClasses(new ClassOptions() { NonPublic = true, OpenGeneric = true })
                .AsImplementedInterfaces()
                .WithTransientLifetime());

Not RC1 compatible

Hi,

it seems that the lib is not 100% RC1 compatible.
I think it has the reason, that you create the solution with Beta 8 and I doesn't start a clean new solution with RC1.
The difference between Beta8 and RC1 solutions are the reference folders named different:
"DNX 4.5.1" -> ".NET Framework 4.5.1"
"DNX Core 5.0" -> ".NET Platform 5.4"

Error message after reference the nuget package in an clean RC1 project:
"The dependency Scrutor 1.1.0 in project MFT.Core does not support framework .NETPlatform,Version=v5.4."
"The dependency Scrutor 1.1.0 in project MFT.Core does not support framework .NETFramework,Version=v4.5.1."

Generic type decorators

Is it possible to register the following (assuming MyRepositoryA and MyRepositoryB already registered as implemented intefaces)?
services.AddSingleton<IRepositoryFactory<IMyRepositoryA>, RepositoryFactory<IMyRepositoryA>>(); services.AddSingleton<IRepositoryFactory<IMyRepositoryB>, RepositoryFactory<IMyRepositoryB>>();
etc.

  • and of course, thanks for the library!

AsImplementedInterfaces() registering too much

I understand that AsImplementedInterfaces() registers all implemented interfaces, however this is sometimes too much. Take for instance, the FluentValidation library, you may scan with the following:

.AddClasses(c => c.AssignableTo(typeof(IValidator<>))).AsImplementedInterfaces()

This will pickup all the AbstractValidator<T> implementations you have, but this class implements the following interfaces:

IValidator<T>
IEnumerable
IEnumerable<IValidationRule>
IValidator

Which means you get entries for all of these, not just the desired IValidator<T> and I need to do the following to get rid of the superfluous entries:

services.RemoveAll<IValidator>();
services.RemoveAll<IEnumerable>();
services.RemoveAll<IEnumerable<IValidationRule>>();

Is there any way to do this without having to remove the extra entries afterwards?

Error - "The non-generic method ...." when using AddTypes [2.0-rc2]

When I try to use the AddTypes as of 2.0rc2 I get the compilation error of below, when trying to add a type class as per your tests ?

The non-generic method 'ITypeSelector.AddTypes(params Type[])' cannot be used with type arguments

I cannot quite understand what the issue is as when I start to make your test code look more like my code it does not complain, but in my project it complains about this. Am I missing a dependency maybe?

When I copied over the TransientService as per your test into my project that worked OK.

When I put my service as the second parameter, and the test TransientService was first the Generic error disappeared.

I started adding more types I need and some combinations work others do no.

Maybe I am misunderstanding the implementation of this?

My target framework is

  • NETCoreApp1.1

image

image

Strongly name assembly

My project is using SignAssembly in its .csproj:

  <PropertyGroup>
    <VersionPrefix>4.0.0</VersionPrefix>
    <SignAssembly>True</SignAssembly>
    <AssemblyOriginatorKeyFile>key.snk</AssemblyOriginatorKeyFile>
    <DelaySign>False</DelaySign>
  </PropertyGroup>

When I compile, I get a warning

Referenced assembly 'Scrutor, Version=2.2.2.0, Culture=neutral, PublicKeyToken=null' does not have a strong name.

Could you sign the assembly in the NuGet package please?

Fix the tooling version

I will PR changing the version to the current LTS (long term support) version 1.0.0-preview2-003156 even if starting from 1.0.0-preview2-003121 the build works, but we should remain updated to the latest LTS if we want support.

Here's the link to download the SDK 1.0.0-preview2-003156 took from .NET Downloads page:
x64 / x86 .exe

Possible to register as matching interface OR as self?

Suppose I have the following services:

[Service]
public class FooService : IFooService
{
...
}

and

[Service]
public class BarService
{
...
}

Then:

serviceCollection.Scan(scan => scan.FromApplicationDependencies()
    .AddClasses(classes => classes.WithAttribute<ServiceAttribute>())
    .AsMatchingInterface()
    .AsSelf()
    .WithSingletonLifetime()
);

registers FooService two times: as IFooService and as FooService.

Is it possible to have a selector that tries to register it as the matching interface, and only if it can't find one, registers it as self?

So in the example above FooService will be registered only as IFooService and BarService will be registered as BarService.

Decorator duplicates lifestyle of decorated instance

The Decorate method creates a decorator that duplicates the lifestyle of the decorated instance. This means that if a service is a Singleton, all its decorators will be Singleton as well. There is currently no way to influence that behavior.

It's quite common to have the need to register decorators with a lifestyle lower than their decoratees. For instance, even though the decoratee might be a singleton, its decorator might require a dependency on a scoped service (a DbContext for instance). This requires the decorator to have a lifestyle of scoped or transient.

On top of that, in case there are multiple services that apply to a decorator (when multiple registrations for the same service type exist), and those services have different lifestyles, so will the applied decorators. In other words, a registration for a certain decorator could cause different decorator instances to exist with different lifestyles. This could lead to confusion.

Appart from this, the chosen behavior is very different from what (most) DI Containers do. This could lead to confusion as well and perhaps problems when migrating from Scrutor to a custom DI Container or vice versa.

Is there a particular reason for this behavior?

AsSelfWithInterfaces fails with open-generic interfaces

Assume you have one generic interface :

public interface IDomainEventHandler { }

public interface IDomainEventHandler<TEvent>
    : IDomainEventHandler where TEvent : DomainEvent
{
    void Handle(TEvent e);
}

With some non generic implementations :

public class SomeEventHandler:
    IDomainEventHandler<EventA>,
    IDomainEventHandler<EventB>
{
    public void Handle(EventA e) { }

    public void Handle(EventB e) { }
}

and some generic ones :

public class CatchAllDomainEventsHandler<TEvent> :
    IDomainEventHandler<TEvent> where TEvent : DomainEvent
{
    public void Handle(TEvent e) { }
}

and you only want one instance of each implementation

services.Scan(scan => scan
    .FromApplicationDependencies()
    .AddClasses(classes => classes.AssignableTo<IDomainEventHandler>())
    .AsSelfWithInterfaces()
    .WithScopedLifetime());

Building container fails with Open generic service type 'IDomainEventHandler`1[TEvent]` requires registering an open generic implementation type..

I think that AsSelfWithInterfaces() should handle that case and throw during registration or only register closed type implementations.

Open-generic registrations can not be decorated

Let me start by saying that I am quite amazed to see what you've been able to build on top of the DI abstraction. The omission of being able to apply decorators (and batch-registration) makes the built-in container IMO a worthless tool for any reasonably sized code base.

That said, I stumbled upon a limitation of your current implementation. Although you're able to apply open-generic decorators to closed-generic registrations, it seems impossible to apply open-generic decorators to open-generic registrations.

This might very well be a limitation of the API you are building upon, but I'll let you be the judge of that.

Here's a failing unit test:

[Fact]
public void CanDecorateOpenGenericTypeBasedOnOpenGenericRegistration()
{
    var provider = ConfigureProvider(services =>
    {
        services.AddTransient(typeof(IQueryHandler<,>), typeof(QueryHandler<,>));
        services.Decorate(typeof(IQueryHandler<,>), typeof(LoggingQueryHandler<,>));
        services.Decorate(typeof(IQueryHandler<,>), typeof(TelemetryQueryHandler<,>));
    });

    var instance = provider.GetRequiredService<IQueryHandler<MyQuery, MyResult>>();

    var telemetryDecorator = Assert.IsType<TelemetryQueryHandler<MyQuery, MyResult>>(instance);
    var loggingDecorator = Assert.IsType<LoggingQueryHandler<MyQuery, MyResult>>(
        telemetryDecorator.Inner);
    Assert.IsType<MyQueryHandler>(loggingDecorator.Inner);
}

NU3008 package integrity check failed

Hello,

I got this error with one guy's laptop on my team, after he try to restore or install again the nuget package for scrutor 3.0.1, he got this error :

package integrity check failed.

Description of the nuget error is available here :
https://docs.microsoft.com/en-us/nuget/reference/errors-and-warnings/nu3008

I don't reproduce the problem on my machine or another colleague laptop. Really strange... We all use VS 2017 with the lastest service package 15.7.1.

Actions we already try to solve the problem :

  • Clean nuget cache on nuget package option in visual studio & by running nuget locals -Clear all
  • Clean .vs, bin, obj folders & try to rebuild again.

The problem is only for the v3.0.1, for v2.2.2 that's work.

Could you check on you side if change hasn't be made on nuget spec.

Thanks for helping.

Kind regards,

Dung Tri.

AsMatchingInterface not working

Steps to reproduce

namespace App
{
    internal interface ICommand { }

    internal class ExtractBundleCommand : ICommand { }

    internal class GetPropertiesCommand : ICommand { }

    class Program
    {
        static void Main(string[] args)
        {
            var collection = new ServiceCollection();

            // Scan assembly for commands
            collection.Scan(scan => scan.FromAssemblyOf<Program>()
                                        .AddClasses(classes => classes.AssignableTo<ICommand>(), false)
                                        .AsMatchingInterface()
                                        .WithSingletonLifetime());
        }
    }
}

Expected behavior

collection.Count == 2

Actual behavior

collection.Count == 0

Calling .AsImplementedInterfaces() can result is duplicate service descriptors getting registered

If a class implements multiple interfaces and the following pseudo-code is executed multiple times for each of the different interface types that class implements, the same service descriptor will be registered multiple times.

services.Scan( x => { x.FromAssemblies([ASSEMBLY_COLLECTION]) .AddClasses(classes => classes.AssignableTo([INTERFACE_TYPE])) .AsImplementedInterfaces() .WithTransientLifetime(); });

Breaking change - Open generic types

Hello,

I have a problem with the changes due to the commit f2bd7d7. In my assembly, I have a class like this :

public class OpenGeneric<T> : IOpenGeneric<T> { }
public interface IOpenGeneric<T> : IOtherInheritance { }
public interface IOtherInheritance { }

When I do .AsImplementedInterfaces(), I now obtain an error (this class was excluded before the changes) : 'Cannot instantiate implementation type 'Scrutor.Tests.OpenGeneric1[T]' for service type 'Scrutor.Tests.IOtherInheritance'.'

So, perhaps it would be nice to have an option to disable autoregistration for open generic types. Otherwise (better option), the IOtherInheritance interface should be excluded from the registration.

Decorator - Could not find any registered services for type

I'm creating a console application to make some tests using Scrutor to scan and decorate some command and event handlers. I'm facing a problem that, when I use the method Decorate it fails because it can't find any registered service for the type.

The example code is the following (also based on your tests):

    public class Program
    {
        private readonly ILogger<Program> _logger;
        private readonly IMediator _mediator;

        public Program(ILogger<Program> logger, IMediator mediator)
        {
            _logger = logger;
            _mediator = mediator;
        }

        public static void Main(string[] args)
        {
            try
            {
                var services = new ServiceCollection()
                    .AddLogging()
                    /*
                    .Scan(s => s.FromAssemblyOf<Program>()
                        .AddClasses(classes => classes.AssignableTo(typeof(ICommandHandler<>)))
                        .AsImplementedInterfaces()
                        .WithScopedLifetime()
                        .AddClasses(classes => classes.AssignableTo(typeof(ICommandHandler<,>)))
                        .AsImplementedInterfaces()
                        .WithScopedLifetime()
                        .AddClasses(classes => classes.AssignableTo(typeof(IEventHandler<>)))
                        .AsImplementedInterfaces()
                        .WithScopedLifetime()
                        .AddClasses(classes => classes.AssignableTo<IUnitOfWork>())
                        .AsImplementedInterfaces()
                        .WithScopedLifetime())
                    //*/
                    .AddScoped<ICommandHandler<Example01Command, bool>, ExampleCommandHandler>()
                    .AddScoped<ICommandHandler<Example02Command>, ExampleCommandHandler>()
                    .AddScoped<ICommandHandler<Example03Command>, ExampleCommandHandler>()
                    .AddScoped<IEventHandler<ExampleEvent>, ExampleEventHandler>()
                    .AddScoped<IUnitOfWork, UnitOfWork>()

                    .AddSingleton<IMediator, Mediator>()
                    .AddSingleton<IHandlerFactory>(k => new DelegateHandlerFactory(k.GetService, k.GetServices))
                    .AddSingleton<Program>();

                //  EXCEPTION fails in this line, either registration by scan or manual
                services
                    .Decorate(typeof(ICommandHandler<>), typeof(AuditCommandHandler<>))
                    .Decorate(typeof(ICommandHandler<,>), typeof(AuditCommandHandler<,>))
                    .Decorate(typeof(IEventHandler<>), typeof(AuditEventHandler<>));

                var builder = new ContainerBuilder();
                builder.Populate(services);
                var serviceProvider = new AutofacServiceProvider(builder.Build());

                serviceProvider.GetRequiredService<ILoggerFactory>()
                    .AddConsole(LogLevel.Trace, true);

                serviceProvider.GetRequiredService<Program>()
                    .RunAsync(args, CancellationToken.None).ConfigureAwait(false).GetAwaiter().GetResult();
            }
            catch (Exception e)
            {
                Console.WriteLine("Global exception has been thrown");
                Console.WriteLine(e);
            }
            finally
            {
                Console.ReadLine();
            }
        }

        public async Task RunAsync(string[] args, CancellationToken ct)
        {
            _logger.LogInformation("Application started...");
            try
            {
                var result = await _mediator.PublishAsync<Example01Command, bool>(new Example01Command(), ct);

                _logger.LogDebug("Credential Login returned {credentialLoginResult}", result);
            }
            catch (Exception e)
            {
                _logger.LogCritical(0, e, "Global exception has been thrown");
            }
            finally
            {
                _logger.LogInformation("Application terminated. Press <enter> to exit...");
            }
        }
    }

The interfaces are as following (it's an internal library used by the company):

public interface IEventHandler<in TEvent> where TEvent : IEvent
{
    // ...
}

public interface ICommandHandler<in TCommand> where TCommand : ICommand
{
    // ...
}

public interface ICommandHandler<in TCommand, TResult> where TCommand : ICommand<TResult>
{
    // ...
}

Suggestion: Missing methods for decorate

It would be great if these methods had been added for Decorator
DecorateTransient<>();
DecorateScoped<>();
DecorateSingleton<>();

Currently, both Decorator and the class which is decorating has been registered by the same LifeTime.

Assume that "MyService" class is registered as Singleton and we need to register "MyServiceDecorator" as Scoped. but currently, it's not possible.

I can make the changes and create a pull request if you want

Support for open generic decorators

The following throws an exception (Could not find any registered services for type 'DiTest.IPrinter`2'.):

  private static void ConfigureServices(ServiceCollection services)
        {
          services.Scan(
                scan => scan.FromAssemblies(typeof(Program).GetTypeInfo().Assembly)
                    .AddClasses(x => x.AssignableTo(typeof(IPrinter<,>)))
                    .AsImplementedInterfaces()
                    .WithSingletonLifetime()
            );
            services.Decorate(typeof(IPrinter<,>), typeof(TextColorDecorator<,>));
            services.Decorate(typeof(IPrinter<,>), typeof(BackgroundColorDecorator<,>));
}

These classes where used:

   public interface IPrinter<T1, T2>
    {
        void Print(T1 arg1, T2 arg2);
    }
    public class Printer : IPrinter<string, string>
    {
        public void Print(string arg1, string arg2)
        {
            Console.WriteLine("Hi {0}, I am decoratee! {1}", arg1, arg2);
        }
    }
    public class TextColorDecorator<T1, T2> : IPrinter<T1, T2>
    {
        private readonly IPrinter<T1, T2> decoratee;

        public TextColorDecorator(IPrinter<T1, T2> decoratee)
        {
            this.decoratee = decoratee;
        }

        public void Print(T1 arg1, T2 arg2)
        {
            var originalColor = Console.ForegroundColor;
            Console.ForegroundColor = ConsoleColor.Red;
            this.decoratee.Print(arg1, arg2);
            Console.ForegroundColor = originalColor;
        }
    }
    public class BackgroundColorDecorator<T1, T2> : IPrinter<T1, T2>
    {
        private readonly IPrinter<T1, T2> decoratee;

        public BackgroundColorDecorator(IPrinter<T1, T2> decoratee)
        {
            this.decoratee = decoratee;
        }

        public void Print(T1 arg1, T2 arg2)
        {
            var originalColor = Console.BackgroundColor;
            Console.BackgroundColor = ConsoleColor.White;
            this.decoratee.Print(arg1, arg2);
            Console.BackgroundColor = originalColor;
        }
    }

A true singleton

I know that the docs say that:

In this case, we want to register the types as all of its implemented interfaces.
So if a type implements 3 interfaces; A, B, C, we'd end up with three separate registrations.

But is it possible to register all of the interfaces so that they resolve to the same singleton, ie. concrete type? Here's an example:

public interface IOne { }
public interface ITwo { }
public interface ISingletonMarker { }

public class TrueSingleton : IOne, ITwo, ISingletonMarker { }

public class Program
{
    public static void Main(string[] args)
    {
        var sp = new ServiceCollection()
            .Scan(scan => scan.FromEntryAssembly()
                .AddClasses(classes => classes.AssignableTo<ISingletonMarker>())
                    // Fixme: this is not correct:
                    .AsImplementedInterfaces()
                    .WithSingletonLifetime())
                .BuildServiceProvider();

        var one = sp.GetRequiredService<IOne>();
        var two = sp.GetRequiredService<ITwo>();

        Assert.Same(one, two);
    }
}

Decorate extension much slower than lambda decorator

Out of curiosity, I did some performance tests of Decorate method. Here are tests classes

public interface IRepository
{
    int Save();
}

public class Repository : IRepository
{
    public int Save()
    {
        return 1;
    }
}

public class CachedRepository : IRepository
{
    private readonly IRepository repository;

    public CachedRepository(IRepository repository)
    {
        this.repository = repository;
    }

    public int Save()
    {
        return 1;
    }
}

public class CachedRepositoryWithImpl : IRepository
{
    private readonly Repository repository;

    public CachedRepositoryWithImpl(Repository repository)
    {
        this.repository = repository;
    }

    public int Save()
    {
        return 1;
    }
}

and benchmark class (using BenchmarkDotNet)

public class DecorateTests
{
    private const int N = 1000000;

    private readonly IServiceProvider manualProvider;
    private readonly IServiceProvider decorateProvider;
    private readonly IServiceProvider lambdaProvider;

    public DecorateTests()
    {
        var manualCollection = new ServiceCollection();
        manualCollection.AddTransient<Repository>();
        manualCollection.AddTransient<IRepository, CachedRepositoryWithImpl>();
        manualProvider = manualCollection.BuildServiceProvider();
        
        var decorateCollection = new ServiceCollection();
        decorateCollection.AddTransient<IRepository, Repository>();
        decorateCollection.Decorate<IRepository, CachedRepository>();
        decorateProvider = decorateCollection.BuildServiceProvider();
        
        var lambdaCollection = new ServiceCollection();
        lambdaCollection.AddTransient<Repository>();
        lambdaCollection.AddTransient<IRepository>(provider => new CachedRepository(provider.GetService<Repository>()));
        lambdaProvider = lambdaCollection.BuildServiceProvider();
    }

    [Benchmark]
    public void Decorate()
    {
        var instance = decorateProvider.GetService<IRepository>();
    }

    [Benchmark]
    public void LambdaDecorator()
    {
        var instance = lambdaProvider.GetService<IRepository>();
    }

    [Benchmark]
    public void ManualDecorator()
    {
        var instance = manualProvider.GetService<IRepository>();
    }
}

The results as follows

          Method |        Mean |     Error |    StdDev |
---------------- |------------:|----------:|----------:|
        Decorate | 2,276.75 ns | 95.751 ns | 282.32 ns |
 LambdaDecorator |   185.99 ns |  7.498 ns |  22.11 ns |
 ManualDecorator |    73.60 ns |  3.427 ns |  10.11 ns |

Decorate performs more than 10x worse than lambda decorator.
What's the reason for that?

Scrutor 1.10.1 fails with MediatR

I had an NET Core application working fine with Scrutor 1.10.0 but when I updated to 1.10.1 I get the following error:

InvalidOperationException: Handler was not found for request of type APIModel+Query.
Container or service locator not configured properly or handlers not registered with your container.
MediatR.Internal.RequestHandlerImpl.GetHandler(TRequest request, CancellationToken cancellationToken, SingleInstanceFactory factory)

I am using MediatR in this project and I have the following configuration:

  services.AddScoped<SingleInstanceFactory>(x => y => x.GetService(y));
  services.AddScoped<MultiInstanceFactory>(x => y => x.GetServices(y));

  services
    .Scan(x =>
      x.FromAssembliesOf(typeof(IMediator), typeof(Startup))
        .AddClasses(y => y.AssignableTo(typeof(IMediator))).AsImplementedInterfaces().WithScopedLifetime()            
        .AddClasses(y => y.AssignableTo(typeof(IRequestHandler<,>))).AsImplementedInterfaces().WithScopedLifetime()            
        .AddClasses(y => y.AssignableTo(typeof(IAsyncRequestHandler<,>))).AsImplementedInterfaces().WithScopedLifetime()
        .AddClasses(y => y.AssignableTo(typeof(INotificationHandler<>))).AsImplementedInterfaces().WithScopedLifetime()
        .AddClasses(y => y.AssignableTo(typeof(IAsyncNotificationHandler<>))).AsImplementedInterfaces().WithScopedLifetime());

What am I missing?

New scan method FromAssemblyInThisApplication

Similar to the one exposed by Castle Windsor.

Castle.MicroKernel.Registration.Classes.FromAssemblyInThisApplication

/// <summary>Scans current assembly and all refernced assemblies with the same first part of the name.</summary>
/// <returns> </returns>
/// <remarks>
///     Assemblies are considered to belong to the same application based on the first part of the name. For example if the method is called from within <c>MyApp.exe</c> and <c>MyApp.exe</c> references
///     <c>MyApp.SuperFeatures.dll</c>, <c>mscorlib.dll</c> and <c>ThirdPartyCompany.UberControls.dll</c> the <c>MyApp.exe</c> and <c>MyApp.SuperFeatures.dll</c> will be scanned for components, and other
///     assemblies will be ignored.
/// </remarks>
[MethodImpl(MethodImplOptions.NoInlining)]
public static FromAssemblyDescriptor FromAssemblyInThisApplication()
{
	var assemblies = new HashSet<Assembly>(ReflectionUtil.GetApplicationAssemblies(Assembly.GetCallingAssembly()));
	return new FromAssemblyDescriptor(assemblies, Filter);
}

This is the extension I implemented over Scrutor, you can integrate it in the project if you think it worth (based on castle source code!):

public static class ScrutorAssemblySelectorExtensions
{
    public static IImplementationTypeSelector FromAssemblyInThisApplication(this IAssemblySelector assemblySelector)
    {
        var assemblies = ReflectionUtils.GetApplicationAssemblies(Assembly.GetCallingAssembly());
        var typeSelector = assemblySelector.FromAssemblies(assemblies);
        return typeSelector;
    }
}

public static class ReflectionUtils
{
    public static IEnumerable<Assembly> GetApplicationAssemblies(Assembly rootAssembly)
    {
        var index = rootAssembly.FullName.IndexOfAny(new[] { '.', ',' });
        if (index < 0)
        {
            throw new ArgumentException(
                string.Format("Could not determine application name for assembly \"{0}\". Please use a different method for obtaining assemblies.",
                                rootAssembly.FullName));
        }

        var applicationName = rootAssembly.FullName.Substring(0, index);
        var assemblies = new HashSet<Assembly>();
        AddApplicationAssemblies(rootAssembly, assemblies, applicationName);
        return assemblies;
    }

    private static void AddApplicationAssemblies(Assembly assembly, HashSet<Assembly> assemblies, string applicationName)
    {
        if (assemblies.Add(assembly) == false)
        {
            return;
        }
        foreach (var referencedAssembly in assembly.GetReferencedAssemblies())
        {
            if (IsApplicationAssembly(applicationName, referencedAssembly.FullName))
            {
                AddApplicationAssemblies(LoadAssembly(referencedAssembly), assemblies, applicationName);
            }
        }
    }

    private static bool IsApplicationAssembly(string applicationName, string assemblyName)
    {
        return assemblyName.StartsWith(applicationName);
    }

    private static Assembly LoadAssembly(AssemblyName assemblyName)
    {
        return Assembly.Load(assemblyName);
    }
}

Scan generics?

I must be doing something dumb... is scanning of generics supported?

I have

public interface IQueryHandler<T> where T : IQuery
{
}

// I have also tried this
// public interface IQueryHandler<IQuery>
// {
// }

public class AbstractQuery : IQuery
{
... some common properties
}

public class QueryOne : AbstractQuery
{}

public class QueryTwo : AbstractQuery
{}

// in Startup.cs
            services.Scan(scan => scan
                .FromAssemblyOf<IQueryHandler<IQuery>>()
                .AddClasses(classes => classes.AssignableTo<IQueryHandler<IQuery>>())
                .AsImplementedInterfaces()
            );

// later
var queryHandler = serviceProvider.GetService<IQueryHandler<QueryOne>>(); // returns null
var queryHandlers = serviceProvider.GetServices<IQueryHandler<IQuery>>(); // returns empty collection

I've just figured out a workaround while I was typing this, that is to use:

.AddClasses(classes => classes.InExactNamespaceOf<IQueryHandler<IQuery>>())

But it would be nice to understand why my first example wasn't working, and if this is a known issue?

Auto add "Default" interface implementation

Hi,

I use Scrutor every day and I love it.
Until now I used it in that style:

services.Scan(scan => scan
    .FromAssemblies(assemblies)
    .AddClasses(classes => classes.AssignableToAny(typeof(IHandleCommand<>), typeof(IHandleQuery<,>)))
    .AsImplementedInterfaces()
    .WithScopedLifetime());

And it worked like charme.
But now I want to add a new scenario, maybe I overlooked it, but I think it's currently not supported?!
I think I saw what I want to do in some other DI container, maybe Ninject, but I'm not sure.

Let's assume we have the following interfaces and classes:

public interface ITest1 : ICommandApi {}
public interface ITest2 : ICommandApi {}
public interface ITest3 : ICommandApi {}

public class Test1 : ITest1 {}
public class Test2 : ITest2 {}
public class Test3 : ITest3 {}

We all know what we would do:

services.AddScoped<ITest1, Test1>();
services.AddScoped<ITest2, Test2>();
services.AddScoped<ITest3, Test3>();

Issue is, let's say we have 100 of this classes and every time you add new one you have not to forget to add it to the ServiceCollection.
In my scenario, I can ensure that there is always just one class that inherits the corresponding interface.

I bet you can already imagine what I want to do :)

Because the interfaces can be distributed all over the place and I don't know where (Because it's an framework used by multiple applications) I had to mark the interfaces somehow, that's the only purpose of ICommandApi.

I want now with scrutor do something like:

services.Scan(scan => scan
    .FromAssemblies(assemblies)
    .AddClasses(classes => classes.ToDefaultImplementation().Where(interface => interface.InheritFrom<ICommandApi>()))
    .AsImplementedInterfaces()
    .WithScopedLifetime());

What does nothing else in the background than this:

services.AddScoped<ITest1, Test1>();
services.AddScoped<ITest2, Test2>();
services.AddScoped<ITest3, Test3>();

Do you think it's possible?

Kind regards

Decorator with Open Generics and Dependencies

Hi. Read through the docs and looked through issues, but still having a syntax problem. Say I a decorator that relies on an additional dependency:

interface IHandler<T> {}

class DecoratedHandler<T> : IHandler<T>
    public DecoratedHandler<T>(IHandler<T>, ISomeOtherService) 

ISomeOtherService is registered in the ServiceCollection, and I register concrete types of IHandler.

services.AddSingleton<IHandler<int>, MyIntHandler>();

I'd like to be able to decorate all IHandler instances. I've tried

services.Decorate(typeof(IHandler<>), typeof(DecoratedHandler<>))

But this is unable to resolve ISomeOtherService. In the docs, it recommends using the service provider to manually create the decorated instance. This is fine, but now I've lost the open generic.

services.Decorate(typeof(IMessageHandler<>), (inner, provider) => new DecoratedHandler<what goes here?>(inner, provider.GetRequiredService<ISomeOtherService>)

Do I need to cast inner? Maybe I'm just unfamiliar with generic syntax in CSharp, but any help would be appreciated.

Support co- and contra-variance

Basically support this:

aspnet/DependencyInjection#453

But do it during registration/scanning-time. StructureMap does this when it scans, and I had to add this manually to MediatR's MS DI extensions. See this code I borrowed from StructureMap:

https://github.com/jbogard/MediatR.Extensions.Microsoft.DependencyInjection/blob/master/src/MediatR.Extensions.Microsoft.DependencyInjection/ServiceCollectionExtensions.cs#L55

This code is pretty complicated inside of StructureMap, so I'd suggest pinging @jeremydmiller for tips on what does what there.

Support for optional open-generic decorators

I thing it would be pretty nice to add .Decorate method overload that accepts Func<Type, bool> where Type is decorated type. If that function returns true than decorator will be applied, otherwise it should skip decorating for specific type. It could be helpful for cases where some decorated types has specific attributes, and if they (attributes on decorated type) exists, then decorator should be applied, otherwise - they should called "as is".
Thanks.
P.S. - sorry for my English)

Is there the ability to detect already registered classes/interfaces and not override them?

Thank you for an incredible library, really appreciate it!

We have a situation where we're creating a bootstrapping library internally to handle most of our setup for a core web api library so that we can quickly spin up web api projects without having to do a lot of configuration/setup.

The bootstrapping library contains this library and runs a call to register classes from the web api library as such:

services.Scan(scan => scan
	.FromAssemblies(assemblies)
	.AddClasses()
	.AsImplementedInterfaces()
	.WithTransientLifetime());

This is called in the Startup.cs class as a static call:

public void ConfigureServices(IServiceCollection services)
{
  // Internal        

  Bootstrapping.Configure(services);

  //add additional services
}

The problem here is if a developer puts their additional services first, the above code will override that service.

Is there a way to detect if a service has been loaded and skip that registration?
Thanks!

Singleton resolved from different scopes are not the same

I have some test methods where I am trying to test for captive dependencies by resolving from different scopes and then asserting for sameness. Here is an example:

        [TestMethod]
        public void SingletonDependenciesAreSameForAllScopes()
        {
            using (IHost host = Setup.TestHostBuilder().Build())
            {
                var claimsDecorator = host.Services.GetRequiredService<IClaimsDecorator>();
                Assert.IsNotNull(claimsDecorator);

                using (var scope = host.Services.CreateScope())
                {
                    Assert.AreSame(claimsDecorator, scope.ServiceProvider.GetRequiredService<IClaimsDecorator>()); 
                    // ^- Fails with Scrutor registration but not with standard registration
                }
            }
        }

Setup.TestHostBuilder() is just a testing convenience method used to spin up a host with a test configuration and test registrations (ConfigureAppConfiguration and ConfigureServices).

The following Scrutor registration fails the above test:

services.Scan(
   scan =>
   {
                scan.FromAssemblies(bootstrap.AssembliesToScan)
                .AddClasses(classes => classes.AssignableTo<IClaimsDecorator>())
                    .As<IClaimsDecorator>().AsSelf().WithSingletonLifetime()
   }
);

If I comment out the scrutor registration and use the standard registration below, the assertion works as expected:

services.AddSingleton<IClaimsDecorator, InfrastructureClaimsDecorator>();

Is this a bug or is something else going on here that I am not aware of?

Sugestion: TryDecorate or Custom Exception when decorate doesn't find the service type

Currently, when using decorators, if no ServiceDescriptor is found for the type to be decorated, an InvalidOperationException is thrown.

That is, at least for me, a good behavior but because the exception is very broad (let's imagine it can be thrown by Scrutor or some other code) there is no way to be sure the exception was due to a missing service type or any other problem. This can be problematic when using a plugin approach, when you may want scan for all libraries in a given directory and to decorate some types, but those types may or may not exist.

My suggestion is to implement a custom exception (ex: ServiceDescriptorToDecorateNotFoundException that may even have a property with the type not found) or to add a TryDecorate method that returns false if none is found.

Decorating open generics with multiple levels of interface inheritance

Using Scrutor 3.0.2, with a minimal repro over at: https://github.com/wpetrowski/DecorateIssue

Possibly related to #68, as the error is the same, but the setup in my case is a bit different.


I've got some registrations for an IQueryHandler<T> interface that I'm trying to decorate. Most of the implementations directly implement the interface:

public class GetIntQueryHandler : IQueryHandler<int> {}
public class GetDoubleQueryHandler : IQueryHandler<double> {}

Decorating those kinds of classes works fine:

services.Scan(scan => scan
    .FromApplicationDependencies()
        .AddClasses(classes => classes.AssignableTo(typeof(IQueryHandler<>)).Where(type => type != typeof(Decorator<>)))
            .AsImplementedInterfaces());

services.Decorate(typeof(IQueryHandler<>), typeof(Decorator<>));

However there are a few query handlers that have some deeper levels of inheritance:

public interface IMySpecialHandler : IQueryHandler<string> {}
public class MySpecialHandler : IMySpecialHandler {}

When I add in these classes, I get the following error on startup:

System.ArgumentException
  Message=The number of generic arguments provided doesn't equal the arity of the generic type definition.
  StackTrace:
   at System.RuntimeType.MakeGenericType(Type[] instantiation)
   at Microsoft.Extensions.DependencyInjection.ServiceCollectionExtensions.<>c__DisplayClass13_0.<TryDecorateOpenGeneric>g__TryDecorate|0(Type[] typeArguments)
   at System.Linq.Enumerable.Aggregate[TSource,TAccumulate](IEnumerable`1 source, TAccumulate seed, Func`3 func)
   at Microsoft.Extensions.DependencyInjection.ServiceCollectionExtensions.DecorateOpenGeneric(IServiceCollection services, Type serviceType, Type decoratorType)
   at DecorationIssue.Startup.ConfigureServices(IServiceCollection services)

It seems to be related to the way it's filtering the descriptors:

var arguments = services
.Where(descriptor => descriptor.ServiceType.IsAssignableTo(serviceType))
.Select(descriptor => descriptor.ServiceType.GenericTypeArguments)
.ToArray();

Because I'm using AsImplementedInterfaces, there's a descriptor for IMySpecialHandler implemented by MySpecialHandler. And because IMySpecialHandler is assignable to IQueryHandler<>, it gets picked up by the code above. However IMySpecialHandler doesn't have any GenericTypeArguments which causes the exception in the local TryDecorate function just above, when it tries to call MakeGenericType and passes in 0 arguments.

So... after some fiddling, the following argument collection code works for my case:

var arguments = services
    .Where(descriptor => descriptor.ServiceType.IsGenericType && descriptor.ServiceType.GetGenericTypeDefinition() == serviceType.GetGenericTypeDefinition())
    .Select(descriptor => descriptor.ServiceType.GenericTypeArguments)
    .ToArray();

I couldn't say whether this is a safe change to make in all cases though, or the proper way to fix it. I could also fix it by being more careful about which things I register, but... AsImplementedInterfaces is so convenient. 😄

How to scan and decorate an open generic interface used multiple times?

Hello!

Let me start of by asking if my issue is possibly related to #39 , cuz I feel like it might be.

I discovered your project and I really like the possibilities it offers. I am having a tough time trying to get a functionality to work and I'd love to know if I am able to do this or not. (If I can, I'd like to know how)

My project uses commands and queries, and those commands are decorated. My controller has instances of these command/query handlers. When executing the commands, I'd like the decorators to be executed first, in order, until the actual commandhandler is executed.

A little overview of what I would like to accomplish:

public interface ICommandHandler<in TCommand> where TCommand : Command
{
    Task HandleAsync(TCommand command);
}

public class Command { }

public class ExampleCommand : Command
{
    public int Id {get; set; }
}

//Actual handler:
public class ExampleCommandHandler : ICommandHandler<ExampleCommand>
{
    // It has a repo to retrieve data from, this would be injected in the constructor

    public async Task HandleAsync(ExampleCommand command)
    {
         // Do stuff with the repo...
    } 
}

//First decorator
public class Decorator1CommandHandler : ICommandHandler<ExampleCommand> 
{
      private ICommandHandler<ExampleCommand> _innerHandler;
      private IFunctionality<ExampleCommand> _functionality;

      public Decorator1Commandhandler(...)
      {
           // Set properties
      } 

      public Task HandleAsync(ExampleCommand command)
      {
           // Calls the functionality that this decorator is built for. This ALSO has some more dependency injection in it's constructor...
           await _functionality.DoStuff(command);
 
           // Calls the 2nd decorator
           await _innerHandler.HandleAsync(command);           
      }
}

// 2nd decorator
public class Decorator2CommandHandler : ICommandHandler<ExampleCommand> 
{
      private ICommandHandler<ExampleCommand> _innerHandler;
      private IOtherFunctionality<ExampleCommand> _functionality;

      public Decorator2Commandhandler(...)
      {
           // Set properties
      } 

      public Task HandleAsync(ExampleCommand command)
      {
           // Calls the functionality that this decorator is built for. This ALSO has some more dependency injection in it's constructor...
           await _functionality.DoStuff(command);
 
           // Calls the ACTUAL command handler defined above.
           await _innerHandler.HandleAsync(command);           
      }     
}


// Usage of the command stuff
public class ValuesController : BaseController
{
     private readonly ICommandHandler<ExampleCommand> _commandHandler;;
     
     //Imagine this being an endpoint, and the commandhandler being injected in the constructor
     public async Task<string> Endpoint()
     {
          return await _commandHandler.HandleAsync(new ExampleCommand{ Id = 1});
     } 
}

**Startup**:

//This makes sure that every command handler is inserted in the DI container
services.Scan(scan => scan
    .FromAssembliesOf(typeof(IQueryHandler<,>))
        .AddClasses(classes => classes.AssignableTo(typeof(ICommandHandler<,>)))
             .AsImplementedInterfaces()
             .WithTransientLifetime());

//This makes sure that every command handler has its own 2 decorators registered.
services.Decorate(typeof(ICommandHandler<>), typeof(Decorator1CommandHandler<>));
services.Decorate(typeof(ICommandHandler<>), typeof(Decorator2CommandHandler<>));

//Maybe I should decorate the decorate instead of decorating the commandhandler for a 2nd time?

Each command would have the following:

  • (NAME)Command
  • (NAME)CommandHandler
  • (NAME)CommandSecurityValidator (Decorator)
  • (NAME)CommandValidator (Decorator)

Sadly, I can't get this to work! At the moment of writing I can not tell you the exact errors I am getting, but I think that my scenario isn't possible.

If it is, please tell me what I am doing wrong!
If it is not possible, what would be a good container to get this to work anyway? I have some experience with autofac.

MissingTypeRegistrationException is thrown when decorating open generic registered types

Suppose I have a very simple generic interface as below:

interface IGeneric<T>
{
    void DoSomething(T value);
}

I then add a generic implementation that is then registered using open generics syntax:

class Generic<T> : IGeneric<T>
{
    public void DoSomething(T value)
    {
        Console.WriteLine("Something");
    }
}

...

services.AddTransient(typeof(IGeneric<>), typeof(Generic<>));

When I try to decorate this inteface for a specific type, I get an exception:

services.Decorate<IGeneric<int>, GenercDecorator<int>>();

Throws:

Scrutor.MissingTypeRegistrationException: 'Could not find any registered services for type 'IGeneric<int>'.'

This completely kills a few real world scenarios for my use case, as I want to provide a standard implementation by default, but have specific decorators for specific model types.

To work around this I'd have to scan all possible types and register the IGeneric for each of them, which is just not practical and will outright now work for some cases where you can't know the T for all use cases.

EDIT:
Found a somewhat similar issue raised by Steven (@dotnetjunkie): #39

My scenario is slightly different in that I'm not requesting generic decorators, but closed decorators on top of generic registrations.

Is it possible to make what I want to work with the existing constraints?

I have to say though... reading the outcome of #39 makes me quite sad. Open generic decorators are a killer feature, so now I'm also reconsidering using this default container.

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.