預設範本所產生的程式碼會產生與NorthwindEntities資料實體(new NorthwindEntities())產生依賴關係。每個API Controller都還要處理db.Products ,這造成API Controller的職責不夠單一,這在裡我們就先採用Repository Pattern 1 來分離資料庫處理程式碼與API Controller的相依關係。
public class ProductsController : ApiController
{
//private NorthwindEntities db = new NorthwindEntities();
private IProductRepository _product;
//public ProductsController()
//{
// _product = new ProductRepository();
//}
/// <summary>
/// 注入實體之用
/// </summary>
/// <param name="r">實作的介面</param>
public Products2Controller(IProductRepository r)
{
_product = r;
}
// GET api/Products
public IEnumerable<Product> GetProducts()
{
return _product.GetAll();
}
// 省略
}
最終希望可以把Controller裡特定實體相依性給移除。然後透過IoC/DI套件的幫忙,讓我們可以透過Repository介面的幫助下去自動產生實體。
以下會介面二套IoC/DI套件,並討論在MVC與Web API的實作方式。
Web API DI Framework - Autofac
Install-Package autofac.WebApi
Autofac算是老牌的IoC與DI實作套件,它對於Web API/Web API 2 或是MVC 2/MVC 3/MVC 4/MVC 5 都有快速與良好的支援。這是讓我選擇使用它的一個主因。
以下是《ASP.NET MVC 4網站開發美學 》第七章 Page 7-46頁的程式碼:
// 容器建立者
ContainerBuilder builder = new ContainerBuilder();
// 註冊型別
builder.RegisterAssemblyTypes(Assembly.GetExecutingAssembly())
.Where(t => t.Name.EndsWith("Repository")).AsImplementedInterfaces();
// 註冊服務(示範)
// builder.RegisterAssemblyTypes(Assembly.GetExecutingAssembly()) _
// .Where(Function(t) t.Name.EndsWith("Service")) _
// .AsImplementedInterfaces()
builder.RegisterAssemblyTypes(Assembly.GetExecutingAssembly())
.Where(t => !t.IsAbstract & typeof(ApiController)
.IsAssignableFrom(t))
.InstancePerMatchingLifetimeScope(AutofacWebApiDependencyResolver.ApiRequestTag);
// 建立容器
IContainer container = builder.Build();
// 解析容器內的型別
AutofacWebApiDependencyResolver resolver = new AutofacWebApiDependencyResolver(container);
// 註冊相依解析者
config.DependencyResolver = resolver;
以上是Autofac在Web API剛推出時所撰寫的程式碼。不過後來Autofac 3.x.x針對Web API方面有進行重構,以下是新版程式碼:
using Autofac;
using Autofac.Integration.WebApi;
// 容器建立者
var builder = new ContainerBuilder();
// 註冊Web API Controllers
builder.RegisterApiControllers(Assembly.GetExecutingAssembly());
// 註冊相依關係
builder.Register(c => new ProductRepository()).As<IProductRepository>().InstancePerApiRequest();
// 建立容器
var container = builder.Build();
// 建立相依解析器
var resolver = new AutofacWebApiDependencyResolver(container);
// 組態Web API相依解析器
GlobalConfiguration.Configuration.DependencyResolver = resolver;
透過builder.Register()
方法非常容易就註冊實體類別與介面的對應關係。現在API Controller已經可以在沒有任何實體,透過Autofac套件的幫忙正常運作。
2 以上兩段程式碼都能正常運作。
Web API 2 DI Framework - Autofac
Install-Package autofac.WebApi2
程式碼完全相容,不過WebApiConfig.cs註冊有些差異:
ASP.NET Web API
WebApiConfig.SetAutofacDI(GlobalConfiguration.Configuration);
ASP.NET Web API 2
GlobalConfiguration.Configure(WebApiConfig.SetAutofacDI);
Web API DI Framework - Unity Application Block
Install-Package unity.WebAPI
以下是《ASP.NET MVC 4網站開發美學 》第七章 Page 7-48頁的程式碼:
public static void Register(HttpConfiguration config)
{
// 以上省略
var container = BuildUnityContainer();
GlobalConfiguration.Configuration.DependencyResolver = new Unity.WebApi.UnityDependencyResolver(container);
}
private static IUnityContainer BuildUnityContainer()
{
var container = new UnityContainer();
// register all your components with the container here
// e.g. container.RegisterType<ITestService, TestService>();
container.RegisterType<IProductRepository, ProductRepository>();
return container;
}
讀者如果實作做過一次,就會發現程式碼其實只有一行,將實體與介面的對應關係透過container.RegisterType()方法一一註冊進去即可。
3 可另外參考91哥的[ASP.NET Web API]3 分鐘搞定 DI framework–Unity Application Block
Web API 2 DI Framework - Unity Application Bloc
unity.WebAPI 撰文期間(2013/10/29)未支援 ASP.NET Web API 2。
ASP.NET MVC 4 DI Framework - Autofac
MVC專案先透過Repository Pattern來整理Controller裡的資料庫存取程式碼。然後使用NuGet安裝必需要的元件:
Install-Package autofac.Mvc4
autofac.Mvc4會自動加入相依的組件。我們盡量保持 Global.asax 檔的乾淨,新增一個AutofacConfig.cs來整理我們的DI Framework程式碼:
using Autofac;
using Autofac.Integration.Mvc;
public static class AutofacConfig
{
public static void Register()
{
// 容器建立者
var builder = new ContainerBuilder();
// 註冊Controllers
builder.RegisterControllers(Assembly.GetExecutingAssembly());
// 註冊型別
builder.RegisterAssemblyTypes(Assembly.GetExecutingAssembly())
.Where(t => t.Name.EndsWith("Repository"))
.AsImplementedInterfaces();
// 註冊服務(示範)
// builder.RegisterAssemblyTypes(Assembly.GetExecutingAssembly())
// .Where(Function(t) t.Name.EndsWith("Service"))
// .AsImplementedInterfaces()
// 建立容器
var container = builder.Build();
// 指定解析器
DependencyResolver.SetResolver(new AutofacDependencyResolver(container));
}
}
MVC 4的Autofac註冊程式碼和Web API其實差不多,透過builder.RegisterAssemblyTypes()
方法去搜尋尾綴(Repository | Service)的檔案名稱,並進行註冊動作。
搜尋尾綴的方法,在大部分的專案情況下都可以符合需求了。不過還有一種註冊方法也不錯用,就是透過搜尋NameSpace的方式來搜尋與註冊。
圖片來源:http://weblogs.asp.net/bsimser/
如果你的Models很複雜,可能會利用目錄來整理相關類別與實作。那麼我們只需要透過NameSpace來進行搜尋與註冊的動作即可:
public static void Register()
{
// 容器建立者
var builder = new ContainerBuilder();
// 註冊Controllers
builder.RegisterControllers(Assembly.GetExecutingAssembly());
// 註冊組件型別
// 這裡我們透過 Namespace 來取得所有介面與介面實作
builder.RegisterAssemblyTypes(Assembly.GetExecutingAssembly())
.Where(t => t.Namespace.EndsWith(".Repositories"))
.AsImplementedInterfaces();
// 建立容器
var container = builder.Build();
// 指定解析器
DependencyResolver.SetResolver(new AutofacDependencyResolver(container));
}
透過Lambda運算式「t.Namespace.EndsWith(".Repositories")
」找出此NameSpace下所有介面與實作並進行註冊動作。
ASP.NET MVC 5 DI Framework - Autofac
Install-Package autofac.Mvc5
ASP.NET MVC 5必須用使用autofac.Mvc5套件版本。
實作程式碼與ASP.NET MVC 4一模一樣。互相通用。
ASP.NET MVC 4 DI Framework - Unity Application Block
Install-Package unity.Mvc4
這邊讓我偷懶一下,我直修改安裝好的Bootstrapper.cs:
public static IUnityContainer Initialise()
{
var container = BuildUnityContainer();
DependencyResolver.SetResolver(new UnityDependencyResolver(container));
return container;
}
private static IUnityContainer BuildUnityContainer()
{
var container = new UnityContainer();
// register all your components with the container here
// it is NOT necessary to register your controllers
// e.g. container.RegisterType<ITestService, TestService>();
container.RegisterType<IProductRepository, ProductRepository>();
RegisterTypes(container);
return container;
}
public static void RegisterTypes(IUnityContainer container)
{
}
我是習慣將組態檔整理至App_Start目錄,這樣比較統一。新增介面與實體的對應關係只有一行程式碼:「container.RegisterType<IProductRepository, ProductRepository>();
」,使用方式和Web API的Unity.WebApi一樣簡便。
ASP.NET MVC 5 DI Framework - Unity Application Block
Install-Package unity.Mvc4
目前在ASP.NET MVC 5使用與設置並無差異,測試unity.Mvc4組件在MVC 5專案可以正常作運。
IoC / DI套件執行效率
目前世面上的IoC / DI套件很多,有位Daniel 比較受歡迎的IoC / DI套件的執行效率。如果看到文末的Updates ,就知道這篇文章有非常不錯的參考價值,因為套件會不算改版升級,作者從2011年一直到2013年都有在更新。
所以請問一下目前有針對web api2支援的嗎??我目前找了一堆,都顯示這種錯誤,無法載入檔案或組件 'System.Web.Http, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35' 或其相依性的其中之一。 找到的組件資訊清單定義與組件參考不符。 (發生例外狀況於 HRESULT: 0x80131040)
回覆刪除Version=4.0.0.0,應該是 Web API 不是 Web API 2 哦!
刪除不好意思,我目前已經更新到最新版的web api2.1了,目前使用您文內介紹的Autofac,不過在您指到的這段code。
刪除ASP.NET Web API 2
1 GlobalConfiguration.Configure(WebApiConfig.SetAutofacDI);
SetAutofacDI解析不到,請問一下除了安裝autofac.WebApi2,還需要安裝什麼,或者是什麼原因呢??請指導小弟一下好嗎??非常的感謝。
WebApiConfig.SetAutofacDI ... 要自己寫呀。
回覆刪除怎麼寫,本 Blog 旁邊有一本書,沒記錯的話,書裡面有教 :P
你好,你說的書是指「asp.net mvc5網站開發美學」這本嗎?
刪除這本我手邊有一本,但是找不到相關的介紹耶,還是只在mvc4的那本才有介紹?
謝謝
MVC 4
刪除你好,我是你的asp.net mvc 4 網站開發美學的讀者,我在實作web api的章節時,做到相依注入的部分在使用autofac這個套件時發生問題
回覆刪除照書上的步驟做下來,在瀏覽器輸入http://http://localhost:62284/api/products/時會出現
An error has occurred.
No constructors on type 'MvcApplication2.Controllers.APIs.ProductsController' can be found with the constructor finder 'Autofac.Core.Activators.Reflection.DefaultConstructorFinder'.
Autofac.Core.DependencyResolutionException
於 Autofac.Core.Activators.Reflection.ReflectionActivator.ActivateInstance(IComponentContext context, IEnumerable`1 parameters) 於 Autofac.Core.Resolving.InstanceLookup.Activate(IEnumerable`1 parameters) 於 Autofac.Core.Resolving.InstanceLookup.Execute() 於 Autofac.Core.Resolving.ResolveOperation.GetOrCreateInstance(ISharingLifetimeScope currentOperationScope, IComponentRegistration registration, IEnumerable`1 parameters) 於 Autofac.Core.Resolving.ResolveOperation.ResolveComponent(IComponentRegistration registration, IEnumerable`1 parameters) 於 Autofac.Core.Resolving.ResolveOperation.Execute(IComponentRegistration registration, IEnumerable`1 parameters) 於 Autofac.Core.Lifetime.LifetimeScope.ResolveComponent(IComponentRegistration registration, IEnumerable`1 parameters) 於 Autofac.ResolutionExtensions.TryResolveService(IComponentContext context, Service service, IEnumerable`1 parameters, Object& instance) 於 Autofac.ResolutionExtensions.ResolveOptionalService(IComponentContext context, Service service, IEnumerable`1 parameters) 於 Autofac.ResolutionExtensions.ResolveOptional(IComponentContext context, Type serviceType, IEnumerable`1 parameters) 於 Autofac.ResolutionExtensions.ResolveOptional(IComponentContext context, Type serviceType) 於 Autofac.Integration.WebApi.AutofacWebApiDependencyScope.GetService(Type serviceType) 於 System.Web.Http.Dispatcher.DefaultHttpControllerActivator.GetInstanceOrActivator(HttpRequestMessage request, Type controllerType, Func`1& activator) 於 System.Web.Http.Dispatcher.DefaultHttpControllerActivator.Create(HttpRequestMessage request, HttpControllerDescriptor controllerDescriptor, Type controllerType)
這個錯誤訊息~~原本以為我下載的版本較新(autofac.webapi 3.1.0版)將SetAutofacDI這個方法的內容改成你這裡寫的
新版本,結果還是會出現這個錯誤訊息,可否請你解答一下??謝謝