经过一番搜索,我终于找到了根据传入的请求路径在运行时创建控制器的最佳解决方案。
这是解决方案 - 您必须使用中间件。在中间件中,您可以根据请求路由进行编译,并将其作为 AssemblyPart 添加到您当前的程序集中。例子:
请求
http://localhost:56002/Account/getdata
进来。您可以在中间件中获取“/Account/getdata”,并使用内部名称为getdata的get方法编译一个帐户控制器。中间件编译控制器,将其作为AssemblyPart添加到当前运行的程序集中,中间件完成后,控制器就可以直接使用了。
首先您必须添加一个中间件。在那里你可以
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
//...
app.UseMiddleware<MyMiddleware>();
中间件可能如下所示:
public class MyMiddleware
{
public MyMiddleware(RequestDelegate requestDel, ControllerGenerator generator)
{
RequestDel = requestDel;
Generator = generator;
}
public RequestDelegate RequestDel { get; }
public ControllerGenerator Generator { get; }
public async Task Invoke(HttpContext context)
{
if (context.Request.Path.HasValue)
{
var queryParams = context.Request.Path.Value;
var entityName = queryParams.Split("/").Where(s => !string.IsNullOrEmpty(s)).FirstOrDefault();
var result = Generator.AppendController(entityName);
Console.WriteLine(result + ", " + queryParams);
}
await RequestDel.Invoke(context);
}
}
在我的例子中,控制器生成器看起来像这样。这里重要的是ApplicationPartManager,它可以用来将程序集(部件)添加到当前程序集。
public class ControllerGenerator
{
private readonly ApplicationPartManager _partManager;
private readonly IHostingEnvironment _hostingEnvironment;
public ControllerGenerator(
ApplicationPartManager partManager,
IHostingEnvironment env)
{
_partManager = partManager;
_hostingEnvironment = env;
}
public bool AppendController(string className)
{
var generator = new WebApiGenerator();
Assembly assembly = generator.Exists(className) ?
generator.GetAssembly(className) : generator.CreateDll(className);
if (assembly != null)
{
_partManager.ApplicationParts.Add(new AssemblyPart(assembly));
// Notify change
MyActionDescriptorChangeProvider.Instance.HasChanged = true;
MyActionDescriptorChangeProvider.Instance.TokenSource.Cancel();
return true;
}
return false;
}
}
ActionDescriptorChangeProvider 用于通知框架某些内容已更改并且必须重新加载。
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);
}
}
别忘了在启动时注册那个动作描述符:
public void ConfigureServices(IServiceCollection services)
{
services.AddSingleton<IActionDescriptorChangeProvider>(MyActionDescriptorChangeProvider.Instance);
services.AddSingleton(MyActionDescriptorChangeProvider.Instance);
我的代码生成器只是创建了一个控制器和一个实体类。控制器返回一个启用 OData 的实体列表,其中包含三个属性。没什么特别的,只是在这里为想要尝试的人提供一个工作示例。
public class WebApiGenerator
{
public WebApiGenerator()
{
}
private static CSharpCompilation GenerateCode(string sourceCode, string className)
{
var codeString = SourceText.From(sourceCode);
var options = CSharpParseOptions.Default.WithLanguageVersion(LanguageVersion.CSharp9);
var parsedSyntaxTree = SyntaxFactory.ParseSyntaxTree(codeString, options);
var references = new List<MetadataReference>
{
MetadataReference.CreateFromFile(typeof(object).Assembly.Location),
MetadataReference.CreateFromFile(typeof(Console).Assembly.Location),
MetadataReference.CreateFromFile(typeof(System.Runtime.AssemblyTargetedPatchBandAttribute).Assembly.Location),
MetadataReference.CreateFromFile(typeof(Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfo).Assembly.Location),
};
var referencedAssemblies = AppDomain.CurrentDomain.GetAssemblies();
foreach (var referenced in referencedAssemblies)
{
string location = null;
try
{
location = referenced.Location;
}
catch
{
}
if (location != null)
{
references.Add(MetadataReference.CreateFromFile(location));
}
}
string outputDll = className + ".dll";
return CSharpCompilation.Create(outputDll,
new[] { parsedSyntaxTree },
references: references,
options: new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary,
optimizationLevel: OptimizationLevel.Release,
assemblyIdentityComparer: DesktopAssemblyIdentityComparer.Default));
}
public bool Exists(string className)
{
string outputDll = className + ".dll";
return File.Exists(outputDll);
}
public Assembly GetAssembly(string className)
{
string outputDll = className + ".dll";
return Assembly.LoadFrom(outputDll);
}
public Assembly CreateDll(string className)
{
className = className.Replace(" ", string.Empty);
string outputDll = className + ".dll";
if (File.Exists(outputDll)) return Assembly.LoadFrom(outputDll);
var code = new StringBuilder()
.AppendLine("using Microsoft.AspNetCore.Mvc;")
.AppendLine("using Microsoft.Extensions.Logging;")
.AppendLine("using System;")
.AppendLine("using System.Collections.Generic;")
.AppendLine("using System.Linq;")
.AppendLine("using Microsoft.AspNet.OData;")
.AppendLine("")
.AppendLine("namespace ControllerLibrary")
.AppendLine("{")
.AppendLine($"public class {className}")
.AppendLine("{")
.AppendLine(" public string FirstProperty { get; set; }")
.AppendLine(" public string SecondProperty { get; set; }")
.AppendLine(" public int IntValue { get; set; }")
.AppendLine("}")
.AppendLine($"[ApiController]")
.AppendLine($"[Route(\"[controller]\")]")
.AppendLine($"public class {className}Controller : ControllerBase")
.AppendLine(" {")
.AppendLine(" [HttpGet(\"GetData\")]")
.AppendLine(" [EnableQuery]")
.AppendLine($" public IList<{className}> Get()")
.AppendLine(" {")
.AppendLine($" var list = new List<{className}>();");
for (int i = 0; i < 3; i++)
{
code.AppendLine($"list.Add(new {className} {{ FirstProperty = \"First prop of {className}\", SecondProperty = \"asd\", IntValue = {i} }});");
}
code
.AppendLine(" return list;")
.AppendLine(" }")
.AppendLine(" }")
.AppendLine("}");
File.WriteAllText("code.txt", code.ToString());
var result = GenerateCode(code.ToString(), className).Emit(outputDll);
//CompilerResults results = codeProvider.CompileAssemblyFromSource(parameters, code.ToString());
if (!result.Success)
{
Console.WriteLine("Compilation done with error.");
var failures = result.Diagnostics.Where(diagnostic => diagnostic.IsWarningAsError || diagnostic.Severity == DiagnosticSeverity.Error);
foreach (var diagnostic in failures)
{
Console.Error.WriteLine("{0}: {1}", diagnostic.Id, diagnostic.GetMessage());
}
}
else
{
Console.WriteLine("Build Succeeded");
return Assembly.LoadFrom(outputDll);
}
return null;
}
}