WPF vs Caliburn - IoC Là Gì Và Cấu Hình Bootstrapper Cho Việc Sử Dụng IoC


Trong bài viết Helloworld project chúng ta đã có class Bootstrapper đơn giản, song để dùng được IoC trong Caliburn.Micro chúng ta cần viết lại class này. Qua bài này chúng ta sẽ hiểu hơn về việc đặt tên cho một class trong Caliburn và cách dùng IoC...





I> IoC là gì


IoC viết tắc của Inversion Of Control, IoC là một khái niệm/mô hình/nguyên lý lớn trong việc phát triển những ứng dụng phức tạp, mục đích làm giảm sự phụ thuộc giữa các tầng, các thành phần và các lớp bằng cách nghịch đảo luồng điểu khiển của ứng dụng, chúng ta có thể hiểu rằng IoC là một nhà cung cấp(provider hay container), trong thực tế khi một Project lớn thì việc có nhiều Component, Module, Class... là bình thường song việc quản lý cũng như khởi tạo các đối tượng trên sẽ phức tạp dần theo quy mô dự án, vì thế IoC là giải pháp tối ưu cho việc quản lý, khởi tạo và cung cấp các đối tượng khi cần thiết .


Hai triển khai phổ biến của  IoC là Dependency Injection (chống lại sự phụ thuộc) và Service location.



Thật tế, vấn đề quản lý việc tạo và hủy đối tượng với việc sử dụng IoC có lẽ cần một chút phép màu, vì rằng: ai sẽ giải phóng các đối tượng nếu bạn không chịu trách nhiệm khởi tạo chúng?
Điểm chính của một Framework là nó cung cấp IoC dưới dạng một thành-phần phần mềm gọi là Container. Giống như cái tên nó đã có ngụ ý, container sẽ biết những thành phần nào cần cho ứng dụng của bạn và cố gắng đủ thông minh để hiểu bạn muốn thành phần nào. Điều này xẩy ra khi bạn thực hiện một truy vấn và yêu cầu nó trả về một thành-phần mà nó là một trong những thực thể thường trú trong container . Đây chính là ý nghĩa của IoC. Sẽ không còn việc các Instance của lớp được tạo ra bằng cách sử dụng Constructor được định nghĩa sẳn trong lớp đó theo cách truyền thống(new A();). Thay vào đó chúng ta sẽ đăng ký chúng vào trong container và khi cần chung ta chỉ việc truy vấn container sau đó nó sẽ cho bạn một instance của thành phần mà bạn yêu cầu.


Đa phần IoC được các cộng đồng OpenSource phát triển là chính, Microsoft không chính thức phát tiển mô hình này cho nền tảng .NET(họ có cách khác tương tự về một số vấn đề Module) bởi vì về cơ bản nó vẫn là một điều mới mẻ và mơ hồ cho đa phần những người tiếp cận .NET.  IoC đưa đến một cấu trúc tốt hơn, khả năng module hóa, khả năng kiểm lổi và bảo trì ứng ứng dụng tốt hơn. Một khi đã phát hiện ra điều này, các lập trình viên sẽ không bào giờ quay trở lại còn đường xưa cũ để xây dựng phần mềm.



II> Cấu hình Bootstrapper


Để dùng được IoC chúng ta cần phải quan tâm nhiều đến class Bootstrapper (xem và sử dụng lại Helloworld project), những class chúng ta muốn được IoC quản lý phải được Export, nếu class ấy là một trong nhiều implement cho một interface thì phải Export rõ ràng và khi GetInstance phải truy vấn với nhiều thông tin hơn. Ví dụ




[code language="csharp"]
internal interface IPage
{
}

// Implement của IPage
[Export("PageOne",typeof(IPage))]
internal class PageOne: IPage
{
}

[Export("PageTwo",typeof(IPage))]
internal class PageTwo: IPage
{
}

//------------------------------------
internal interface INews
{
}

// Implement của INews
[Export(typeof(INews))]
internal class News: INews
{
}
[/code]

class Bootstrapper.cs




[code language="csharp"]
internal sealed class Bootstrapper : BootstrapperBase
{
private CompositionContainer _container;

/// <summary>
/// Constructor
/// </summary>
public Bootstrapper()
{
StartRuntime();
}

/// <summary>
/// Override to configure the framework and setup your IoC container.
/// </summary>
protected override void Configure()
{
_container = new CompositionContainer(
new AggregateCatalog(AssemblySource.Instance.Select(x =>
new AssemblyCatalog(x)).OfType<ComposablePartCatalog>())
);

var batch = new CompositionBatch();

batch.AddExportedValue<IWindowManager>(new WindowManager());
batch.AddExportedValue<IEventAggregator>(new EventAggregator());
batch.AddExportedValue(_container);

_container.Compose(batch);
}

/// <summary>
/// Gets the instance.
/// </summary>
/// <param name="serviceType">Type of the service.</param>
/// <param name="key">The key.</param>
/// <returns></returns>
/// <exception cref="System.Exception"></exception>
protected override object GetInstance(Type serviceType, string key)
{
var contract = string.IsNullOrEmpty(key) ? AttributedModelServices.GetContractName(serviceType) : key;
var exports = _container.GetExportedValues<object>(contract);

var enumerable = exports as object[] ?? exports.ToArray();
if (enumerable.Any())
return enumerable.First();

throw new Exception(string.Format("Could not locate any instances of contract {0}.", contract));
}

/// <summary>
/// Gets all instances.
/// </summary>
/// <param name="serviceType">Type of the service.</param>
/// <returns></returns>
protected override IEnumerable<object> GetAllInstances(Type serviceType)
{
return _container.GetExportedValues<object>(AttributedModelServices.GetContractName(serviceType));
}

/// <summary>
/// Override this to provide an IoC specific implementation.
/// </summary>
/// <param name="instance">The instance to perform injection on.</param>
protected override void BuildUp(object instance)
{
_container.SatisfyImportsOnce(instance);
}

/// <summary>
/// Override this to add custom behavior to execute after the application starts.
/// </summary>
/// <param name="sender">The sender.</param>
/// <param name="e">The args.</param>
protected override void OnStartup(object sender, StartupEventArgs e)
{
base.OnStartup(sender, e);

DisplayRootViewFor<MainViewModel>();
}
}
[/code]

Đừng quên add reference "System.ComponentModel.Composition"



III> Sử dụng IoC


Tiến hành xây dựng phần sườn project




  1. Tạo một folder "Models"

  2. Tạo một interface "Models/IPage.cs"

  3. Tạo hai class implement cho interface "IPage" là "PageOne" và "PageTwo", tất cả ghi vào file "Models/Page.cs"

  4. Mở file "Views/MainView.xaml" và thêm vào hai TextBox và hai Button

  5. Thêm vào file "ViewModels/MainViewModels.cs" nội dung như dưới


Yêu cầu: Tôi có 2 TextBox lần lược hiển thị nội dung được Binding đến property "MainViewModel.Page" là "Page.Name" và "Page.Index", tùy và việc tôi nhấn vào Button "PageOne" hay "PageTwo" mà nội dung hiển thị được thay đổi. Các bạn chú ý rằng không hề có việc tôi new một thể hiện của interface"IPage" và gán cho property "MainViewModel.Page" ở code dưới vì tôi dùng IoC để new một class.


MainViewModel.cs




[code language="csharp"]
[Export(typeof(MainViewModel))]
internal class MainViewModel : INotifyPropertyChanged
{
private IPage _page;

public IPage Page
{
set
{
_page = value;
OnPropertyChanged("Page");
}
get { return _page; }
}

public event PropertyChangedEventHandler PropertyChanged;

private void OnPropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}

/// <summary>
/// Constructor
/// </summary>
public MainViewModel()
{

}

/// <summary>
/// Show PageOnr
/// </summary>
public void PageOne()
{
Page = IoC.Get<IPage>("PageOne");
}

/// <summary>
/// Show PageTwo
/// </summary>
public void PageTwo()
{
Page = IoC.Get<IPage>("PageTwo");
}
}
[/code]

IPage.cs




[code language="csharp"]
internal interface IPage
{
/// <summary>
/// Dislay name
/// </summary>
string Name { set; get; }

/// <summary>
/// Number of it
/// </summary>
int Index { set; get; }
}
[/code]

Page.cs




[code language="csharp"]
[Export("PageOne",typeof(IPage))]
internal class PageOne: IPage
{
private string _name = string.Empty;
private int _index;

/// <summary>
/// Dislay name
/// </summary>
public string Name
{
set { _name = value; }
get { return _name; }
}

/// <summary>
/// Number of it
/// </summary>
public int Index
{
get { return _index; }
set { _index = value; }
}

public PageOne()
{
Name = "PageOne";
Index = new Random().Next(100);
}
}

[Export("PageTwo",typeof(IPage))]
internal class PageTwo: IPage
{
private string _name = string.Empty;
private int _index;

/// <summary>
/// Dislay name
/// </summary>
public string Name
{
set { _name = value; }
get { return _name; }
}

/// <summary>
/// Number of it
/// </summary>
public int Index
{
get { return _index; }
set { _index = value; }
}

public PageTwo()
{
Name = "PageTwo";
Index = new Random().Next(100);
}
}
[/code]

MainView.xaml




[code language="xml"]
<Window x:Class="HelloWorl.Views.MainView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainView" Height="200" Width="300">
<Grid>
<StackPanel Margin="10,20,10,50" Orientation="Vertical" VerticalAlignment="Top">
<TextBox x:Name="Name" Height="21"
Text="{Binding Page.Name, UpdateSourceTrigger=PropertyChanged}"
BorderBrush="#FF3399FF" Margin="0,1" />
<TextBox x:Name="Index" Height="21"
Text="{Binding Page.Index, UpdateSourceTrigger=PropertyChanged}"
BorderBrush="#FF3399FF" Margin="0,1" />
</StackPanel>
<StackPanel VerticalAlignment="Bottom" Margin="0,0,0,20">
<Button Content="PageOne" x:Name="PageOne" Height="21"
HorizontalAlignment="Center" VerticalAlignment="Bottom"
Padding="2,1,5,1" MaxWidth="100" MinWidth="100" Margin="0,1"
Background="#FF3399FF" Foreground="White"/>
<Button Content="PageTwo" x:Name="PageTwo" Height="21"
HorizontalAlignment="Center" VerticalAlignment="Bottom"
Padding="2,1,5,1" MaxWidth="100" MinWidth="100" Margin="0,1"
Background="#FF3399FF" Foreground="White" />
</StackPanel>
</Grid>
</Window>
[/code]

Kết quả là


Bootstrapper caliburnTải 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