[.NET]透過 Managed Extensibility Framework (MEF) 建立可擴充及可客製的系統

  • 10928
  • 0
  • .NET
  • 2014-10-17

Managed Extensibility Framework (MEF) 可以讓您建立輕量型可擴充應用程式。
以下依MSDN上的計算機範例(Console程式)來說明擴充及客製的方式。

Managed Extensibility Framework (MEF) 可以讓您建立輕量型可擴充應用程式。

以下亂馬客依MSDN上的計算機範例(Console程式)來說明擴充及客製的方式。

image

 

方案如下,

image

 

1.定義Interface

1.1.計算機(ICalculator),裡面有(Calculate)計算的Method,接受一個運算字串。

1.2.運算字串(例:5-3)可區分為左運算元(5),運算子(-)及右運算元(3)

public interface ICalculator
{
	string Calculate(string input);
}

public interface IOperation
{
	int Operate(int left, int right);
}

//「中繼資料檢視」(Metadata View) 的介面
//http://msdn.microsoft.com/zh-tw/library/ee155691(v=vs.110).aspx
public interface IOperationData
{
	char Symbol { get; }
}

 

2.實作 interface (專案要加入 System.ComponentModel.Composition.dll參考)

2.1.計算機

[Export(typeof(ICalculator))]
[PartCreationPolicy(CreationPolicy.NonShared)]
public class MyCalculator:ICalculator 
{
	[ImportMany]
	IEnumerable<Lazy<IOperation, IOperationData>> operations;

	public string Calculate(string input)
	{
		int left, right;
		char operation;
		int foundOperatorIndex = FindFirstNonDigit(input);
		if (foundOperatorIndex < 0)
		{
			return "Could not parse command.";
		}
		try
		{
			left = int.Parse(input.Substring(0, foundOperatorIndex));
			right = int.Parse(input.Substring(foundOperatorIndex + 1));
		}
		catch (Exception)
		{

			return "Could not parse command.";
		}
		operation = input[foundOperatorIndex];
		
		//透過 Metadata 找出對應的運算子
		var matchOP = operations.Where((op) => op.Metadata.Symbol.Equals(operation))
						.FirstOrDefault();

		if (matchOP != null)
		{
			return matchOP.Value.Operate(left, right).ToString();
		}
		return "Operation Not Found!";
	}

	/// <summary>
	/// 尋找運算子
	/// </summary>
	/// <param name="s"></param>
	/// <returns></returns>
	private int FindFirstNonDigit(string s)
	{
		for (int i = 0; i < s.Length; i++)
		{
			if(!char.IsDigit(s[i]))
			{
				return i;
			}
				
		}
		return -1;
	}
}

 

2.2.加、減運算子

[Export(typeof(IOperation))]
[ExportMetadata("Symbol", '+')]
[PartCreationPolicy(CreationPolicy.NonShared)]
public class Add: IOperation
{
	public int Operate(int left, int right)
	{
		return left + right;
	}
}

[Export(typeof(IOperation))]
[ExportMetadata("Symbol", '-')]
[PartCreationPolicy(CreationPolicy.NonShared)]
public class Subtract : IOperation
{
	public int Operate(int left, int right)
	{
		return left - right;
	}
}

 

3.程式在使用時,要讓這些 Parts 可以媒合配對(專案要加入System.ComponentModel.Composition.dll參考),

class Program
{
	private CompositionContainer _container;

	[Import(typeof(ICalculator))]
	public ICalculator calculator;

	public Program()
	{
		var catalog = new AggregateCatalog();
		catalog.Catalogs.Add(new AssemblyCatalog(typeof(MyCalculator).Assembly));
		string extensionPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Extensions");
		catalog.Catalogs.Add(new DirectoryCatalog(extensionPath));

		_container = new CompositionContainer(catalog);
		_container.ComposeParts(this);
	}

	static void Main(string[] args)
	{
		Program p = new Program();  
		String s;
		Console.WriteLine("Enter Command:");
		while (true)
		{
			s = Console.ReadLine();
			Console.WriteLine(p.calculator.Calculate(s));
		}
	}
}

建置並執行程式,加、減會算出結果,但其他運算(如取餘數)就沒辦法了!

image

 

所以再來就是要擴充這個程式。

4.增加另外一個專案,來擴充計算機取餘數的能力 (專案要加入System.ComponentModel.Composition.dll參考)

[Export(typeof(IOperation))]
[ExportMetadata("Symbol", '%')]
[PartCreationPolicy(CreationPolicy.NonShared)]
public class Mod : IOperation
{
	public int Operate(int left, int right)
	{
		return left % right;
	}
}

 

建置完成後,將DLL放到程式的Extensions目錄之中,再執行程式,會發現計算機已經有取餘數的能力哦!

image

 

除了擴充能力外,如果想要取代原本的功能,可以在Metadata中再加入Priority屬性,

然後在找出對應的運算子的Logic中再加入取得 Priority屬性 最高的運算子,

這樣就可以達到取代原本功能的能力(客製化)。

 

1.Metadata View中再加入Priority屬性,因為要相容,所以設定預設值為0,這樣舊的程式就不需要多宣告這個屬性值。

public interface IOperationData
{
	char Symbol { get; }
	
	[DefaultValue(0)]
	int Priority { get; }
}

 

2.找出對應的運算子的Logic中再加入取得 Priority屬性 最高(OrderByDescending)的運算子

var matchOP = operations.Where((op) => op.Metadata.Symbol.Equals(operation))
	.OrderByDescending(op=>op.Metadata.Priority).FirstOrDefault();

 

3.增加客製化的加號運算子,並設定Priority屬性值為1,如下,

[Export(typeof(IOperation))]
[ExportMetadata("Symbol", '+')]
[ExportMetadata("Priority", 1)]
[PartCreationPolicy(CreationPolicy.NonShared)]
public class Add: IOperation
{
	public int Operate(int left, int right)
	{
		int bonus = 100;
		//客製化 多送 100
		return left + right + bonus;
	}
}

 

建置完成後,將DLL放到程式的Extensions目錄之中,再執行程式,會發現計算機已使用客製的加號運算子!

image

 

結論

如果不喜歡設定ExportMetadata中的屬性是使用Magic String的話,可以參考 Exports and Metadata 裡面 Using a Custom Export Attribute 的部份!

除了明確的標示Import/Export外,在.NET 4.5中也提供 convention 方式來配對,詳細可參考「Managed Extensibility Framework Improvements in .NET 4.5」。

如果不想用Config去設定DI的話,可以試看看 MEF or  StructureMap 哦!

 

 

Demo 方案:MEF-SimpleCalculator.zip

 

 

參考資料

Managed Extensibility Framework (MEF)

Overriding MEF Metadata

Exports and Metadata

Managed Extensibility Framework Improvements in .NET 4.5

MEF開發系列 - Managed Extensibility Framework(MEF)的概念與簡介

Hi, 

亂馬客Blog已移到了 「亂馬客​ : Re:從零開始的軟體開發生活

請大家繼續支持 ^_^