Part 1 Adding controllers at runtime
Adding controllers at runtime in ASP.NET Core involves the ApplicationPartManager and IActionDescriptorChangeProvider. Let’s say that we hardcode the creation of the controller
private Assembly CreateController(string name)
{
string code = new StringBuilder()
.AppendLine("using System;")
.AppendLine("using Microsoft.AspNetCore.Mvc;")
.AppendLine("namespace TestBlocklyHtml.Controllers")
.AppendLine("{")
.AppendLine("[Route(\"api/[controller]\")]")
.AppendLine("[ApiController]")
.AppendLine(string.Format("public class {0} : ControllerBase", name))
.AppendLine(" {")
.AppendLine(" public string Get()")
.AppendLine(" {")
.AppendLine(string.Format("return \"test - {0}\";", name))
.AppendLine(" }")
.AppendLine(" }")
.AppendLine("}")
.ToString();
var codeString = SourceText.From(code);
var options = CSharpParseOptions.Default.WithLanguageVersion(LanguageVersion.CSharp7_3);
var parsedSyntaxTree = SyntaxFactory.ParseSyntaxTree(codeString, options);
var references = new 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),
MetadataReference.CreateFromFile(typeof(RouteAttribute).Assembly.Location),
MetadataReference.CreateFromFile(typeof(ApiControllerAttribute).Assembly.Location),
MetadataReference.CreateFromFile(typeof(ControllerBase).Assembly.Location),
};
var codeRun = CSharpCompilation.Create("Hello.dll",
new[] { parsedSyntaxTree },
references: references,
options: new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary,
optimizationLevel: OptimizationLevel.Release,
assemblyIdentityComparer: DesktopAssemblyIdentityComparer.Default));
using (var peStream = new MemoryStream())
{
if (!codeRun.Emit(peStream).Success)
{
return null;
}
return Assembly.Load(peStream.ToArray());
}
}
Then we will load into ApplicationParts
[HttpGet]
public string AddRuntimeController([FromServices] ApplicationPartManager partManager, [FromServices]MyActionDescriptorChangeProvider provider)
{
string name = "andrei" + DateTime.Now.ToString("yyyyMMddHHmmss");
var ass = CreateController(name);
if (ass != null)
{
partManager.ApplicationParts.Add(new AssemblyPart(ass));
// Notify change
provider.HasChanged = true;
provider.TokenSource.Cancel();
return "api/"+ name;
}
throw new Exception("controller not generated");
}
and the code for is
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);
}
}
and it is added to the DI services by
services.AddSingleton<IActionDescriptorChangeProvider>(MyActionDescriptorChangeProvider.Instance);
services.AddSingleton(MyActionDescriptorChangeProvider.Instance);
Usually , you do not need Part 2 – if you do not construct something like Blockly for .NET Core (https://netcoreblockly.herokuapp.com/), that needs to see controllers added by the application
Part 2 Detecting controller added at runtime by others
You usually do not need this – Part 1 is enough. I need because I do construct something like Blockly for .NET Core (https://netcoreblockly.herokuapp.com/), that needs to see controllers added by the application.
So we need ActionDescriptorCollectionProvider – read the remarks at https://docs.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.mvc.infrastructure.actiondescriptorcollectionprovider?view=aspnetcore-3.1 . We can obtain by DI from IActionDescriptorCollectionProvider and convert to ActionDescriptorCollectionProvider
The code is
//in the class , obtained by DI from IActionDescriptorCollectionProvider
private readonly ActionDescriptorCollectionProvider cp;
internal void registerCallback()
{
cp.GetChangeToken().RegisterChangeCallback(a =>
{
//a is your class because of this parameter below
//do your work
s.registerCallback();
}, this);
}
( All code is taken from https://github.com/ignatandrei/netcoreblockly)