【问题标题】:MVC 6 RC2 Controllers in another assembly另一个组件中的 MVC 6 RC2 控制器
【发布时间】:2017-07-26 13:04:00
【问题描述】:

在 MVC 6 RC1 中,我们使用 IAssemlbyProvider 接口来注册在运行时发现的程序集并注入其他控制器类型,类似的 fashion to this post.. 现在随着 RC2 版本的发布,IAssemblyProvider 已被删除并已更改到(see reference)

我们的框架版本目前是net46

自升级以来,我们在外部程序集中(未引用)的控制器正在返回 404 状态。

我们已尝试通过ApplicationPartManager 手动将控制器添加到已注册的控制器中。

var mvcBuilder = services.AddMvc();
var controllerFeature = new ControllerFeature();
mvcBuilder.PartManager.PopulateFeature(controllerFeature);

var moduleControllers = ModulesManager.GetControllers();
foreach (var c in moduleControllers)
    controllerFeature.Controllers.Add(c);

mvcBuilder.PartManager.PopulateFeature(controllerFeature);

还有……

services.AddMvc().ConfigureApplicationPartManager(app =>
{
    var controllerFeature = new ControllerFeature();
    app.PopulateFeature(controllerFeature);

    var moduleControllers = ModulesManager.GetControllers();
    foreach (var c in moduleControllers)
        controllerFeature.Controllers.Add(c);

    app.PopulateFeature(controllerFeature);
});

现在程序集肯定会加载到AppDomain,因为我们的依赖注入系统正在为外部程序集中的其他项目查找和填充服务。

在我们之前的实现中,使用 IAssemblyProvider 可以很好地工作。

public class ModuleAwareAssemblyProvider : IAssemblyProvider
{
    private readonly DefaultAssemblyProvider _defaultProvider;

    public ModuleAwareAssemblyProvider(DefaultAssemblyProvider defaultProvider)
    {
        _defaultProvider = defaultProvider;
    }

    public IEnumerable<Assembly> CandidateAssemblies
    {
        get
        {
            return _defaultProvider.CandidateAssemblies.Concat(ModulesManager.Assemblies).Distinct();
        }
    }
}

我知道 RC2 仍然相对较新,但如果有人有在启动时注册额外控制器的经验会有所帮助。

干杯,尼科

【问题讨论】:

    标签: c# asp.net-core-mvc


    【解决方案1】:

    在直接与ControllerFeature 合作一段时间后没有结果,是时候回到基础了。

    基本上在应用程序启动时,控制器被注册控制器功能容器不是来自控制器功能。这是关键,因为您需要注册控制器。

    我在浏览GitHub repository for RC2 时遇到了ControllerFeatureProvider。如前所述。

    Discovers controllers from a list of <see cref="ApplicationPart"/>
    

    然后有一个方法进一步深入到PopulateFeature,我们可以看到它抓取注册到应用程序的所有部分并提取控制器接口(@98​​7654331@ 方法值得回顾)。

    /// <inheritdoc />
    public void PopulateFeature(
        IEnumerable<ApplicationPart> parts,
        ControllerFeature feature)
    {
        foreach (var part in parts.OfType<IApplicationPartTypeProvider>())
        {
            foreach (var type in part.Types)
            {
                if (IsController(type) && !feature.Controllers.Contains(type))
                {
                    feature.Controllers.Add(type);
                }
            }
        }
    }
    

    所以现在我们知道如何找到控制器了,它们来自注册到应用程序的ApplicationPart。下一个问题是我们如何创建应用程序部分

    经过一些审查并尝试使用依赖注入,手动将部件添加到应用程序以注册我的部件后,我遇到了另一个概念。

    接口IMvcBuilder 具有扩展方法AddApplicationPart,它将Assembly 添加到应用程序部分。这是通过将程序集包装在AssemblyPart 应用程序部件中来完成的。在查看AssemblyPart 时,此部件将在程序集中找到的所有类型返回给调用部件系统(在我们的例子中为ControllerFeatureProvider)。

    /// <inheritdoc />
    public IEnumerable<TypeInfo> Types => Assembly.DefinedTypes;
    

    现在AssemblyPart 的有趣之处在于方法GetReferencePaths()

    /// <inheritdoc />
    public IEnumerable<string> GetReferencePaths()
    {
        var dependencyContext = DependencyContext.Load(Assembly);
        if (dependencyContext != null)
        {
            return dependencyContext.CompileLibraries.SelectMany(library => library.ResolveReferencePaths());
        }
    
        // If an application has been compiled without preserveCompilationContext, return the path to the assembly
        // as a reference. For runtime compilation, this will allow the compilation to succeed as long as it least
        // one application part has been compiled with preserveCompilationContext and contains a super set of types
        // required for the compilation to succeed.
        return new[] { Assembly.Location };
    }
    

    看来最后的难题是在模块(或外部程序集)的 project.json 文件中启用 preserveCompilationContext

    "preserveCompilationContext": {
        "type": "boolean",
        "description": "Set this option to preserve reference assemblies and other context data to allow for runtime compilation.",
        "default": false
    }
    

    最后,实现和解决这个问题变得非常简单。我们的每个外部程序集(或模块)都是通过我们的ModuleManager 类加载的。这有一个所有引用的模块程序集的列表。所以在注册MVC的Startup.cs文件中的ConfigureServices方法中,我们只需为每个模块组件调用扩展方法AddApplicationPart

    var mvcBuilder = services.AddMvc();
    foreach(var module in ModulesManager.ReferencedModules)
    {
        mvcBuilder.AddApplicationPart(module.ReferencedAssembly);
    }
    

    完成这些小改动后,我的外部控制器停止返回 404

    【讨论】:

    • 我对你的ModuleManager 课程有点困惑。是ModuleManager 还是ModulesManager?无论哪种方式,我似乎都无法在任何文档或源代码中找到对它的引用。我该如何获取它并逐步通过ReferencedModules
    • ModulesManager 是一个自定义类(我们自己的类),用于根据特定路径和引用版本查找模块。您应该能够轻松实现自己认为合适的版本。
    • 感谢您的精彩解释。 preserveCompilationContext 为我做了诀窍。我无法相信整个 asp.net 核心的文档记录有多么糟糕。我了解它的新文档,但更高级的文档将非常受欢迎。
    • 感谢您的研究,这正是我需要知道的类似情况。
    【解决方案2】:

    只需添加 Startup -> ConfigureServices

    services.AddMvc()
    .AddApplicationPart(typeof(OtherAssemblyController).GetTypeInfo().Assembly)
    .AddControllersAsServices();
    

    【讨论】:

    • OtherAssemblyController 在依赖项目中,我无法添加对它的引用。那我该怎么办?
    【解决方案3】:

    我使用应用程序部分使用以下构建器扩展方法来实现插件功能。我希望它对某人有用。

    ASP.NET 核心 1.1

    using Microsoft.AspNetCore.Hosting;
    using Microsoft.Extensions.Configuration;
    using Microsoft.Extensions.DependencyInjection;
    using System.IO;
    using System.Runtime.Loader;
    
    namespace AppPartTest1.Web.Helpers.Features
    {
        public static class FeaturesBuilderExtensions
        {
            //
            // Summary:
            //     Adds Features supports to the Application.
            //
            // Parameters:
            //   builder:
            //     The Microsoft.Extensions.DependencyInjection.IMvcBuilder.
            public static IMvcBuilder AddFeaturesSupport(this IMvcBuilder builder, IConfigurationRoot Configuration, IHostingEnvironment environment)
            {
    
                var fileNames = Directory.GetFiles("Features", "*.dll");
    
                foreach (string fileName in fileNames)
                {
                    builder.AddApplicationPart(AssemblyLoadContext.Default.LoadFromAssemblyPath(Path.Combine(environment.ContentRootPath, fileName)));
                }
    
                return builder;
            }
        }
    }
    

    打电话

        public Startup(IHostingEnvironment env)
        {
            var builder = new ConfigurationBuilder()
                .SetBasePath(env.ContentRootPath)
                .AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
                .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true)
                .AddEnvironmentVariables();
            Configuration = builder.Build();
    
            environment = env;
        }
    
        public IHostingEnvironment environment { get; set; }
        public IConfigurationRoot Configuration { get; }
    
        // This method gets called by the runtime. Use this method to add services to the container.
        public void ConfigureServices(IServiceCollection services)
        {
            // Add framework services.
            var builder = services.AddMvc()
                .AddFeaturesSupport(this.Configuration, this.environment);
        }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2014-09-17
      • 2016-10-04
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2016-04-24
      相关资源
      最近更新 更多