aspnetcore插件开发dll热加载

aspnetcore,dll · 浏览次数 : 0

小编点评

**项目结构** ```csharp . ├── repository │ ├── IRepositoryProvider.cs │ └── RepositoryProvider.cs ├── controller │ ├── MyControllerFilter.cs ├── startup │ ├── FileWatcherService.cs ├── app.cs ``` **`IRepositoryProvider` 接口** ```csharp using System.Reflection; public interface IRepositoryProvider { IRepository GetRepository(string serviceeName); } ``` **`RepositoryProvider` 类** ```csharp using System.Reflection; public class RepositoryProvider : IRepositoryProvider { private string _path; public RepositoryProvider(string path) { _path = path; } public IRepository GetRepository(string x) { var path = Path.Combine(_path, $"{x}.Repository.dll"); var assembly = Assembly.LoadFromStream(File.Open(path, FileMode.Open, FileAccess.Read)); var types = assembly.GetTypes().Where(t => typeof(IRepository).IsAssignableFrom(t) & !t.IsInterface); return (IRepository)Activator.CreateInstance(types.First()); } } ``` **`MyControllerFilter` 类** ```csharp 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().As().InstancePerLifetimeScope(); } } } ``` **`FileWatcherService` 类** ```csharp using Microsoft.Extensions.Hosting; using System; using System.Diagnostics; using System.IO; using System.Threading; using System.Threading.Tasks; public class FileWatcherService : IHostedService { private readonly string _watchedFolder; private readonly FileSystemWatcher _fileSystemWatcher; private readonly IHostApplicationLifetime _appLifetime; public FileWatcherService(IHostApplicationLifetime appLifetime) { _watchedFolder = Path.Combine(Directory.GetCurrentDirectory(), "lib"); //细化指定类型的dll _appLifetime = appLifetime; } public async 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; await Task.CompletedTask; } public async 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(); } } ``` **`app.cs`** ```csharp using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using System.Reflection; public class Startup { public void Configure(IApplicationBuilder app, IWebHostEnvironment env) { // 注册依赖项 var container = app.ApplicationServices.GetRequiredService(); container.Application.Use(_fileWatcherService); // 启动应用程序 app.Run(); } } ```

正文

该项目比较简单,只是单纯的把业务的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)

与aspnetcore插件开发dll热加载相似的内容:

aspnetcore插件开发dll热加载

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

aspnetcore插件开发dll热加载 二

这一篇文章应该是个总结。 投简历的时候是不是有人问我有没有abp的开发经历,汗颜! 在各位大神的尝试及自己的总结下,还是实现了业务和主机服务分离,通过dll动态的加载卸载,控制器动态的删除添加。 项目如下: 演示效果: 下面就是代码部分: 重点 1.IActionDescriptorChangePr

NET9 AspnetCore将整合OpenAPI的文档生成功能而无需三方库

OpenAPI 规范是用于描述 HTTP API 的标准。该标准允许开发人员定义 API 的形状,这些 API 可以插入到客户端生成器、服务器生成器、测试工具、文档等中。尽管该标准具有普遍性和普遍性,但 ASP.NET Core 在框架内默认不提供对 OpenAPI 的支持。 当前 ASP.NET

解决aspnetcore-browser-refresh.js:234 WebSocket connection to 'wss://localhost:62356/Admin/' failed问题

前言 前段时间升级了Visual Studio到v17.1.1最新版本,然后今天来运行之前的一个.net5项目一直提示:aspnetcore-browser-refresh.js:234 WebSocket connection to 'wss://localhost:62356/Admin/' f

Asp-Net-Core学习笔记:单元测试和集成测试

## 前言 我在使用 AspNetCore 的这段时间内,看了很多开源项目和博客,发现各种 .Net 体系的新技术很多人都有关注和使用,但却很少有人关注测试。 测试是软件生命周期中的一个非常重要的阶段,对于保证软件的可靠性具有极其重要的意义。在应用程序的开发过程中,为了确保它的功能与预期一致,必须对

基于 .net core 8.0 的 swagger 文档优化分享-根据命名空间分组显示

之前也分享过 Swashbuckle.AspNetCore 的使用,不过版本比较老了,本次演示用的示例版本为 .net core 8.0,从安装使用开始,到根据命名空间分组显示,十分的有用

在FreeSQL中实现「触发器」和软删除功能

前言 最近做新项目,技术栈 AspNetCore + FreeSQL 这个ORM真的好用,文档也很完善,这里记录一下两个有关「触发器」的功能实现 修改实体时记录更新时间 模型代码 我的模型都是基于这个 ModelBase 派生的,自带三个属性字段 public abstract class Mode

Asp-Net-Core开发笔记:FrameworkDependent搭配docker部署

## 前言 之前我写过一篇使用 docker 部署 AspNetCore 应用的文章,这种方式搭配 CICD 非常方便, build 之后 push 到私有的 dockerhub ,在生产服务器上 pull 下来镜像就可以直接运行了。 然而,有时需要一种更传统的部署方式,比如在本地打包可执行文件之后

Asp-Net-Core开发笔记:使用原生的接口限流功能

前言 之前介绍过使用 AspNetCoreRateLimit 组件来实现接口限流 从 .Net7 开始,AspNetCore 开始内置限流组件,当时我们的项目还在 .Net6 所以只能用第三方的 现在都升级到 .Net8 了,当然是得来试试这个原生组件 体验后:配置使用都比较简单,不过功能也没有 A

武装你的WEBAPI-OData使用Endpoint

本文属于 OData 系列文章 Introduction 更新: 由于新版的 OData 已经默认使用了 endpoint 模式(Microsoft.AspNetCore.OData 8.0.0),不再需要额外配置,本文已经过时(asp.net core 3.1)。 最近看 OData 的 devb