我在外部库中有MVC区域,它们拥有自己的区域注册码,就像普通的MVC区域一样。为每个dll(模块)调用此区域注册,并且我已经验证了RouteTable包含加载完成后所加载模块的所有路由。
当我在主站点中引用这些外部区域时,它们会被拉入bin目录并加载正常。也就是说,当请求外部库中存在的路由时,正确的类型将传递给我的自定义控制器工厂(Ninject),并且可以实例化控制器。
一旦我将这些dll移动到bin目录之外(比如说到一个Modules文件夹),就会出现路由问题。我已检查过RouteTable是否具有所有必需的路由,但是当请求进入ninject控制器工厂时,请求的类型为null。从这里阅读an SO link here,当ASP.NET MVC找不到匹配请求路由的控制器或者不知道如何理解路由时,似乎会发生这种行为。
在外部加载模块时,我确保通过调用Assemby.LoadFrom(modulePath);
我做了一些研究,似乎在尝试在bin之外加载库时,您需要在app.config中指定私有探测为pointed out here;。我已经设置为'bin \ Modules',这也是mvc区域模块也被移动的地方。
有没有人有任何想法为什么简单地将一个mvc区域项目移到bin文件夹之外会导致传递给控制器工厂的请求类型为null,导致控制器被实例化?
修改
下面是一段代码片段,用于创建新的Ninject内核,从文件中读取模块名称列表以启用,然后在bin / Modules目录中搜索已启用的模块。模块通过程序集加载器加载,其区域已注册,然后加载到ninject内核中。
// comma separated list of modules to enable
string moduleCsv = ConfigurationManager.AppSettings["Modules.Enabled"];
if (!string.IsNullOrEmpty(moduleCsv)) {
string[] enabledModuleList = moduleCsv.Replace(" ", "").Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
_Modules = enabledModuleList ?? new string[0];
// load enabled modules from bin/Modules.
var moduleList = Directory.GetFiles(Server.MapPath("~" + Path.DirectorySeparatorChar + "bin" + Path.DirectorySeparatorChar + "Modules"), "*.dll");
foreach (string enabledModule in enabledModuleList) {
string modulePath = moduleList.Single(m => m.Contains(enabledModule));
// using code adapted from from AssemblyLoader
var asm = AssemblyLoader.LoadAssembly(modulePath);
// register routes for module
AreaRegistrationUtil.RegisterAreasForAssemblies(asm);
// load into Ninject kernel
kernel.Load(asm);
}
}
这是Ninject控制器工厂的关键,它接收上述Ninject内核并处理制作控制器的请求。对于存在于bin / Modules中的程序集内的控制器,GetControllerType(...)将为请求的控制器名称返回null。
public class NinjectControllerFactory : DefaultControllerFactory
{
#region Instance Variables
private IKernel _Kernel;
#endregion
#region Constructors
public NinjectControllerFactory(IKernel kernel)
{
_Kernel = kernel;
}
protected override Type GetControllerType(System.Web.Routing.RequestContext requestContext, string controllerName)
{
// Is null for controller names requested outside of bin directory.
var type = base.GetControllerType(requestContext, controllerName);
return type;
}
protected override IController GetControllerInstance(System.Web.Routing.RequestContext requestContext, Type controllerType)
{
IController controller = null;
if (controllerType != null)
controller = _Kernel.Get(controllerType) as IController;
return controller;
}
}
更新Ninject Nuget安装
由于某种原因,我无法通过NuGet安装Ninject.MVC3。单击安装按钮时,Visual Studio提供了一些schemaVersion错误(我已经安装了其他Nuget软件包,如ELMAH btw)。
我确实找到了其他有趣的东西,那就是如果我将额外的模块组件传递给我所拥有的NinjectControllerFactory并在无法解析类型时搜索它们,它会找到正确的类型并且能够构建控制器。这导致了另一个奇怪的问题。
从外部模块请求的第一条路线是auth和注册模块中的/ Account / LogOn。虚拟路径提供程序在找到视图并尝试将其呈现为抱怨丢失的命名空间后,会在此处引发错误。这会导致错误路由触发,由ErrorHandling模块处理。奇怪的是,这加载并渲染得很好!
所以我仍然坚持两个问题; 1)必须做一些狡猾的黑客攻击并将额外的模块组件传递给NinjectControllerFactory,以便能够解析外部模块中控制器的类型 2)一个特定模块的错误,它抱怨没有找到命名空间
这两个问题显然是相互关联的,因为装配加载只是没有加载并使所有可用的东西都需要。如果所有这些mvc区域都从bin目录加载,一切正常。所以它显然是命名空间/程序集加载问题。
答案 0 :(得分:1)
LoadFrom将程序集加载到加载上下文中。默认Load上下文中的其他类不能使用这些类型。可能这就是为什么找不到控制器的原因。
如果您知道必须加载哪些程序集,那么您应该始终使用Assembly.Load()。如果您不知道目录中哪些程序集已解密,则要么从文件名中猜测程序集名称,要么使用Assembly.ReflectionOnlyLoadFrom()(最好使用临时AppDomain)来获取程序集名称。然后使用Assembly.Load()使用程序集名称加载程序集。
如果您的程序集包含NinjectModules,您还可以使用kernel.Load(),它执行我上面描述的操作。但它只加载包含至少一个模块的程序集。
阅读http://msdn.microsoft.com/en-us/library/dd153782.aspx有关不同程序集上下文的内容。
以下是Ninject代码库的一个小摘录。我删除了不必要的东西,但没有尝试编译或运行,所以可能有一些小问题。
public class AssemblyLoader
{
public void LoadAssemblies(IEnumerable<string> filenames)
{
GetAssemblyNames(filenames).Select(name => Assembly.Load(name));
}
private static IEnumerable<AssemblyName> GetAssemblyNames(IEnumerable<string> filenames)
{
var temporaryDomain = CreateTemporaryAppDomain();
try
{
var assemblyNameRetriever = (AssemblyNameRetriever)temporaryDomain.CreateInstanceAndUnwrap(typeof(AssemblyNameRetriever).Assembly.FullName, typeof(AssemblyNameRetriever).FullName);
return assemblyNameRetriever.GetAssemblyNames(filenames.ToArray());
}
finally
{
AppDomain.Unload(temporaryDomain);
}
}
private static AppDomain CreateTemporaryAppDomain()
{
return AppDomain.CreateDomain(
"AssemblyNameEvaluation",
AppDomain.CurrentDomain.Evidence,
AppDomain.CurrentDomain.SetupInformation);
}
private class AssemblyNameRetriever : MarshalByRefObject
{
public IEnumerable<AssemblyName> GetAssemblyNames(IEnumerable<string> filenames)
{
var result = new List<AssemblyName>();
foreach(var filename in filenames)
{
Assembly assembly;
try
{
assembly = Assembly.LoadFrom(filename);
}
catch (BadImageFormatException)
{
// Ignore native assemblies
continue;
}
result.Add(assembly.GetName(false));
}
return result;
}
}
}