aspnetcore外掛開發dll熱載入

星仔007發表於2024-04-26

該專案比較簡單,只是單純的把業務的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)

相關文章