• 在.NET Core中使用简单的插件化机制


    前言

    插件化,其实也并不是什么新东西了,像nopCommerce等开源项目都有类似的机制,而且功能比较完善和齐全。

    相信大家都对接过不少支付方式,支付宝、微信以及各大银行或第三方的支付公司。

    我们可以把支付相关的操作抽象出来,无非就是支付,异步回调,退款,查询等几个重要的操作。

    这个时候我们可以将各种支付方式都做为一个插件,这些插件都实现上面的操作,这样我们整合一个入口,去加载相应的插件即可。

    这个入口常规情况就是MVC或者是WEB API。

    下面来实现一个简单的例子。

    简单的例子

    首先建立一个公共的插件抽象类,简单起见,里面就包含一个无参的抽象方法Handle,这个方法就像上面提到的支付,退款等操作。

    每一个新的插件都必须要继承这个抽象类并且实现这个抽象方法!

    public abstract class BasePluginsService
    {
        public abstract string Handle();
    }
    

    假设现在有两个插件AA和BB,我们会把AA和BB各建一个类库。

    其中,AA的就只是返回了AA相关的字符串。

    public class PluginsService : Common.BasePluginsService
    {
        public override string Handle()
        {
            return "Plugins.AA";
        }
    }
    

    BB的也同样只是返回了BB相关的字符串。

    public class PluginsService : Common.BasePluginsService
    {
        public override string Handle()
        {
            return "Plugins.BB";
        }
    }
    

    接下来,主要还是我们的入口,Web项目的处理。

    在入口处的处理主要是利用反射去确实要调用那个插件里面的Handle方法。

    下面是获取相应插件实例的方法。

    private async Task<Common.BasePluginsService> GetPlugin(string type)
    {
        string cacheKey = $"plugin:{type}";
        //先尝试从缓存中取
        if (_cache.TryGetValue(cacheKey, out Common.BasePluginsService service))
        {
            return service;
        }
        else
        {
            var baseDirectory = Directory.GetCurrentDirectory();        
            var dll = $"Plugins.{type}.dll";
            //Plugins的完整路径
            var path = Path.Combine(baseDirectory, _options.PluginsPath, dll);                
            try
            {          
                //预防无法更新dll
                byte[] bytes = await System.IO.File.ReadAllBytesAsync(path);
                var assembly = Assembly.Load(bytes);
                //创建实例
                var obj = (Common.BasePluginsService)assembly.CreateInstance($"Plugins.{type}.PluginsService");
    
                if (obj != null)
                {
                    _cache.Set(cacheKey, obj, DateTimeOffset.Now.AddSeconds(60));
                }
    
                return obj;
            }
            catch (Exception)
            {
                return null;
            }
        }
    }
    

    这里主要有下面两个操作

    一、 缓存反射结果

    为了避免每次获取插件都去进行反射操作,这里引用了MemoryCache来缓存了反射的结果。

    关于缓存,这里需要注意一个问题,缓存的时间!这个对新增加一个插件是没有什么影响,但是对修改一个已经存在的插件就影响比较大了!

    缓存时候过长会导致没有办法实时出现修改的效果,可以考虑缓存比较短的时间。

    二、 动态替换dll

    为了避免出现对一个现存的插件修改之后,无法正常替换的情形,往往提示的是正常使用。需要做一些处理,避免一直占用这个资源!

    最后就是Action的操作了。

    [HttpGet]
    public async Task<string> GetAsync(string type)
    {
        if (string.IsNullOrWhiteSpace(type))
        {
            return "type is empty";
        }
    
        var plugin = await this.GetPlugin(type);
    
        if (plugin != null)
        {
            return plugin.Handle();
        }
    
        return "default";
    }
    

    到这里,基本就完成了。

    先来看看项目的大致框架:

    我们是将所有的插件放到Plugins这个项目文件中的。Web项目并不直接引用Plugins下面的项目。

    运行时是动态加载指定目录里面的dll,然后完成调用的。

    下面简单来看看效果:

    通过修改type达到选择不同插件的效果,然后对某个插件进行修改之后,也能正常替换和生效。

    当然,实际中可能并没有那么直接就能拿type,而是是要根据传进来的参数去搜一下数据库,然后才能拿到type。这也完全取决于不同的设计。

    总结

    插件化机制,可以简单的认为是反射的一个实际应用,这个已经能满足不少常规性的要求了。

    但是完整的插件化还有诸多要考量的东西,这个可以参考nop的实现。

    它还是有不少好处的,个人认为,最主要的还是隔离了不同的插件,将它们之间相互影响的可能性降低。

    最后附上本文的示例Demo:

    PluginsDemo

  • 相关阅读:
    扩展的friend语法
    常量表达式
    字符串过滤,排序输出数字的问题
    decltype类型声明- 现代C++新特性总结
    auto类型-现代C++新特性
    指针相关总结2
    指针相关总结1
    发现XMind一个超级牛逼的功能
    空类指针为什么可以调用类的成员函数 以及 A(){}和A();
    SSAS父子层次结构的增强-UnaryOperatorColumn属性
  • 原文地址:https://www.cnblogs.com/catcher1994/p/using-plugins-in-dotnetcore.html
Copyright © 2020-2023  润新知