Daniel Grandis Daniel Grandis - 1 month ago 11
C# Question

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?

Answer

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.