首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >.net core 插件式开发

.net core 插件式开发

作者头像
FreeTimeWorker
发布2020-12-11 11:03:09
1.2K0
发布2020-12-11 11:03:09
举报

插件式开发

思考一种情况,短信发送,默认实现中只写了一种实现,因为某些原因该模块的所依赖的第三方无法继续提供服务,或者对于winform程序,某按钮单击,需要在运行时增加额外的操作,或者替换目前使用的功能,对于类似这样的需求,可以考虑使用插件式的方式搭建框架,以实现更灵活的可拆卸动态增加功能。 .net core 中提供了一种热加载外部dll的方式,可以满足该类型的需求 AssemblyLoadContext

流程

1,定义针对系统中所有可插拔点的接口 2,针对接口开发插件/增加默认实现 3,根据需要,在运行时执行相应的逻辑 4,在动态载入dll时谨防内存泄漏

代码

1,定义接口

在单独的类库中定义针对插拔点的接口

    public interface ICommand
    {
        string Name { get; }
        string Description { get; }
        int Execute();
    }

2,开发插件

新建类库,引用接口所在的类库,值得注意的的是 CopyLocalLockFileAssemblies,表示将所有依赖项生成到生成目录,对于插件中有对其他项目或者类库有引用的这个属性是必须的,Private表示引用的类库为公共程序集,该属性默认为true,为使插件可以正确在运行时加载,该属性必须为 ** false **

<Project Sdk="Microsoft.NET.Sdk">
	<PropertyGroup>
		<TargetFramework>net5.0</TargetFramework>
		<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
	</PropertyGroup>
	<ItemGroup>
	  <PackageReference Include="AutoMapper" Version="10.1.1" />
	  <PackageReference Include="System.Text.Json" Version="4.6.0" />
	</ItemGroup>
	<ItemGroup>
	  <ProjectReference Include="..\Plugins\Plugins.csproj">
		  <Private>false</Private>
		  <ExcludeAssets>runtime</ExcludeAssets>
		</ProjectReference>
	</ItemGroup>
</Project>

修改完类库中这两处的值以后添加类,继承自ICommand 将接口定义的方法和属性做相关的实现,如下

    public class Class1 : ICommand
    {
        public string Name => "Classb";
        public string Description => "Classb Description";
        public int Execute()
        {
            var thisv = JsonSerializer.Serialize(this);
            Assembly ass = typeof(AutoMapper.AdvancedConfiguration).Assembly;
            Console.WriteLine(ass.FullName);
            Console.WriteLine(thisv);
            Console.WriteLine("111111111111111111111111111111111111111111");
            return 10000;
        }
    }

3,根据需要在运行时执行相应逻辑

编写用于运行时 插件加载上下文, 该类主要负责将给定路径的dll加载到当前应用程序域,静态方法用户获取实现了插件接口的实例

  public class PluginLoadContext : AssemblyLoadContext
    {
        private AssemblyDependencyResolver _resolver;
        public PluginLoadContext(string pluginPath,bool isCollectible) :base(isCollectible)
        {
            _resolver = new AssemblyDependencyResolver(pluginPath);
        }
        //加载依赖项
        protected override Assembly Load(AssemblyName assemblyName)
        {
            string assemblyPath = _resolver.ResolveAssemblyToPath(assemblyName);
            if (assemblyPath != null)
            {
                return LoadFromAssemblyPath(assemblyPath);
            }
            return null;
        }
        protected override IntPtr LoadUnmanagedDll(string unmanagedDllName)
        {
            string libraryPath = _resolver.ResolveUnmanagedDllToPath(unmanagedDllName);
            if (libraryPath != null)
            {
                return LoadUnmanagedDllFromPath(libraryPath);
            }
            return IntPtr.Zero;
        }
  
        public static List<ICommand> CreateCommands(string[] pluginPaths)
        {
            List<Assembly> _assemblies = new List<Assembly>();
            foreach (var pluginPath in pluginPaths)
            {
                string pluginLocation = Path.GetFullPath(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, pluginPath.Replace('\\', Path.DirectorySeparatorChar)));
                var assembly = AppDomain.CurrentDomain.GetAssemblies().FirstOrDefault(o => o.Location == pluginLocation);
                //根据程序集的物理位置判断当前域中是否存在该类库,如果不存在就读取,如果存在就从当前程序域中读取,由于AssemblyLoadContext已经做了相应的上下文隔离
                //,所以即便是名称一样位置一样也可以重复加载,执行也可以按照预期执行,但由于会重复加载程序集,就会造成内存一直增加导致内存泄漏
                if (assembly == null)
                {
                    PluginLoadContext pluginLoadContext = new PluginLoadContext(pluginLocation, true);
                    assembly = pluginLoadContext.LoadFromAssemblyName(new AssemblyName(Path.GetFileNameWithoutExtension(pluginLocation)));
                }
                _assemblies.Add(assembly);
            }
            var results = new List<ICommand>();
            foreach (var assembly in _assemblies)
            {
                foreach (Type type in assembly.GetTypes())
                {
                    if (typeof(ICommand).IsAssignableFrom(type))
                    {
                        ICommand result = Activator.CreateInstance(type) as ICommand;
                        if (result != null)
                        {
                            results.Add(result);
                        }
                    }
                }
            }
            return results;
        }
    }

调用

            try
            {
                //插件添加后,相应的位置保存下载
                string[] pluginPaths = new string[]
                {
                    "Plugin/PluginA/PluginA.dll",//将插件所在类库生成后的文件复制到PluginA下边
                };
                var i = 0;
                while (true)
                {
                    List<ICommand> commands = PluginLoadContext.CreateCommands(pluginPaths);
                    foreach (var command in commands)
                    {
                        Console.WriteLine(command.Name);
                        Console.WriteLine(command.Description);
                        Console.WriteLine(command.Execute());
                    }
                }
                
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex);
            }
            Console.ReadKey();

图2中去掉了当前程序集中根据地址确定是否重新加载插件,可以看到内存的使用量在一直增加,最终一定会导致溢出。

对比图 1

对比图 2

对于插件卸载,我认为没有必要去考虑,对于同一类型插件,只需要将不同版本的放到不同的位置,在一个公共位置维护当前使用的插件所在位置,如果有更新直接找最新的实现去执行就行,卸载很麻烦,需要删除掉所有的依赖项,还容易出错,不解决就是最好的解决方案

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2020-12-09 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 插件式开发
  • 流程
  • 代码
    • 对于插件卸载,我认为没有必要去考虑,对于同一类型插件,只需要将不同版本的放到不同的位置,在一个公共位置维护当前使用的插件所在位置,如果有更新直接找最新的实现去执行就行,卸载很麻烦,需要删除掉所有的依赖项,还容易出错,不解决就是最好的解决方案
    相关产品与服务
    短信
    腾讯云短信(Short Message Service,SMS)可为广大企业级用户提供稳定可靠,安全合规的短信触达服务。用户可快速接入,调用 API / SDK 或者通过控制台即可发送,支持发送验证码、通知类短信和营销短信。国内验证短信秒级触达,99%到达率;国际/港澳台短信覆盖全球200+国家/地区,全球多服务站点,稳定可靠。
    领券
    问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档