Can not resolve a generic service using manual interception with ASP.NET Core 2 and Castle.Core

866 Views Asked by At

I know there are a lot of question similar to this one but actually none of them solved my issue. I created a new Asp.Net Core 2 application. Now I am trying to use an intercepter for a specific service to fetch some data into this service(I am using Castle.Core nuget package). I have a generic IConfigurationInterceptor<> and a real implementation ConfigurationInterceptor<>

Here is the interface:

public interface IConfigurationInterceptor<T> : IInterceptor where T : class { }

public class ConfigurationInterceptor<T> : IConfigurationInterceptor<T> where T : class
{
     public ConfigurationInterceptor(ConfigurationInfo<T> configurationInfo, 
        some other services)
    {
        _configurationInfo = configurationInfo;  
       //.....
    }
    public void Intercept(IInvocation invocation)
    {
        invocation.ReturnValue = somefunc(someconfig, invocation.Arguments);
    }
}

Then I have an extension method like below:

public static void AddSingletonConfiguration<TInterface, TImplementation>(
    this IServiceCollection services, string featureName) 
    where TImplementation : class, TInterface where TInterface : class
{
    var info = new ConfigurationInfo<TImplementation>(featureName, typeof(TInterface));
    var generator = new ProxyGenerator();

    services.AddSingleton(x =>
    {
        var ic = x.GetService<Func<ConfigurationInfo<TImplementation>, 
            IConfigurationInterceptor<TImplementation>>>();

        var icTemp = ic.Invoke(info);

        return (TInterface) generator.CreateInterfaceProxyWithoutTarget(
            info.ServiceType, icTemp);
    });
}

But when I get to this line of code:

 var ic = x.GetService<Func<ConfigurationInfo<TImplementation>, 
    IConfigurationInterceptor<TImplementation>>>();

it returns me a null value for ic:

ConfigurationInfo class is just a simple class I create for storing some extra data.

public sealed class ConfigurationInfo<TImpl>
{
    public Type ServiceType { get; }
    public string FeatureName { get; }

    public ConfigurationInfo(string featureName, Type serviceType)
    {
        FeatureName = featureName;
        ServiceType = serviceType;
    }

    public override string ToString() 
        => $"{FeatureName} ({ServiceType} -> {typeof(TImpl)})";
}

In my ConfigureServices I have these both lines:

services.AddSingleton(typeof(IConfigurationInterceptor<>), 
    typeof(ConfigurationInterceptor<>));
services.AddSingletonConfiguration<IStaticDataConfiguration, StaticDataConfiguration>(
    "SomeFeatureKey");

I am not sure why ic variable is null because previously another project was using Autofac and was working perfectly but in the startup you would find something like this:

builder.RegisterGeneric(typeof(ConfigurationInterceptor<>))
    .As(typeof(IConfigurationInterceptor<>)).SingleInstance();

builder.RegisterConfiguration<IStaticDataConfiguration, StaticDataConfiguration>(
    "SomeFeatureKey");

and the extension method was like this one:

public static void RegisterConfiguration<TInterface, TImplementation>(
    this ContainerBuilder builder, string featureName) 
    where TImplementation : class, TInterface
{
    var info = new ConfigurationInfo<TImplementation>(featureName, typeof(TInterface));
    var generator = new ProxyGenerator();

    builder
        .Register(c =>
        {
            var ic = c.Resolve<Func<ConfigurationInfo<TImplementation>, 
                IConfigurationInterceptor<TImplementation>>>()(info);
            return generator.CreateInterfaceProxyWithoutTarget(info.ServiceType, ic);
        })
        .As<TInterface>()
        .SingleInstance();
}

Any help would be appreaciated.

EDIT 1:

Now I changed from method GetService<> to method GetRequiredService<> and throws an exception like below:

No service for type 'System.Func'2[StaticDataProvider.DomainModel.ConfigurationInfo'1[StaticDataProvider.Services.StaticDataConfiguration],StaticDataProvider.Services.Interfaces.IConfigurationInterceptor'1[StaticDataProvider.Services.StaticDataConfiguration]]' has been registered.

EDIT 2:

To wrap it up here is the issue: In my current project in Asp.Net core I can not get a Func<X, B> while in the Asp.Net MVC 5 project(It is a whole different project) I can get a Func<X, B> using Autofac. I think this has to do with parametrized instantiation feature in Autofac provided by default: here

Now, I dont know if in Asp.Net Core default DI container has something like this 'parametrized instantiation' feature where it allows me resolving Func<X, B> instead of B.

3

There are 3 best solutions below

0
Rey On BEST ANSWER

Here is what I had to do: Modified ConfigureService method like below:

public void ConfigureServices(IServiceCollection services)
{
    IConfigurationInterceptor<T> GetConfigurationInterceptor<T>(ConfigurationInfo<T> info) where T : class 
    { 
        return new ConfigurationInterceptor<T>(info, services.GetService<IConfigurationProvider>(), Configuration);
    }

    services.AddSingletonConfiguration<IStaticDataConfiguration, StaticDataConfiguration>("someFeatureKey", GetConfigurationInterceptor);
 }

Then modified extension methods like below:

public static void AddSingletonConfiguration<TInterface, TImplementation>(this IServiceCollection services, 
    string featureName, Func<ConfigurationInfo<TImplementation>, IConfigurationInterceptor<TImplementation>> ic) where TImplementation : class, TInterface where TInterface : class
{ 
    var info = new ConfigurationInfo<TImplementation>(featureName, typeof(TInterface));
    var generator = new ProxyGenerator();
    services.AddSingleton(x =>
    {
        var icTemp = ic.Invoke(info);
        return (TInterface) generator.CreateInterfaceProxyWithoutTarget(info.ServiceType, icTemp);
    });
}
public static TInterface GetService<TInterface>(this IServiceCollection services) where TInterface : class
{ 
    var serviceProvider = services.BuildServiceProvider();
    return serviceProvider.GetRequiredService<TInterface>();
}

Now its working fine but the idea is that I had to create Func<X, B> myself and pass as a parameter to extension method.

5
Travis Illig On

I'm guessing the root of the problem is in the fairly complex manual wiring up of the interceptors.

If you're using interceptors with Autofac, it'd be better to use the Autofac.Extras.DynamicProxy2 package and wire up interceptors using the built-in Autofac functionality instead of trying to chain a bunch of resolutions together with functions and parameters. I see a lot of little gotchas in here like how you're setting up a singleton interface proxy without a target but I'm not entirely clear how the target gets added post-facto. There's a lot of complexity you can avoid by using the tools provided.

That said, I'm also looking at the exception message. Without a stack trace I can't 100% guarantee it, but a search on the Autofac source indicates that's not a message that came from Autofac - it's likely, then, a message from the default Microsoft.Extensions.DependencyInjection container. That indicates you may not actually have everything wired up the way you think you do.

I'd back up a bit and just get simple things working and ensure they're coming from Autofac. If you decide you don't want Autofac in play, make sure you've removed it entirely from the equation. Basically, just make sure it's clean and working in the general sense.

After that, add things back slowly, one at a time. I might recommend putting a reproduction in a unit test where you use these registration mechanisms and get things working without the complexity of the entire app weighing down. Unwind it from there. If it's too complex to unit test... maybe that's an indicator you should simplify it and refactor. Make it testable.

0
Travis Illig On

I'll leave my previous answer for posterity, but... The default Microsoft IoC provider is very simple and does not support all the features of Autofac. You won't get parameterized resolution or auto-generated factories from it.