本文长度为5669字,预计阅读9分钟
前言
最近有个产品需要设计重构,主要后端是C#和前端是Android程序,后端也考虑过微服务,但是觉得根据用户体量来说,有点太重了,但是也是想要团队分工,所以就考虑了MEF的方式,原来MEF的插件方式在《C# MEF插件的使用及Demo分享》的文章中介绍过,不过当时用的是WinForm版本,现在是要在NET5上使用,所以就专门做了DEMO程序来验证可行性。
实现效果
同一个API根据参数不同调用的第一个插件函数。
同一个API根据参数不同调用的第二个插件函数。
关于MEF和MEF2
微卡智享
微软发布了四个版本的 MEF:
MEF2 是微软后来以 NuGet 包形式发布的 MEF2;适用于 .NET Framework 4.5 及以上、.NET Core 和各种 .NET 移动平台。它的接口相比于 .NET Framework 中原生带的已经变了,中文和英文的参考资料很少,本章Demo也是在网上找了不少,然后自己研究测试后可用的,也算是比较花时间了。
代码实现
微卡智享
01
创建.Net5的WebApi项目
项目是系统默认的生成项目,这里倒是没什么可说的,只不过最后需要将MEF的类做依赖注入,最后再来操作这个。
02
创建Mef接口类
1.创建WebMef.Core的类库
也是新建一个.net5的类库,这个类库里用来写Mef插件的接口
2.添加Mef2的Nuget包
在Nuget包中添加Microsoft.Composition组件。这个组件即为MEF2的组件。
3.创建IMSG接口
创建一个IMsg的接口,里面定义了两个属性和三个函数方法。
namespace WebMef.Core
{
public interface IMsg
{
string InfName { get; }
int InfCode { get; set; }
void Send(string msg);
string Send(string msg, int infcode);
string Recv(int infcode);
}
}
03
新建插件类继承接口
然后在右键添加引用中加入刚才创建的WebMef.Core的类库
两个插件的类继承自IMsg,然后写实现方法。
插件1的ReadInf.cs
using System;
using System.Collections.Generic;
using System.Composition;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using WebMef.Core;
namespace WebMef.Plugin1
{
[Export(typeof(IMsg))]
public class ReadInf : IMsg
{
public string InfName { get=> "阅读"; }
public int InfCode { get ; set; }
public string Recv(int infcode)
{
string msg = "当前页面为:" + InfName + " 序号为:" + infcode;
return msg;
}
public void Send(string msg)
{
InfCode++;
}
public string Send(string msg, int infcode)
{
int oldinfcode = InfCode;
InfCode += infcode;
string retmsg = "当前页面为:" + InfName + " 原序号为:" + oldinfcode + " 现序号为:" + InfCode + " 增加了" + infcode;
return retmsg;
}
}
}
插件2的WriteInf.cs
using System;
using System.Collections.Generic;
using System.Composition;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using WebMef.Core;
namespace WebMef.Plugin2
{
[Export(typeof(IMsg))]
public class WriteInf : IMsg
{
public string InfName => "写作";
public int InfCode { get ; set; }
public string Recv(int infcode)
{
string msg = "页面为:" + InfName + " 序号为:" + infcode;
return msg;
}
public void Send(string msg)
{
InfCode++;
}
public string Send(string msg, int infcode)
{
int oldinfcode = InfCode;
InfCode += infcode;
string retmsg = msg + "页面为:" + InfName + " 原号为:" + oldinfcode + " 现号为:" + InfCode + " 增加了" + infcode;
return retmsg;
}
}
}
到这一步,插件的实现就基本完成了,要注意的点就是要在类的前面加上[Export(typeof(IMsg))]
04
创建Mef的注册类
上面几步已经把简单的接口及实现方法都写完了,接下来要在WebMef.Core的类库中创建一个MefRegister注册类。
加载后的组件要加上特性ImportMany,而定义的组合窗口是使用CompositionHost,这里就和最初的Mef完全不一样的了。
加入一个public async Task<string> Start()用于处理WebApi启动时的依赖注册。这个函数比较核心,完整MefRegister代码
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using System.Composition;
using System.Composition.Hosting;
using System.IO;
using System.Runtime.Loader;
using System.Linq;
using System.Composition.Convention;
namespace WebMef.Core
{
public class MefRegister
{
[ImportMany]
public static IEnumerable<IMsg> Msgs { get; set; }
//定义组合容器
private CompositionHost _container;
public async Task<string> Start()
{
var assembiles = Directory.GetFiles(AppContext.BaseDirectory, "*.dll", SearchOption.TopDirectoryOnly)
.Select(AssemblyLoadContext.Default.LoadFromAssemblyPath);
var conventions = new ConventionBuilder();
conventions.ForTypesDerivedFrom<IMsg>()
.Export<IMsg>()
.Shared();
_container = new ContainerConfiguration().WithAssemblies(assembiles, conventions).CreateContainer();
Msgs = _container.GetExports<IMsg>();
return await Task.FromResult("MEF组件加载完成");
}
public void Stop()
{
Msgs = null;
_container.Dispose();
}
}
}
05
WebApi启动注入依赖
完成上面的所有步骤后,接下来就是最后一步实现,在WebApi启动时将MEF注入依赖。主要就是修改startup的类。
在ConfigureServices加入 services.AddSingleton<MefRegister>();
将Configure函数的输入参数加上IHostApplicationLifetime这个参数
函数最后再加入上面这段代码,即可实现注入。
startup.cs
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using WebMef.Core;
namespace WebMefDemo
{
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.AddControllersWithViews();
services.AddSingleton<MefRegister>();
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostApplicationLifetime appLifetime, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseExceptionHandler("/Home/Error");
}
app.UseStaticFiles();
app.UseRouting();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
});
var mefreg = app.ApplicationServices.GetRequiredService<MefRegister>();
appLifetime.ApplicationStarted.Register(() =>
{
mefreg.Start().Wait();
});
appLifetime.ApplicationStopped.Register(() =>
{
mefreg.Stop();
});
}
}
}
06
ApiConntrol调用
上面都完成后最后一步就是测试调用了。新建一个MefConntrol的类,加入一个Get方法,根据输入的name的名称查找出对应的MEF插件,然后直接调用接口实现的Send方法即可。
using Microsoft.AspNetCore.Mvc;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using WebMef.Core;
namespace WebMefDemo.Controllers
{
[ApiController]
[Route("api/[controller]")]
public class MefController : ControllerBase
{
[HttpGet("Msg")]
public string GetMsgs(string name)
{
if (name == null) return "找不到对应的组件";
var msg = MefRegister.Msgs.FirstOrDefault(t => t.InfName == name);
string now = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss");
return msg.Send(now, 1);
}
}
}
需要注意的是默认的Plugin1和Plugin2两个插件编译生成时并不是默认生成在WebMefDemo的bin下面,需要自己配置输出,或是生成后拷贝到WebMefDemo的bin目录下才能获取到。
最终就实现了MEF2在.net5的webapi中的使用,调用的效果就是文章开头实现的图片。
https://github.com/Vaccae/DotNet5WebApiMEF2Demo.git