C# MEF插件化开发
什么是 MEF?
Managed Extensibility Framework (MEF) 是用于创建可扩展的轻量级应用程序的库。 它让应用程序开发人员得以发现和使用扩展且无需配置。 它还让扩展开发人员得以轻松地封装代码并避免脆弱的紧密依赖性。 MEF 让扩展不仅可在应用程序内重复使用,还可以跨程序重复使用。
扩展性问题
想象你是必须为扩展性提供支持的大型应用程序的设计者。 你的应用程序必须包含可能很多的较小组件,且其负责创建和运行较小组件。
解决问题最简单的方法就是将组件作为源代码包括在你的应用程序并直接从代码中调用它们。 这有很多明显缺点。 最重要的是,你无法在未调试源代码的情况下添加新组件,这是一项可能被接受的限制(例如在 Web 应用程序中),但不适用于客户端应用程序。 同样造成问题的是,你无法访问组件的源代码,因为组件可能由第三方进行了发展,同时由于同样的原因你不能允许第三方访问你的源代码。
一个稍许复杂的方法为提供扩展点或接口来允许应用程序和组件间的分离。 在此模块下,你可能提供组件可实现的接口以及使接口能与应用程序交互使用的 API。 这解决了要求源代码访问的问题,但它仍然存在自身问题。
因为应用程序缺少自行发现组件的能力,所以必须明确对其指出可用且应加载的组件。 这通常通过在配置文件中明确记录可用组件来完成。 这意味着确保组件正确变成了维护问题,尤其当要求执行更新的人员是最终用户而非开发人员时。
此外,组件能够与另外的组件进行沟通,除了通过应用程序自身的严格定义的通道。 通常不会出现应用程序设计者未预测到特定通信的需求的情况。
最终,组件开发人员必须接受包含他们实现的接口的程序集所在的硬依赖项。 这使得在一个以上的应用程序中使用组件变得困难,且它还会在你创建组件的测试框架时造成问题。
MEF 所含内容
MEF 通过组合提供了一种隐式发现它们的方法,而不是明确记录可用组件。 MEF 组件(称为一个部件),以声明方式详细说明了其依赖项(称为导入)及其可提供的功能(称为导出)。 当创建一个部分时,MEF 组合引擎利用从其他部分获得的功能满足其导入需要。
该方法解决了在之前部分中讨论的问题。 因为 MEF 部分以声明方式详细说明了其可在运行时发现自身的功能,这意味着应用程序不使用硬编码引用或脆弱的配置文件也能够使用 MEF 部分。 MEF 让应用程序得以通过元数据发现和检查部分,而无须将部分实例化甚至于加载其程序集。 因此,无须仔细说明应当何时加载扩展以及如何加载。
除了它提供的导出之外,部件能够指出其导入(将由其他部件填写)。 这不仅使部件之间的通信成为可能还使其变得简单,并且实现了良好的代码分解。 例如,对于许多组件都很普通的服务可被分解为单独部分,可轻易地进行调试和替换。
因为 MEF 模块在特定的应用程序集上没有硬依赖项,所以它允许扩展在不同应用程序之间重复使用。 这使得开发用于测试扩展组件的测试工具(独立于应用程序)变得简单。
通过使用 MEF 编写的可扩展应用程序声明了一个可由扩展组件填写的输入,它还可能为了向扩展揭示应用程序服务而声明导出。 每个扩展组件都声明一个导出,还有可能声明导入。 通过此方法,扩展组件自身为自动可扩展。
C# .net framework 实现方式
添加引用 System.ComponentModel.Composition
基类:
[InheritedExport(typeof(BllBase))]
public abstract class BllBase
{
/// <summary>
/// 组合编码
/// </summary>
public string ZuHeBianMa { get; protected set; }
public string BP_BianMa { get; protected set; }
public string TZ_BianMa { get; protected set; }
/// <summary>
/// 尺寸
/// </summary>
public string Size { get; set; }
/// <summary>
/// 计算类型
/// </summary>
public string XianLiangJiSuan { get; protected set; }
/// <summary>
/// 色号计算规则,MX,SX
/// </summary>
public string SeHaoGuiZe { get; protected set; }
public abstract bool Do(string fileName);
}
实现类1:
public class BllJueCe_01 : BllBase
{
//readonly string reg_str = @"^(MX|SX)([a-zA-Z\d]+-\d+)([a-zA-Z]?)(__\d+[Xx]\d+)$";
readonly string reg_str = @"^(MX|SX)([a-zA-Z\d]+-\d+)(__\d+[Xx]\d+)$";
public override bool Do(string fileName)
{
Regex reg = new Regex(reg_str, RegexOptions.IgnoreCase);
var m = reg.Match(fileName);
if (m.Success)
{
this.SeHaoGuiZe = m.Groups[1].Value.ToUpper();
this.Size = m.Groups[3].Value.ToUpper().Substring(2);
this.XianLiangJiSuan = "11CT";
BP_BianMa = "BP" + m.Groups[2].Value + m.Groups[3].Value;
TZ_BianMa = "TZ" + m.Groups[2].Value + m.Groups[3].Value;
ZuHeBianMa = m.Groups[2].Value + m.Groups[3].Value;
return true;
}
return false;
}
}
实现类2
public class BllJueCe_02 : BllBase
{
//readonly string reg_str = @"^(MX|SX)([a-zA-Z\d]+-\d+)([a-zA-Z])(\d+)(__\d+[Xx]\d+)$";
readonly string reg_str = @"^(MX|SX)([a-zA-Z\d]+-\d+)([MS])(\d+)?([wW]?)(__\d+[Xx]\d+)$";
public override bool Do(string fileName)
{
Regex reg = new Regex(reg_str, RegexOptions.IgnoreCase);
var m = reg.Match(fileName);
if (m.Success)
{
this.SeHaoGuiZe = m.Groups[1].Value.ToUpper();
this.Size = m.Groups[6].Value.ToUpper().Substring(2);
if (!String.IsNullOrEmpty(m.Groups[4].Value))
{
BP_BianMa = "BP" + m.Groups[2].Value + "#" + m.Groups[4].Value + m.Groups[5].Value + m.Groups[6].Value;
TZ_BianMa = "TZ" + m.Groups[2].Value + "#" + m.Groups[4].Value + m.Groups[6].Value;
this.XianLiangJiSuan = m.Groups[4].Value.ToUpper() + "CT";
}
else
{
BP_BianMa = "BP" + m.Groups[2].Value + m.Groups[5].Value + m.Groups[6].Value;
TZ_BianMa = "TZ" + m.Groups[2].Value + m.Groups[6].Value;
this.XianLiangJiSuan = "11CT";
}
ZuHeBianMa = m.Groups[2].Value + m.Groups[3].Value + m.Groups[4].Value;
return true;
}
return false;
}
}
插件加载工厂
using NZExcelData.Libs;
using System.Collections.Generic;
using System.ComponentModel.Composition;
using System.ComponentModel.Composition.Hosting;
using System.Reflection;
namespace NZExcelData
{
class BllFactory
{
[ImportMany]
public List<BllBase> Services { get; set; }
public void Compose()
{
var catalog = new AssemblyCatalog(Assembly.GetExecutingAssembly());
CompositionContainer container = new CompositionContainer(catalog);
container.ComposeParts(this);
}
}
}
使用:直接调用 bllFactory.Services 就是插件的集合
BllFactory _bllFactory;
BllFactory bllFactory
{
get
{
if (_bllFactory == null)
{
_bllFactory = new BllFactory();
_bllFactory.Compose();
}
return _bllFactory;
}
}
.net core 中使用案例
定义接口
[InheritedExport(typeof(IPageDynamicData))]
public interface IPageDynamicData
{
string Name { get; }
string LoadData(EntitiesContext entities, string articleID, UserInfo loginUser);
}
实现插件1
/// <summary>
/// 文章评论
/// </summary>
public class PageDynamicData_Comment : IPageDynamicData
{
public string Name { get; } = "comment";
public string LoadData(EntitiesContext entities, string articleID, UserInfo loginUser)
{
List<DiskWriteArticleData_Comment> Comments = entities.data_ArticleComments.Where(w => w.ArticleID == articleID && w.Status == 1)
.OrderByDescending(o => o.CreateTime).Select(s => new DiskWriteArticleData_Comment()
{
NickName = s.NickName,
CreateTime = s.CreateTime,
Comment = s.Comment
}).ToList();
DynamicViewBag b = new DynamicViewBag();
b.AddValue("Data", Comments);
b.AddValue("ArticleID", articleID);
string tempPath = System.IO.Path.Combine(PathProvider.GetRootPageDynamicDataTemplate(), "comments.cshtml");
return RazorHelper.RenderRazorFile(tempPath, "compents", viewBag: b);
}
}
实现插件2
/// <summary>
/// 关联文章
/// </summary>
public class PageDynamicData_UnionArticle : IPageDynamicData
{
public string Name { get; } = "unionarticle";
public string LoadData(EntitiesContext entities, string articleID, UserInfo loginUser)
{
var title = entities.data_Articles.Where(w => w.ArticleID == articleID).Select(s => s.ArticleTitle).FirstOrDefault();
YESCMS.Search.DOSearch serach = new Search.DOSearch();
Search.SearchResult result = serach.Search("", title, 1, 20);
if (result.Results.Count > 0)
{
DynamicViewBag b = new DynamicViewBag();
b.AddValue("Data", result.Results);
string tempPath = System.IO.Path.Combine(PathProvider.GetRootPageDynamicDataTemplate(), "union_article.cshtml");
return RazorHelper.RenderRazorFile(tempPath, "union_article", viewBag: b);
}
else
return "";
}
}
插件加载工厂
public class PageDynamicDataProvider
{
[ImportMany]
public List<IPageDynamicData> comments { get; set; }
void Compose()
{
var catalog = new AssemblyCatalog(Assembly.GetExecutingAssembly());
CompositionContainer container = new CompositionContainer(catalog);
container.ComposeParts(this);
}
public static PageDynamicDataProvider _intance;
static PageDynamicDataProvider()
{
_intance = new PageDynamicDataProvider();
_intance.Compose();
}
internal static IPageDynamicData GetIntance(string name)
{
return _intance.comments.Where(w => w.Name == name.ToLower()).FirstOrDefault();
}
}
从文件加载:
public class UpdateHandleFactory
{
[ImportMany]
public List<IUpdateHandle> Services { get; set; }
public void Compose()
{
var tempUpdatePath = System.IO.Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "__temp_update__\\handle");
using var catalog = new AggregateCatalog();
catalog.Catalogs.Add(new System.ComponentModel.Composition.Hosting.DirectoryCatalog(tempUpdatePath));
CompositionContainer container = new CompositionContainer(catalog);
container.ComposeParts(this);
}
}