Registration of IoC containers from several assemblies - c#

I try to understand what is best practice for registration of the objects into ioc container from different projects of one solution.
I have a solution with 4 projects and I saw a solution to create installers in each of the projects and then in one place call somtehing like this:
_container = new WindsorContainer();
var assemblyNames = new[] {"Dal", "Utils" };
_container.Install(assemblyNames.Select(
x => (IWindsorInstaller)new AssemblyInstaller(Assembly.Load(x),
new InstallerFactory())).ToArray());
But also I saw a solution that in each project, there is a creation of container, and inside there is a registration of the objects that are relevant to this specific project.
My question is: what is the best practice for this situation?

Every executable project should have a container of its own as they are capable of running independently of other executable projects. Library project usually provide dependencies that are consumed in the executable project and as such it is the executable project's responsibility to register those dependencies in its container if it wants to make use of them with the library not having a container of its own.
Having multiple containers could cause a variety of issues for example, if a class is registered as a singleton (one reference shared among all consumers of the dependency) having multiple containers would result in multiple instance of the class being created (one in each container)
It could also cause issue if there are cross-project dependencies as the container would not be able to resolve a dependency registered in another container.

Usually I create one project which contains most of my shared resources such as models, libraries and also IoC configuration which I use in the other projects. By configuring the IoC container from this project I'm saving myself some copy pasting. With this I keep the possibility to override the configuration from the project in which you're using this configuration.
In the end it's all about maintainability. If you use the same dependencies throughout all of your projects it'd be a pain in the arse to configure it time after time for each individual project.

We have an example here : A web solution which consists of many projects : One Core project and many other which reference the core. All of them end up in different folders (or copied after build to the core/bin if you like). At start time we load them dynamically and then load the IWindsorInstallers from them. We load dlls like that :
public static void LoadMoules()
{
string basePath = AppDomain.CurrentDomain.BaseDirectory;
string binPath = Path.Combine(basePath, "bin");
DirectoryInfo binFolder = new DirectoryInfo(binPath);
IEnumerable<FileInfo> binFiles = binFolder.EnumerateFiles("*.dll", SearchOption.AllDirectories);
Assembly[] domainAssemblies = AppDomain.CurrentDomain.GetAssemblies();
try
{
foreach (var binFile in binFiles)
{
AssemblyName assemblyName = AssemblyName.GetAssemblyName(binFile.FullName);
if (domainAssemblies.All(x => x.FullName != assemblyName.FullName))
{
Assembly.Load(assemblyName.FullName);
Debug.WriteLine($"Loading {assemblyName.FullName}");
}
}
}
catch (Exception exception)
{
DynamicModuleLoaderEventSource.Log.FailedToLoadAssembly(exception.Message + exception.StackTrace);
} }
Then we get the registrations :
try
{
foreach(Assembly moduleAssembly in installationModules)
{
// satellite proejct assemblies
Debug.WriteLine(moduleAssembly.FullName);
ContainerRegisterEventSource.Log.InstallAssembly(moduleAssembly.FullName);
container.Install(FromAssembly.Instance(moduleAssembly));
}
container.Install(FromAssembly.This()); // the core assembly
}
catch(Exception exception)
{
ContainerRegisterEventSource.Log.Exception(exception.Message + exception.StackTrace);
Trace.WriteLine("SetupContainerForMigrations error : " + exception.Message + Environment.NewLine + exception.StackTrace);
}
Doing it this way you will end up with one container with all the dependencies.

Related

How to separate asp.net core mvc project into multiple assembly (.dll)?

How to separate asp.net core mvc project into multiple assembly (.dll)?
I have 3 projects
MyApp Project
Controllers
HomeController.cs
Models
Views
Home
Index.cshtml
HR Project
Controllers
EmployeeController.cs
Models
EMPLOYEE.cs
Views
Employee
Index.cshtml
ACC Project
Controllers
ChartAccountController.cs
Models
ACCOUNT.cs
Views
ChartAccount
Index.cshtml
I want to compile into dll
HR Project
HR.dll
HR.Views.dll
ACC Project
ACC.dll
ACC.Views.dll
I want to add reference those dll (HR.dll, HR.Views.dll, ACC.dll, ACC.Views.dll) into MyApp Project.
And then run MyApp project can access Employee & Chart Account module too.
*** Original Answer (Update below)
If you want to do this you'll need to do the following 2 steps:
You need to load the controllers from the external dlls
There is already a solution for this on stackoverflow: How to use a controller in another assembly in ASP.NET Core MVC 2.0?
It says the following:
Inside the ConfigureServices method of the Startup class you have
to call the following:
services.AddMvc().AddApplicationPart(assembly).AddControllersAsServices();
You need to load the your compiled Razor views, I guess this is what you have in your HR.Views.dll and ACC.Views.dll.
There is also already a solution for this on stackoverflow:
How CompiledRazorAssemblyPart should be used to load Razor Views?
Loading Razor Class Libraries as plugins
This is one possible solution from the links above:
What you need to do is:
services.AddMvc()
.SetCompatibilityVersion(CompatibilityVersion.Version_2_1)
.ConfigureApplicationPartManager(ConfigureApplicationParts);
and configure the parts like this
private void ConfigureApplicationParts(ApplicationPartManager apm)
{
var rootPath = HostingEnvironment.ContentRootPath;
var pluginsPath = Path.Combine(rootPath, "Plugins");
var assemblyFiles = Directory.GetFiles(pluginsPath, "*.dll", SearchOption.AllDirectories);
foreach (var assemblyFile in assemblyFiles)
{
try
{
var assembly = Assembly.LoadFile(assemblyFile);
if (assemblyFile.EndsWith(".Views.dll"))
apm.ApplicationParts.Add(new CompiledRazorAssemblyPart(assembly));
else
apm.ApplicationParts.Add(new AssemblyPart(assembly));
}
catch (Exception e) { }
}
}
If you also have javascript and css files in our separate MVC projects, you will need to embed this to your dll otherwise your main app wont see it. So in your HR and ACC project, you'll need to add this in your .csproj file:
<ItemGroup>
<EmbeddedResource Include="wwwroot\**" />
</ItemGroup>
And just to be clear, I agree with the other comments, I don't think it is a good architecture, but it is possible to do it if you want.
*** Updated (Worked)
Just a step:
Edit Startup.cs in ConfigureServices method
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2).ConfigureApplicationPartManager(ConfigureApplicationParts);
and method:
private void ConfigureApplicationParts(ApplicationPartManager apm)
{
var rootPath = Path.GetDirectoryName(Assembly.GetEntryAssembly().Location);
var assemblyFiles = Directory.GetFiles(rootPath , "*.dll");
foreach (var assemblyFile in assemblyFiles)
{
try
{
var assembly = Assembly.LoadFile(assemblyFile);
if (assemblyFile.EndsWith(this.GetType().Namespace + ".Views.dll") || assemblyFile.EndsWith(this.GetType().Namespace + ".dll"))
continue;
else if (assemblyFile.EndsWith(".Views.dll"))
apm.ApplicationParts.Add(new CompiledRazorAssemblyPart(assembly));
else
apm.ApplicationParts.Add(new AssemblyPart(assembly));
}
catch (Exception e) { }
}
}
What you're wanting is not possible, or perhaps better said: it's not a good idea, for sure.
In general, you need to use Razor Class Libraries. All common functionality would then go into those RCLs. Your entire HR and ACC projects could be RCLs, unless you need to run those independently, as full web apps, as well. In which case, you'd need a structure more like:
HR RCL
HR Web App (depends on HR RCL)
ACC RCL
ACC Web App (depends on ACC RCL)
Main App (depends on HR RCL and ACC RCL)
In either case, you'd put all the controllers, views, and static resources that need to be shared in the RCL, so if you do have actual HR/ACC web apps, those would be pretty light: mostly just consisting of Program and Startup and a dependency on their respective RCLs.
For more information, see the documentation on Razor Class Libraries.

Autofac in a Multi-Project Solution

I'm using Autofac as a dependency injection system in a C# solution that spans several class libraries and several executables. I'm using modules to configure Autofac, but that still leaves me with the issue of building the DI container, which varies depending upon which executable I'm composing it for.
I tried using Autofac's RegisterAssemblyModules, but you have to give it a list of assemblies to scan, and until some Type in a class library assembly is used, the assembly isn't loaded, and hence not available to scan.
Some people recommend loading every assembly in the bin directory which might have an Autofac module definition in it. But that seems to pose the risk of an undesired assembly getting slipped into action.
So what I came up with is this static class, which is defined in a common class library:
public static class Container
{
private static IContainer _instance;
private static Dictionary<string, Assembly> _moduleAssemblies = new Dictionary<string, Assembly>();
public static void RegisterAutofacModuleAssembly<T>()
where T : class
{
var assembly = typeof(T).Assembly;
if( !_moduleAssemblies.ContainsKey( assembly.FullName ) )
_moduleAssemblies.Add( assembly.FullName, assembly );
}
public static IContainer Instance
{
get
{
if( _instance == null )
{
var builder = new ContainerBuilder();
builder.RegisterAssemblyModules( _moduleAssemblies.Select( ma => ma.Value ).ToArray() );
_instance = builder.Build();
}
return _instance;
}
}
}
You use this by including lines like this in the startup code of an application:
public static void Main(string[] args)
{
AutoFacRegistrar.Container.RegisterAutofacModuleAssembly<ScannerApp>();
AutoFacRegistrar.Container.RegisterAutofacModuleAssembly<AppConfiguration>();
Is this a reasonable solution? If there's a better, more flexible one I'd be interested in learning about it.
Issue in IOptions<> Binding System
In implementing #Nightowl888's suggestion, I ran into a problem with the Microsoft configuration IOptions<> system. Here's how I'm trying to configure Autofac to resolve AppConfiguration objects:
protected override void Load( ContainerBuilder builder )
{
base.Load( builder );
var config = new ConfigurationBuilder()
.AddJsonFile( AppConfiguration.WebJobsConfigFile, false )
.AddUserSecrets<ConfigurationModule>()
.AddEnvironmentVariables()
.Build();
builder.Register<AppConfiguration>( ( c, p ) =>
{
var retVal = new AppConfiguration( c.Resolve<ILogger>() );
config.Bind( retVal );
return retVal;
} )
.SingleInstance();
}
The problem occurs in the call to Bind(). As it traverses and parses the configuration information, it expects to create various objects thru parameterless constructors...which makes it difficult to use constructor injectcion.
If I can't use constructor injection, I need to be able to resolve against the DI container within constructor code. I don't see how I can define a library assembly which doesn't hardwire in a particular DI container's resolution semantics.
Thoughts? Other than "abandon the IOptions<> system", which I've considered, but it provides a number of benefits I'd like to maintain.
Every executable application should have its own unique DI configuration. Libraries and frameworks should be built to be DI-friendly, but not actually reference any DI container.
A composition root is an application's configuration. Sharing it between applications is similar to sharing a .config file between applications - that is, it is not usually done. See Composition Root Reuse.
If you are going to use autofac modules, they should be a part of the application that uses them, not included in the assembly with the components that are being composed. While it may seem like you are gaining something by not having to repeat the configuration code in every application, the main issue with doing this is that it means your application has lost one of the main benefits of DI - that is, it cannot provide an alternative implementation of any given component. The whole point of making a library loosely-coupled is that it allows the decision of how the application will be coupled together to ultimately be made by the application that hosts the components.
Pet peeve: Also note that how many projects or solutions you have has absolutely nothing to do with the runtime behavior of an application (such as how it is composed). Projects and solutions are a way to organize code before it is compiled - once the code is compiled, there is no concept of "project" or "solution", all you are left with are "assemblies" that may depend on other "assemblies". For each application you end up with an executable assembly and 0 or more dependent assemblies. The composition root should only exist in the executable assembly.

StructureMap Registries in Unit Test Projects: No default Instance is registered

Using StructureMap...
I have a series of assemblies & a single IoC entry-point within the BUSINESS layer wherein I define my ContainerRegistry like so:
BUSINESS.DLL REGISTRY:
The WorkflowProvider class lives in its' own library. As such, I am passing-in the BUSINESS LAYERS assembly into the WorkflowProvider so it can "find" certain (expected) classes within the BUSINESS LAYER dynamically.
// I have simplified this class for the question
public ContainerRegistry()
{
Scan(
scan =>
{
scan.TheCallingAssembly();
scan.WithDefaultConventions();
scan.LookForRegistries();
scan.AssembliesFromApplicationBaseDirectory(f => f.FullName.StartsWith("My.Awesome.Application", true, null));
scan.AddAllTypesOf(typeof(IWorkflowProvider));
scan.SingleImplementationsOfInterface();
});
For<IWorkflowProvider>().Use<WorkflowProvider>()
.Ctor<Assembly>().Is(Assembly.GetExecutingAssembly());
}
THE EXCEPTION:
Now, I want to write a set of Unit Tests for all assemblies - including the BUSINESS & WORKFLOW assemblies. However, when I try to create an instance of the WorkflowProvider I am getting the following exception:
var container = IoC.Initialize();
var workflowProvider = container.GetInstance<WorkflowProvider>()
No default Instance is registered and cannot be automatically
determined for type 'System.Reflection.Assembly'
THE GOAL:
Additionally, when I use the BUSINESS LAYERS ContainerRegistry from the Unit Test project I want to substitute the UnitTest Assembly in this part of the mapping:
For<IWorkflowProvider>().Use<WorkflowProvider>()<br/>
.Ctor<Assembly>().Is(Assembly.GetExecutingAssembly());
QUESTIONS:
Does the Unit Test project also need its' own Registry?
And, if so, what does it look like?
And, if so, how do I consume 2 registries in 1 project?
How do I substitute the GetExecutingAssembly portion?
What is the right approach here?
Hopefully, I am explaining this right.
Thanks
This is the only solution I have found. I am hoping someone else will come up with a better solution.
var args = new ExplicitArguments();
args.Set<Assembly>(Assembly.GetExecutingAssembly());
var container = IoC.Initialize();
var workflowProvider = container.GetInstance<WorkflowProvider>(args);

Dependency Registrar for other assembly

I have a simple question about dependecy registration.
I'm developing a brand new web application that use Engine Context paradigm with Autofac container. For any library on the solution I have one class implementing IDependencyRegistrar that implement a common Register method, due to add one the container some specific implementation of some interfaces and components.
In this way, a base Core library (running at application startup) provide a RegisterDependencies method that lookup on every Executing Assembly to discover all the DDL's used by the application and registering them on Autofac Container.
The code that provide this behavior is:
builder = new ContainerBuilder();
var drTypes = typeFinder.FindClassesOfType<IDependencyRegistrar>();
var drInstances = new List<IDependencyRegistrar>();
foreach (var drType in drTypes)
drInstances.Add((IDependencyRegistrar) Activator.CreateInstance(drType));
//sort
drInstances = drInstances.AsQueryable().OrderBy(t => t.Order).ToList();
foreach (var dependencyRegistrar in drInstances)
dependencyRegistrar.Register(builder, typeFinder, config);
builder.Update(container);
Where the FindClassOfType<IDependencyRegistrar> works thanks to a Method implementation like that:
public virtual IList<Assembly> GetAssemblies()
{
var addedAssemblyNames = new List<string>();
var assemblies = new List<Assembly>();
if (LoadAppDomainAssemblies)
AddAssembliesInAppDomain(addedAssemblyNames, assemblies);
AddConfiguredAssemblies(addedAssemblyNames, assemblies);
return assemblies;
}
And, AddAssemblyInAppDomain is:
private void AddAssembliesInAppDomain(List<string> addedAssemblyNames, List<Assembly> assemblies)
{
foreach (Assembly assembly in AppDomain.CurrentDomain.GetAssemblies())
{
if (Matches(assembly.FullName))
{
if (!addedAssemblyNames.Contains(assembly.FullName))
{
assemblies.Add(assembly);
addedAssemblyNames.Add(assembly.FullName);
}
}
}
}
The problem is: when I end up on adding in mysolution the MVC project (the front-end), I've referenced on it only direct accessing library (service layer and some infrastructure components) but no DataLayer components and some other DLL. Due to the fact that MVC not referencing directly some libraries of deep layers, my Engine Context doesn't see the others sub-components and not registering them on the Autofac container, causing a
'no registered services'
exception when execution make explicit request on them.
The whole system just works if I add reference to any library from the MVC project but, for layered architectured application, this is not a best practice: my MVC need to know nothing about DataLayer or others low-layered services.
However, in this way, no ExecutingAssembly are discovered, so, not dependency are registered anymore.
Wich is the best approch to resolve this situation without referencing all assemblies directly from main MVC project?
What you are trying to do is described in Autofac documentation as Assembly Scanning, take a look here. Basically, to get all assemblies in IIS-hosted application you need this piece of code:
var assemblies = BuildManager.GetReferencedAssemblies().Cast<Assembly>();
EDIT:
Ok, so I understand the situation is like this:
Project Web is a MVC web app.
Project Model is a class library where you have your contracts (interfaces) defined, e.g. for DAL, but also for Web.
Project DAL contains some implementations of contracts from Model.
There might be some additional class libraries, but they all uses Model for contracts.
So to sum up - all projects have reference to Model, but they have no references to each other.
I think for every library (except Model) you should create a module. To do so, create a class implementing Module type from Autofac library and override Load method - put all your module registration in there. Then, in Web app start you should load all assemblies and register their modules. But, as you mentioned, assemblies other than Web are not present in bin directory; you should copy them there "manually", for example in Post-Build action (Project Properties -> Build Events -> Post-Build action). The following command should do the work:
xcopy /Y "$(TargetDir)*.dll" "$(ProjectDir)..\{Your Web App}\bin"
Also, in your solution properties you should set, that Web project "depends" on all other projects. It would assure all other libraries would be build before Web. It does not add any reference between these assemblies.
Then, during application startup, you should search for you assemblies in bin folder and register each assembly module, like this:
var containerBuilder = new ContainerBuilder();
containerBuilder.RegisterControllers(typeof(MvcApplication).Assembly);
var libFolder = new DirectoryInfo(HostingEnvironment.MapPath("~/bin"));
var libFiles = libFolder.GetFiles("*.dll", SearchOption.AllDirectories);
foreach (var lib in libFiles)
{
var asm = Assembly.LoadFrom(lib.FullName);
containerBuilder.RegisterAssemblyModules(asm);
}
var container = containerBuilder.Build();
You might want to add some filter to libFolder.GetFiles() to retreive only your assemblies, not all from bin.
If your other assemblies contains Mvc Controllers, you should take a look how to manage the situation here (see Initializer class). Basically, in pre-start of application you would need to add assemblies to BuildManager. Otherwise, the code above should work just fine.
If you are working on a non-web project then my answer might help?
To your Ioc class add a method i.e:
public static void SetIocForTesting(bool forUnitTesting)
{
_testContext = forUnitTesting;
}
Sample container set-up code, delegate out the responsibility of getting the assemblies to load into the builder. i.e GetModules():
public static IContainer Container
{
get
{
if (_container != null)
{
return _container;
}
var builder = new ContainerBuilder();
foreach (var lib in GetModules())
{
builder.RegisterAssemblyModules(lib);
}
_container = builder.Build();
return _container;
}
}
When scanning for Assemblies, switch on the testContext variable:
private static IEnumerable<Assembly> GetModules()
{
if (_testContext)
{
return AppDomain.CurrentDomain.GetAssemblies();
}
var currentPath = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
if (currentPath == null)
{
throw new NullReferenceException("Unable to build the container because currentPath variable is null.");
}
// XXXX = assign a wild card
var libFolder = new DirectoryInfo(currentPath);
var libFiles = libFolder.GetFiles("XXXX.*.dll", SearchOption.TopDirectoryOnly);
return libFiles.Select(lib => Assembly.LoadFrom(lib.FullName)).ToList();
}
When unit testing your IoC provider and a registration:
protected virtual void GivenThat()
{
IocProvider.SetIocForTesting(true);
}
.. you have a method that switches the IoC to ensure it works correctly with all assemblies referenced and loaded by your test project. The above method lives inside an abstract base class I use for BDD style unit testing.
Your test project usually ends up referencing a lot of assemblies which means resolving services have a higher success rate.
Finally, for non UnitTesting code add a static constructor:
static IocProvider()
{
_testContext = false;
}
This will ensure a default work flow for production code.
Feel free to play with the above format to suit your needs; I hope it helps someone in the way the above question and answer helped me.

Ninject to load different implementations

I have a web service (let's call it WebSite) that uses an Interface (IDataService). the webservice project implements a "fake" service with hardcoded objects (DesignDataService) that I use to develop the websit while I wait for my colleagues to build the real implementation (BreadDataService).
My NinjectWebCommon is currently like this:
private static void RegisterServices(IKernel kernel)
{
kernel.Bind<IDataService>().To<DesignDataService>();
}
What I want is to be able to provide my colleague a way to test the BreadDataService on my WebService, while I can go on using DesignDataService. I can't use the BreadDataService on my machine because it requires some assemblies that I don't have (+ the database).
So, what is the approach here? The current dependency tree is like that:
ServiceCore (defines IDataService)
WebSite uses ServiceCore
BreadDataService uses ServiceCore
I don't want to reference the BreadDataService project inside the WebSite Project, I was maybe thinking about a folder in WebSite where they can put the BreadDataService dll and ninject takes it depending on some configuration in web.config.
Hints?
Something like this will do the trick
load external assemblies
search for an implementation
default to your design time version if none are found
Here's the basic code
IEnumerable<Assembly> assemblies = this.LoadAssemblies(#"C:\Temp");
Type implementation = FindImplementation(assemblies, typeof(IDataService));
IKernel kernel = new StandardKernel();
kernel.Bind<IDataService>().To(implementation ?? typeof(DesignDataService));
This method will load external assemblies (such as plugins) from a specific folder
private IEnumerable<Assembly> LoadAssemblies(string folder)
{
IEnumerable<string> dlls =
from file in new DirectoryInfo(folder).GetFiles()
where file.Extension == ".dll"
select file.FullName;
IList<Assembly> assemblies = new List<Assembly>();
foreach (string dll in dlls)
{
try
{
assemblies.Add(Assembly.LoadFile(dll));
}
catch
{
}
}
return assemblies;
}
And this method will search a set of assemblies for an implementation. Please note that I have specifically used SingleOrDefault() so that this will fail if there is more than one implementation.
private Type FindImplementation(
IEnumerable<Assembly> assemblies,
Type serviceType)
{
var implementationType = (
from dll in assemblies
from type in dll.GetExportedTypes()
where serviceType.IsAssignableFrom(type)
where !type.IsAbstract
where !type.IsGenericTypeDefinition
select type)
.SingleOrDefault();
return implementationType;
}
I used qujck approach to build these extension methods.
The main difference is that it relies on Ninject.Extensions.Conventions' FromAssembliesInPath method

Categories