C# - Tìm Hiểu Về MEF Trong Lập Trình C#


Hôm nay tôi sẽ giới thiệu với các bạn một Framework đến từ Microsoft - framework này không quá lớn nhưng là khá mạnh khi các bạn làm việc với Module/Plug-in trong một dự án .NET thuần túy. Hy vọng chúng ta sẽ hiểu về "Managed Extensibility Framework" hay còn gọi là MEF phần nào qua bài viết này.





I> MEF là gì


Như tôi nói ở trên, MEF được Microsoft phát triển và bổ sung vào .NET 4.0 và cao hơn vì thế chúng ta không cần phải tải thêm DLLs cũng như không phải có một cấu hình phức tạp nào để có thể sử dụng nó. MEF  cho phép chúng ta làm việc theo dạng module một cách đơn giản, giảm thiểu sự ràng buộc giữa các lớp các đối tượng với nhau. Trước kia khi mà MEF chưa ra đời chúng ta thường chật vật với một dự án với quá nhiều thành phần liên kết với nhau, việc khởi tạo cũng như quản lý các thành phần này vô cùng phức tạp cho các nhà phát triển phần mềm, sau đó MS đã cho ra mắt ý tưởng là module hóa các dự án với nhiều thành phần gắn kết nhau - thứ nhất chúng ta sẽ có cái nhìn rõ ràng và sáng sủa hơn, thứ hai việc nâng cấp và mở rộng ứng dụng cũng trở nên dễ dàng hơn khi mà mã nguồn không cần phải thay đổi nhiều. Các bạn có thể hình dung qua hình ảnh sau:


mef c#


Nếu các bạn để ý, MEF có phần giống với IoC, về mặt nào đó chúng giống nhau song lại không trùng lắp, MEF có một số chức năng thua xa IoC nhưng nó không bị đào thải vì nó có điểm mạnh riêng và không quá phức tạp cho việc cài đặt.



II> Ví dụ ứng dụng


Trước tiên chúng ta cần tạo hai project, một là "Program console" và một là "Library" như sau, trong đó ModuleA là "Library project"


mef c# example


Để có thể sử dụng được MEF chúng ta cần add reference cho hai project như hình trên




  1. "Microsoft.Practices.ServiceLocation"

  2. "System.ComponentModel.Composition"


Tiến hành cấu hình MEF cho "RUN.MEF project" bằng cách tạo class "MEFServiceLocator" kế thừa "ServiceLocatorImplBase", class này khi được tạo ra sẽ chịu trách nhiệm cung cấp Instance khi cần thiết.
MEFServiceLocator.cs




[code language="csharp"]
internal class MEFServiceLocator : ServiceLocatorImplBase
{
/// <summary>
/// MEF Compisition container
/// </summary>
private readonly CompositionContainer compositionContainer;

/// <summary>
/// Initializes a new instance of the <see cref="MEFServiceLocator"/> class.
/// </summary>
/// <param name="compositionContainer">The MEF composition container.</param>
public MEFServiceLocator(CompositionContainer compositionContainer)
{
this.compositionContainer = compositionContainer;
}

/// <summary>
/// Resolves the instance of the requested service.
/// </summary>
/// <param name="serviceType">Type of instance requested.</param>
/// <returns>
/// The requested service instance.
/// </returns>
protected override IEnumerable<object> DoGetAllInstances(Type serviceType)
{
var list = new List<object>();
IEnumerable<Lazy<object, object>> exports = this.compositionContainer.GetExports(serviceType, null, null);
list.AddRange(exports.Select(export => export.Value));
return list;
}

/// <summary>
/// Resolves all the instances of the requested service.
/// </summary>
/// <param name="serviceType">Type of service requested.</param><param name="key">Name of registered service you want. May be null.</param>
/// <returns>
/// Sequence of service instance objects.
/// </returns>
protected override object DoGetInstance(Type serviceType, string key)
{
List<Lazy<object, object>> exports = this.compositionContainer.GetExports(serviceType, null, key).ToList();
IOrderedEnumerable<Lazy<object, object>> orderByDescending = exports
.Where(export => ((IDictionary<string, object>) export.Metadata).ContainsKey("Priority"))
.OrderByDescending(export => ((IDictionary<string, object>) export.Metadata)["Priority"]);

if (orderByDescending.Any())
{
return orderByDescending.First().Value;
}

if (exports.Any())
{
if (exports.Count > 1)
{
return exports[0].Value;
}

return exports.Single().Value;
}

throw new ActivationException(
this.FormatActivationExceptionMessage(new CompositionException("Export not found"), serviceType, key));
}
[/code]

Quay lại "ModuleA project" chúng ta define một interface và một lớp implement cho interface này như sau
IHello.cs




[code language="csharp"]
public interface IHelloWorld
{
/// <summary>
/// Say hello
/// </summary>
void Hello();
}
[/code]

Hello.cs




[code language="csharp"]
[Export(typeof (IHelloWorld))]
public class HelloWorld : IHelloWorld
{
private int count = 0;

/// <summary>
/// Say hello
/// </summary>
public void Hello()
{
Console.WriteLine("{0} - Hello world", count);
count++;
}
}
[/code]

Chú ý rằng : các module bạn muốn MEF quản lý và làm việc thì cần phải "Export", nếu trường hợp bạn có nhiều implement cho một interface thì bạn cần export thêm key và khi getInstance cần truyền vào key đó(xem thêm ở SourceCode kèm theo)
Và bây giờ chúng ta sẽ sử dụng MEF, quay về class program.cs của "RUN.MEF", trước khi làm việc gì đó chúng ta cần Created Data cho Container, ở đây tôi làm việc đó qua hàm Create(), ở hàm này cứ mỗi module tương ứng một file Dll và tôi sẽ load file này lên và watch tất cả các Class được "Export". Tại hàm Main() tôi dùng như bình thường nhưng các bạn có để ý rằng tôi không hề new một object nào và gán cho property trước khi dùng chúng mà các property được set giá trị bằng "ServiceLocator.Current.GetInstance();" - đó là điều quan trọng và ở bất kỳ đâu trong ứng dụng, bạn chỉ cần get instance như thế là lấy đc instance mong muốn và không cần thông qua các obj khác(instance này có thể được nhiều instance khác dùng chung và updated data bằng cách này).
Program.cs




[code language="csharp"]
public class Program
{
private static CompositionContainer _compositionContainer;

private static IHelloWorld ModuleA
{
get { return ServiceLocator.Current.GetInstance<IHelloWorld>(); }
}

private static void Main(string[] args)
{
Create();

ModuleA.Hello();
ModuleA.Hello();

Console.ReadLine();
}

public static void Create()
{
var aggregateCatalog = new AggregateCatalog();
aggregateCatalog.Catalogs.Add(new DirectoryCatalog(AppDomain.CurrentDomain.BaseDirectory,
"ModuleA.Dll"));
aggregateCatalog.Catalogs.Add(new DirectoryCatalog(AppDomain.CurrentDomain.BaseDirectory,
"ModuleB.Dll"));


_compositionContainer = new CompositionContainer(aggregateCatalog);
var mefServiceLocator = new MEFServiceLocator(_compositionContainer);
ServiceLocator.SetLocatorProvider(() => mefServiceLocator);
}
}
[/code]

Còn tiếp...


Sourcecode tại đây, chúc các bạn thành công!
Phạm Tuân


Chúc các bạn thành công!
PHẠM TUÂN