Dependency Injection (به اختصار DI، ترجمه فارسی : تزریق وابستگی) الگویی است که جهت پیاده سازی اصل Dependency Inversion در طراحی شی گراء مطرح شده است. در صورتی که با مفاهیم Dependency Injection و IoC Container آشنایی ندارید، می توانید به مطالعه ی این مقاله از آقای Martin Fowler بپردازید. Castle Windsor یکی از IoC Container های معروف، رایگان و متن باز نوشته شده برای NET. می باشد. در این مقاله به پیاده سازی این الگو در ASP.NET Web API با کتابخانه ی Castle Windsor میپردازیم.
- پیاده سازی Controller ها و Service ها
ابتدا Controller ها و کلاس های سرویس مورد نظر را خود را پیاده سازی کنید. سپس با استفاده از Constructor Injection کلاس های سرویس خود را به داخل Controller ها Inject کنید. مثال ساده ی زیر را در نظر بگیرید :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
public interface IUserService { List<AppUser> GetList(); } public class SimpleUserService : IUserService { public List<AppUser> GetList() { // // Retrieve some data // } } |
1 2 3 4 5 6 7 8 9 10 11 12 13 |
public class UserController : ApiController { private readonly IUserService _userService; public UserController(IUserService userService) { _userService = userService; } public List<AppUser> Get() { return _userService.GetList(); } } |
- اضافه کردن و Config کتابخانه Castle Windsor به پروژه
جهت اضافه کردن این کتابخانه به پروژه از دستور زیر در Package Manager Console استفاده نمایید :
1 |
install-package Castle.Windsor |
برای کار با Castle Windsor مانند هر IoCC دیگری، نیاز دارید تا Dependency ها و کلاس های پیاده سازی آنها را معرفی کنید. برای این کار کلاس فوق را در App_Start پروژه ایجاد میکنیم :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
using Castle.MicroKernel.Registration; using Castle.Windsor; . . . public static class CastleConfig { public static IWindsorContainer GetContainer() { var container = new WindsorContainer(); container.Register(Component.For<IUserService>() .ImplementedBy<SimpleUserService>()); container.Register(Component.For<UserController>().LifestylePerWebRequest()); return container; } } |
کلاس IUserService و پیاده سازی آن و همچنین کلاس UserController را در Container ثبت کردیم. از آنجا که Controller ها به ازای هر Request باید ایجاد شوند، از متد LifestylePerWebRequest برای ثبت UserController استفاده کردیم. در این مثال فرض شده است که UserService یک سرویس Stateless بوده و تعریف آن به صورت Singleton مانعی ندارد. به همین دلیل هیچ نوع LifeStyle خاصی برای آن نوشته نشده است تا Windsor از LifeStyle پیش فرض (یعنی Singleton) استفاده نماید.
حال کافی است تا ControllerActivator مخصوص به خود را نوشته و در آن از Windsor برای ایجاد Controller ها استفاده نماییم. سپس ControllerActivator نوشته شده را به عنوان سرویس پیش فرض تعریف میکنیم تا WebAPI برای ساخت Controller ها از کلاس فوق استفاده نماید.
- پیاده سازی ControllerActivator سفارشی
برای ساخت ControllerActivator سفارشی، نیاز به پیاده سازی اینترفیس IHttpControllerActivator دارید :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
using System.Net.Http; using System.Web.Http.Controllers; using System.Web.Http.Dispatcher; using Castle.Windsor; . . . public class CastleControllerActivator : IHttpControllerActivator { private IWindsorContainer _container; public CastleControllerActivator(IWindsorContainer container) { _container = container; } public IHttpController Create(HttpRequestMessage request, HttpControllerDescriptor controllerDescriptor, Type controllerType) { return (IHttpController)_container.Resolve(controllerType); } } |
همانطور که در کد مشاهده میکنید اینترفیس IWindsorContainer از طریق Constructor به این کلاس تزریق شده است تا Activator نوشته شده بتواند برای ساخت Controller ها از آن استفاده نماید. برای اتمام کار تنها کافی است تا یک نمونه از کلاس CastleControllerActivator را به عنوان ControllerActivator پیش فرض در Global.Asax معرفی کنید :
1 2 3 4 5 6 7 8 9 10 11 12 13 |
public class WebApiApplication : System.Web.HttpApplication { protected void Application_Start() { . . . var container = CastleConfig.GetContainer(); var contollerActivator = new CastleControllerActivator(container); GlobalConfiguration.Configuration.Services.Replace(typeof(IHttpControllerActivator), contollerActivator); } } |
نکته : در صورتی که تعداد Controller های پروژه ی شما زیاد است، می توانید به جای ثبت تک به تک آنها در Container، از امکانات Windsor برای ثبت دسته ای Dependency ها استفاده بکنید. به مثال اصلاح شده ی فوق دقت کنید :
1 2 3 4 5 6 7 8 9 10 11 |
public static IWindsorContainer GetContainer() { var container = new WindsorContainer(); container.Register(Component.For<IUserService>() .ImplementedBy<SimpleUserService>()); //container.Register(Component.For<UserController>().LifestylePerWebRequest()); container.Register(Classes.FromThisAssembly().BasedOn<ApiController>().LifestylePerWebRequest()); return container; } |
آقای احمدی سلام
لطفا آموزش های مربوط به دامین دریون را تکمیل نمایید و بعد سراغ مطالب دیگر روید لطفا از این پراکنده گویی بپرهیزید. ما منتظر بخش های بعدی از این سری هستیم
متشکرم
سلام آقای حمیدان
بخش سوم از سری آموزش DDD در هفته جاری تکمیل شده و بر روی سایت قرار میگیرد.
باید در نظر داشته باشید که نوشتن مقالات آموزشی پیرامون بحث طراحی نرم افزار بسیار زمانبر تر از نوشتن در حوزه ی تکنولوژی می باشد. شخصا هم حساسیت خاصی بر روی این مطالب دارم و حتما سعی میکنم در فراغ خاطر کامل شروع به نوشتن این مباحث کنم و پس از نوشتن، چند بار مطالب رو بازبینی میکنم. به همین علت کمی زمان بر است.
موفق باشید
با سلام و احترام
با تشکر فراوان از مطلب مفید شما
می خواستم بدونم چطور به صورت دسته ای کلاس ها و اینترفیس را با هم لینک کنم.
مثلا نام گذاری ها به صورت زیر باشد
Product
IProductService
ProductServiceImpl
با تشکر
سلام
خواهش میکنم لطف دارید
لطفا سوالتون رو با جزئیات بیشتری مطرح بفرمایید، رابطه Product با دو کلاس دیگر رو متوجه نشدم.
سلام
به عنوان مثال
اینترفیس های
IProductService
IUserService
ITestService
توسط کلاس های زیر
ProductServiceImpl
UserServiceImpl
TestServiceImpl
پیاده سازی شده اند
حالا برای معرفی به صورت تک تک میخواهیم به صورت دسته ای این کار را انجام دهیم
مانند مثال خودتون
//container.Register(Component.For().LifestylePerWebRequest());
container.Register(Classes.FromThisAssembly().BasedOn().LifestylePerWebRequest());
حالا به جای عبارت زیر
container.Register(Component.For().ImplementedBy());
؟؟؟؟؟؟
چی جایگزین کنیم که همه اینتر فیس ها و کلاس های مربوطه به کانتکس معرفی شوند
اگر Interface های شما از یک Interface واحد ارث بری میکنند (برای مثال IService) میتونید از این روش استفاده کنید :
container.Register(Classes.FromThisAssembly().BasedOn< IService >().WithServiceDefaultInterfaces());
آقای احمدی عزیز سلام
از مطلب با ارزش شما متشکرم
آیا DI های دیگر را هم بررسی کرده اید؟ مقایسه ای بین DI های متداول دارید؟ مشخصا من بین Castel Windsor / Structure Map / Ninject در اینترنت مقایسه ی خوبی پیدا نکردم. همگی در ویژگی های پایه مثل هم هستند اما در استفاده های advanced که معمولا شش تا دوازده ماه پس از استارت پروژه پیش می آیند خیلی کسی مقایسه ی بدور از تعصب و مناسبی ندارد. شما نظرتان چیست؟
با احترام و تشکر
محمد حامد
سلام محمد جان
قبل از پاسخ به سوال شما عرض کنم که، استفاده از عبارت DI برای کتابخانه هایی مثل Windsor و Ninject و … درست نیست. DI در واقع به “تکنیک” پاس کردن از بیرون (یا اصطلاحا تزریق) وابستگی ها به یک کلاس گفته می شود. کتابخانه های مطرح شده در واقع IoC Container هستند. در واقع با کمک Container های فوق می توان این تکنیک را عملیاتی کرد.
در مورد سوال هم باید بگم که بعضی از Container ها قابلیت های اضافه ای نسبت به بقیه دارند اما به طور کل تفاوت چشم گیری از نظر قابلیت بین آنها وجود ندارد (حداقل بنده ندیدم). یکی از دلایلی که شخصا Castle رو به دو Container دیگری که فرمودید ترجیح میدم، Documentation بهتر و همچنین ابزارهای مکمل قوی تر مثل پروژه های Castle.DynamicProxy و LoggingServices است.
تقریبا میشه گفت الان همه ی Container های معروف هم سطح هستند و تفاوت و برتری خاصی بینشون وجود نداره.
سلام و عرض ادب خدمت استاد عزیزم
شاید سوالم بی ربط به موضوع باشه ولی واقعیتش چاره ای نداشتم
من یه سوال راجع Interceptor های Castle Windsor داشتم
کل پروژه های من در یک Solution قرار دارند.
دو تا Bounded Context دارم BC1 و BC2
هر BC یک لایه Application دارد
یه کلاس Interceptor در لایه Framework دارم که میخوام روی لایه Application در BC1 و BC2 بزارم
در کلاس Interceptor یه IUnitOfWork دارم که داخل پارامترهای Constructor گذاشتم.
برای هر BC در IOC:
IUnitOfWork مربوط به همان BC رو Register کردم (با Name های جدا )
کلاس Interceptor مربوط به همان BC رو Register کردم (با Name های جدا) و Depend ش کردم به IUnitOfWork خود اون BC (از طریق نام)
حالا میخوام هر Application با Interceptor خودش Register بشه ، و یه جورایی وصل بشه به Interceptor خودش
نمیدونم شاید دارم اشتباه فکر میکنم،
برای Regsiter کردن Componentها با Type یکسان باید با نام های جدا Register کنیم (اگر دست بگم)
به نظرم اینجا هم صورت مسئله تقریبا مشابه است با این تفاوت که ارتباط کلاس Interceptor با کلاس Application از طریق IOC ایجاد میشود.
در Castle Windsor دو تا Interface:
Castle.DynamicProxy.IInterceptorSelector
و
Castle.MicroKernel.Proxy.IModelInterceptorsSelector
هم وجود دارند ولی نمیدونم ارتباطی با این صورت مسئله دارند یا خیر.
ممنون میشم اگر نظرتون رو بدونم
سلام مجدد
استاد فکر کنم راهش رو پیدا کردم
موقع معرفی Interceptor به یک Component (نام Interceptor هم میخوام وارد کنم تا وصل شود به Interceptor خودش) باید از دستور زیر استفاده کنیم:
Interceptors(InterceptorReference.ForKey(“registered interceptor named”).Anywhere
ولی زمانی که میخوام Component مورد نظر رو دسته ای Register کنم مجبورم Interceptors رو تو متد Configure بنویسم و اگر آخرش اون Anywhere رو بنویسم Compile Error میده
اگر هم Anywhere رو ننویسم Interceptor اصلا عمل نمیکنه
ولی زمانی که دسته ای Register نمیکنم چون به نوشتن Anywhere گیر نمیده در نتیجه همه چیز Ok میشه
میبخشید زیادی نوشتم، وقت تو گرفتم
سلام مهندس
نیازی به استفاده از Anywhere نیست. سناریویی که مدنظر شما هست با Name های متفاوت برای Interceptor ها و Dependent کردن Application Service ها به این نام ها، قابل انجام هست. چون توضیحش اینجا سخت بود، نمونه کد رو براتون آپلود کردم :
دانلود نمونه کد
موفق باشید
سلام هادی جان
ممنون از وقتی که گذاشتی برای پاسخ به سوالم، من ۳،۴ روز شهرستان بودم، دسترسی به کامپیوتر و نت نداشتم، بخاطر همین امروز موفق شدم پاسخ شما رو ببینم
میبخشید نمونه کد رو دانلود کردم ولی کانفیگ Castle داخلش نبود، پروژه کلاینتش لود نشد.
خیلی خیلی ممنون ازت.
سلام
خواهش میکنم
محمد جان مشکل پروژه ی کلاینت حل شد. از همون لینک بالا مجدد سورس رو دانلود بفرمایید.
ممنون از کمکت هادی جان
سلام استاد خسته نباشید
استاد تو دات نت core
LifestylePerWebRequest به خاطر نبود System.Web در قابلیت های Castle وجود نداره
جایگزینی LifestylePerWebRequest میدونید چی هست؟
سلام، خیلی ممنونم تشکر
باید از LifeStyleScoped استفاده بفرمایید