我需要在 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 错误。
然后第二个请求实际上将按预期工作:
第二个请求返回预期结果:
我一定做错了,可能我的中间件在流程中执行得太晚,无法立即反射(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/