在使用.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