Quilv Blog

Hướng dẫn chi tiết implement Dependence Injection với Unity Container trong Xamarin

July 30, 2020

Để hiểu được bài này thì phải nắm được thế nào là Dependence Injection (DI). Nếu chưa nắm được thì đọc ở đây.

Ví dụ đơn giản để hiểu cách sử dụng Dependence Injection :

Thiết kế ứng dụng load danh sách Product gồm 2 thông tin:

  • Name
  • Price

Xây dựng theo mô hình MVVM:

  • Models: Product chứa 2 thông tin Name & Price
  • Views: ProductsPage : show thông tin bao gồm name & price của list Products trong listview.
  • ViewModels: ProductsViewModel: chứa list Products được load từ 1 server thông qua 1 Service Class. ( ProductsService)

Yêu cầu sử dụng Dependence Injection để giảm sự liên kết cứng giữa ProductsViewModel & Service (ProductsService) để có thể sử dụng Unit Test.

screen shot 2020 07 30 at 15 48 33

Trong trường hợp không sử dụng DI, thì các class được cấu hình như sau:

screen shot 2020 07 30 at 16 08 36

Với các Class:

//Product
public class Product
{
    public string Name { get; set; }
    public double Price { get; set; }
    public override string ToString()
    {
        return $"{Name} : {Price} USD";
    }
}

//Interface
public interface IProductsService
{
    IEnumerable<Product> Getproducts();
}

//Service Load Test Data
public class ProductsService: IProductsService
{
    public ProductsService()
    {
    }

    public IEnumerable<Product> Getproducts()
    {
        return new List<Product> {
            new Product { Name = "Food 1", Price = 5 },
            new Product { Name = "Food 2", Price = 12 },
        };
    }
}

// ViewsModel
public class ProductsViewModel
{
    public IEnumerable<Product> Products { get; set; }
    public ProductsViewModel()
    {
        var productsService = new ProductsService();
        Products = productsService.Getproducts();
    }
}

Như trên, thì ta thấy sự liên kết cứng giữa ProductsViewModel & ProductService. Nếu trường hợp ProductService do bên thứ 3 cung cấp & họ bỏ phương thức khởi tạo ko có tham số, nếu như trong code chúng ta có rất nhiều chỗ implement phương thức khởi tạo đó, thì chúng ta phải thay đổi lại hết. Và cái này liên kết cứng nên chúng ta không thể viết Unit Test cho trường hợp này được.

Tiến hành implement DI với Unity Container trong Xamarin.

  1. Thay code của lớp ProductsViewModel như sau:
public class ProductsViewModel
    {
        private readonly IProductsService _productsService;

        public IEnumerable<Product> Products { get; set; }


        public ProductsViewModel(IProductsService productsService)
        {
            _productsService = productsService; //new ProductsService();

            DownloadProduct();
        }

        void DownloadProduct() {
            Products = _productsService.Getproducts();
        }
    }
  1. Add Nudget sau:
  • Unity
  • Unity.ServiceLocation
  • CommonServiceLocator
  1. Thay đổi code-behind của App.xaml:
public partial class App : Application
    {
        public App()
        {
            InitializeComponent();

            //Init UnityContainer
            UnityContainer unityContainer = new UnityContainer();
            unityContainer.RegisterType<IProductsService, ProductsService>();

            var unityServiceLocator = new UnityServiceLocator(unityContainer);
            ServiceLocator.SetLocatorProvider(() => unityServiceLocator);

            MainPage = new ProductsPage();
        }

        protected override void OnStart()
        {
        }

        protected override void OnSleep()
        {
        }

        protected override void OnResume()
        {
        }
    }

Khi đối tượng của ProductViewModel được tạo, và nó yêu cầu truyền đối tượng IProductsService, và đối tượng này được tự động tạo ra từ ServiceLocator từ Unity Container.

Lúc này khi khởi chạy sẽ sinh 1 lỗi: không tìm thấy default constructor của class ProductViewModel, vì ProductsPage.xaml đang binding tới.

<ContentPage.BindingContext>
    <viewModels:ProductsViewModel/>
</ContentPage.BindingContext>

Để giải quyết vấn đề này thì có 2 cách để xử lý:

Cách 1: Chuyển BindingContext vào code behind để xử lý.

public partial class ProductsPage : ContentPage
{
    public ProductsPage()
    {
        InitializeComponent();

        BindingContext = ServiceLocator.Current.GetInstance<ProductsViewModel>();
    }
}

Cách 2: Sử dụng binding trên xaml với create ViewModelLocator:

Tạo class ViewModelLocaltor trong folder ViewModels:

public class ViewModelLocator
{
    public ProductsViewModel ProductsViewModel {
        get
        {
            return ServiceLocator.Current.GetInstance<ProductsViewModel>();
        }
    }
}

Tạo StaticResource trong App.xaml để sử dụng cho toàn proj với xaml file.

 <Application.Resources>
    <ResourceDictionary>
        <vm:ViewModelLocator x:Key="Locator"/>
    </ResourceDictionary>
</Application.Resources>

Trong xaml file:

<?xml version="1.0" encoding="UTF-8"?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:viewModels="clr-namespace:UnityIoCContainer.ViewModels"
             xmlns:ios="clr-namespace:Xamarin.Forms.PlatformConfiguration.iOSSpecific;assembly=Xamarin.Forms.Core"
             ios:Page.UseSafeArea="True"
             x:Class="UnityIoCContainer.Views.ProductsPage"
             BindingContext="{Binding ProductsViewModel, Source={StaticResource Locator}}">

    <!--<ContentPage.BindingContext>
        <viewModels:ProductsViewModel/>
    </ContentPage.BindingContext>-->

    <ContentPage.Content>
        <ListView ItemsSource="{Binding Products}"/>
    </ContentPage.Content>
</ContentPage>

Như vậy là đã implement xong DI trong Xamarin sử dụng Unity Container.

Full Source code : check ở đây


Written by Quilv Follow me on Facebook