iT邦幫忙

第 11 屆 iT 邦幫忙鐵人賽

DAY 27
2

前言

產生Controller物件相關物件關係如下面UML圖

relationship_pic.PNG

透過ControllerFactory建立一個Controller控制器物件.而ControllerFactory依賴IControllerActivator物件產生Controller.

上面IControllerActivator可以透過建立使用我們的依賴注入容器來替換原本反射產生物件.

DependencyResolverMVC提供的一個可替換物注入點,今天我們會藉由他來我們實現注入MVC方式.

Aufofac依賴注入容器

在實現自己的DependencyResolver前先談談Autofac容器做甚麼用的?

我之前有寫一篇IOC(控制反轉),DI(依賴注入) 深入淺出~~

講述 IOC(控制反轉),DI(依賴注入) 這兩個設計技巧的理念核心.

言簡意賅可以統一交由容器來幫忙管理物件生命週期和建立方式,也管理物件相依性,兩個重點我們使得只需要提供使用類別的特徵(型別或其他可辨別特徵),容器就提供給我們相對應的物件.

Autofac有需多使用方式這裡就不一一介紹,有興趣讀者可以上網google或是查閱Autofac官方文件

IDependencyResolver介面

DependencyResolver是一個靜態物件,MVC application使用同一個解析器(DefaultDependencyResolver)而他有一個SetResolver方法可以替換成其他DependencyResolver

IDependencyResolver有兩個方法需要實現.

public interface IDependencyResolver
{
    object GetService(Type serviceType);
    IEnumerable<object> GetServices(Type serviceType);
}

MVC依賴於GetServiceGetServices,取得物件實例並提供一個抽象提供外部提供修改或擴充.

預設使用(DefaultDependencyResolver)這個解析器來取得我們物件(DefaultDependencyResolver解析器使用Activator.CreateInstance(serviceType);建立物件)

建立CustomerDependencyResolver(IDependencyResolver)

這邊我們利用autofac來完成建立物件動作,先建立一個ILifetimeScope _container由建構子注入此物件.

public class CustomerDependencyResolver : IDependencyResolver
{
    private readonly ILifetimeScope _container;

    public CustomerDependencyResolver(ILifetimeScope container)
    {
        if (container == null)
            throw new ArgumentNullException(nameof (container));

        _container = container;
    }

    public object GetService(Type serviceType)
    {
        return _container.ResolveOptional(serviceType);
    }

    public IEnumerable<object> GetServices(Type serviceType)
    {
        return (IEnumerable<object>) _container.ResolveOptional(typeof (IEnumerable<>).MakeGenericType(serviceType));
    }
}

GetService呼叫ResolveOptional方法透過Type到容器中搜尋匹配的物件並返回.

CustomerControllerActivator(IControllerActivator)

IControllerActivator有一個Create方法,ControllerFacotry靠它來幫我們產生使用Controller物件,而我們在這邊建立自己IControllerActivator並在Create方法中實現自己得邏輯.透過DependencyResolver來產生物件(替換成CustomerDependencyResolver)

public class CustomerControllerActivator : IControllerActivator
{
    public IController Create(RequestContext requestContext, Type controllerType)
    {
        return (IController) DependencyResolver.Current.GetService(controllerType);
    }
}

我們會在Autofac容器註冊目前Assembly所有繼承IController物件.

//注入typeof(MvcApplication).Assembly 中所有繼承IController物件.
builder.RegisterControllers(typeof(MvcApplication).Assembly);

在上面CustomerControllerActivator.Create會透Autofac解析器幫我們建立Controller

在Application_Start中MVC替換成自己的解析器

  1. 首先利用ControllerBuilderSetControllerFactory方法,重新替換使用ControllerFacotry.

  2. 在利用builder.RegisterControllers注入typeof(MvcApplication).Assembly中所有繼承IController物件.

  3. 註冊IMemberService介面物件(裡面有一個int GetMemberBalance(int memberId);方法來模擬取得會員餘額)

  4. DependencyResolver.SetResolver(new CustomerDependencyResolver(builder.Build()))替換成我們使用的解析器

因為ControllerFacotry預設使用DefaultControllerActivator,而我們需要替換成自己建立得CustomerControllerActivator並利用容器來幫我們注入.

public class MvcApplication : HttpApplication
{
    protected void Application_Start()
    {
        RouteConfig.RegisterRoutes(RouteTable.Routes);
        BundleConfig.RegisterBundles(BundleTable.Bundles);
        //把DefaultControllerFactory 中的IControllerActivator替換成我們自己寫的CustomerControllerActivator
        ControllerBuilder.Current.SetControllerFactory(
            new DefaultControllerFactory(new CustomerControllerActivator()));
        AutofacRegister();
    }

    private static void AutofacRegister()
    {
        ContainerBuilder builder = new ContainerBuilder();
        //注入typeof(MvcApplication).Assembly 中所有繼承IController物件.
        builder.RegisterControllers(typeof(MvcApplication).Assembly);
        builder.RegisterType<MemberService>().As<IMemberService>();
        //替換成自己的DependencyResolver
        DependencyResolver.SetResolver(new CustomerDependencyResolver(builder.Build()));
    }
}

在Controller使用注入

HomeController控制器中在建構子注入,並呼叫IMemberService.GetMemberBalance方法

執行專案請求Home/About頁面可以看到ViewBag.Message已經成功顯示一個HardCode餘額了.

public class HomeController : Controller
{
    private readonly IMemberService _service;

    public HomeController(IMemberService service)
    {
        _service = service;
    }

    public ActionResult About()
    {
        ViewBag.Message = $"Member Balance { _service.GetMemberBalance(123)}";

        return View();
    }
}

小結:

DefaultControllerActivator使用反射建立一個Controller物件

然而IControllerActivator提供一個產生Controller接口,而我們可以藉由實現此介面並使用DependencyResolver靜態物件產生Controller物件(藉由容器框架產生).

最後會把Controller依賴物件藉由依賴注入容器注入進去.

Github範例程式原始碼 CustomerContainer分支上


上一篇
[Day26] 動手DIY改造 Asp.net MVC- Route解析機制
下一篇
[Day28] 動手DIY改造 Asp.net MVC- 建立自己ActionInvoker和Model綁定機制
系列文
從Asp.net框架角度進入Asp.net MVC原始碼30

尚未有邦友留言

立即登入留言