>我有一个关于依赖注册的简单问题。
我正在开发一个全新的 Web 应用程序,该应用程序将引擎上下文范式与 Autofac 容器一起使用。对于解决方案上的任何库,我有一个实现IDependencyRegistrar
实现通用Register
方法的类,因为添加一个容器,一些接口和组件的特定实现。
通过这种方式,基本核心库(在应用程序启动时运行)提供了一种RegisterDependencies
方法,该方法查找每个正在执行的程序集,以发现应用程序使用的所有 DDL 并将其注册到 Autofac 容器上。
提供此行为的代码是:
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);
由于这样的方法实现,FindClassOfType<IDependencyRegistrar>
工作的地方:
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;
}
而且,AddAssemblyInAppDomain
是:
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);
}
}
}
}
问题是:当我最终在mysolution中添加MVC项目(前端)时,我只引用了直接访问库(服务层和一些基础设施组件),但没有数据层组件和其他一些DLL。由于 MVC 没有直接引用一些深层库,我的引擎上下文看不到其他子组件,也没有在 Autofac 容器上注册它们,从而导致
"没有注册服务"
执行时异常 对它们发出显式请求。
如果我从 MVC 项目中添加对任何库的引用,整个系统就可以工作,但对于分层架构应用程序,这不是最佳实践:我的 MVC 不需要了解 DataLayer 或其他低层服务。
但是,通过这种方式,不会发现执行程序集,因此不再注册依赖项。
在不直接从主 MVC 项目引用所有程序集的情况下,是否是解决此问题的最佳方法?
您尝试执行的操作在 Autofac 文档中描述为程序集扫描,请查看此处。基本上,若要获取 IIS 承载的应用程序中的所有程序集,您需要这段代码:
var assemblies = BuildManager.GetReferencedAssemblies().Cast<Assembly>();
编辑:
好的,所以我理解情况是这样的:
Project Web 是一个 MVC Web 应用程序。
项目模型是一个类库,您可以在其中定义合约(接口),例如,用于 DAL,也用于 Web。
DAL项目包含模型中的契约的一些实现。
可能还有一些额外的类库,但它们都使用协定模型。
所以总结一下 - 所有项目都有对模型的引用,但它们彼此之间没有引用。
我认为对于每个库(模型除外),您应该创建一个模块。为此,请从库中创建一个实现Module
类型的类Autofac
并覆盖Load
方法 - 将所有模块注册放在那里。然后,在 Web 应用启动中,应加载所有程序集并注册其模块。但是,正如您提到的,bin 目录中不存在 Web 以外的程序集;您应该将它们"手动"复制到那里,例如在"生成后"操作("项目属性"->"生成事件"-">"生成后"操作)中。以下命令应完成工作:
xcopy /Y "$(TargetDir)*.dll" "$(ProjectDir)..{Your Web App}bin"
此外,在解决方案属性中,应设置该 Web 项目"依赖于"所有其他项目。它将确保所有其他库将在Web之前构建。它不会在这些程序集之间添加任何引用。
然后,在应用程序启动期间,您应该在 bin 文件夹中搜索程序集并注册每个程序集模块,如下所示:
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();
您可能希望向libFolder.GetFiles()
添加一些筛选器,以便仅检索程序集,而不是全部从 bin 中检索。
如果其他程序集包含 Mvc 控制器,则应在此处查看如何管理这种情况(请参阅Initializer
类)。基本上,在应用程序的预启动中,您需要将程序集添加到BuildManager
。否则,上面的代码应该可以正常工作。
如果您正在从事非 Web 项目,那么我的答案可能会有所帮助?
在您的 Ioc 类中添加一个方法,即:
public static void SetIocForTesting(bool forUnitTesting)
{
_testContext = forUnitTesting;
}
示例容器设置代码,将加载程序集的责任委托给生成器。 即 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;
}
}
扫描程序集时,打开 testContext 变量:
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();
}
对 IoC 提供程序和注册进行单元测试时:
protected virtual void GivenThat()
{
IocProvider.SetIocForTesting(true);
}
.. 您有一个切换 IoC 的方法,以确保它与您的测试项目引用和加载的所有程序集一起正常工作。上面的方法位于我用于 BDD 样式单元测试的抽象基类中。
测试项目通常最终会引用大量程序集,这意味着解析服务的成功率更高。
最后,对于非单元测试代码,添加一个静态构造函数:
static IocProvider()
{
_testContext = false;
}
这将确保生产代码的默认工作流。
随意使用上述格式以满足您的需求;我希望它能像上述问题和答案帮助我一样帮助某人。