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!!
Related
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()
Similar to How to Inject Dependencies to Dynamically Loaded Assemblies, I want to inject dependencies into classes from dynamically loaded assemblies. Is this possible with the .NET 6.0 DI Container? If so, how? If not, is there a light-weight IOC container that can you might recommend? (Not adding a 2nd IOC system to the project would be preferred.)
(Note: there will be only 2-4 maximum possible dependencies to inject, so a fake injection system with if/switch statements could be acceptable.)
One challenge: ILogger<> typically expects a type, but the loading .dll has no compile-time knowledge of the types in the dynamically loaded assemblies, and vice-versa. I could use the non-generic ILogger interface, but am not sure if that works with DI.
EDIT:
Expanding example, as requested:
Given: All potential dependencies to inject come from the Microsoft.Extensions.Hosting nuget package. The two we initially expect to use are ILogging<> and IConfiguration.
Type desiredClass = <Type found in the dynamically loaded assembly>;
//The below line does not inject dependencies. I am trying to find out what will.
object classInstace = Activator.CreateInstance(desiredClass);
MethodInfo selectedMethod = desiredClass.GetMethods
.Single(m=>m.Name=="Execute" && m.GetParameters().All(p=>p.IsOptional));
//Schedule the method in HangFire
RecurringJob.AddOrUpdate(
() => selectedMethod!.Invoke(
ClassInstance,
Array.Empty<object?>(),
scheduleForThisTask);
I found a very ugly way to get this done, and very much hope there is a cleaner way to accomplish this. (Please let there be a built-in way to do this that I have missed.)
Create an extension method GetInjectedObject for IService Provider:
public static object GetInjectedObject(this IServiceProvider serviceProvider, Type type)
{
//Dependency injection the ugly way
//Find the constructor that asks for the most injected parameters
var constructor = type.GetConstructors().Where(cn =>
cn.GetParameters().All(par => serviceProvider.GetServices(par.ParameterType).Any()))
.OrderByDescending(cn => cn.GetParameters().Length).FirstOrDefault();
if (null == constructor)
throw new Exception($"Type {type.Name} does not have a constructor without non-injectible parameters.");
//Get the needed parameters from the IServiceProvider
var constructorParameters =
constructor.GetParameters().Select(par => serviceProvider.GetService(par.ParameterType)).ToArray();
//Create the object with the parameters
var classInstance = Activator.CreateInstance(type, constructorParameters);
return classInstance;
}
and then create the objects like this:
_classInstance = serviceProvider.GetInjectedObject(Class);
Perhaps I misunderstood the problem, might it work for you to:
Have an assembly shared by the loading assembly (i.e. your Composition Root) and the dynamically loaded assembly? This assembly could contain an interface that dynamically loaded types must implement. (you might already have a shared assembly)
Load the dynamically loaded assembly at startup at the point that you're still wiring the DI Container?
In that case I expect an interface similar to:
namespace MySharedAssembly
{
public interface ITask
{
void Execute(TaskSchedule schedule);
}
public class TaskSchedule { ... }
}
In the dynamically loaded assembly:
namespace MyDynamicAssembly
{
public class HelloWorldTask : ITask
{
public void Execute(TaskSchedule schedule)
{
Console.WriteLine("Hello world!");
}
}
}
By doing so, you can:
Easily find all types in the dynamically loaded assembly by the ITask interface.
Register them by their concrete type in the DI Container.
Resolve them by their concrete type when needed, while their dependencies are injected by the DI Container.
For instance:
string dynamicallyLoadedAssemblyPath = "c:\\...\etc\etc\myAssembly.dll";
// Load plugin assembly
Assembly assembly =
Assembly.Load(AssemblyName.GetAssemblyName(dynamicallyLoadedAssemblyPath));
// Load plugin types
Type plugins = assembly.GetExportedTypes().Where(typeof(ITask).IsAssignableFrom);
// Register plugins in DI Container
foreach (var plugin in plugins)
{
services.AddTransient(plugin, plugin);
}
// Add jobs after container was constructed
IServiceProvider provider = ...
foreach (var plugin in plugins)
{
var scheduleForThisTask = GetSchedule(plugin);
RecurringJob.AddOrUpdate(() =>
{
// It might be important to execute each task in its own scope.
using (var scope = provider.CreateScope())
{
var task = (ITask)scope.ServiceProvider.GetRequiredService(plugin);
task.Execute(scheduleForThisTask);
}
}
}
I am using VS 2017 and .NET Core.
Using Dependency Injection, I would like to register my service at runtime, dynamically. My goal is to write instances of my service that implement the service interface inside of separate assemblies. The servicename/assembly name will then be added to some sort of configuration file (or db table).
My registration code would do something like this:
var ServiceTypeName = LoadServiceAssembly(AssemblyName);
var serviceProvider = new ServiceCollection()
.AddTransient<IDILogger, "ConsoleDILogger">() // <--- Goal
.BuildServiceProvider();
var logger = serviceProvider.GetService(IDILogger);
Clearly, the AddTransient line will not work as such a method does not exist. It does, however, depict the idea. I want to register the type by a string name so that the loader application need not be recompiled everytime I add a new service type.
I cannot seem to find how to do this. Any suggestions would be welcome.
TIA
You could read configured type from the settings, load the required type via reflection and register it in service collection:
// Read from config
var assemblyPath = "...";
var typeName = "...";
var assembly = Assembly.LoadFrom(assemblyPath);
var loggerType = assembly.GetType(typeName);
var serviceProvider = new ServiceCollection()
.AddTransient(typeof(IDILogger), loggerType)
.BuildServiceProvider();
var logger = serviceProvider.GetService<IDILogger>();
Such dynamic approach will not require any recompilation if you add or reconfigure new logger.
That's obviously not possible as is, however, I used something similar to this in a project to avoid having to add each new type to the container:
var assembly = typeof(YourClass).Assembly; // I actually use Assembly.LoadFile with well-known names
var types = assembly.ExportedTypes
// filter types that are unrelated
.Where(x => x.IsClass && x.IsPublic);
foreach (var type in types)
{
// assume that we want to inject any class that implements an interface
// whose name is the type's name prefixed with I
services.AddScoped(type.GetInterface($"I{type.Name}"), type);
}
For your specific case, you could even make this shorter:
var type = assembly.ExportedTypes.First(x => x.Name == runtimeName);
services.AddScoped(typeof(IDILogger), type);
A very genuine question and with references to different answers by users, here's how I have solved in .NET 6
In program.cs added the following
//Register Service Modules to DI
builder.Services.IncludeServiceModule(builder.Configuration);
The called static function contains something like this
public static class ServiceModule
{
public static IServiceCollection IncludeServiceModule(this IServiceCollection services,
IConfiguration configuration)
{
var appServices = System.Reflection.Assembly.Load("FMDeBill.Service").GetTypes().Where(s => s.Name.EndsWith("Service") && s.IsInterface == false).ToList();
foreach (var appService in appServices)
//services.AddTransient(appService.GetInterface($"I{appService.Name}"), appService);
services.Add(new ServiceDescriptor(appService, appService, ServiceLifetime.Scoped));
return services;
}
}
The assembly name is the name of the project/assembly with services. Any service that is not an interface and ends with "Service" such as "CategoryService" is registered dynamically.
Auto-Register Dependency Injected Services in .NET Core
I wrote this method to auto-register all your services and consumer interfaces and classes at runtime for Dependency Injection by the IoC Container in .NET. All you have to do is add your interfaces and/or concrete classes to the enums lists below and the RegisterServices() method will add them for dependency injection in your .NET application. You can then add them to constructors or call them for dependency injection by .NET.
I chose to load services from an enum rather than say a JSON or other configuration file for security reasons. It also reduces dependencies and also locks the applications state, as well as forces development to lock the app to compilation. Developers must modify, add, remove service types and keep them closely coupled to the code. Changing a configuration file is too dangerous!
LET'S BEGIN
You will need to create two files then change the Startup.cs file in .NET.
Create a file called ServiceList.cs in .NET. This one is just a couple enums where you can add your list of types you want registered as services or consumers of services. If you have many classes that inherit from an Interface, just add lists of those in services. But it will accept concrete types, as well. But if you add an interface, the RegisterServices method below will locate all the child classes that implement the interface and register those, as well. The RegisterServices() method will grab them and register all your services with the IoC in .NET for you.
// ADD SERVICES YOU WANT REGISTERED
enum ServicesList
{
ISampleService,
IAnotherService,
AConcreteClassService
}
// ADD CONSUMERS YOU WANT REGISTERED
enum ConsumersList
{
MyClass1,
MyClass2,
ISomeConsumerTypes
}
Create a second class file called RegisterServices.cs. Add the following code. This is the main method that registers all the services listed in the enums above. It is called RegisterServices.cs.
// REGISTER SERVICES
// This will pull all the services you added to the ServicesList.cs
// enum and try and register them with the Services Provider in .NET
static class RegisterServices
{
// You can add the Logger here if you like.
internal static void Start(IServiceCollection services, ILogger logger = null)
{
// Extract out all service enum values into a single list.
List<string> allTypesToAdd = new List<string>();
allTypesToAdd.AddRange(Enum.GetNames(typeof(ServicesList)).ToList());
allTypesToAdd.AddRange(Enum.GetNames(typeof(ConsumersList)).ToList());
// For now I am just getting the active running assembly
Assembly assembly = Assembly.GetExecutingAssembly();
IEnumerable<TypeInfo> assemblyTypes = assembly.DefinedTypes;
List<string> missingEnumTypes = new List<string>();
bool isTypeFound = false;
// Loop through all services in the collection.
// If your service type is not listed, add it.
foreach (string typeToAdd in allTypesToAdd)
{
// Verify the enum type to add to the service collection exists in the application.
isTypeFound = false;
foreach (TypeInfo type in assemblyTypes)
{
if (type.Name == typeToAdd)
{
if (type.IsInterface)
{
// Add the Interface and any concrete classes
// that are implementations of the parent interface.
var childOfInterface = assembly.GetTypes().Where(t => type.AsType().IsAssignableFrom(t));
foreach (Type c in childOfInterface)
{
if (typeToAdd != c.Name)
{
// For now this just assumes you need a request
// scoped service lifetime services. Change as needed.
services.TryAddScoped(type.AsType(), c);
logger?.LogInformation(LogEventIDs.Information_General_ID, "INFORMATION: A new Service Class Was Added: services.TryAddScoped(" + typeToAdd + "," + c.Name + ")");
}
}
} else {
// Only add the concrete class
// For now just use scoped service lifetime
services.TryAddScoped(type.AsType());
logger?.LogInformation(LogEventIDs.Information_General_ID, "INFORMATION: A new Service Class Was Added: services.TryAddScoped(" + typeToAdd + ")");
}
isTypeFound = true;
break;
}
}
// If users added types in the enum lists
// thats not found, flag as a warning!
if (!isTypeFound)
{
missingEnumTypes.Add(typeToAdd);
}
}
// If a bad enum service name was added, log that as a warning.
if (missingEnumTypes.Count > 0)
{
string items = string.Empty;
foreach (string s in missingEnumTypes)
{
if (items != string.Empty) items += " | ";
items += s;
}
logger?.LogWarning(LogEventIDs.Warning_General_ID, "WARNING: These Types/Interfaces/Classes added to Services were not found in the application >>> " + items);
}
}
}
Register Services consumes the enum list of Services and Consumers above.
The last step is to call the method above inside your Startup.cs .NET file in Core. Add RegisterServices.Start() static method call with your ConfigureServices class inside Startup.cs in the root of your .NET Core application. I also add the logger as a parameter but this version just use the services parameter. "services" is whatever the parameter is in your
ConfigureServices method in Startup.cs:
RegisterServices.Start(services);
HOW TO USE DEPENDENCY INJECTION
After you run RegisterServices in your .NET application and Startup.cs calls it, all your services (and child classes derived from interfaces) are now registered!
To call a Service and have it auto-implemented when you instantiate a class in .NET appears to be inconsistent. The IoC Container will auto-inject all constructor services in MVC Controllers, for example, but NOT regular classes. To solve that I recommend you try and inject everything into your controllers, then use the IServiceProvider in regular class constructors to help you auto-inject all other classes with the services they need (see below).
If you are in ASP.NET Core, your best strategy is to ALWAYS add each service to your controller's constructor using interfaces. You can then have full access to every service you need or any service a child object inside the controller might need. But there will be times you have classes you call outside the controllers that inject services but are not auto-injected. So below are some examples of how to do that and still honor the dependency injection model.
Note: If you are an expert at this, please suggest below in comments how I can improve on this idea, as this is the best model I have for now that is simple and easy to use.
// HOW TO USE SERVICES?
// CONTROLLERS (Web Applications)
// Always inject the services you need into the controller's constructor.
// The IoC Container in .NET always auto-injects these objects
// for you and are 100% ready to access. If using ASP.NET, always use the
// constructor of the controller to inject services.
public class HomeController : BaseController
{
private readonly ISampleService _myservice;
public HomeController(ISampleService myservice){
_myservice = myservice;
}
// You can now access your "_myservice" in any action method of the controller
}
// NON-CONTROLLERS and NON-INJECTED CONSTRUCTORS
// If you cant inject the service object into an ASP.NET Controller
// but still need to instantiate the object, your best alternative
// is to inject the ServiceProvider into your Controller or Class
// constructor first. IoC auto-injects the service collection
// so you can now access it to create child objects you can
// tell .NET to auto-inject with their own services when created
// using the registered services in your enum as an example.
public MyClass (IServiceProvider myservice) {
// Here are 3 ways to force the IoC to auto-inject your dependencies
var obj1 = myservice.GetService<SampleService>();
var obj2 = myservice.GetService(SampleService) as ISampleService;
var obj3 = myservice.GetRequiredService(SampleService) as ISampleService;
var obj4 = (SampleService)myservice.GetService(typeof(SampleService));
}
Below is one of the Service Interface types in the enum above and the child classes that got registered which are now available to use as services in the code above after running the RegisterServices call:
// SERVICE INTERFACE
public interface ISampleService
{
void Message(string message);
}
// SERVICE CONCRETE CLASS
class SampleService : ISampleService
{
public void Message(string message)
{
Console.WriteLine($"{message}");
}
}
// SERVICE CONCRETE CLASS
class AnotherSampleService : ISampleService
{
public void Message(string message)
{
Console.WriteLine($"{message}");
}
}
You can use factory to achieve that.
services.AddScoped(provider =>
{
//Resolve some service at runtime.
var aService = provider.GetService<AServiceType>();
//Any synchronous logic here
return new MyDynamicService();
});
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.
We have a Web API project and using the Autofac Web API Integration as the IoC container. The code that we use to register all of our types is as follows:
public class CompositionRootConfigurator
{
public static AutofacWebApiDependencyResolver Configure(Assembly servicesAssembly)
{
var container = BuildContainer(servicesAssembly);
var resolver = new AutofacWebApiDependencyResolver(container);
return resolver;
}
public static IContainer BuildContainer(Assembly servicesAssembly)
{
/*TO DELETE ONCE THE REFERENCES ISSUE IS RESOLVED!*/
var dummy = new EmployeesBL(new ContextFactory(new DBContextFactory(new RoleBasedSecurity(), new Identity())));
var builder = new ContainerBuilder();
if (servicesAssembly != null) // this is a temporary workaround, we need a more solid approach here
{
builder.RegisterApiControllers(servicesAssembly);
}
/* Registers all interfaces and their implementations from the following assemblies in the IoC container
* 1. CB.CRISP.BL
* 2. CB.CRISP.BL.CONTRACTS
* 3. CB.CRISP.DAL
* 4. CB.CRISP.DAL.CONTRACTS
* The current assembly is excluded because the controllers were registered with the builder.RegisterApiControllers expression above.
*/
var appAssemblies = AppDomain.CurrentDomain
.GetAssemblies()
.Where(a => a.ToString().StartsWith("CB.CRISP"))
.ToArray();
builder.RegisterAssemblyTypes(appAssemblies).AsImplementedInterfaces();
if (servicesAssembly != null)
{
builder.RegisterAssemblyTypes(servicesAssembly).AsImplementedInterfaces();
}
return builder.Build();
}
}
Now suppose we have a MyType which implements IMyType and this is the only one that must be a single instance per request and it will be injected in several objects along the hierarchy.
I am at a loss in how to specify this within this existing code. If I just go ahead and just do
builder.RegisterType<MyType>()
.As<IMyType>()
.InstancePerRequest();
since it will also be registered with all the others Will one registration overwrite the other one, will they be duplicated, are there potential problems?
Thank you for your insight.
Autofac will override the first registration and will accept the last one. Here is more detail.
So you should register MyType after registering all type.
I haven't seen any potential problem of this.
But you can register all types like this to be sure.
builder.RegisterAssemblyTypes(servicesAssembly).Except<MyType>().AsImplementedInterfaces();
Here is more detail about scanning.