aspnetcore插件开发dll热加载

该项目比较简单,只是单纯的把业务的dll模块和controller的dll做了一个动态的添加删除处理,目的就是插件开发。由于该项目过于简单,请勿吐槽。复杂的后续可以通过泛型的实体、dto等做业务和接口的动态区分。

项目结构如下:

上面的两个模块是独立通过dll加载道项目中的

repository动态的核心思想在此项目中是反射

复制代码
public interface IRepositoryProvider
{
    IRepository GetRepository(string serviceeName);
}
public class RepositoryProvider : IRepositoryProvider
{
    public IRepository GetRepository(string x)
    {
        var path = $"{Directory.GetCurrentDirectory()}\\lib\\{x}.Repository.dll";
        var _AssemblyLoadContext = new AssemblyLoadContext(Guid.NewGuid().ToString("N"), true);
        Assembly assembly = null;
        using (var fs = new FileStream(path, FileMode.Open, FileAccess.Read))
        {
             assembly = _AssemblyLoadContext.LoadFromStream(fs);
        }
           
        //var assembly = Assembly.LoadFrom(path);
        var types = assembly.GetTypes()
            .Where(t => typeof(IRepository).IsAssignableFrom(t) && !t.IsInterface);
        return (IRepository)Activator.CreateInstance(types.First());
    }
}

通过一个provider注入来获取示例,这个repository的示例既然是动态热拔插,能想到暂时只能是反射来做这一块了。

复制代码
using Autofac;
using IOrder.Repository;
using Order.Repository;

namespace AutofacRegister
{
    public class RepositoryModule:Module
    {
        protected override void Load(ContainerBuilder builder)
        {
            //builder.RegisterType<Repository>().As<IRepository>().SingleInstance();
            builder.RegisterType<RepositoryProvider>().As<IRepositoryProvider>().InstancePerLifetimeScope();
        }
    }
}

controller插件这一块大同小异,这个控制器是通过程序集注入来实现的

复制代码
  public class MyControllerFilter : IStartupFilter
  {
     
      private readonly PluginManager pluginManager;
      List<string> controllers = new List<string> { "First","Second" };
      public MyControllerFilter(PluginManager pluginManager)
      {
          this.pluginManager = pluginManager;
          controllers.ForEach(x => pluginManager.LoadPlugins($"{Directory.GetCurrentDirectory()}\\lib\\", $"{x}.Impl.dll"));
      }
      Action<IApplicationBuilder> IStartupFilter.Configure(Action<IApplicationBuilder> next)
      {
          BindingFlags bindingFlags = BindingFlags.Instance | BindingFlags.Public | BindingFlags.DeclaredOnly;
          return app =>
          {

              app.UseRouting();
              app.UseEndpoints(endpoints =>
              {
                  foreach (IPlugin item in pluginManager.GetPlugins())
                  {
                      foreach (MethodInfo mi in item.GetType().GetMethods(bindingFlags))
                      {
                          endpoints.MapPost($"/{item.GetType().Name.Replace("Service", "")}/{mi.Name}", async (string parameters, HttpContext cotext) =>
                          {

                              var task = (Task)mi.Invoke(item, new object[] { parameters });
                              if (task is Task apiTask)
                              {
                                  await apiTask;

                                  // 如果任务有返回结果
                                  if (apiTask is Task<object> resultTask)
                                  {
                                      var res = await resultTask;
                                      return Results.Ok(JsonConvert.SerializeObject(res));
                                  }
                              }

                              // 如果方法没有返回 Task<ApiResult>,返回 NotFound
                              return Results.NotFound("Method execution did not return a result.");
                          });
                      }
                  }
              });
              next(app);
          };
      }
  }

但是有一个问题,它的变化势必需要重新渲染整个controller,我只能重启他的服务了。

复制代码
using Microsoft.Extensions.Hosting;
using System;
using System.Diagnostics;
using System.IO;
using System.Threading;
using System.Threading.Tasks;

namespace StartupDiagnostics
{
    public class FileWatcherService : IHostedService
    {
        private readonly string _watchedFolder;
        private FileSystemWatcher _fileSystemWatcher;
        private readonly IHostApplicationLifetime _appLifetime;
        public FileWatcherService(IHostApplicationLifetime appLifetime)
        {
            _watchedFolder =Path.Combine(Directory.GetCurrentDirectory(),"lib"); //细化指定类型的dll
            _appLifetime = appLifetime;
        }

        public Task StartAsync(CancellationToken cancellationToken)
        {
            _fileSystemWatcher = new FileSystemWatcher(_watchedFolder);
            _fileSystemWatcher.NotifyFilter = NotifyFilters.LastWrite | NotifyFilters.FileName | NotifyFilters.DirectoryName;
            _fileSystemWatcher.Changed += OnFileChanged;
            _fileSystemWatcher.Created += OnFileChanged;
            _fileSystemWatcher.Deleted += OnFileChanged;
            _fileSystemWatcher.Renamed += OnFileChanged;
            _fileSystemWatcher.EnableRaisingEvents = true;

            return Task.CompletedTask;
        }

        public Task StopAsync(CancellationToken cancellationToken)
        {
            _fileSystemWatcher.Dispose();
            return Task.CompletedTask;
        }

        private void OnFileChanged(object sender, FileSystemEventArgs e)
        {
            // 文件夹内容发生变化时重新启动应用程序
            var processStartInfo = new ProcessStartInfo
            {
                FileName = "dotnet",
                Arguments = $"exec \"{System.Reflection.Assembly.GetEntryAssembly().Location}\"",
                UseShellExecute = false
            };

          
            _appLifetime.ApplicationStopped.Register(() =>
            {
                Process.Start(processStartInfo);
            });
            _appLifetime.StopApplication();

        }
    }
}

repository这一块页面效果没法展示,

controllerr可以通过swagger来看看,first和second这可以通过删除dll和添加dll来增加和删除controller给第三方。

这是控制台展示的重启效果

源代码如下:

liuzhixin405/AspNetCoreSimpleAop (github.com)