Autofac failing to Resolve Module when module instantiated with Activator - c#

I am attempting to use a custom attribute to define modules in external libraries I want my framework container to load. I’m able to scan the assembly, find and validate my types, and return a list of instantiated IModules. However when I attempt to resolve a Type that was registered in the external module the type fails to resolved.
Main library targeting .Net Standard 2.0
public static List<IModule> DiscoverContainerModules()
{
var modules = new List<IModule>();
var assemblies = DiscoverAssemblies();
foreach (var assembly in assemblies)
{
modules.AddRange(from type in assembly.GetTypes()
where type.GetCustomAttribute<AppkitModuleAttribute>() != null
where type.IsAssignableTo<IModule>()
select Activator.CreateInstance(type) into module
select module as IModule);
}
return modules;
}
Extension method to register modules
public static void UseAppkitModules(this ContainerBuilder builder)
{
var modules = AppkitPluginDiscovery.DiscoverContainerModules();
foreach (var module in modules)
{
builder.RegisterModule(module);
}
}
From the hosted application targeting .Net Core 6.0
builder.UseAppkitModules();
And finally an example of a module from another .Net Standard 2.0 library
[AppkitModule(nameof(DisplayModule))]
public class DisplayModule : Module
{
protected override void Load(ContainerBuilder builder)
{
builder.Register(c =>
{
var conductor = c.Resolve<IConductor>();
var logger = c.Resolve<ILogger>();
return new DisplaySubsystem(
conductor: conductor,
logger: logger);
});
}
}
The module works fine if I simply new it up builder.RegisterModule(new DisplayModule());
And I’ve confirmed the Load method is called using my reflection methods. Why is Creating the module instance with Activator behaving differently. I’ve also confirm the assembly is loaded.
I’ve moved the module into the parent library and it fails as well. I’ve broken down all the extension methods into a large code block and get the same results. I’ve even create the instance with Activator and got the same results.

Solved:
The root of the issues was my method to load and scan the assemblies with my custom attribute applied. I was using Assembly.LoadFile(path). It would seem that didn't provide the required dependancies to load my module. I updated the method to use Assembly.LoadFrom(path) and all modules and types resolve perfectly.
Full Solution - Load Assemblies
private static List<Assembly> LoadReferencedAssemblies()
{
var assemblies = new List<Assembly>();
var path = AppContext.BaseDirectory;
var directory = new DirectoryInfo(path);
if (!directory.Exists) return assemblies;
var libraries = directory.GetFiles("*.dll");
assemblies.AddRange(libraries.Select(fileInfo => Assembly.LoadFrom(fileInfo.FullName)));
return assemblies;
}
Get Assemblies with Custom Attributes
private static List<Assembly> DiscoverAssemblies()
{
var assemblies = new List<Assembly>();
var attributes = DiscoverAttributes();
var domain = LoadReferencedAssemblies();
foreach (var assembly in domain)
{
assemblies.AddRange(
from type in assembly.GetTypes()
where attributes.Any(attribute => type.GetCustomAttribute(attribute) != null)
where !assemblies.Contains(assembly)
select assembly);
}
return assemblies;
}
Get All Types with Module Attribute
public static List<AppkitAssemblyType> DiscoverModules()
{
var modules = new List<AppkitAssemblyType>();
modules.AddRange(
from assembly in AppkitAssemblies
from type in assembly.GetTypes()
where type.GetCustomAttribute<AppkitModuleAttribute>() != null
select new AppkitAssemblyType(type));
return modules;
}
Builder Extension
public static ContainerBuilder RegisterAppkitModules(this ContainerBuilder builder)
{
var modules = AppkitPluginDiscovery.DiscoverModules();
foreach (var module in modules)
{
builder.RegisterAssemblyModules(module.Type, module.Assembly);
}
return builder;
}
Example Module in Referenced Assembly
[AppkitModule(nameof(DisplayModule))]
public class DisplayModule : Module
{
protected override void Load(ContainerBuilder builder)
{
builder.RegisterType<DisplaySubsystem>().AsSelf().SingleInstance();
}
}
Finally Container Configuration
builder.RegisterAppkitModules()

Related

Autofac can't resolve dependency

I can't resolve registered type IQMService.
Error:No accessible constructors were found for the type '__ASP.FastObjectFactory_app_web_gmbeg5il'.
Below is the code invoke Resolve:
public IQMService qMService;
public QMModule QMModule;
public PageBase() {
qMService = AutofacDependency.Resolve<IQMService>();
QMModule = new QMModule(qMService);
}
Definition of model:
public interface IQMService{
somefunction()
}
public class QMService : IQMService{
public QMService()
{
someprocess();
}
}
Previously working code of AutofacDependency() defined:
static AutofacDependency()
{
Assembly[] assemblys = AppDomain.CurrentDomain.GetAssemblies ();
var builder = new ContainerBuilder();
builder.RegisterAssemblyTypes (assemblys);
builder.RegisterModule (new ConfigurationSettingsReader ("autofac"));
container = builder.Build();
}
After I upgrade .net from 4.0 to 4.8, and update autofac from 3.5.2 to 6.4.0, ConfigurationSettingsReader no longer exist, so I have to update the config file and change the code to below, not work,
static AutofacDependency()
{
Assembly[] assemblys = AppDomain.CurrentDomain.GetAssemblies ();
var builder = new ContainerBuilder();
builder.RegisterAssemblyTypes (assemblys);
//builder.RegisterModule (new ConfigurationSettingsReader ("autofac"));
ConfigurationBuilder cfgbuilder = new ConfigurationBuilder();
cfgbuilder.AddXmlFile("autofac.config");
var module = new ConfigurationModule(cfgbuilder.Build());
builder.RegisterModule(module);
container = builder.Build();
}
I also test this way
static AutofacDependency()
{
Assembly[] assemblys = AppDomain.CurrentDomain.GetAssemblies ();
var builder = new ContainerBuilder();
builder.RegisterAssemblyTypes (assemblys);
builder.RegisterType<Strix.QM.Service.QMService>().As<Strix.QM.Service.IQMService>().InstancePerLifetimeScope();
container = builder.Build();
}
But I always get the error No accessible constructors were found even though I can it's defined. Thank you if any help, and if any necessary information missing please comment I will append it.
The short version here is that this...
Assembly[] assemblys = AppDomain.CurrentDomain.GetAssemblies ();
var builder = new ContainerBuilder();
builder.RegisterAssemblyTypes (assemblys);
...is really not something you should ever do. This literally registers every type in the entire running system, including everything in every .NET base library, into dependency injection. Be way, way more selective than that when registering. I'd bet if you remove that and only register the stuff you need, then DI won't try to resolve the odd .NET stuff that doesn't have accessible constructors.

Execute method inside implemented interface in multiple modular plugins

I am implementing a plugin architecture for a .net core MVC application.
A requirement of each of my plugins is to implement an interface from a centralised core library in which the main mvc application will then call the implemented interface method in each of the plugin modules.
Now I get my list of assemblies and from my .NET Core Startup file call the LoadModuleAssemblies method:
public static void LoadModuleAssemblies(IServiceCollection services, FileInfo[] assemblyList)
{
foreach (var dir in assemblyList)
{
if (dir.Name != CoreLibFile)
{
var asl = new AssemblyLoader(dir.DirectoryName);
var assembly = asl.LoadFromAssemblyPath(dir.FullName);
var implementedList = assembly.GetTypes()
.Where(x => x.GetTypeInfo().ImplementedInterfaces.Contains(typeof(IModule))).ToList();
if (implementedList.Any())
{
services.AddMvc()
.AddApplicationPart(assembly)
.AddControllersAsServices();
//Unsure how to load IModule implemented interface and call GetProperties method in the interface.
}
}
}
}
My Interface looks like this:
public interface IModule
{
ModuleProperties GetProperties();
List<string> GetViews();
}
I want to be able to call the GetProperties method in the implemented IModule for each of my plugins.
How can I do this?
You need to use Reflection to create instances of the plugin, and then call its GetProperties method
if (implementedList.Any())
{
services.AddMvc()
.AddApplicationPart(assembly)
.AddControllersAsServices();
foreach(var type in implementedList)
{
var module = Activator.CreateInstance(type) as IModule;
var properties = module.GetProperties();
var views = module. GetViews();
//make use of the properties and views...
}
}

How do I selectively change lifetime scope during Autofac assembly scanning and registration?

I have a class that uses AutoFac to build a container from the assemblies in the binary folder. This essentially iterates over the dll's and registers classes with an interface:
private static void RegisterAssembly(ContainerBuilder builder, string assemblyNamePattern)
{
// Get the path to the currently executing assembly. We will load dll's from here only.
var path = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
if (String.IsNullOrWhiteSpace(path))
return;
// Get a list of all the assemblies in this folder
var assembliesToRegister = (Directory.EnumerateFiles(path, assemblyNamePattern, SearchOption.TopDirectoryOnly)
.Select(Assembly.LoadFrom)
.ToArray());
// Register the dll's
builder.RegisterAssemblyTypes(assembliesToRegister).AsImplementedInterfaces();
}
That's working fine and I can create instances of the classes I need. The problem I have is that there are couple of these classes that are singletons (Redis caching for example), so need to be registered as such. In code, they're defined using the Lazy<>:
private static readonly Lazy<CacheManager> _instance = new Lazy<CacheManager>(() => new CacheManager());
public static ICacheManager Instance
{
get { return _instance.Value; }
}
What I am wondering is whether there is some way to tell AutoFac that certain classes need to be registered as singletons. I'm guessing I can go through the container after it's been built and change specific definitions to be singletons, but it would be far more reliable if I could get Autofac to do it automatically at registration.
As a cheap solution, when the DLL's are scanned, you can exclude one or more interfaces:
// Register the dll's, except the singleton ones.
builder.RegisterAssemblyTypes(assembliesToRegister)
.AsImplementedInterfaces()
.Except<IOne>()
.Except<ITwo>();
This allows for everything except the singletons to be registered automatically. Next, register the singletons:
AddSingletonInstance<IOne>(builder, assembliesToRegister, "OneType");
AddSingletonInstance<ITwo>(builder, assembliesToRegister, "TwoType");
This relies on a generic method that scans the assembly list created earlier:
private static void AddSingletonInstance<T>(ContainerBuilder builder, Assembly[] assembliesToRegister, string typeName)
{
var singletonType = (from asm in assembliesToRegister
from tt in asm.DefinedTypes
where tt.Name == typeName
select tt).FirstOrDefault();
if (singletonType != null)
builder.RegisterType(singletonType).As<T>().SingleInstance().ExternallyOwned();
}
It's not ideal, but it works.

Register Assembly Modules from Base Folder

I have WinForms project thats has 2 references to Assemblies I want to remove. There are only there because in need them in Bootstrap to do a typeof in the registration of Modules.
var builder = new ContainerBuilder();
var AssemblyCore = typeof (Fle.SQLServer.Core.Impl.Checker.Registry).Assembly;
builder.RegisterAssemblyModules(AssemblyCore);
var AssemblyXml = typeof (Fle.SQLServer.XmlFiles.Registry).Assembly;
builder.RegisterAssemblyModules(AssemblyXml);
Structure map has something that is called AssembliesFromApplicationBaseDirectory does autofac has the same or something similar? Or can i register a RegisterAsseblyModels with path?
If Assemblies are not referenced in the project, one option is to Load the assemblies from the directory and then Register them with Autofac builder as shown below. Below example is not a complete example. It just demonstrates the possible solution of the original problem.
public class BootStrapper
{
public BootStrapper()
{
var builder = new ContainerBuilder();
Assembly[] assemblies =
GetAssembliesFromApplicationBaseDirectory(
x => x.FullName.StartsWith("Fle.SQLServer"));
builder.RegisterAssemblyTypes(assemblies)
.AsImplementedInterfaces();
builder.RegisterAssemblyModules(assemblies);
}
private static Assembly[] GetAssembliesFromApplicationBaseDirectory(Func<AssemblyName, bool> condition)
{
string baseDirectoryPath =
AppDomain.CurrentDomain.BaseDirectory;
Func<string, bool> isAssembly =
file => string.Equals(
Path.GetExtension(file), ".dll", StringComparison.OrdinalIgnoreCase);
return Directory.GetFiles(baseDirectoryPath)
.Where(isAssembly)
.Where(f => condition(new AssemblyName(f)))
.Select(Assembly.LoadFrom)
.ToArray();
}
}
Reference: translating-structure-map-into-autofac

Unable to resolve a controller that was loaded from external dll

I am building a Web API using MVC4 Web API with an IoC container (Simple Injector in this case, but I don't think this problem is related to that container) that should expose a variety of CRUD and query operations. The reason for using IOC in my case is that we are a dev shop and I need to be able to let customers build their own web API controllers to expose the data they need to expose need from our system. Consequently, I was hoping to design my solution in a way that allowed me to dogfood my own product by making all the controllers, both ours and our customers', external and loadable through IOC.
The website does not have any reference to the library but the library contains controllers that I want to use in the website. The types are registered in the container and the DependencyResolver is set to the custom dependency resolver. I have the code finding the dll plugin and loading the controller type but when I try to navigate to the route that it would represent it says it can't find it.
i.e. if I try to navigate to /api/Test1Api I should see the text "hello world"
My problem here is that although I have loaded my controller type, I am unable to translate that into a route that the website says is there.
I get the error
No HTTP resource was found that matches the request URI
No type was found that matches the controller named 'Test1Api'.
Here is how I register the container
public static class SimpleInjectorInitializer
{
/// <summary>Initialize the container and register it as MVC3 Dependency Resolver.</summary>
public static void Initialize()
{
//// Did you know the container can diagnose your configuration? Go to: http://bit.ly/YE8OJj.
// Create the IOC container.
var container = new Container();
InitializeContainer(container);
container.RegisterMvcAttributeFilterProvider();
// Verify the container configuration
container.Verify();
// Register the dependency resolver.
GlobalConfiguration.Configuration.DependencyResolver =
new SimpleInjectorWebApiDependencyResolver(container);
}
private static void InitializeContainer(Container container)
{
var appPath = AppDomain.CurrentDomain.BaseDirectory;
string[] files = Directory.GetFiles(appPath + "\\bin\\Plugins", "*.dll",
SearchOption.AllDirectories);
var assemblies = files.Select(Assembly.LoadFile);
// register Web API controllers
var apiControllerTypes =
from assembly in assemblies
where !assembly.IsDynamic
from type in assembly.GetExportedTypes()
where typeof(IHttpController).IsAssignableFrom(type)
where !type.IsAbstract
where !type.IsGenericTypeDefinition
where type.Name.EndsWith("Controller", StringComparison.Ordinal)
select type;
// register MVC controllers
var mvcControllerTypes =
from assembly in assemblies
where !assembly.IsDynamic
from type in assembly.GetExportedTypes()
where typeof(IController).IsAssignableFrom(type)
where !type.IsAbstract
where !type.IsGenericTypeDefinition
where type.Name.EndsWith("Controller", StringComparison.Ordinal)
select type;
foreach (var controllerType in apiControllerTypes)
{
container.Register(controllerType);
}
foreach (var controllerType in mvcControllerTypes)
{
container.Register(controllerType);
}
}
}
Any help is appreciated.
One big warning about your solution. It is quite crucial to register your controller into your container as well (this advice holds for all DI frameworks, although some frameworks by default force you to register concrete types as well). Otherwise you will certainly get bitten by the same problem as this developer had.
Since the IHttpControllerTypeResolver makes use of the IAssembliesResolver, the simplest (and safest) solution is to simply ask the IHttpControllerTypeResolver for all controls to register. Your SimpleInjectorInitializer in that case will look like this:
public static class SimpleInjectorInitializer
{
public static void Initialize()
{
// Create the IOC container.
var container = new Container();
InitializeContainer(container);
container.RegisterMvcAttributeFilterProvider();
// Verify the container configuration
container.Verify();
// Register the dependency resolver.
GlobalConfiguration.Configuration.DependencyResolver =
new SimpleInjectorWebApiDependencyResolver(container);
}
private static void InitializeContainer(Container container)
{
GlobalConfiguration.Configuration.Services
.Replace(typeof(IAssembliesResolver),
new CustomAssembliesResolver());
var services = GlobalConfiguration.Configuration.Services;
var controllerTypes = services.GetHttpControllerTypeResolver()
.GetControllerTypes(services.GetAssembliesResolver());
// register Web API controllers (important!)
foreach (var controllerType in controllerTypes)
{
container.Register(controllerType);
}
}
}
Also note that your CustomAssembliesResolver can be made considerably easier:
public class CustomAssembliesResolver
: DefaultAssembliesResolver
{
private Assembly[] plugins = (
from file in Directory.GetFiles(
appPath + "\\bin\\Plugins", "*.dll",
SearchOption.AllDirectories)
let assembly = Assembly.LoadFile(file)
select assembly)
.ToArray();
public override ICollection<Assembly> GetAssemblies()
{
var appPath =
AppDomain.CurrentDomain.BaseDirectory;
return base.GetAssemblies()
.Union(this.plugins).ToArray();
}
}
I figured it out!
So I created a new class in my Web Api call CustomAssembliesResolver that inherits from DefaultAssembliesResolver. Essentially I add my assembly to the list of assemblies that are parsed when looking for controllers. I still have the code that uses Simple Injector for the DI portion of the solution.
public class CustomAssembliesResolver : DefaultAssembliesResolver
{
public override ICollection<Assembly> GetAssemblies()
{
var appPath = AppDomain.CurrentDomain.BaseDirectory;
var baseAssemblies = base.GetAssemblies();
var assemblies = new List<Assembly>(baseAssemblies);
var files = Directory.GetFiles(appPath + "\\bin\\Plugins", "*.dll",
SearchOption.AllDirectories);
var customAssemblies = files.Select(Assembly.LoadFile);
// register Web API controllers
var apiControllerAssemblies =
from assembly in customAssemblies
where !assembly.IsDynamic
from type in assembly.GetExportedTypes()
where typeof(IHttpController).IsAssignableFrom(type)
where !type.IsAbstract
where !type.IsGenericTypeDefinition
where type.Name.EndsWith("Controller", StringComparison.Ordinal)
select assembly;
foreach (var assembly in apiControllerAssemblies)
{
baseAssemblies.Add(assembly);
}
return assemblies;
}
}
I also added the following line to the beginning of the App_Start in the Global.asax.cs
GlobalConfiguration.Configuration.Services.Replace(typeof(IAssembliesResolver), new CustomAssembliesResolver());
Hope this helps someone!
A huge, huge thank you to Steven for his help and insight! I really appreciate it!!

Categories