Skip to content

Instantly share code, notes, and snippets.

@davidfowl
Last active October 7, 2023 07:34
Show Gist options
  • Save davidfowl/76833774c8c7fd0b355af92569e16899 to your computer and use it in GitHub Desktop.
Save davidfowl/76833774c8c7fd0b355af92569e16899 to your computer and use it in GitHub Desktop.
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.Loader;
using System.Threading;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Mvc.ApplicationParts;
using Microsoft.AspNetCore.Mvc.Infrastructure;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Primitives;
namespace LazyControllers
{
public class Startup
{
// This method gets called by the runtime. Use this method to add services to the container.
// For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940
public void ConfigureServices(IServiceCollection services)
{
services.AddControllers();
// Add the module loader as a singleton
services.AddSingleton<ModuleLoader>();
// Add the same module loader instance as an IActionDescriptorChangeProvider so we can trigger updates
services.AddSingleton<IActionDescriptorChangeProvider>(sp => sp.GetRequiredService<ModuleLoader>());
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env, ModuleLoader moduleLoader)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseRouting();
app.Use(async (context, next) =>
{
if (context.Request.Path.StartsWithSegments("/load/M1"))
{
var module = AssemblyLoadContext.Default.LoadFromAssemblyPath(Path.Combine(Environment.CurrentDirectory, @"..\Module1\bin\Debug\netcoreapp3.1\Module1.dll"));
moduleLoader.AddType(module);
}
if (context.Request.Path.StartsWithSegments("/load/M2"))
{
var module = AssemblyLoadContext.Default.LoadFromAssemblyPath(Path.Combine(Environment.CurrentDirectory, @"..\Module2\bin\Debug\netcoreapp3.1\Module2.dll"));
moduleLoader.AddType(module);
}
await next();
});
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
}
}
public class ModuleLoader : ApplicationPart, IApplicationPartTypeProvider, IActionDescriptorChangeProvider
{
private ImmutableArray<TypeInfo> _types = ImmutableArray.Create<TypeInfo>();
private CancellationTokenSource _cts = new();
public ModuleLoader(ApplicationPartManager applicationPartManager)
{
applicationPartManager.ApplicationParts.Add(this);
}
public void AddType(Assembly assembly)
{
ImmutableInterlocked.Update(
ref _types,
static (types, a) => types.AddRange(a.GetTypes().Select(t => t.GetTypeInfo())),
assembly);
Interlocked.Exchange(ref _cts, new())?.Cancel();
}
public IChangeToken GetChangeToken()
{
return new CancellationChangeToken(_cts.Token);
}
public IEnumerable<TypeInfo> Types => _types;
public override string Name => "Dynamic Module Loader Part";
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment