asp.net-mvc - 如何在 ASP.NET Core 6 MVC 应用程序中动态添加 Controller

标签 asp.net-mvc asp.net-web-api

我需要在 ASP.NET Core 6 MVC 应用程序中动态创建 Controller 。

我找到了一些方法来实现这一点,但不完全是。

我能够动态添加我的 Controller ,但不知何故它仅反射(reflect)在第二个请求上。

所以这就是我所做的:首先按如下方式初始化我的控制台应用程序:

using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Mvc.ApplicationParts;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.AspNetCore.Mvc.Infrastructure;

namespace DynamicControllerServer
{
    internal class Program
    {
        static void Main(string[] args)
        {
            var builder = WebApplication.CreateBuilder(args);

            builder.Services.AddControllers();

            ApplicationPartManager partManager = builder.Services.AddMvc().PartManager;

            // Store thePartManager in my Middleware to be able to add controlelr after initialization is done
            MyMiddleware._partManager = partManager;

            // Register controller change event
            builder.Services.AddSingleton<IActionDescriptorChangeProvider>(MyActionDescriptorChangeProvider.Instance);
            builder.Services.AddSingleton(MyActionDescriptorChangeProvider.Instance);

            var app = builder.Build();

            app.UseAuthorization();
            app.MapControllers();

            // Add Middleware which is responsible to cactn the request and dynamically add the missing controller
            app.UseMiddleware<MyMiddleware>();

            app.RunAsync();

            Console.WriteLine("Server has been started successfully ...");

            Console.ReadLine();
        }
    }
}

然后我的中间件看起来像这样:它基本上检测到 url 中存在“dynamic”关键字。如果是这样,它将加载包含DynamicController的程序集:

using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc.ApplicationParts;
using System;
using System.Reflection;

namespace DynamicControllerServer
{
    public class MyMiddleware
    {
        public RequestDelegate _next { get; }
        private string dllName = "DynamicController1.dll";
        static public ApplicationPartManager _partManager = null;

        public MyMiddleware(RequestDelegate next)
        {
            _next = next;
        }

        public async Task Invoke(HttpContext httpContext)
        {

            if (httpContext.Request.Path.HasValue)
            {
                var queryParams = httpContext.Request.Path.Value;
                if(httpContext.Request.Path.Value.Contains("api/dynamic"))
                {
                    // Dynamically load assembly 
                    Assembly assembly = assembly = Assembly.LoadFrom(@"C:\Temp\" + dllName);

                    // Add controller to the application
                    AssemblyPart _part = new AssemblyPart(assembly);
                    _partManager.ApplicationParts.Add(_part);

                    // Notify change
                    MyActionDescriptorChangeProvider.Instance.HasChanged = true;
                    MyActionDescriptorChangeProvider.Instance.TokenSource.Cancel();
                }
            }

            await _next(httpContext); // calling next middleware

        }
    }
}

ActionDescriptorChange 提供程序如下所示:

using Microsoft.AspNetCore.Mvc.Infrastructure;
using Microsoft.Extensions.Primitives;

namespace DynamicControllerServer
{
    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);
        }
    }
}

动态 Controller 位于单独的dll中,非常简单:

using Microsoft.AspNetCore.Mvc;

namespace DotNotSelfHostedOwin
{
    [Route("api/[controller]")]
    [ApiController]
    public class DynamicController : ControllerBase
    {
        public string[] Get()
        {
            return new string[] { "dynamic1", "dynamic1", DateTime.Now.ToString() };
        }
    }
}

以下是该项目中使用的包:

<PackageReference Include="Microsoft.AspNetCore" Version="2.2.0" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.2.3" />

这“几乎”工作得很好......当第一个请求发出时:

https://localhost:5001/api/dynamic

然后它进入中间件并加载程序集,但返回 404 错误。

然后第二个请求实际上将按预期工作:

enter image description here

第二个请求返回预期结果:

enter image description here

我一定做错了,可能我的中间件在流程中执行得太晚,无法立即反射(reflect)动态 Controller 。

问题是:实现这一目标的正确方法应该是什么?

我的第二个问题是,现在保存动态 Controller 的外部 dll 已更新。

如何重新加载该 Controller 以获得新定义?

如有任何帮助,我们将不胜感激

提前致谢

尼克

最佳答案

这是我自己的问题的答案,以防它可以帮助那里的人。 似乎从中间件构建和加载 Controller 总是会在第一次调用时失败。 这是有道理的,因为我们已经在 http 管道中了。 我最终从中间件外部做了同样的事情。 基本上,我的应用程序检测到 Controller 组件中的更改,卸载原始组件并加载新组件。 您不能使用默认上下文,因为它不允许为同一程序集重新加载不同的 dll:

var assembly = AssemblyLoadContext.Default.LoadFromAssemblyPath(assemblyPath); // Produce an exception on updates

为了能够为同一程序集重新加载新的 dll,我将每个 Controller 加载到其自己的程序集上下文中。为此,您需要创建自己的从 AssemblyLoadContext 派生的类并管理程序集加载:

public class MyOwnContext: AssemblyLoadContext
    {
      // You can find lots of example in the net
    }

当你想卸载程序集时,只需卸载上下文即可:

MyOwnContextObj.Unload();

现在要动态添加或删除 Controller ,您需要保留 PartManager 和 ApplicationPart 的引用。 添加 Controller

ApplicationPart part = new AssemblyPart(assembly);
_PartManager.ApplicationParts.Add(part);

删除:

_PartManager.ApplicationParts.Remove(part);

当然一旦完成,仍然使用以下代码来确认更改:

MyActionDescriptorChangeProvider.Instance.HasChanged = true;
MyActionDescriptorChangeProvider.Instance.TokenSource.Cancel();

允许在不中断服务的情况下动态更新 Controller 。 希望这对人们有帮助。

关于asp.net-mvc - 如何在 ASP.NET Core 6 MVC 应用程序中动态添加 Controller ,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/74477394/

相关文章:

asp.net - 无法转换类型为 ‘System.Web.UI.LiteralControl’ 的对象错误

asp.net-mvc - 已为布局页面定义部分但未呈现 "~/Views/Shared/_Layout.cshtml": "head"

asp.net - 页面检查器 : URL must map to a project in the current solution

c# - Entity Framework 5 使用 SaveChanges 添加审计日志

c# - 从 ViewBag 不可用的 Web Api 项目使用 Mvc Mailer 发送电子邮件

启用了 CORS 的 c# Web Api 并且请求的资源上存在可怕的 No 'Access-Control-Allow-Origin' header

c# - breezejs 和 EF6 中基于角色的安全性

c# - 从资源文件动态生成可用的本地化

c# - MVC Web API post 方法接收到 MailMessage 对象为空

c# - 自定义属性对象生命周期和隔离