Dependency Injection ด้วย Spring.NET/Microsoft Unity… Actions!

ตัวอย่าง Movie Finder

ตัวอย่าง Movie Finder ซึ่งเป็นตัวอย่างที่ classic มากๆสำหรับอธิบาย DI นี้มากจาก DI by Martin Fowler โดยเราจะมี class MovieLister เป็น dependent และกำหนดให้ interface IMovieFinder เป็น dependency สำหรับให้บริการค้นหา movie กับ MovieLister แล้วผมสร้าง service stub ง่ายๆชื่อว่า SimpleMovieFinder ขึ้นมาเพื่อใช้ทดสอบ MovieLister ของผมเบื้องต้นก่อน เขียน code ได้ดังนี้

// Movie Model
public class Movie 
{
    public string Title { get; set; }
    public string Director { get; set; }
}

// MovieFinder Service
public interface IMovieFinder
{
    IEnumerable<Movie> FindAll();
}

// MovieLister Model
public class MovieLister 
{
    public IMovieFinder MovieFinder { protected get; set; }

    public IEnumerable<Movie> MoviesDirectedBy(string director)
    {
        var movies = from n in MovieFinder.FindAll()
        where n.Director.Equals(director, StringComparison.CurrentCultureIgnoreCase)
        select n;
        return movies;
    }
}

// SimpleMovieFinder Stub Service
public class SimpleMovieFinder : IMovieFinder 
{
    public IEnumerable<Movie> FindAll()
    {
        var movies = from m in Enumerable.Range(1, 100)
        select new Movie {
            Title = string.Format("Title{0}", m),
            Director = string.Format("Director{0}", m),
        };
        return movies;
    }
}

Code ทดสอบ MovieLister ที่ยังไม่มี DI

[TestMethod]
public void TestWithoutDI()
{
    var aMovieLister = new MovieLister
    {
        MovieFinder = new SimpleMovieFinder(),//Inject SimpleMovieFinder
    };

    var movie = aMovieLister.MoviesDirectedBy("Director10").Single();

    Console.WriteLine("Title:{0}, Director:{1}", movie.Title, movie.Director);
}

ต่อไปผมจะแสดงกรณีที่ code ทดสอบของผมใช้ DI เป็นหัวข้อๆไป เชิญชมได้เลยครับ

Dependency Injection ด้วย Spring.NET Framework

เริ่มต้น load มา install ที่เครื่องเราก่อนตาม link ที่หัวข้อได้เลยครับ

Spring.NET XML-Based Config

1. เพิ่ม lib Spring.NET Spring.Core.dll

2. เพิ่ม App.config

<configuration>
  <configSections>
    <sectionGroup name="spring">
      <section name="context" type="Spring.Context.Support.ContextHandler, Spring.Core"/>
      <section name="objects" type="Spring.Context.Support.DefaultSectionHandler, Spring.Core" />
    </sectionGroup>
  </configSections>

  <spring>

    <context>
      <resource uri="config://spring/objects"/>
    </context>

      <object id="MyMovieFinder"
      type="DI.Test.MovieFinder.SimpleMovieFinder, DI.Test"/>

    </objects>

  </spring>
</configuration>

3. Code ทดสอบ XML Based Config Spring.NET DI

using Spring.Context;
using Spring.Context.Support;
[TestMethod]
public void TestXmlBasedSpringDI()
{
    IApplicationContext ctx = ContextRegistry.GetContext();

    var aMovieLister = ctx["MyMovieLister"] as MovieLister;

    var movie = aMovieLister.MoviesDirectedBy("Director10").Single();

    Console.WriteLine("Title:{0}, Director:{1}", movie.Title, movie.Director);
}

Spring.NET Code-Based Config

1. เพิ่ม reference lib Spring.NET Core: Spring.Core.dll และ Spring.Aop.dll,  Spring.Core.Configuration.dll

2. เพิ่ม reference lib Spring.NET Core Configuration: Spring.Core.Configuration.dll

3. เพิ่ม class MovieFinderBootstrapper

using Spring.Context.Attributes;

[Configuration]
public class MovieFinderBootstrapper
{
    [Definition]
    public virtual MovieLister MovieLister()
    {
        var movieLister = new MovieLister
        {
            MovieFinder = SimpleMovieFinder(),
        };

        return movieLister;
    }
    [Definition]
    public virtual IMovieFinder SimpleMovieFinder()
    {
        return new SimpleMovieFinder();
    }

}

4. Code ทดสอบ Code-Based Config Spring.NET DI

using Spring.Context;
using Spring.Context.Support;
[TestMethod]
public void TestCodeBasedSpringDI()
{
    IApplicationContext ctx = CreateContainerUsingCodeConfig();

    var aMovieLister = ctx.GetObject<MovieLister>();

    var movie = aMovieLister.MoviesDirectedBy("Director10").Single();

    Console.WriteLine("Title:{0}, Director:{1}", movie.Title, movie.Director);
}

private static IApplicationContext CreateContainerUsingCodeConfig()
{
    CodeConfigApplicationContext ctx = new CodeConfigApplicationContext();
    ctx.ScanAllAssemblies();
    ctx.Refresh();
    return ctx;
}

Dependency Injection ด้วย Microsoft Unity

เริ่มต้น load มา install ที่เครื่องเราก่อนตาม link ที่หัวข้อได้เลยครับ

Microsoft Unity Code-Based Config

1. เพิ่ม reference lib Microsoft Unity Microsoft.Practices.Unity.dll

2. แก้ไข MovieLister โดยเพิ่ม annotation Dependency ให้ property MovieFinder

public class MovieLister
{
    [Dependency]
    public IMovieFinder MovieFinder { protected get; set; }

    public IEnumerable<Movie> MoviesDirectedBy(string director)
    {
        var movies = from n in MovieFinder.FindAll()
                        where n.Director.Equals(director, StringComparison.CurrentCultureIgnoreCase)
                        select n;
        return movies;
    }
}

3. Code ทดสอบ Code-Based Microsoft Unity DI

using Microsoft.Practices.Unity;
[TestMethod]
public void TestCodeBasedUnityDI()
{
    IUnityContainer container = new UnityContainer()
    .RegisterType<IMovieFinder, SimpleMovieFinder>();

    var aMovieLister = container.Resolve<MovieLister>();
    var movie = aMovieLister.MoviesDirectedBy("Director10").Single();

    Console.WriteLine("Title:{0}, Director:{1}", movie.Title, movie.Director);
}

Microsoft Unity XML-Based Config

1. เพิ่ม reference lib Microsoft Unity Microsoft.Practices.Unity.dll, Microsoft.Practices.Unity.Configuration.dll

2. แก้ไข MovieLister โดยเพิ่ม annotation Dependency ให้ property MovieFinder (เหมือน Code-Based Config)

3. เพิ่ม App.config

<configuration>
  <configSections>
    <section name="unity"
    type="Microsoft.Practices.Unity.Configuration.UnityConfigurationSection, Microsoft.Practices.Unity.Configuration"/>
  </configSections>

  <unity xmlns="http://schemas.microsoft.com/practices/2010/unity">

    <container>
      <register type="DI.Test.MovieFinder.IMovieFinder, DI.Test" mapTo="DI.Test.MovieFinder.SimpleMovieFinder, DI.Test" />
    </container>

  </unity>
</configuration>

4. เขียน Code ทดสอบ

using Microsoft.Practices.Unity;
using Microsoft.Practices.Unity.Configuration;
[TestMethod]
public void TestXmlBasedUnityDI()
{
    IUnityContainer container = new UnityContainer().LoadConfiguration();

    var aMovieLister = container.Resolve<MovieLister>();
    var movie = aMovieLister.MoviesDirectedBy("Director10").Single();

    Console.WriteLine("Title:{0}, Director:{1}", movie.Title, movie.Director);
}

เสร็จแล้วครับ Dependency Injection ด้วย Spring.NET/Microsoft Unity… Actions!

แถมให้อีกนิด

Dependency Injection ด้วย Ninject

เริ่มต้น load มา install ที่เครื่องเราก่อนตาม link ที่หัวข้อได้เลยครับ

Ninject Code-Based Config

1. เพิ่ม reference library Ninject Ninject.dll

2. แก้ไข MovieLister โดยเพิ่ม annotation Inject ให้ property MovieFinder

using Ninject;

public class MovieLister //MovieLister Model
{
    [Inject]
    public IMovieFinder MovieFinder { protected get; set; }

    public IEnumerable<Movie> MoviesDirectedBy(string director)
    {
        var movies = from n in MovieFinder.FindAll()
                        where n.Director.Equals(director, StringComparison.CurrentCultureIgnoreCase)
                        select n;
        return movies;
    }
}

3. เพิ่ม class MovieFinderNinjectModule

using Ninject;
using Ninject.Modules;
public class MovieFinderNinjectModule : NinjectModule
{
    public override void Load()
    {
        Bind<IMovieFinder>().To<SimpleMovieFinder>();
        Bind<MovieLister>().ToSelf();
    }
}

4. Code ทดสอบ Code-Based Config Ninject DI

using Ninject;
using Ninject.Modules;
[TestMethod]
public void TestNinjectDI()
{
    IKernel ninjectKernel = new StandardKernel(new MovieFinderNinjectModule());

    var aMovieLister = ninjectKernel.Get<MovieLister>();

    var movie = aMovieLister.MoviesDirectedBy("Director10").Single();

    Console.WriteLine("Title:{0}, Director:{1}", movie.Title, movie.Director);
}

Ninject ไม่ชอบ xml-based config มันก็เลยไม่สนับสนุน เพราะว่า

  • การ Configuration ของเรามีความซับซ้อน(complex ) และ ใช้คำเยอะมากเกินไป(verbose) เพระว่าเราจะต้องเขียนชื่อ assembly และ ระบุชื่อ type ทุกชนิดที่จำเป็นในการ Configuration ของเรา
  • มันเป็นอะไรที่ง่ายมากๆที่จะทำลายโปรแกรมของเราโดยผ่านการพิมพ์ผิดพลาดเพียงเล็กน้อยเท่านั้น
  • โดยปกติเครื่องมือ Studio พัฒนา Software จะมี refactoring code แถมให้เราด้วย ซึ่งมันจะช่วยเราแก้ไข code ทุกๆจุดโดยอัตโนมัติ อย่างเช่นเปลี่ยนชื่อ class หรือ property ไรงี้ และแน่นอนมันจะไม่เกิดผลกับการ injection objects(wire up objects) ด้วย xml-config ของเราด้วย นั่นเป็นงานที่เราจะต้องกลับไปแก้ไข DI xml-config เอง (งานเข้า)
  • โดยปกติ Studio พัฒนา Software จะ compile code ไม่ผ่านถ้าหากเราระบุ Class มั่วๆ ที่ไม่มีใน assembly library จากของเรา และจากของคนอื่น ที่เราได้เพิ่มเข้ามาใน projects แต่ว่ามันจะ compile ผ่านเมื่อเรา wire up objects ด้วย xml-config ซึ่งเราจะรู้ว่ามันผิดพลาดก็ตอน run โปรแกรมนี้ขึ้นมาแล้วเท่านั้น (งานเข้าหนักกว่าเดิมอีกทีนี้)

สรุปว่า Ninject เป็น DI code-based config ที่มีรูปแบบ fluent interface(หรือที่เรียกกันว่า “embedded domain-specific language”) ในการประกาศชนิดของ objects เพื่อ binding(wiring) พวกมันเข้าด้วยกัน

Dependency Injection ด้วย Autofac

เริ่มต้น load มา install ที่เครื่องเราก่อนตาม link ที่หัวข้อได้เลยครับ

1. เพิ่ม reference library Autofac Autofac.dll

2. Code ทดสอบ Code-Based Config Autofac DI

using Autofac;

[TestMethod]
public void TestCodeBasedAutofacDI()
{
    var builder = new ContainerBuilder();

    builder.Register(c => new MovieLister()).As<MovieLister>().PropertiesAutowired();
    builder.RegisterType<SimpleMovieFinder>().As<IMovieFinder>();

    var container = builder.Build();
    var aMovieLister = container.Resolve<MovieLister>();

    var movie = aMovieLister.MoviesDirectedBy("Director10").Single();

    Console.WriteLine("Title:{0}, Director:{1}", movie.Title, movie.Director);
}

Dependency Injection ด้วย Castle Windsor

1. install Castle Windsor ด้วย NuGet Package Manager Console

PM> Install-Package Castle.Windsor

2. เขียน Class Installer สำหรับเพื่อ install component ต่างๆให้ DI container

public class MovieStoreInstaller : IWindsorInstaller
{
    public void Install(IWindsorContainer container, IConfigurationStore store)
    {
        container.Register(
            Component.For<IMovieFinder>().ImplementedBy<SimpleMovieFinder>(),
            Component.For<MovieLister>());
    }
}

3. Code ทดสอบ Code-Based Config Castle Windsor DI

[TestMethod]
public void TestCodeBasedCastleWindsorDI()
{
	using (var container = new WindsorContainer())	
	{		
		container.Install(new MovieStoreInstaller());

                var aMovieLister = container.Resolve();

                var movie = aMovieLister.MoviesDirectedBy("Director10").Single();

                Console.WriteLine("Title:{0}, Director:{1}", movie.Title, movie.Director)
	}
}

ผมขอจบบทความ Dependency Injection ด้วย Spring.NET/Microsoft Unity… Actions! ไว้เพียงเท่านี้ครับ

ขอบคุณครับ 🙂

ล้มแล้วลุกคือคนเป็น(ที่สำเร็จ) ล้มแล้วไม่ลุกคือคนตาย(จากความสำเร็จ)

— Chav.P–

Advertisements

#net, #c, #castle-windsor, #design, #di, #ioc, #ninject, #pattenrs