带有MEF的ASP.NET MVC4:对MEF控制器的处理请求

时间:2015-01-26 17:04:46

标签: c# asp.net-mvc-4 plugins mef

我们创建了一个ASP.NET MVC 4应用程序,允许通过MEF动态加载“模块”(一个或多个控制器,视图等的包)。这些模块位于目录下,位于主机基本目录下的一个名为Modules的文件夹中。目录层次结构如下所示:

/MVC_MEF_Host
    /bin
    /obj
    /Scripts
    /Views
    /Modules
        /Module1
            /bin
            /Scripts
            /Views
        /Module2
            /bin
            /Scripts
            /Views
        /Module3
            ...

然而,我们遇到了一种奇怪的行为。针对作为模块一部分的控制器(即驻留在模块的/ bin /目录中)的请求将按顺序处理,而针对其程序集驻留在主机应用程序的/ bin /目录中的控制器的请求将并行处理(是我们所期望的。)

奇怪的是,我不确定应用程序如何区分这两者。主机程序集中的控制器将像模块中的程序集一样导出。所有控制器都以[PartCreationPolicy(CreationPolicy.NonShared)]导出。我们使用自定义控制器工厂来实例化控制器。两种类型的控制器之间的唯一区别是模块控制器位于模块的/ bin /目录中,而不是位于主机的/ bin /目录中。

幕后有一些特殊的待遇。我们设置了几个测试来确定这个问题的根源。我们发现,即使我们的自定义控制器工厂应该是我们的应用程序甚至考虑从哪个组件拉出控制器,但是在我们的控制器工厂被调用之前,对模块控制器的请求依次进入。换句话说,甚至在我们查阅合成容器以确定要使用的控制器之前,调用是按顺序进行的。当请求针对其程序集位于host / bin /中的控制器时,对控制器工厂的调用是并行的。

我们已经考虑了几种解决方法(目前领先的一种是将模块程序集复制到主机的/ bin /中),但每种解决方法都有一个严重的缺点,会影响我们的预期工作流程。

我已经包含了我们的方法的精简版本,它执行合成,以防这是我们在合成过程中所做的事情的副作用。

static void _Compose(List<string> moduleDirectories)
{
    ResetViewEngine(moduleDirectories);         // adds the modules' /Views/ paths to the view engine

    AggregateCatalog aggCat = new AggregateCatalog();

    // Load host bin directory
    aggCat.Catalogs.Add(new DirectoryCatalog(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "bin")));

    foreach (string pluginPath in moduleDirectories)
    {
        string modulePath = Path.Combine(pluginPath, "bin");

        if (Directory.Exists(modulePath))
        {
            // Add directory to private paths, required for plugin dependencies                                   
            #pragma warning disable 618
            AppDomain.CurrentDomain.AppendPrivatePath(modulePath);  // this is obsolete, but since we're not constructing an appDomain...
            #pragma warning restore 618

            foreach (string file in Directory.GetFiles(modulePath, PLUGIN_FILENAME_PATTERN))
            {
                Assembly asm = LoadAssemblyFromFile(file);      // reads the bytes of the assembly into memory and then loads via Assembly.Load()

                if (asm != null)
                {
                    AssemblyCatalog ac = new AssemblyCatalog(asm);
                    aggCat.Catalogs.Add(ac);
                }
                else
                {
                    Log("Could not load assembly: " + file);
                }
            }
        }
        else
        {
            Log("Module path " + modulePath + " does not exist.");
        }
    }

    // partsContainer is a private static member of our PartsBootstrapper class
    partsContainer = new CompositionContainer(aggCat, true);
    partsContainer.ComposeParts();
}

那么,我如何获得并行处理模块控制器的请求,就像主机/ bin /中的控制器请求一样?

1 个答案:

答案 0 :(得分:4)

对于未来遇到这种情况的人,我们找到了解决方案。我不一定理解它为什么会起作用,但似乎确实有效。

基本上,我们的自定义控制器工厂继承自DefaultControllerFactory。我们依赖于GetControllerSessionBehavior方法的默认实现,因为它结果总是SessionStateBehavior.Default。通过将我们的控制器工厂更改为继承自IControllerFactory,我们能够定义自己的GetControllerSessionBehavior实现。

我们的实现返回SessionStateBehavior.ReadOnly但这还不够。 MVC阻止我们写入会话,尽管我们已经读到它实际上允许会话写入时会话状态行为是ReadOnly(并且只是回到SessionStateBehavior.Required状态)。因此,我们添加了一个新的ExportMetadata,允许我们为SessionStateBehavior方法指定GetControllerSessionBehavior,这似乎有效。现在,我们默认为SessionStateBehavior.ReadOnly,除非元数据指定了不同的行为。

我们仍然不明白为什么主机&bin / s /中的程序集与模块程序集的处理方式不同,但此解决方案适用于我们。