Unity與Web Api的Controller注入

  • 739
  • 0
  • 2020-03-06

在使用.net core寫web api的時候,Controller的注入也是使用建構子注入並預設註冊在Startup.cs檔中
可是當我使用.net framework的web api加上Unity注入的時候卻出現錯誤訊息:
嘗試建立‘OrderController’ 時發生錯誤。請檢查該controller 類別是否有提供不帶任何參數的公開建構函式

本篇會說明怎麼解決這個問題
範例原始碼https://github.com/shadow061103/Unity_WsApiDemo

首先必須要知道web api的Controller是怎麼建成的,
再針對可以擴充的部分做抽換
請先閱讀過HuanLin的這篇文章
https://www.huanlintalk.com/2014/08/how-web-api-controller-is-created.html
 

通常解決方式有兩種
1.抽換IHttpControllerActivator,自訂類別實作IHttpControllerActivator,
並取代Web Api的預設實作(DefaultHttpControllerActivator)
2.抽換IDependencyResolver,自訂類別實作IDependencyResolver,
並取代Web Api的預設實作(EmptyResolver)

 

自訂IHttpControllerActivator

IHttpControllerActivator是定義如何建立Controller
裡面只有一個方法Create
如下

public interface IHttpControllerActivator
{
IHttpController Create(
HttpRequestMessage request,
HttpControllerDescriptor controllerDescriptor,
Type controllerType
  )
}

我們先自訂一個類別繼承IHttpControllerActivator
這邊還可以在request上面增加自訂的屬性

public class CustomHttpControllerActivator : IHttpControllerActivator
    {
        public IHttpController Create(HttpRequestMessage request, 
            HttpControllerDescriptor controllerDescriptor, Type controllerType)
        {
            if (controllerType == typeof(OrderController))
            {
                var orderService = new OrderService();
                request.Properties.Add("RequestTime",DateTime.Now);
                return new OrderController(orderService);

            }

            return null;
        }
    }

實作完類別之後還要在Web Api內部的預設實作抽換成我們的才行
兩個地方可以做抽換,一個是在程式進入點Global.asax的Application_Start方法內
或是WebApiConfig.cs內
以下直接做範例

protected void Application_Start()
        {
            GlobalConfiguration.Configure(WebApiConfig.Register);
            GlobalConfiguration.Configuration.Services.Replace(typeof(IHttpControllerActivator),
           new CustomHttpControllerActivator());

        }
 public static void Register(HttpConfiguration config)
        {
            // Web API 設定和服務

            // Web API 路由
            config.MapHttpAttributeRoutes();

            config.Routes.MapHttpRoute(
                name: "DefaultApi",
                routeTemplate: "api/{controller}/{id}",
                defaults: new { id = RouteParameter.Optional }
            );

            //替換 IHttpControllerActivator 實作
            config.Services.Replace(typeof(IHttpControllerActivator),
                new CustomHttpControllerActivator());
        }

除了以上兩種是手工的之外
我們也可以靠DI容器注入
這邊用Unity示範
首先把我們IHttpControllerActivator的實作改掉
改成用建構式注入IUnityContainer(這邊省略)
現在會變比較簡潔,但是需要先去WebApiConfig註冊型別

 public IHttpController Create(HttpRequestMessage request, 
            HttpControllerDescriptor controllerDescriptor, Type controllerType)
        {
            var controller = _container.Resolve(controllerType);
            return controller as IHttpController;
        }

WebApiConfig.cs

 var container = new UnityContainer();
            container.RegisterType<OrderController>();
            container.RegisterType<IOrderService, OrderService>();
            var customControllerActivator=new CustomHttpControllerActivator(container);
            config.Services.Replace(typeof(IHttpControllerActivator),
                customControllerActivator);

 

自訂IDependencyResolver

除了靠Services屬性去抽換,還可以透過HttpConfiguration提供的DependencyResolver屬性
他的型別是IDependencyResolver介面

在建成Controller的流程中,IHttpControllerActivator 的GetInstanceOrActivator 會呼叫GetDependencyScope 
來取得一個IDependencyScope物件,然後呼叫他的GetService方法來取得controller物件,這邊取得的IDependencyScope物件是
web api的預設實作EmptyResolve

擴充方法GetDependencyScope的source code

static IDependencyScope GetDependencyScope(this HttpRequestMessage request)
{
IDependencyScope result;
// 嘗試從當前request 物件的Properties 屬性中取得IDependencyScope 物件。
if (!request.Properties.TryGetValue<IDependencyScope>(
HttpPropertyKeys.DependencyScope, out result))
 {

IDependencyResolver dependencyResolver =
request.GetConfiguration().DependencyResolver;

result = dependencyResolver.BeginScope();

request.Properties[HttpPropertyKeys.DependencyScope] = result;
request.RegisterForDispose(result);
 }
return result;
}

Web api要解析型別的時候會先去取得與request相關的IDependencyScope
如果當前的request並沒有關聯的IDependencyScope物件 那就透過HttpConfiguration.DependencyResolver來取得全域的

DI容器,然後再利用這容器建一個新的IDependencyScope物件,存入當前的request

public interface IDependencyResolver : IDependencyScope, IDisposable
{
IDependencyScope BeginScope();
}

public interface IDependencyScope
{
Object GetService(Type serviceType);
IEnumerable<Object> GetServices(Type serviceType);
}

 

IDependencyResolver只定義一個BeginScope方法
這個方法會回傳一個IDependencyScope物件
同時IDependencyResolver又繼承IDependencyScope介面
所以他也有這介面定義的兩個方法

開始看範例
首先要實作IDependencyResolver
 

 public class CustomDependencyResolver : IDependencyResolver
    {
        public IDependencyScope BeginScope()
        {
            return this;
        }

        public void Dispose()
        {
            //不需要做任何事
        }

        public object GetService(Type serviceType)
        {
            //判斷要解析的物件
            if (serviceType == typeof(OrderController))
            {
                var service = new OrderService();
                return new OrderController(service);
            }
            return null;
        }

        public IEnumerable<object> GetServices(Type serviceType)
        {
            return new List<object>();
        }
    }

然後接著替換掉原本的預設實作
 

public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
  {
config.DependencyResolver = new MyDependencyResolver();
  }
}

 

如果想使用Unity來實作的話
一樣先繼承IDependencyResolver
 

public class UnityDependencyResolver : IDependencyResolver
    {
        protected readonly IUnityContainer _container;

        public UnityDependencyResolver(IUnityContainer container)
        {
            _container = container;
        }
        public IDependencyScope BeginScope()
        {
            IUnityContainer childContainer = _container.CreateChildContainer();
            return new UnityDependencyResolver(childContainer);
        }

        public void Dispose()
        {
            _container.Dispose();
        }

        public object GetService(Type serviceType)
        {
            try
            {
                return _container.Resolve(serviceType);
            }
            catch (Exception e)
            {
                return null;
            }
        }

        public IEnumerable<object> GetServices(Type serviceType)
        {
            try
            {
                return _container.ResolveAll(serviceType);
            }
            catch
            {
                return new List<object>();
            }
        }
    }

接著再去WebApiConfig註冊
 

public static class WebApiConfig
    {
        public static void Register(HttpConfiguration config)
        {
            
            //替換IDependencyResolver
            //config.DependencyResolver=new CustomDependencyResolver();
            //DI容器使用UnityDependencyResolver 來解析
            var container=new UnityContainer();
            var lifeManager = new HierarchicalLifetimeManager();
            container.RegisterType<IOrderService, OrderService>(lifeManager);
            var resolver=new UnityDependencyResolver(container);
            config.DependencyResolver = resolver;

        }
    }

 

以上提供四種做法可以參考
資料來源為dotNet相依注入第5張內容
訊息處理常式https://docs.microsoft.com/zh-tw/aspnet/web-api/overview/advanced/http-message-handlers