ASP.Net Core 在运行时注册控制器

Posted

技术标签:

【中文标题】ASP.Net Core 在运行时注册控制器【英文标题】:ASP.Net Core register Controller at runtime 【发布时间】:2018-02-19 18:01:58 【问题描述】:

我在问自己是否可以在运行时加载带有Controllers 的 DLL 并使用它。

我发现的唯一解决方案是通过ApplicationPartStartUp 上添加程序集:

var builder = services.AddMvc();
builder.AddApplicationPart(
    AssemblyLoadContext.Default.LoadFromAssemblyPath(
        @"PATH\ExternalControllers.dll"
));

有谁知道是否可以随时注册Controller,因为该解决方案的问题是,当您想添加另一个包含Controllers 的DLL 时,您必须重新启动WebService .如果您可以随时添加它们并在应用程序中随时注册它们,那就太好了。

【问题讨论】:

例如,当我想编写一个基于插件的 WebAPI 时,您可以通过简单地将 DLL 添加到文件夹中来修改 WebAPI,然后抓取它并将其加载到 WebAPI 中。这样 WebAPI 是超级动态的,而原始的 WebAPI 是愚蠢的。它通过每个插件提高了他的智力。 而重启应用的问题是什么? 问题是@poke 在生产中重新启动它有点烦人。我的意思是它没有没有这个功能那么糟糕,但如果它可以在没有重新启动的情况下制作,那将是完美的。 那你就倒霉了。标准的依赖注入系统不允许您在实现服务集合后更改它。因此,您必须为此使用不同的 DI 容器,以便以后进行修改(或添加 DI 模块;我相信 Autofac 会这样做)。那时,您可能已经在复杂性方面非常深入,所以我真的不确定这是否值得。 – 您是否考虑过使用多个独立的 Web 应用程序? @ChrisPratt 如果您正在构建一个可重用的框架,那么在某些情况下,您希望使用可从调用程序集配置的通用参数动态创建控制器。 【参考方案1】:

现在可以在 .net core 2.0+ 上实现这一点

请看代码ActionDescriptorCollectionProvider.cs:

    public ActionDescriptorCollectionProvider(
        IEnumerable<IActionDescriptorProvider> actionDescriptorProviders,
        IEnumerable<IActionDescriptorChangeProvider> actionDescriptorChangeProviders)
    
        _actionDescriptorProviders = actionDescriptorProviders
            .OrderBy(p => p.Order)
            .ToArray();

        _actionDescriptorChangeProviders = actionDescriptorChangeProviders.ToArray();

        ChangeToken.OnChange(
            GetCompositeChangeToken,
            UpdateCollection);
    

第一步:实现IActionDescriptorChangeProvider类:

public class MyActionDescriptorChangeProvider : IActionDescriptorChangeProvider

    public static MyActionDescriptorChangeProvider Instance  get;  = new MyActionDescriptorChangeProvider();

    public CancellationTokenSource TokenSource  get; private set; 

    public bool HasChanged  get; set; 

    public IChangeToken GetChangeToken()
    
        TokenSource = new CancellationTokenSource();
        return new CancellationChangeToken(TokenSource.Token);
    

第 2 步:在 Startup.ConfigureServices() 上添加单例:

public void ConfigureServices(IServiceCollection services)

    services.AddMvc();
    services.AddSingleton<IActionDescriptorChangeProvider>(MyActionDescriptorChangeProvider.Instance);
    services.AddSingleton(MyActionDescriptorChangeProvider.Instance);

第三步:在运行时注册控制器:

public class TestController : Controller

    private readonly ApplicationPartManager _partManager;
    private readonly IHostingEnvironment _hostingEnvironment;
    public TestController(
        ApplicationPartManager partManager,
        IHostingEnvironment env)
    
        _partManager = partManager;
        _hostingEnvironment = env;
    
    public IActionResult RegisterControllerAtRuntime()
    
        string assemblyPath = @"PATH\ExternalControllers.dll";
        var assembly = AssemblyLoadContext.Default.LoadFromAssemblyPath(assemblyPath);
        if (assembly != null)
        
            _partManager.ApplicationParts.Add(new AssemblyPart(assembly));
            // Notify change
            MyActionDescriptorChangeProvider.Instance.HasChanged = true;
            MyActionDescriptorChangeProvider.Instance.TokenSource.Cancel();
            return Content("1");
        
        return Content("0");
    

【讨论】:

您能解释一下这是如何工作的,或者为什么以及是否有文档?谢谢。 @cnxiaboy 你真的需要对单例 MyActionDescriptorChange() 的 3 个不同引用吗?是否可以删除静态 Instance 属性和 services.AddSingleton(MyActionDescriptorChangeProvider.Instance) 行,然后是 services.AddSingleton(new MyActionDescriptorChangeProvider())?【参考方案2】:

现在可以了。

请查看关于how to add dynamic controllers的更新文档:

public class GenericControllerFeatureProvider : IApplicationFeatureProvider<ControllerFeature>

    public void PopulateFeature(IEnumerable<ApplicationPart> parts, ControllerFeature feature)
    
        // This is designed to run after the default ControllerTypeProvider, 
        // so the list of 'real' controllers has already been populated.
        foreach (var entityType in EntityTypes.Types)
        
            var typeName = entityType.Name + "Controller";
            if (!feature.Controllers.Any(t => t.Name == typeName))
            
                // There's no 'real' controller for this entity, so add the generic version.
                var controllerType = typeof(GenericController<>)
                    .MakeGenericType(entityType.AsType()).GetTypeInfo();
                feature.Controllers.Add(controllerType);
            
        
    

【讨论】:

我不明白你的问题? 此控制器注册在设置过程中仅进行一次,并非对控制器的每个请求都正确 是的。我只是在我的本地主机上快速运行,它只被调用了一次。提供者类型注册一次然后多次调用也是有道理的。 如果你想用它来定义包含哪些控制器,哪些不包含,那么在开头使用feature.Controllers.Clear(),然后循环遍历你想要包含的所有控制器(使用反射) 您如何为您的控制器命名?因为路由是根据类名猜测控制器的。

以上是关于ASP.Net Core 在运行时注册控制器的主要内容,如果未能解决你的问题,请参考以下文章

[Asp.Net Core]内置容器基本使用

在 2 个控制器 ASP.NET Core 3.1 MVC 之间传递 ID

从 ASP.NET Core 中的控制器操作运行后台任务

在 ASP.NET Core Web API 中注册一个新的 DelegatingHandler

如何在 ASP.NET Core 中使控制器作用域或单例而不是瞬态?

如何更改 ASP.NET Core API 中的默认控制器和操作?