0

I have c# library that referenced in main project. The library

  1. Gets main project assembly;
  2. Retrieves all types using System.Reflection;
  3. Should creates type using Activator.CreateInstance (I’m not sure this is the best way).

The library doesn’t know anything about main project, only a metadata that can be obtained through reflection. How the dependencies can be resolved?

private readonly Assembly _assembly;

public Injector()
{
    _assembly = Assembly.GetEntryAssembly();
}

public List<string> GetTypes()
{
    return _assembly
        .GetTypes()
        .Select(x => x.FullName)
        .ToList();
}

public object GetType(string typeName)
{
    Type type = _assembly
        .GetTypes()
        .First(x => x.FullName == typeName);

    object instance = Activator.CreateInstance(type);

    return instance;
}

Possible issue: different IoC containers (third-party libraries, own-written).

What is the best way to handle this problem and keep library more automatic without forcing users provide a lot of settings? If it's not possible, could you please offer any other solutions? Thanks.

EDIT: How to provide dependencies to instance in Activator.CreateInstanceor create instance directly from main(source) project? It should be allowed to create any instance that contains in main project. Yes, main project also doesn’t know anything about library. So, it’s desirable to make minimum code changes in main project.

EDIT 2: The library won't be used in source project, it will have own UI interface. For example, Swagger API

Damir Shakenov
  • 331
  • 4
  • 17
  • Possible duplicate of [C# Reflection: Get \*all\* active assemblies in a solution?](https://stackoverflow.com/questions/851248/c-sharp-reflection-get-all-active-assemblies-in-a-solution) – Morten Bork Sep 13 '18 at 09:49
  • @MortenBork, no it's not a duplicate. – Damir Shakenov Sep 13 '18 at 09:53
  • Why do you don't use one of the established di-conainers? Some can be used to register all public types when you register just the assembly. But this is not really clean, especially if you want to register different implementations for a interface. – Rabban Sep 13 '18 at 10:08
  • @Rabban, If I properly understand you, I can't use one of the established di-containers, since the library doesn't know what container is used – Damir Shakenov Sep 13 '18 at 10:11
  • 1
    Is this some plugin system do you want to achieve? You can also use di containers to do this, without knowing the plugins before. Simple Injector supports this behavior: [Plugin registration](https://simpleinjector.readthedocs.io/en/latest/advanced.html#registering-plugins-dynamically). – Rabban Sep 13 '18 at 10:21
  • @Rabban Seems like plugin system should know some information about included plugins, but in my case there’s no such information – Damir Shakenov Sep 14 '18 at 06:25
  • @DamirShakenov If you not provide this informations from your system, where do you want to know how to use the types from the plugins? – Rabban Sep 14 '18 at 08:53
  • @Rabban The plugin won't be used in source project, it will have own UI interface. For example, [Swagger API](https://github.com/domaindrivendev/Swashbuckle.AspNetCore) – Damir Shakenov Sep 14 '18 at 11:58

2 Answers2

2

I've found a way how to handle this.

.NET Standard featured a new interface IServiceProvider that must be implemented by any IoC container in Startup.ConfigureServices(). We can pass this IServiceProvider to the library constructor and use method GetService(Type type) to create service with all resolved dependency injections.

Source project (e.g. Autofac container in Startup.cs):

    // This method gets called by the runtime. Use this method to add services to the container.
    public IServiceProvider ConfigureServices(IServiceCollection services)
    {
        services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);

        var builder = new ContainerBuilder();
        builder.RegisterType<CalculationService>().As<ICalculationService>();
        builder.RegisterType<Logger>().As<ILogger>();
        builder.Populate(services);
        var container = builder.Build();
        return new AutofacServiceProvider(container);
    }

    // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
    public void Configure(IApplicationBuilder app, IHostingEnvironment env, IServiceProvider diService)
    {
        if (env.IsDevelopment())
        {
            app.UseDeveloperExceptionPage();
        }
        else
        {
            app.UseHsts();
        }

        app.UseHttpsRedirection();
        app.UseMvc();

        var injection = new Injector(diService);
        var result = injection.RunMethod(typeof(ICalculationService).FullName, "Sum", new int[] { 1, 2, 3 });
    }

Library project:

public class Injector
{
    private readonly Assembly _assembly;
    private readonly IServiceProvider _serviceProvider;

    public Injector(IServiceProvider serviceProvider)
    {
        _assembly = Assembly.GetEntryAssembly();
        _serviceProvider = serviceProvider;
    }

    public object RunMethod(string className, string methodName, params object[] parameters)
    {
        var classType = _assembly
            .GetType(className);

        object instance = _serviceProvider.GetService(classType);

        var method = classType.GetMethod(methodName);

        var result = method.Invoke(instance, parameters);

        return result;
    }
}
Damir Shakenov
  • 331
  • 4
  • 17
1

Dependencies are resolved automatically as long as the resolved libraries are under the same folder (or in the GAC) If the libraries are under specific folder, the automatic resolve will probably fail, but you can handle it with AppDomain.AssemblyResolve event.

Besides, it seems that you are are trying to implement a sort of plugin/addon host, perhaps you can try using Managed Extensibility Framework instead of manually implement solution through reflection.

Edit: Following a code snippet of event usage, but it need to be adapted depending on your environment

static Injector()
{
     // Usage of static constructor because we need a unique static handler
     // But feel free to move this part to a more global location
     AppDomain.CurrentDomain.AssemblyResolve += CurrentDomain_AssemblyResolve;
}

private Assembly CurrentDomain_AssemblyResolve(object sender, ResolveEventArgs args)
{
  string foundAssemblyPath = string.Empty;

  // args.Name contains name of the missing assembly
  // some project-specific and environment-specific code should be added here to manually resolve the dependant library
  // In this example I will consider that the libraries are located under /plugins folder
  foundAssemblyPath = $@"{Path.GetDirectoryName(Application.StartupPath)}\plugins\{args.Name}.dll";

  return Assembly.LoadFile(foundAssemblyPath);
}
Oxald
  • 837
  • 4
  • 10
  • Could you please provide code how `AppDomain.AssemblyResolve` can be implemented in current code? As I see it tries to place a correct assembly to create instance, but I already have a correct assembly. – Damir Shakenov Sep 13 '18 at 10:23
  • `AppDomain.AssemblyResolve` - there's no problem with Assembly to resolve it. `MEF` - The library is not a plugin, so MEF doesn't fit here, since there's no information about import/export, only information about `EntryAssembly`. – Damir Shakenov Sep 14 '18 at 07:05