Use a simple plugin mechanism in .NET Core

foreword

Plug-in is actually nothing new. Open source projects such as nopCommerce have similar mechanisms, and the functions are relatively complete and complete.

I believe that everyone has connected with many payment methods, such as Alipay, WeChat and major banks or third-party payment companies.

We can abstract payment-related operations, which are nothing more than payment, asynchronous callback, refund, query and other important operations.

At this time, we can use various payment methods as a plug-in, and these plug-ins implement the above operations, so that we can integrate an entry and load the corresponding plug-ins.

The normal case of this entry is MVC or WEB API.

Let's implement a simple example.

simple example

First, create a public plugin abstract class. For simplicity, it contains a no-parameter abstract method Handle , which is like the payment, refund and other operations mentioned above.

Every new plugin must inherit this abstract class and implement this abstract method!

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

Assuming that there are two plug-ins AA and BB, we will build a class library for each of AA and BB.

Among them, AA just returns the AA related string.

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

BB's also just returns BB-related strings.

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

Next, it is mainly our entry, the processing of Web projects.

The processing at the entry is mainly to use reflection to actually call the Handle method in that plug-in.

Here's how to get an instance of the corresponding plugin.

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;
        }
    }
}

There are mainly the following two operations

1. Cache reflection results

In order to avoid the reflection operation every time the plugin is acquired, the MemoryCache is referenced here to cache the reflection results.

Regarding caching, there is a problem to pay attention to here, the time of caching! This has no effect on adding a new plug-in, but it has a greater impact on modifying an existing plug-in!

If the cache time is too long, there will be no way to modify the effect in real time. You can consider the cache time for a relatively short time.

2. Dynamic replacement of dll

In order to avoid the situation that an existing plug-in cannot be replaced normally after modification, it is often prompted to use it normally. Some processing needs to be done to avoid occupying this resource all the time!

The last is the operation of 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";
}

At this point, it's basically done.

Let's take a look at the general framework of the project:

We put all the plugins in the Plugins project file. Web projects do not directly reference projects under Plugins.

The runtime dynamically loads the dll in the specified directory, and then completes the call.

Here's a simple look at the effect:

By modifying the type to achieve the effect of selecting different plug-ins, and then modifying a plug-in, it can also be replaced and taken effect normally.

Of course, in practice, it may not be so direct to get the type, but to search the database according to the parameters passed in, and then get the type. It also all depends on the different designs.

Summarize

The plug-in mechanism can be simply regarded as a practical application of reflection, which can already meet many conventional requirements.

However, there are still many things to consider in the complete plug-in, which can refer to the implementation of nop.

It still has many advantages. Personally, the most important thing is to isolate different plug-ins and reduce the possibility of mutual influence between them.

Finally, the sample Demo of this article is attached:

PluginsDemo

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=324406226&siteId=291194637