Click here to Skip to main content
15,881,757 members
Please Sign up or sign in to vote.
0.00/5 (No votes)
See more: , +
Hello all,

I have used Autofac to write a Console application in Net6 for Windows, Ubuntu and Raspberry.

I have managed to wire up my dependencies using an external json file for Autofac as shown below.

C#
protected override void Load(ContainerBuilder builder)
{
  var currentExecutionLocation = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
  var autofacConfigurationFileName = Path.Combine(currentExecutionLocation, "AutofacConfiguration.json");
  Console.WriteLine($"* Wiring up Autofac configuration using file {autofacConfigurationFileName}");
  var config = new ConfigurationBuilder();
  config.AddJsonFile(autofacConfigurationFileName);
  var module = new ConfigurationModule(config.Build());
  builder.RegisterModule(module);}


That all worked out nicely and gave me the possibility to reconfigure the dependencies without recompiling the code.
However, for different reasons I've decided to leave Autofac and start using the .Net Core DI framework.

I removed Autofac NuGet packages and the Autofac code altogether and added some new usings

C#
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.DependencyInjection;


After doing some modifications to the code, the application runs again and I can register the dependencies programmatically using the code below
C#
public static IHost CreateHost(IConfigurationBuilder builder)
{
    WireupExternalModuleLoading();
    WireupLog4Net();
    ConfigBuilder(builder);
    var host = Host.CreateDefaultBuilder()
        .ConfigureServices((context, services) =>
        {
            services.AddSingleton<IFileLogging, FileLogging>();
            services.AddSingleton<IHardwareEngine, HardwareEngineStub>();
            services.AddSingleton<IProgramTests, ProgramTests>();
        }).Build();
    return host;
}

private static void ConfigBuilder(IConfigurationBuilder builder)
{
    builder.SetBasePath(Directory.GetCurrentDirectory())
        .AddJsonFile($"appsettings.json", optional: false, reloadOnChange: true)
        .AddJsonFile($"appsettings.{Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT") ?? "Production"}.json", optional: true)
        .AddEnvironmentVariables();
}



Now I want to have the same functionality as I had with Autofac, i.e. the possibility to load the configuration (mapping between interfaces and concrete classes) from an external json (or equivalent) file.
Basically removing the code
C#
services.AddSingleton<IFileLogging, FileLogging>();
services.AddSingleton<IHardwareEngine, HardwareEngineStub>();
services.AddSingleton<IProgramTests, ProgramTests>();

and replacing it with a json file (or equivalent) that is loaded at runtime.

What I have tried:

I thought that it would be fairly easy to find some tips on how to load the configuration from a file, but yet again my Google Fu skills have failed me.

My previous Autofac json file looked something like this:

XML
{
  "components": [
    {
      "type": "UtilitiesLibrary.Logging.FileLogging, UtilitiesLibrary",
      "services": [
        {
          "type": "UtilitiesLibrary.Logging.IFileLogging, UtilitiesLibrary"
        }
      ],
      "instanceScope": "single-instance",
      "injectProperties": true
    },
    {
      "type": "HardwareEngineStub.HardwareEngine, HardwareEngineStubLibrary",
      "services": [
        {
          "type": "AccordionDefinitionsLibrary.IHardwareEngine, AccordionDefinitionsLibrary"
        }
      ],
      "instanceScope": "single-instance",
      "injectProperties": true
    },
    {
      "type": "AccordionMainLibrary.ProgramTests, AccordionMainLibrary",
      "services": [
        {
          "type": "AccordionDefinitionsLibrary.IProgramTests, AccordionDefinitionsLibrary"
        }
      ],
      "instanceScope": "single-instance",
      "injectProperties": true
    }
  ]
}


Is someone would be kind enough to give me a pointer or push me in the right direction that would be appreciated.

Thanks in advance,
King regards
Magnus
Posted
Updated 3-Sep-22 22:36pm

You could register factories instead of classes with the DI container. Then have the factories create the instance(s) from config file settings.
 
Share this answer
 
Comments
Magnus Sydoff 4-Sep-22 4:19am    
Thanks @TimWallace for your answer. I wrote something that might not be a factory but solves the problem anyway. I'll post it in case someone else might need something similar.
In order to solve the problem I finally ending up writing something like the code below.

First some module definitions

C#
public static string ModulesFolderName = "Modules";

public interface IModuleDataConfiguration
{
    public IModuleInfo ConcreteClassImplementation { get; }
    public IModuleInfo InterfaceDefinition { get; }
    string ServiceLifetime { get; }
}

public interface IModuleInfo
{
    public string FileName { get; set; }
    public string DefinedType { get; }
}

C#
[DataContract]
public class ModuleDataConfiguration : IModuleDataConfiguration
{
    [DataMember(Name = "ConcreteClassImplementation")]
    public IModuleInfo ConcreteClassImplementation { get; set; }

    [DataMember(Name = "InterfaceDefinition")]
    public IModuleInfo InterfaceDefinition { get; set; }

    [DataMember(Name = "ServiceLifetime")]
    public string ServiceLifetime { get; set; }

    public ModuleDataConfiguration(IModuleInfo concreteClassImplementation, IModuleInfo interfaceDefinition, string serviceLifetime)
    {
        this.ConcreteClassImplementation = concreteClassImplementation;
        this.InterfaceDefinition = interfaceDefinition;
        this.ServiceLifetime = serviceLifetime;
    }
}


and then a method for loading it and adding it to the DI container.

C#
private static void AddConfiguration(IServiceCollection services, ModuleDataConfiguration config)
    {
        var folderToLoadModulesFrom = Path.Combine(Path.GetDirectoryName(typeof(Program).Assembly.Location), ModulesFolderName);
        if (!File.Exists(Path.Combine(folderToLoadModulesFrom, config.InterfaceDefinition.FileName)))
        {
            Console.WriteLine($"****** Unable to find the file {config.InterfaceDefinition.FileName} for the interface. This config will not be used.");
            return;
        }
        if (!File.Exists(Path.Combine(folderToLoadModulesFrom, config.InterfaceDefinition.FileName)))
        {
            Console.WriteLine($"****** Unable to find the file {config.ConcreteClassImplementation.FileName} for the class implementation. This config will not be used.");
            return;
        }

        Assembly assemblyForInterface = Assembly.LoadFrom(Path.Combine(folderToLoadModulesFrom, config.InterfaceDefinition.FileName));
        Assembly assemblyForImplementation = Assembly.LoadFrom(Path.Combine(folderToLoadModulesFrom, config.ConcreteClassImplementation.FileName));

        var exportedTypes = assemblyForImplementation.GetExportedTypes();
        var implementationClass = exportedTypes.FirstOrDefault(t => t.Name == config.ConcreteClassImplementation.DefinedType && t.IsClass && !t.IsAbstract && !t.IsNested);
        if (implementationClass == null)
        {
            Console.WriteLine($"****** Unable to find implementation {config.ConcreteClassImplementation.DefinedType} in file {config.ConcreteClassImplementation.FileName}. This config will not be used.");
            return;
        }
        var interfaceForImplementation = assemblyForInterface.GetExportedTypes().FirstOrDefault(t => t.Name == config.InterfaceDefinition.DefinedType && t.IsPublic && !t.IsNested);
        if (interfaceForImplementation == null)
        {
            Console.WriteLine($"****** Unable to find interface {config.InterfaceDefinition.DefinedType} in file {config.InterfaceDefinition.FileName}. This config will not be used.");
            return;
        }

        if (!(ServiceLifetime.TryParse(typeof(ServiceLifetime), config.ServiceLifetime, out object lifetimeObject)) || (lifetimeObject is null))
        {
            Console.WriteLine($"****** Unable to parse {nameof(ServiceLifetime)} from value {config.ServiceLifetime}. This config will not be used. [Valid values  are : {nameof(ServiceLifetime.Singleton)}, {nameof(ServiceLifetime.Scoped)}, {nameof(ServiceLifetime.Transient)}]");
            return;
        }
        ServiceLifetime lifetime = (ServiceLifetime)lifetimeObject;

        services.Add(new ServiceDescriptor(interfaceForImplementation, implementationClass, lifetime));
    }


and finally adding a json file looking something like this.
(The json file is loaded using Newtonsoft)

{
  "Configurations": [
    {
      "ConcreteClassImplementation": {
        "FileName": "HardwareEngineStubLibrary.dll",
        "DefinedType": "HardwareEngine"
      },
      "InterfaceDefinition": {
        "FileName": "../AccordionDefinitionsLibrary.dll",
        "DefinedType": "IHardwareEngine"
      },
      "ServiceLifetime": "Singleton"
    },

    {
      "ConcreteClassImplementation": {
        "FileName": "HardwareEngineStubLibrary.dll",
        "DefinedType": "HardwareAbstraction"
      },
      "InterfaceDefinition": {
        "FileName": "../EsharpDefinitions.dll",
        "DefinedType": "IHardwareAbstraction"
      },
      "ServiceLifetime": "Singleton"
    },

    {
      "ConcreteClassImplementation": {
        "FileName": "HardwareEngineStubLibrary.dll",
        "DefinedType": "HardwareEngine"
      },
      "InterfaceDefinition": {
        "FileName": "../EsharpDefinitions.dll",
        "DefinedType": "IChannelCapability"
      },
      "ServiceLifetime": "Singleton"
    }
  ]
}
 
Share this answer
 

This content, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)



CodeProject, 20 Bay Street, 11th Floor Toronto, Ontario, Canada M5J 2N8 +1 (416) 849-8900