مطالب
لینک‌های هفته‌ی آخر بهمن

وبلاگ‌ها ، سایت‌ها و مقالات ایرانی (داخل و خارج از ایران)

امنیت

Visual Studio

ASP. Net

طراحی و توسعه وب

اس‌کیوال سرور

سی شارپ

عمومی دات نت

مسایل اجتماعی و انسانی برنامه نویسی

کتاب‌های رایگان جدید

متفرقه
مطالب
EF Code First #12

پیاده سازی الگوی Context Per Request در برنامه‌های مبتنی بر EF Code first

در طراحی برنامه‌های چند لایه مبتنی بر EF مرسوم نیست که در هر کلاس و متدی که قرار است از امکانات آن استفاده کند، یکبار DbContext و کلاس مشتق شده از آن وهله سازی شوند؛ به این ترتیب امکان انجام امور مختلف در طی یک تراکنش از بین می‌رود. برای حل این مشکل الگویی مطرح شده است به نام Session/Context Per Request و یا به اشتراک گذاری یک Unit of work در لایه‌های مختلف برنامه در طی یک درخواست، که در ادامه یک پیاده سازی آن‌را با هم مرور خواهیم کرد.
البته این سشن با سشن ASP.NET یکی نیست. در NHibernate معادل DbContextایی که در اینجا ملاحظه می‌کنید، Session نام دارد.


اهمیت بکارگیری الگوی Unit of work و به اشتراک گذاری آن در طی یک درخواست

در الگوی واحد کار یا همان DbContext در اینجا، تمام درخواست‌های رسیده به آن، در صف قرار گرفته و تمام آن‌ها در پایان کار، به بانک اطلاعاتی اعمال می‌شوند. برای مثال زمانیکه شیءایی را به یک وهله از DbContext اضافه/حذف می‌کنیم، یا در ادامه مقدار خاصیتی را تغییر می‌دهیم، هیچکدام از این تغییرات تا زمانیکه متد SaveChanges فراخوانی نشود، به بانک اطلاعاتی اعمال نخواهند شد. این مساله مزایای زیر را به همراه خواهد داشت:

الف) کارآیی بهتر
در اینجا از یک کانکشن باز شده، حداکثر استفاده صورت می‌گیرد. چندین و چند عملیات در طی یک batch به بانک اطلاعاتی اعمال می‌گردند؛ بجای اینکه برای اعمال هرکدام، یکبار اتصال جداگانه‌ای به بانک اطلاعاتی باز شود.

ب) بررسی مسایل همزمانی
استفاده از یک الگوی واحد کار، امکان بررسی خودکار تمام تغییرات انجام شده بر روی یک موجودیت را در متدها و لایه‌های مختلف میسر کرده و به این ترتیب مسایل مرتبط با ConcurrencyMode عنوان شده در قسمت‌های قبل به نحو بهتری قابل مدیریت خواهند بود.

ج) استفاده صحیح از تراکنش‌ها
الگوی واحد کار به صورت خودکار از تراکنش‌ها استفاده می‌کند. اگر در حین فراخوانی متد SaveChanges مشکلی رخ دهد، کل عملیات Rollback خواهد شد و تغییری در بانک اطلاعاتی رخ نخواهد داد. بنابراین استفاده از یک تراکنش در حین چند عملیات ناشی از لایه‌های مختلف برنامه، منطقی‌تر است تا اینکه هر کدام، در تراکنشی جدا مشغول به کار باشند.


کلاس‌های مدل مثال جاری

در مثالی که در این قسمت بررسی خواهیم کرد، از کلاس‌های مدل گروه محصولات کمک گرفته شده است:

using System.Collections.Generic; 
namespace EF_Sample07.DomainClasses { public class Category { public int Id { get; set; } public virtual string Name { get; set; } public virtual string Title { get; set; } public virtual ICollection<Product> Products { get; set; } } }

using System.ComponentModel.DataAnnotations; 
namespace EF_Sample07.DomainClasses { public class Product { public int Id { get; set; } public string Name { get; set; } public decimal Price { get; set; }
[ForeignKey("CategoryId")] public virtual Category Category { get; set; } public int CategoryId { get; set; } } }


در کلاس Product، یک خاصیت اضافی به نام CategoryId اضافه شده است که توسط ویژگی ForeignKey، به عنوان کلید خارجی جدول معرفی خواهد شد. از این خاصیت در برنامه‌های ASP.NET برای مقدار دهی یک کلید خارجی توسط یک DropDownList پر شده با لیست گروه‌ها، استفاده خواهیم کرد.



پیاده سازی الگوی واحد کار

همانطور که در قسمت قبل نیز ذکر شد، DbContext در EF Code first بر اساس الگوی واحد کار تهیه شده است، اما برای به اشتراک گذاشتن آن بین لایه‌های مختلف برنامه نیاز است یک لایه انتزاعی را برای آن تهیه کنیم، تا بتوان آن‌را به صورت خودکار توسط کتابخانه‌های Dependency Injection یا به اختصار DI در زمان نیاز به استفاده از آن‌، به کلاس‌های استفاده کننده تزریق کنیم. کتابخانه‌ی DI ایی که در این قسمت مورد استفاده قرار می‌گیرد، کتابخانه معروف StructureMap است. برای دریافت آن می‌توانید از Nuget استفاده کنید؛ یا از صفحه اصلی آن در Github : (^).
اینترفیس پایه الگوی واحد کار ما به شرح زیر است:

using System.Data.Entity;
using System; 
namespace EF_Sample07.DataLayer.Context { public interface IUnitOfWork { IDbSet<TEntity> Set<TEntity>() where TEntity : class; int SaveChanges(); } }

برای استفاده اولیه آن، تنها تغییری که در برنامه حاصل می‌شود به نحو زیر است:

using System.Data.Entity;
using EF_Sample07.DomainClasses; 
namespace EF_Sample07.DataLayer.Context { public class Sample07Context : DbContext, IUnitOfWork { public DbSet<Category> Categories { set; get; } public DbSet<Product> Products { set; get; }
#region IUnitOfWork Members public new IDbSet<TEntity> Set<TEntity>() where TEntity : class { return base.Set<TEntity>(); } #endregion } }

توضیحات:
با کلاس Context در قسمت‌های قبل آشنا شده‌ایم. در اینجا به معرفی کلاس‌هایی خواهیم پرداخت که در معرض دید EF Code first قرار خواهند گرفت.
DbSetها هم معرف الگوی Repository هستند. کلاس Sample07Context، معرفی الگوی واحد کار یا Unit of work برنامه است.
برای اینکه بتوانیم تعاریف کلاس‌های سرویس برنامه را مستقل از تعریف کلاس Sample07Context کنیم، یک اینترفیس جدید را به نام IUnitOfWork به برنامه اضافه کرده‌ایم.
در اینجا کلاس Sample07Context پیاده سازی کننده اینترفیس IUnitOfWork خواهد بود (اولین تغییر).
دومین تغییر هم استفاده از متد base.Set می‌باشد. به این ترتیب به سادگی می‌توان به DbSetهای مختلف در حین کار با IUnitOfWork دسترسی پیدا کرد. به عبارتی ضرورتی ندارد به ازای تک تک DbSetها یکبار خاصیت جدیدی را به اینترفیس IUnitOfWork اضافه کرد. به کمک استفاده از امکانات Generics مهیا، اینبار
uow.Set<Product> 

معادل همان db.Products سابق است؛ در حالتیکه از Sample07Context به صورت مستقیم استفاده شود.
همچنین نیازی به پیاده سازی متد SaveChanges نیست؛ زیرا پیاده سازی آن در کلاس DbContext قرار دارد.


استفاده از الگوی واحد کار در کلاس‌های لایه سرویس برنامه

using EF_Sample07.DomainClasses;
using System.Collections.Generic; 
namespace EF_Sample07.ServiceLayer { public interface ICategoryService { void AddNewCategory(Category category); IList<Category> GetAllCategories(); } }

using EF_Sample07.DomainClasses;
using System.Collections.Generic; 
namespace EF_Sample07.ServiceLayer { public interface IProductService { void AddNewProduct(Product product); IList<Product> GetAllProducts(); } }

لایه سرویس برنامه را با دو اینترفیس جدید شروع می‌کنیم. هدف از این اینترفیس‌ها، ارائه پیاده سازی‌های متفاوت، به ازای ORMهای مختلف است. برای مثال در کلاس‌های زیر که نام آن‌ها با Ef شروع شده است، پیاده سازی خاص Ef Code first را تدارک خواهیم دید. این پیاده سازی، قابل انتقال به سایر ORMها نیست چون نه پیاده سازی یکسانی را از مباحث LINQ ارائه می‌دهند و نه متدهای الحاقی همانندی را به همراه دارند و نه اینکه مباحث نگاشت کلاس‌های آن‌ها به جداول مختلف یکی است:

using System.Collections.Generic;
using System.Data.Entity;
using System.Linq;
using EF_Sample07.DataLayer.Context;
using EF_Sample07.DomainClasses; 
namespace EF_Sample07.ServiceLayer { public class EfCategoryService : ICategoryService { IUnitOfWork _uow; IDbSet<Category> _categories; public EfCategoryService(IUnitOfWork uow) { _uow = uow; _categories = _uow.Set<Category>(); }
public void AddNewCategory(Category category) { _categories.Add(category); }
public IList<Category> GetAllCategories() { return _categories.ToList(); } } }

using System.Collections.Generic;
using System.Data.Entity;
using System.Linq;
using EF_Sample07.DataLayer.Context;
using EF_Sample07.DomainClasses; 
namespace EF_Sample07.ServiceLayer { public class EfProductService : IProductService { IUnitOfWork _uow; IDbSet<Product> _products; public EfProductService(IUnitOfWork uow) { _uow = uow; _products = _uow.Set<Product>(); }
public void AddNewProduct(Product product) { _products.Add(product); }
public IList<Product> GetAllProducts() { return _products.Include(x => x.Category).ToList(); } } }


توضیحات:
همانطور که ملاحظه می‌کنید در هیچکدام از کلاس‌های سرویس برنامه، وهله سازی مستقیمی از الگوی واحد کار وجود ندارد. این لایه از برنامه اصلا نمی‌داند که کلاسی به نام Sample07Context وجود خارجی دارد یا خیر.
همچنین لایه اضافی دیگری را به نام Repository جهت مخفی سازی سازوکار EF به برنامه اضافه نکرده‌ایم. این لایه شاید در نگاه اول برنامه را مستقل از ORM جلوه دهد اما در عمل قابل انتقال نیست و سبب تحمیل سربار اضافی بی موردی به برنامه می‌شود؛ ORMها ویژگی‌های یکسانی را ارائه نمی‌دهند. حتی در حالت استفاده از LINQ، پیاده سازی‌های یکسانی را به همراه ندارند.
بنابراین اگر قرار است برنامه مستقل از ORM کار کند، نیاز است لایه استفاده کننده از سرویس برنامه، با دو اینترفیس IProductService و ICategoryService کار کند و نه به صورت مستقیم با پیاده سازی آن‌ها. به این ترتیب هر زمان که لازم شد، فقط باید پیاده سازی‌های کلاس‌های سرویس را تغییر داد؛ باز هم برنامه نهایی بدون نیاز به تغییری کار خواهد کرد.

تا اینجا به معماری پیچیده‌ای نرسیده‌ایم و اصطلاحا over-engineering صورت نگرفته است. یک اینترفیس بسیار ساده IUnitOfWork به برنامه اضافه شده؛ در ادامه این اینترفیس به کلاس‌های سرویس برنامه تزریق شده است (تزریق وابستگی در سازنده کلاس). کلاس‌های سرویس ما «می‌دانند» که EF وجود خارجی دارد و سعی نکرده‌ایم توسط لایه اضافی دیگری آن‌را مخفی کنیم. شیوه کار با IDbSet تعریف شده دقیقا همانند روال متداولی است که با EF Code first کار می‌شود و بسیار طبیعی جلوه می‌کند.


استفاده از الگوی واحد کار و کلاس‌های سرویس تهیه شده در یک برنامه کنسول ویندوزی

در ادامه برای وهله سازی اینترفیس‌های سرویس و واحد کار برنامه، از کتابخانه StructureMap که یاد شد، استفاده خواهیم کرد. بنابراین، تمام برنامه‌های نهایی ارائه شده در این قسمت، ارجاعی را به اسمبلی StructureMap.dll نیاز خواهند داشت.
کدهای برنامه کنسول مثال جاری را در ادامه ملاحظه خواهید کرد:

using System.Collections.Generic;
using System.Data.Entity;
using EF_Sample07.DataLayer.Context;
using EF_Sample07.DomainClasses;
using EF_Sample07.ServiceLayer;
using StructureMap; 
namespace EF_Sample07 { class Program { static void Main(string[] args) { Database.SetInitializer(new MigrateDatabaseToLatestVersion<Sample07Context, Configuration>());
HibernatingRhinos.Profiler.Appender.EntityFramework.EntityFrameworkProfiler.Initialize(); ObjectFactory.Initialize(x => { x.For<IUnitOfWork>().CacheBy(InstanceScope.Hybrid).Use<Sample07Context>(); x.For<ICategoryService>().Use<EfCategoryService>(); });
var uow = ObjectFactory.GetInstance<IUnitOfWork>(); var categoryService = ObjectFactory.GetInstance<ICategoryService>();
var product1 = new Product { Name = "P100", Price = 100 }; var product2 = new Product { Name = "P200", Price = 200 }; var category1 = new Category { Name = "Cat100", Title = "Title100", Products = new List<Product> { product1, product2 } }; categoryService.AddNewCategory(category1); uow.SaveChanges(); } } }

در اینجا بیشتر هدف، معرفی نحوه استفاده از StructureMap است.
ابتدا توسط متد ObjectFactory.Initialize مشخص می‌کنیم که اگر برنامه نیاز به اینترفیس IUnitOfWork داشت، لطفا کلاس Sample07Context را وهله سازی کرده و مورد استفاده قرار بده. اگر ICategoryService مورد استفاده قرار گرفت، وهله مورد نظر باید از کلاس EfCategoryService تامین شود.
توسط ObjectFactory.GetInstance نیز می‌توان به وهله‌ای از این کلاس‌ها دست یافت و نهایتا با فراخوانی uow.SaveChanges می‌توان اطلاعات را ذخیره کرد.

چند نکته:
- به کمک کتابخانه StructureMap، تزریق IUnitOfWork به سازنده کلاس EfCategoryService به صورت خودکار انجام می‌شود. اگر به کدهای فوق دقت کنید ما فقط با اینترفیس‌ها مشغول به کار هستیم، اما وهله‌سازی‌ها در پشت صحنه انجام می‌شود.
- حین معرفی IUnitOfWork از متد CacheBy با پارامتر InstanceScope.Hybrid استفاده شده است. این enum مقادیر زیر را می‌تواند بپذیرد:

public enum InstanceScope
{
        PerRequest = 0,
        Singleton = 1,
        ThreadLocal = 2,
        HttpContext = 3,
        Hybrid = 4,
        HttpSession = 5,
        HybridHttpSession = 6,
        Unique = 7,
        Transient = 8,
} 

برای مثال اگر در برنامه‌ای نیاز داشتید یک کلاس به صورت Singleton عمل کند، فقط کافی است نحوه کش شدن آن‌را تغییر دهید.
حالت PerRequest در برنامه‌های وب کاربرد دارد (و حالت پیش فرض است). با انتخاب آن وهله سازی کلاس مورد نظر به ازای هر درخواست رسیده انجام خواهد شد.
در حالت ThreadLocal، به ازای هر Thread، وهله‌ای متفاوت در اختیار مصرف کننده قرار می‌گیرد.
با انتخاب حالت HttpContext، به ازای هر HttpContext ایجاد شده، کلاس معرفی شده یکبار وهله سازی می‌گردد.
حالت Hybrid ترکیبی است از حالت‌های HttpContext و ThreadLocal. اگر برنامه وب بود، از HttpContext استفاده خواهد کرد در غیراینصورت به ThreadLocal سوئیچ می‌کند.


استفاده از الگوی واحد کار و کلاس‌های سرویس تهیه شده در یک برنامه ASP.NET MVC

یک برنامه خالی ASP.NET MVC را آغاز کنید. سپس یک HomeController جدید را نیز به آن اضافه نمائید و کدهای آن‌را مطابق اطلاعات زیر تغییر دهید:
using System.Web.Mvc;
using EF_Sample07.DomainClasses;
using EF_Sample07.ServiceLayer;
using EF_Sample07.DataLayer.Context;
using System.Collections.Generic; 
namespace EF_Sample07.MvcAppSample.Controllers { public class HomeController : Controller { IProductService _productService; ICategoryService _categoryService; IUnitOfWork _uow; public HomeController(IUnitOfWork uow, IProductService productService, ICategoryService categoryService) { _productService = productService; _categoryService = categoryService; _uow = uow; }
[HttpGet] public ActionResult Index() { var list = _productService.GetAllProducts(); return View(list); }
[HttpGet] public ActionResult Create() { ViewBag.CategoriesList = new SelectList(_categoryService.GetAllCategories(), "Id", "Name"); return View(); }
[HttpPost] public ActionResult Create(Product product) { if (this.ModelState.IsValid) { _productService.AddNewProduct(product); _uow.SaveChanges(); }
return RedirectToAction("Index"); }
[HttpGet] public ActionResult CreateCategory() { return View(); }
[HttpPost] public ActionResult CreateCategory(Category category) { if (this.ModelState.IsValid) { _categoryService.AddNewCategory(category); _uow.SaveChanges(); }
return RedirectToAction("Index"); } } }

نکته مهم این کنترلر، تزریق وابستگی‌ها در سازنده کلاس کنترلر است؛ به این ترتیب کنترلر جاری نمی‌داند که با کدام پیاده سازی خاصی از این اینترفیس‌ها قرار است کار کند.
اگر برنامه را به همین نحو اجرا کنیم، موتور ASP.NET MVC ایراد خواهد گرفت که یک کنترلر باید دارای سازنده‌ای بدون پارامتر باشد تا من بتوانم به صورت خودکار وهله‌ای از آن‌را ایجاد کنم. برای رفع این مشکل از کتابخانه StructureMap برای تزریق خودکار وابستگی‌ها کمک خواهیم گرفت:

using System;
using System.Data.Entity;
using System.Web.Mvc;
using System.Web.Routing;
using EF_Sample07.DataLayer.Context;
using EF_Sample07.ServiceLayer;
using StructureMap; 
namespace EF_Sample07.MvcAppSample
{ // Note: For instructions on enabling IIS6 or IIS7 classic mode, // visit http://go.microsoft.com/?LinkId=9394801
public class MvcApplication : System.Web.HttpApplication { public static void RegisterGlobalFilters(GlobalFilterCollection filters) { filters.Add(new HandleErrorAttribute()); }
public static void RegisterRoutes(RouteCollection routes) { routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapRoute( "Default", // Route name "{controller}/{action}/{id}", // URL with parameters new { controller = "Home", action = "Index", id = UrlParameter.Optional } // Parameter defaults ); }
protected void Application_Start() { Database.SetInitializer(new MigrateDatabaseToLatestVersion<Sample07Context, Configuration>()); HibernatingRhinos.Profiler.Appender.EntityFramework.EntityFrameworkProfiler.Initialize(); AreaRegistration.RegisterAllAreas(); RegisterGlobalFilters(GlobalFilters.Filters); RegisterRoutes(RouteTable.Routes); initStructureMap(); }
private static void initStructureMap() { ObjectFactory.Initialize(x => { x.For<IUnitOfWork>().HttpContextScoped().Use(() => new Sample07Context()); x.ForRequestedType<ICategoryService>().TheDefaultIsConcreteType<EfCategoryService>(); x.ForRequestedType<IProductService>().TheDefaultIsConcreteType<EfProductService>(); });
//Set current Controller factory as StructureMapControllerFactory ControllerBuilder.Current.SetControllerFactory(new StructureMapControllerFactory()); }
protected void Application_EndRequest(object sender, EventArgs e) { ObjectFactory.ReleaseAndDisposeAllHttpScopedObjects(); } }
public class StructureMapControllerFactory : DefaultControllerFactory { protected override IController GetControllerInstance(RequestContext requestContext, Type controllerType) { return ObjectFactory.GetInstance(controllerType) as Controller; } } }

توضیحات:
کدهای فوق متعلق به کلاس Global.asax.cs هستند. در اینجا در متد Application_Start، متد initStructureMap فراخوانی شده است.
با پیاده سازی ObjectFactory.Initialize در کدهای برنامه کنسول معرفی شده آشنا شدیم. اینبار فقط حالت کش شدن کلاس Context برنامه را HttpContextScoped قرار داده‌ایم تا به ازای هر درخواست رسیده یک بار الگوی واحد کار وهله سازی شود.
نکته مهمی که در اینجا اضافه شده‌است، استفاده از متد ControllerBuilder.Current.SetControllerFactory می‌باشد. این متد نیاز به وهله‌ای از نوع DefaultControllerFactory دارد که نمونه‌ای از آن‌را در کلاس StructureMapControllerFactory مشاهده می‌کنید. به این ترتیب در زمان وهله سازی خودکار یک کنترلر، اینبار StructureMap وارد عمل شده و وابستگی‌های برنامه را مطابق تعاریف ObjectFactory.Initialize ذکر شده، به سازنده کلاس کنترلر تزریق می‌کند.
همچنین در متد Application_EndRequest با فراخوانی ObjectFactory.ReleaseAndDisposeAllHttpScopedObjects از نشتی اتصالات به بانک اطلاعاتی جلوگیری خواهیم کرد. چون وهله الگوی کار برنامه HttpScoped تعریف شده، در پایان یک درخواست به صورت خودکار توسط StructureMap پاکسازی می‌شود و به نشتی منابع نخواهیم رسید.


استفاده از الگوی واحد کار و کلاس‌های سرویس تهیه شده در یک برنامه ASP.NET Web forms

در یک برنامه ASP.NET Web forms نیز می‌توان این مباحث را پیاده سازی کرد:

using System;
using System.Data.Entity;
using EF_Sample07.DataLayer.Context;
using EF_Sample07.ServiceLayer;
using StructureMap; 
namespace EF_Sample07.WebFormsAppSample { public class Global : System.Web.HttpApplication { private static void initStructureMap() { ObjectFactory.Initialize(x => { x.For<IUnitOfWork>().HttpContextScoped().Use(() => new Sample07Context()); x.ForRequestedType<ICategoryService>().TheDefaultIsConcreteType<EfCategoryService>(); x.ForRequestedType<IProductService>().TheDefaultIsConcreteType<EfProductService>();
x.SetAllProperties(y=> { y.OfType<IUnitOfWork>(); y.OfType<ICategoryService>(); y.OfType<IProductService>(); }); }); }
void Application_Start(object sender, EventArgs e) { Database.SetInitializer(new MigrateDatabaseToLatestVersion<Sample07Context, Configuration>()); HibernatingRhinos.Profiler.Appender.EntityFramework.EntityFrameworkProfiler.Initialize(); initStructureMap(); }
void Application_EndRequest(object sender, EventArgs e) { ObjectFactory.ReleaseAndDisposeAllHttpScopedObjects(); }

در اینجا کدهای کلاس Global.asax.cs را ملاحظه می‌کنید. توضیحات آن با قسمت ASP.NET MVC آنچنان تفاوتی ندارد و یکی است. البته منهای تعاریف SetAllProperties که جدید است و در ادامه به علت اضافه کردن آن‌ها خواهیم رسید.
در ASP.NET Web forms برخلاف ASP.NET MVC نیاز است کار وهله سازی اینترفیس‌ها را به صورت دستی انجام دهیم. برای این منظور و کاهش کدهای تکراری برنامه می‌توان یک کلاس پایه را به نحو زیر تعریف کرد:

using System.Web.UI;
using StructureMap; 
namespace EF_Sample07.WebFormsAppSample { public class BasePage : Page { public BasePage() { ObjectFactory.BuildUp(this); } } }

سپس برای استفاده از آن خواهیم داشت:

using System;
using EF_Sample07.DataLayer.Context;
using EF_Sample07.DomainClasses;
using EF_Sample07.ServiceLayer; 
namespace EF_Sample07.WebFormsAppSample { public partial class AddProduct : BasePage { public IUnitOfWork UoW { set; get; } public IProductService ProductService { set; get; } public ICategoryService CategoryService { set; get; }
protected void Page_Load(object sender, EventArgs e) { if (!IsPostBack) { bindToCategories(); } }
private void bindToCategories() { ddlCategories.DataTextField = "Name"; ddlCategories.DataValueField = "Id"; ddlCategories.DataSource = CategoryService.GetAllCategories(); ddlCategories.DataBind(); }
protected void btnAdd_Click(object sender, EventArgs e) { var product = new Product { Name = txtName.Text, Price = int.Parse(txtPrice.Text), CategoryId = int.Parse(ddlCategories.SelectedItem.Value) }; ProductService.AddNewProduct(product); UoW.SaveChanges(); Response.Redirect("~/Default.aspx"); } } }


اینبار وابستگی‌های کلاس افزودن محصولات، به صورت خواصی عمومی تعریف شده‌اند. این خواص عمومی توسط متد SetAllProperties که در فایل global.asax.cs معرفی شدند، باید یکبار تعریف شوند (مهم!).
سپس اگر دقت کرده باشید، اینبار کلاس AddProduct از BasePage ما ارث بری کرده است. در سازند کلاس BasePage، با فراخوانی متد ObjectFactory.BuildUp، تزریق وابستگی‌ها به خواص عمومی کلاس جاری صورت می‌گیرد.
در ادامه نحوه استفاده از این اینترفیس‌ها را جهت مقدار دهی یک DropDownList یا ذخیره سازی اطلاعات یک محصول مشاهده می‌کنید. در اینجا نیز کار با اینترفیس‌ها انجام شده و کلاس جاری دقیقا نمی‌داند که با چه وهله‌ای مشغول به کار است. تنها در زمان اجرا است که توسط StructureMap ، به ازای هر اینترفیس معرفی شده، وهله‌ای مناسب بر اساس تعاریف فایل Global.asax.cs در اختیار برنامه قرار می‌گیرد.

کدهای کامل مثال‌های این سری را از آدرس زیر هم می‌توانید دریافت کنید: (^)


به روز رسانی
کدهای قسمت جاری را به روز شده جهت استفاده از EF 6 و StructureMap 3 در VS 2013، از اینجا می‌توانید دریافت کنید:
EF_Sample07  
مطالب
بررسی تغییرات Reflection در NET Core.
API کار با امکانات Reflection نیز در NET Core. نسبت به نگارش کامل دات نت، دارای تغییراتی است که در ادامه مهم‌ترین‌های آن‌ها را بررسی خواهیم کرد.


پیشنیازهای کار با Reflection در NET Core.

ابتدا نیاز است اسمبلی System.Reflection به قسمت وابستگی‌های فایل project.json اضافه شود:
"dependencies": {
    "System.Reflection": "4.3.0"
},
البته اگر وابستگی دیگری در فایل project.json، این وابستگی را پیشتر مورد استفاده قرار داده باشد، نیازی به ذکر مجدد آن نیست. برای این منظور می‌توان فایل project.lock.json را جهت یافتن مدخل System.Reflection بررسی کرد. اگر این مدخل در فایل lock موجود بود، توسط پروژه قابل دسترسی است و نیازی به ذکر صریح آن نخواهد بود.
پس از این مورد، ذکر فضای نام System.Reflection را در ابتدای کدها نیز فراموش نکنید. همین مورد بسیاری از خطاهای انتقال کدها را بدون تغییری برطرف می‌کند:
 using System.Reflection;


نوشتن کدهای شرطی، برای کامپایل یک کد، مخصوص فریم ورک‌های مختلف

در کدهای سی شارپ می‌توان توسط if# و else#، کامپایلر را جهت استفاده یا عدم استفاده‌ی کدهای نوشته شده، راهنمایی کرد. برای این منظور در NET Core.، به فایل project.json مراجعه کرده و ثابت دلخواهی را به نام COREFX به قسمت buildOptions اضافه می‌کنیم.
"buildOptions": {
   "define": [ "COREFX" ]
},
 پس از تعریف ثابت COREFX، اکنون نحوه‌ی استفاده‌ی از آن به صورت ذیل خواهد بود:
 #if COREFX
 // corefx codes
#else
 // other frameworks
#endif
از این روش می‌توان برای کامپایل یک فایل کد cs، جهت فریم ورک‌های مختلف استفاده کرد. در حالت فوق، کامپایلر فقط قسمت if# را پردازش کرده و از قسمت else# به طور کامل صرفنظر خواهد کرد.


اضافه شدن متدهای GetMethodInfo و GetTypeInfo

بسیاری از امکانات System.Type نگارش کامل دات نت به TypeInfo در NET Core. منتقل شده‌اند. برای مثال بجای کد پیشین
 var members = obj.GetType().GetMembers();
اینبار باید نوشت
 var members = obj.GetType().GetTypeInfo().GetMembers();
و یا نمونه‌ی if/else دار آن به صورت ذیل خواهد بود:
 #if COREFX
 toType.GetTypeInfo().IsGenericType
#else
 toType.IsGenericType
#endif
در اینجا قسمت if جهت NET Core. نوشته شده‌است و قسمت else برای نگارش کامل دات نت قابل استفاده خواهد بود.


یافتن اسمبلی جاری

در NET Core. متد GetExecutingAssembly حذف شده‌است و باید ابتدا نوع کلاس جاری را یافت و سپس توسط GetTypeInfo به Assembly آن دسترسی یافت:
 #if COREFX
 return typeof(MyClassName).GetTypeInfo().Assembly;
#else
 return System.Reflection.Assembly.GetExecutingAssembly();
#endif


خلاصه‌ی بحث

برای تبدیل کدهای مبتنی بر Reflection موجود، ابتدا از وجود وابستگی System.Reflection اطمینان حاصل کرده، فضای نام آن‌‌را الحاق و در موارد جزئی نیاز است از متد GetTypeInfo برای دسترسی به خواص API قبلی استفاده کرد.
بازخوردهای دوره
بایدها و نبایدهای استفاده از IoC Containers
اگر ترکیبی از Service Locator و poor man's dependency injection استفاده شود چه ایراداتی دارد ؟
مثلاً این کد
protected readonly IUnitOfWork UnitOfWork;

        protected BaseOperation():this(ObjectFactory.GetInstance<IUnitOfWork>())
        {
            
        }
        protected BaseOperation(IUnitOfWork uow)
        {
            UnitOfWork = uow;
        }
به غیر از این که کلاس مورد نظر به Container وابسته هست آیا ایراد دیگری هم هست یا خیر ؟
مطالب
ساخت یک Web API که از عملیات CRUD پشتیبانی می کند
در این مقاله با استفاده از ASP.NET Web API یک سرویس HTTP خواهیم ساخت که از عملیات CRUD پشتیبانی می‌کند. CRUD مخفف Create, Read, Update, Delete است که عملیات پایه دیتابیسی هستند. بسیاری از سرویس‌های HTTP این عملیات را بصورت REST API هم مدل سازی می‌کنند. در مثال جاری سرویس ساده ای خواهیم ساخت که مدیریت لیستی از محصولات (Products) را ممکن می‌سازد. هر محصول شامل فیلدهای شناسه (ID)، نام، قیمت و طبقه بندی خواهد بود.

سرویس ما متدهای زیر را در دسترس قرار می‌دهد.

 Relative URl
 HTTP method
 Action
 api/products/  GET  گرفتن لیست تمام محصولات
 api/products/id/  GET  گرفتن یک محصول بر اساس شناسه
 api/products?category=category/  GET  گرفتن یک محصول بر اساس طبقه بندی
 api/products/  POST  ایجاد یک محصول جدید
 api/products/id/  PUT  بروز رسانی یک محصول
 api/products/id/  DELETE  حذف یک محصول

همانطور که مشاهده می‌کنید برخی از آدرس ها، شامل شناسه محصول هم می‌شوند. بعنوان مثال برای گرفتن محصولی با شناسه 28، کلاینت یک درخواست GET را به آدرس زیر ارسال می‌کند:

http://hostname/api/products/28

منابع

سرویس ما آدرس هایی برای دستیابی به دو نوع منبع (resource) را تعریف می‌کند:

URI
 Resource
 api/products/  لیست تمام محصولات
 api/products/id/  یک محصول مشخص

متد ها

چهار متد اصلی HTTP یعنی همان GET, PUT, POST, DELETE می‌توانند بصورت زیر به عملیات CRUD نگاشت شوند:

  • متد GET یک منبع (resource) را از آدرس تعریف شده دریافت می‌کند. متدهای GET هیچگونه تاثیری روی سرور نباید داشته باشند. مثلا حذف رکوردها با متد اکیدا اشتباه است.
  • متد PUT یک منبع را در آدرس تعریف شده بروز رسانی می‌کند. این متد برای ساختن منابع جدید هم می‌تواند استفاده شود، البته در صورتی که سرور به کلاینت‌ها اجازه مشخص کردن آدرس‌های جدید را بدهد. در مثال جاری پشتیبانی از ایجاد منابع توسط متد PUT را بررسی نخواهیم کرد.
  • متد POST منبع جدیدی می‌سازد. سرور آدرس آبجکت جدید را تعیین می‌کند و آن را بعنوان بخشی از پیام Response بر می‌گرداند.
  • متد DELETE منبعی را در آدرس تعریف شده حذف می‌کند.

نکته: متد PUT موجودیت محصول (product entity) را کاملا جایگزین میکند. به بیان دیگر، از کلاینت انتظار می‌رود که آبجکت کامل محصول را برای بروز رسانی ارسال کند. اگر می‌خواهید از بروز رسانی‌های جزئی/پاره ای (partial) پشتیبانی کنید متد PATCH توصیه می‌شود. مثال جاری متد PATCH را پیاده سازی نمی‌کند.

یک پروژه Web API جدید بسازید

ویژوال استودیو را باز کنید و پروژه جدیدی از نوع ASP.NET MVC Web Application بسازید. نام پروژه را به "ProductStore" تغییر دهید و OK کنید.

در دیالوگ New ASP.NET Project قالب Web API را انتخاب کرده و تایید کنید.

افزودن یک مدل

یک مدل، آبجکتی است که داده اپلیکیشن شما را نمایندگی می‌کند. در ASP.NET Web API می‌توانید از آبجکت‌های Strongly-typed بعنوان مدل هایتان استفاده کنید که بصورت خودکار برای کلاینت به فرمت‌های JSON, XML مرتب (Serialize) می‌شوند. در مثال جاری، داده‌های ما محصولات هستند. پس کلاس جدیدی بنام Product می‌سازیم.

در پوشه Models کلاس جدیدی با نام Product بسازید.

حال خواص زیر را به این کلاس اضافه کنید.

namespace ProductStore.Models
{
    public class Product
    {
        public int Id { get; set; }
        public string Name { get; set; }
        public string Category { get; set; }
        public decimal Price { get; set; }
    }
}

افزودن یک مخزن

ما نیاز به ذخیره کردن کلکسیونی از محصولات داریم، و بهتر است این کلکسیون از پیاده سازی سرویس تفکیک شود. در این صورت بدون نیاز به بازنویسی کلاس سرویس می‌توانیم منبع داده‌ها را تغییر دهیم. این نوع طراحی با نام الگوی مخزن یا Repository Pattern شناخته می‌شود. برای شروع نیاز به یک قرارداد جنریک برای مخزن‌ها داریم.

روی پوشه Models کلیک راست کنید و گزینه Add, New Item را انتخاب نمایید.

نوع آیتم جدید را Interface انتخاب کنید و نام آن را به IProductRepository تغییر دهید.

حال کد زیر را به این اینترفیس اضافه کنید.

namespace ProductStore.Models
{
    public interface IProductRepository
    {
        IEnumerable<Product> GetAll();
        Product Get(int id);
        Product Add(Product item);
        void Remove(int id);
        bool Update(Product item);
    }
}
حال کلاس دیگری با نام ProductRepository در پوشه Models ایجاد کنید. این کلاس قرارداد IProductRepository را پیاده سازی خواهد کرد. کد زیر را به این کلاس اضافه کنید.

namespace ProductStore.Models
{
    public class ProductRepository : IProductRepository
    {
        private List<Product> products = new List<Product>();
        private int _nextId = 1;

        public ProductRepository()
        {
            Add(new Product { Name = "Tomato soup", Category = "Groceries", Price = 1.39M });
            Add(new Product { Name = "Yo-yo", Category = "Toys", Price = 3.75M });
            Add(new Product { Name = "Hammer", Category = "Hardware", Price = 16.99M });
        }

        public IEnumerable<Product> GetAll()
        {
            return products;
        }

        public Product Get(int id)
        {
            return products.Find(p => p.Id == id);
        }

        public Product Add(Product item)
        {
            if (item == null)
            {
                throw new ArgumentNullException("item");
            }
            item.Id = _nextId++;
            products.Add(item);
            return item;
        }

        public void Remove(int id)
        {
            products.RemoveAll(p => p.Id == id);
        }

        public bool Update(Product item)
        {
            if (item == null)
            {
                throw new ArgumentNullException("item");
            }
            int index = products.FindIndex(p => p.Id == item.Id);
            if (index == -1)
            {
                return false;
            }
            products.RemoveAt(index);
            products.Add(item);
            return true;
        }
    }
}

مخزن ما لیست محصولات را در حافظه محلی نگهداری می‌کند. برای مثال جاری این طراحی کافی است، اما در یک اپلیکیشن واقعی داده‌های شما در یک دیتابیس یا منبع داده ابری ذخیره خواهند شد. همچنین استفاده از الگوی مخزن، تغییر منبع داده‌ها در آینده را راحت‌تر می‌کند.


افزودن یک کنترلر Web API

اگر قبلا با ASP.NET MVC کار کرده باشید، با مفهوم کنترلر‌ها آشنایی دارید. در ASP.NET Web API کنترلر‌ها کلاس هایی هستند که درخواست‌های HTTP دریافتی از کلاینت را به اکشن متدها نگاشت می‌کنند. ویژوال استودیو هنگام ساختن پروژه شما دو کنترلر به آن اضافه کرده است. برای مشاهد آنها پوشه Controllers را باز کنید.

  • HomeController یک کنترلر مرسوم در ASP.NET MVC است. این کنترلر مسئول بکار گرفتن صفحات وب است و مستقیما ربطی به Web API ما ندارد.
  • ValuesController یک کنترلر نمونه WebAPI است.

کنترلر ValuesController را حذف کنید، نیازی به این آیتم نخواهیم داشت. حال برای اضافه کردن کنترلری جدید مراحل زیر را دنبال کنید.

در پنجره Solution Explorer روی پوشه Controllers کلیک راست کرده و گزینه Add, Controller را انتخاب کنید.

در دیالوگ Add Controller نام کنترلر را به ProductsController تغییر داده و در قسمت Scaffolding Options گزینه Empty API Controller را انتخاب کنید.

حال فایل کنترلر جدید را باز کنید و عبارت زیر را به بالای آن اضافه نمایید.

using ProductStore.Models;
یک فیلد هم برای نگهداری وهله ای از IProductRepository اضافه کنید.
public class ProductsController : ApiController
{
    static readonly IProductRepository repository = new ProductRepository();
}

فراخوانی ()new ProductRepository طراحی جالبی نیست، چرا که کنترلر را به پیاده سازی بخصوصی از این اینترفیس گره می‌زند. بهتر است از تزریق وابستگی (Dependency Injection) استفاده کنید. برای اطلاعات بیشتر درباره تکنیک DI در Web API به این لینک مراجعه کنید.


گرفتن منابع

ProductStore API اکشن‌های متعددی در قالب متدهای HTTP GET در دسترس قرار می‌دهد. هر اکشن به متدی در کلاس ProductsController مرتبط است.

 Relative URl
 HTTP Method
 Action
 api/products/  GET  دریافت لیست تمام محصولات
 api/products/id/  GET  دریافت محصولی مشخص بر اساس شناسه
 api/products?category=category/  GET  دریافت محصولات بر اساس طبقه بندی

برای دریافت لیست تمام محصولات متد زیر را به کلاس ProductsController اضافه کنید.

public class ProductsController : ApiController
{
    public IEnumerable<Product> GetAllProducts()
    {
        return repository.GetAll();
    }
    // ....
}
نام این متد با "Get" شروع می‌شود، پس بر اساس قراردادهای توکار پیش فرض به درخواست‌های HTTP GET نگاشت خواهد شد. همچنین از آنجا که این متد پارامتری ندارد، به URl ای نگاشت می‌شود که هیچ قسمتی با نام مثلا id نداشته باشد.

برای دریافت محصولی مشخص بر اساس شناسه آن متد زیر را اضافه کنید.
public Product GetProduct(int id)
{
    Product item = repository.Get(id);
    if (item == null)
    {
        throw new HttpResponseException(HttpStatusCode.NotFound); 
    }
    return item;
}

نام این متد هم با "Get" شروع می‌شود اما پارامتری با نام id دارد. این پارامتر به قسمت id مسیر درخواست شده (request URl) نگاشت می‌شود. تبدیل پارامتر به نوع داده مناسب (در اینجا int) هم بصورت خودکار توسط فریم ورک ASP.NET Web API انجام می‌شود.

متد GetProduct در صورت نامعتبر بودن پارامتر id استثنایی از نوع HttpResponseException تولید می‌کند. این استثنا بصورت خودکار توسط فریم ورک Web API به خطای 404 (Not Found) ترجمه می‌شود.

در آخر متدی برای دریافت محصولات بر اساس طبقه بندی اضافه کنید.
public IEnumerable<Product> GetProductsByCategory(string category)
{
    return repository.GetAll().Where(
        p => string.Equals(p.Category, category, StringComparison.OrdinalIgnoreCase));
}

اگر آدرس درخواستی پارامتر‌های query string داشته باشد، Web API سعی می‌کند پارامتر‌ها را با پارامتر‌های متد کنترلر تطبیق دهد. بنابراین درخواستی به آدرس "api/products?category=category" به این متد نگاشت می‌شود.

ایجاد منبع جدید

قدم بعدی افزودن متدی به ProductsController برای ایجاد یک محصول جدید است. لیست زیر پیاده سازی ساده ای از این متد را نشان می‌دهد.

// Not the final implementation!
public Product PostProduct(Product item)
{
    item = repository.Add(item);
    return item;
}
به دو چیز درباره این متد توجه کنید:

  • نام این متد با "Post" شروع می‌شود. برای ساختن محصولی جدید کلاینت یک درخواست HTTP POST ارسال می‌کند.
  • این متد پارامتری از نوع Product می‌پذیرد. در Web API پارامترهای پیچیده (complex types) بصورت خودکار با deserialize کردن بدنه درخواست بدست می‌آیند. بنابراین در اینجا از کلاینت انتظار داریم که آبجکتی از نوع Product را با فرمت XML یا JSON ارسال کند.

پیاده سازی فعلی این متد کار می‌کند، اما هنوز کامل نیست. در حالت ایده آل ما می‌خواهیم پیام HTTP Response موارد زیر را هم در بر گیرد:

  • Response code: بصورت پیش فرض فریم ورک Web API کد وضعیت را به 200 (OK) تنظیم می‌کند. اما طبق پروتکل HTTP/1.1 هنگامی که یک درخواست POST منجر به ساخته شدن منبعی جدید می‌شود، سرور باید با کد وضعیت 201 (Created) پاسخ دهد.
  • Location: هنگامی که سرور منبع جدیدی می‌سازد، باید آدرس منبع جدید را در قسمت Location header پاسخ درج کند.

ASP.NET Web API دستکاری پیام HTTP response را آسان می‌کند. لیست زیر پیاده سازی بهتری از این متد را نشان می‌دهد.

public HttpResponseMessage PostProduct(Product item)
{
    item = repository.Add(item);
    var response = Request.CreateResponse<Product>(HttpStatusCode.Created, item);

    string uri = Url.Link("DefaultApi", new { id = item.Id });
    response.Headers.Location = new Uri(uri);
    return response;
}
توجه کنید که حالا نوع بازگشتی این متد HttpResponseMessage است. با بازگشت دادن این نوع داده بجای Product، می‌توانیم جزئیات پیام HTTP response را کنترل کنیم. مانند تغییر کد وضعیت و مقدار دهی Location header.

متد CreateResponse آبجکتی از نوع HttpResponseMessage می‌سازد و بصورت خودکار آبجکت Product را مرتب (serialize) کرده و در بدنه پاسخ می‌نویسد. نکته دیگر آنکه مثال جاری، مدل را اعتبارسنجی نمی‌کند. برای اطلاعات بیشتر درباره اعتبارسنجی مدل‌ها در Web API به این لینک مراجعه کنید.


بروز رسانی یک منبع

بروز رسانی یک محصول با PUT ساده است.

public void PutProduct(int id, Product product)
{
    product.Id = id;
    if (!repository.Update(product))
    {
        throw new HttpResponseException(HttpStatusCode.NotFound);
    }
}
نام این متد با "Put" شروع می‌شود، پس Web API آن را به درخواست‌های HTTP PUT نگاشت خواهد کرد. این متد دو پارامتر می‌پذیرد، یکی شناسه محصول مورد نظر و دیگری آبجکت محصول آپدیت شده. مقدار پارامتر id از مسیر (route) دریافت می‌شود و پارامتر محصول با deserialize کردن بدنه درخواست.


حذف یک منبع

برای حذف یک محصول متد زیر را به کلاس ProductsController اضافه کنید.

public void DeleteProduct(int id)
{
    Product item = repository.Get(id);
    if (item == null)
    {
        throw new HttpResponseException(HttpStatusCode.NotFound);
    }

    repository.Remove(id);
}
اگر یک درخواست DELETE با موفقیت انجام شود، می‌تواند کد وضعیت 200 (OK) را بهمراه بدنه موجودیتی که وضعیت فعلی را نمایش می‌دهد برگرداند. اگر عملیات حذف هنوز در حال اجرا است (Pending) می‌توانید کد 202 (Accepted) یا 204 (No Content) را برگردانید.

در مثال جاری متد DeleteProduct نوع void را بر می‌گرداند، که فریم ورک Web API آن را بصورت خودکار به کد وضعیت 204 (No Content) ترجمه می‌کند.
مطالب
شروع به کار با AngularJS 2.0 و TypeScript - قسمت هفتم - سرویس‌ها و تزریق وابستگی‌ها
یک سرویس در AngularJS 2.0، کلاسی است با هدفی محدود و مشخص. این سرویس‌ها مستقل از کامپوننتی خاص هستند و هدف آن‌ها، به اشتراک گذاشتن اطلاعات و یا منطقی بین کامپوننت‌های مختلف می‌باشد. همچنین از آن‌ها برای کپسوله سازی تعاملات خارجی، مانند دسترسی به داده‌ها نیز استفاده می‌شود.


نگاهی به نحوه‌ی عملکرد سرویس‌ها و تزریق وابستگی‌ها در AngularJS 2.0

فرض کنید کلاس سرویسی، به نحو ذیل تعریف شده‌است:
 export class MyService {}
این کلاس، خارج از کلاس متناظر با یک کامپوننت قرار داد. بنابراین برای استفاده‌ی از آن، می‌توان آن‌را به صورت مستقیم، داخل کلاسی که به آن نیاز دارد، وهله سازی/نمونه سازی نمود و استفاده کرد:
 let svc = new MyService();
هر چند این روش کار می‌کند، اما نمونه‌ی ایجاد شده، سطح دسترسی محلی، در این کلاس دارد و در خارج آن قابل دسترسی نیست. بنابراین نمی‌توان از آن برای به اشتراک گذاشتن اطلاعات و منابع، بین کامپوننت‌های مختلف استفاده کرد.
همچنین در این حالت، mocking این سرویس برای نوشتن unit tests نیز مشکل می‌باشد.

راه بهتر و توصیه شده‌ی در اینجا، ثبت و معرفی این سرویس‌ها به AngularJS 2.0 است. سپس AngularJS 2.0 به ازای هر کلاس سرویس معرفی شده‌ی به آن، یک وهله/نمونه را ایجاد می‌کند. بنابراین طول عمر سرویس‌های ایجاد شده‌ی در این حالت، singleton است (یکبار ایجاد شده و تا پایان طول عمر برنامه زنده نگه داشته می‌شوند).
پس از آن می‌توان از تزریق کننده‌های توکار AngularJS 2.0، جهت تزریق وهله‌های این سرویس‌ها استفاده کرد.
اکنون اگر کلاسی، نیاز به این سرویس داشته باشد، نیاز خود را به صورت یک وابستگی تعریف شده‌ی در سازنده‌ی کلاس اعلام می‌کند:
 constructor(private _myService: MyService){}
در این حالت زمانیکه کلاس کامپوننت، برای اولین بار وهله سازی می‌شود، سرویس مورد نیاز آن نیز توسط تزریق کننده‌ی توکار AngularJS 2.0، در اختیارش قرار می‌گیرد.
به این فرآیند اصطلاحا dependency injection و یا تزریق وابستگی‌ها می‌گویند. در فرآیند تزریق وابستگی‌ها، یک کلاس، وهله‌های کلاس‌های دیگر مورد نیاز خودش را بجای وهله سازی مستقیم، از یک تزریق کننده دریافت می‌کند. بنابراین بجای نوشتن newها در کلاس جاری، آن‌ها را به صورت وابستگی‌هایی در سازنده‌ی کلاس تعریف می‌کنیم تا توسط AngularJS 2.0 تامین شوند.

با توجه به اینکه طول عمر این وابستگی‌ها singleton است و این طول عمر توسط AngularJS 2.0 مدیریت می‌شود، اطلاعات وهله‌های سرویس‌های مختلف و تغییرات صورت گرفته‌ی در آن‌ها، بین تمام کامپوننت‌ها به صورت یکسانی به اشتراک گذاشته می‌شوند.
به علاوه اکنون امکان mocking سرویس‌ها با توجه به عدم وهله سازی آن‌ها در داخل کلاس‌ها به صورت مستقیم، ساده‌تر از قبل میسر است.


مراحل ساخت یک سرویس در AngularJS 2.0

ساخت یک سرویس در AngularJS 2.0، با ایجاد یک کلاس جدید شروع می‌شود. سپس متادیتای آن افزوده شده و در آخر موارد مورد نیاز آن import خواهند شد. با این موارد پیشتر در حین ساختن یک کامپوننت جدید و یا یک Pipe جدید آشنا شده‌اید و این طراحی یک دست را در سراسر AngularJS 2.0 می‌توان مشاهده کرد.
اولین سرویس خود را با افزودن فایل جدید product.service.ts به پوشه‌ی app\products آغاز می‌کنیم؛ با این محتوا:
import { Injectable } from 'angular2/core';
import { IProduct } from './product';
 
@Injectable()
export class ProductService {
 
    getProducts(): IProduct[] {
        return [
            {
                "productId": 2,
                "productName": "Garden Cart",
                "productCode": "GDN-0023",
                "releaseDate": "March 18, 2016",
                "description": "15 gallon capacity rolling garden cart",
                "price": 32.99,
                "starRating": 4.2,
                "imageUrl": "app/assets/images/garden_cart.png"
            },
            {
                "productId": 5,
                "productName": "Hammer",
                "productCode": "TBX-0048",
                "releaseDate": "May 21, 2016",
                "description": "Curved claw steel hammer",
                "price": 8.9,
                "starRating": 4.8,
                "imageUrl": "app/assets/images/rejon_Hammer.png"
            }
        ];
    }
}
نام کلاس سرویس نیز pascal case است و بهتر است به کلمه‌ی Service ختم شود.
همانند سایر ماژول‌های تعریف شده‌، در اینجا نیز باید کلاس تعریف شده export شود تا در قسمت‌های دیگر قابل استفاده و دسترسی گردد.
سپس در این سرویس، یک متد برای بازگشت لیست محصولات ایجاد شده‌است.
در ادامه یک decorator جدید به نام ()Injectable@  به بالای این کلاس اضافه شده‌است. این متادیتا است که مشخص می‌کند کلاس جاری، یک سرویس AngularJS 2.0 است.
البته باید دقت داشت که این مزین کننده تنها زمانی نیاز است حتما قید شود که کلاس تعریف شده، دارای وابستگی‌های تزریق شده‌ای باشد. اما توصیه شده‌است که بهتر است هر کلاس سرویسی (حتی اگر دارای وابستگی‌های تزریق شده‌ای هم نبود) به این decorator ویژه، مزین شود تا بتوان طراحی یک دستی را در سراسر برنامه شاهد بود.
در آخر هم موارد مورد نیاز، import می‌شوند. برای مثال Injectable در ماژول angular2/core تعریف شده‌است.

هدف از تعریف این سرویس، دور کردن وظیفه‌ی تامین داده، از کلاس کامپوننت لیست محصولات است؛ جهت رسیدن به یک طراحی SOLID.
در قسمت بعدی این سری، این لیست را بجای یک آرایه‌ی از پیش تعریف شده، از یک سرور HTTP دریافت خواهیم کرد.


ثبت و معرفی سرویس جدید ProductService به AngularJS 2.0 Injector

مرحله‌ی اول استفاده از سرویس‌های تعریف شده، ثبت و معرفی آن‌ها به AngularJS 2.0 Injector است. سپس این Injector است که تک وهله‌ی سرویس ثبت شده‌ی در آن‌را در اختیار هر کامپوننتی که آن‌را درخواست کند، قرار می‌دهد.
مرحله‌ی ثبت این سرویس، معرفی نام این کلاس، به خاصیتی آرایه‌ای، به نام providers است که یکی از خواص decorator ویژه‌ی Component است. بدیهی است هر کامپوننتی که در برنامه وجود داشته باشد، توانایی ثبت این سرویس را نیز دارد؛ اما باید از کدامیک استفاده کرد؟
اگر سرویس خود را در کامپوننت لیست محصولات رجیستر کنیم، تک وهله‌ی این سرویس تنها در این کامپوننت و زیر کامپوننت‌های آن در دسترس خواهند بود و اگر این سرویس را در بیش از یک کامپوننت ثبت کنیم، آنگاه دیگر هدف اصلی طول عمر singleton یک سرویس مفهومی نداشته و برنامه هم اکنون دارای چندین وهله از سرویس تعریف شده‌ی ما می‌گردد و دیگر نمی‌توان اطلاعات یکسانی را بین کامپوننت‌ها به اشتراک گذاشت.
بنابراین توصیه شده‌است که از خاصیت providers کامپوننت‌های غیر ریشه‌ای، صرفنظر کرده و سرویس‌های خود را تنها در بالاترین سطح کامپوننت‌های تعریف شده، یعنی در فایل app.component.ts ثبت و معرفی کنید. به این ترتیب تک وهله‌ی ایجاد شده‌ی در اینجا، در این کامپوننت ریشه‌ای و تمام زیر کامپوننت‌های آن (یعنی تمام کامپوننت‌های دیگر برنامه) به صورت یکسانی در دسترس قرار می‌گیرد.
به همین جهت فایل app.component.ts را گشوده و تغییرات ذیل را به آن اعمال کنید:
import { Component } from 'angular2/core';
import { ProductListComponent } from './products/product-list.component';
import { ProductService } from './products/product.service';
 
@Component({
    selector: 'pm-app',
    template:`
    <div><h1>{{pageTitle}}</h1>
        <pm-products></pm-products>
    </div>
    `,
    directives: [ProductListComponent],
    providers: [ProductService]
})
export class AppComponent {
    pageTitle: string = "DNT AngularJS 2.0 APP";
}
در اینجا دو تغییر جدید صورت گرفته‌اند:
الف) خاصیت providers که آرایه‌ای از سرویس‌ها را قبول می‌کند، با ProductService مقدار دهی شده‌است.
ب) در ابتدای فایل، ProductService، از ماژول آن import گردیده‌است.


تزریق سرویس‌ها به کامپوننت‌ها

تا اینجا یک سرویس جدید را ایجاد کردیم و سپس آن‌را به AngularJS 2.0 Injector معرفی نمودیم. اکنون نوبت به استفاده و تزریق آن، به کلاسی است که به این وابستگی نیاز دارد. در TypeScript، تزریق وابستگی‌ها در سازنده‌ی یک کلاس صورت می‌گیرند. هر کلاس، دارای متد سازنده‌ای است که در زمان وهله سازی آن، اجرا می‌شود. اگر نیاز به تزریق وابستگی‌ها باشد، تعریف این سازنده به صورت صریح، ضروری است. باید دقت داشت که هدف اصلی از متد سازنده، آغاز و مقدار دهی متغیرها و وابستگی‌های مورد نیاز یک کلاس است و باید تا حد امکان از منطق‌های طولانی عاری باشد.
در ادامه فایل product-list.component.ts را گشوده و سپس سازنده‌ی ذیل را به آن اضافه کنید:
import { ProductService } from './product.service';
export class ProductListComponent implements OnInit {
    pageTitle: string = 'Product List';
    imageWidth: number = 50;
    imageMargin: number = 2;
    showImage: boolean = false;
    listFilter: string = 'cart';
 
    constructor(private _productService: ProductService) {
    }
سازنده‌ی کلاس عموما پس از لیست خواص آن کلاس تعریف می‌شود و پیش از تعاریف سایر متدهای آن.
روش خلاصه شده‌ای که در اینجا جهت تعریف سازنده‌ی کلاس و متغیر تعریف شده‌ی در آن بکار گرفته شده، معادل قطعه کد متداول ذیل است و هر دو حالت ذکر شده، در TypeScript یکی می‌باشند:
private _productService: ProductService;
constructor(productService: ProductService) {
   _productService = productService;
}
در اینجا سرویس مورد نیاز را به صورت یک متغیر private در سازنده‌ی کلاس ذکر می‌کنیم (مرسوم است متغیرهای private با _ شروع شوند). همچنین این سرویس باید در لیست import ابتدای ماژول جاری نیز ذکر شود.
این وابستگی در اولین باری که کلاس کامپوننت، توسط AngularJS 2.0 وهله سازی می‌شود، از لیست providers ثبت شده‌ی در کامپوننت ریشه‌ی سایت، تامین خواهد شد.
اکنون نوبت به استفاده‌ی از این سرویس تزریق شده‌است. به همین جهت ابتدا لیست عناصر آرایه‌ی خاصیت products را حذف می‌کنیم (برای اینکه قرار است این سرویس، کار تامین اطلاعات را انجام دهد و نه کلاس کامپوننت).
 products: IProduct[];
خوب، در ادامه، کدهای مقدار دهی آرایه‌ی products را از سرویس دریافتی، در کجا قرار دهیم؟ شاید عنوان کنید که در همین متد سازنده‌ی کلاس نیز می‌توان این‌کار را انجام داد.
 this.products = _productService.getProducts();
هر چند در مثال جاری که از یک آرایه‌ی از پیش تعریف شده، برای این مقصود استفاده می‌شود، این مقدار دهی مشکلی را ایجاد نخواهد کرد، اما در قسمت بعدی که می‌خواهیم آن‌را از سرور دریافت کنیم، فراخوانی متد getProducts، اندکی زمانبر خواهد بود. بنابراین رویه‌ی کلی این است که کدهای زمانبر، نباید در سازنده‌ی یک کلاس قرار گیرند؛ چون سبب تاخیر در بارگذاری تمام قسمت‌های آن می‌شوند.
به همین جهت روش صحیح انجام این مقدار دهی، با پیاده سازی life cycle hook ویژه‌ای به نام OnInit است که در قسمت پنجم آن‌را معرفی کردیم:
export class ProductListComponent implements OnInit {
products: IProduct[];

constructor(private _productService: ProductService) {
}

ngOnInit(): void {
    //console.log('In OnInit');
    this.products = this._productService.getProducts();
}
هر نوع عملیات آغازین مقدار دهی متغیرها و خواص کامپوننت‌ها باید در ngOnInit مربوط به هوک OnInit انجام شود که نمونه‌ای از آن‌را در کدهای فوق ملاحظه می‌کنید.
در اینجا اکنون خاصیت products عاری است از ذکر صریح عناصر تشکیل دهنده‌ی آن. سپس وابستگی مورد نیاز، در سازنده‌ی کلاس تزریق شده‌است و در آخر، در رویداد چرخه‌ی حیات ngOnInit، با استفاده از این وابستگی تزریقی، لیست محصولات دریافت و به خاصیت عمومی products نسبت داده شده‌است.

در ادامه برنامه را اجرا کنید. باید هنوز هم مطابق قبل، لیست محصولات قابل مشاهده باشد.


کدهای کامل این قسمت را از اینجا می‌توانید دریافت کنید: MVC5Angular2.part7.zip


خلاصه‌ی بحث
فرآیند کلی تعریف یک سرویس AngularJS 2.0، تفاوتی با ساخت یک کامپوننت یا Pipe سفارشی ندارد. پس از تعریف کلاسی که نام آن ختم شده‌ی به Service است، آن‌را مزین به ()Injectable@ می‌کنیم. سپس این سرویس را در بالاترین سطح کامپوننت‌های موجود یا همان کامپوننت ریشه‌ی سایت، ثبت و معرفی می‌کنیم؛ تا تنها یک وهله از آن توسط AngularJS 2.0 Injector ایجاد شده و در اختیار تمام کامپوننت‌های برنامه قرار گیرد. البته اگر این سرویس تنها در یک کامپوننت استفاده می‌شود و قصد به اشتراک گذاری اطلاعات آن‌را نداریم، می‌توان سطح سلسله مراتب دسترسی به آن‌را نیز کاهش داد. برای مثال این سرویس را در لیست providers همان کامپوننت ویژه، ثبت و معرفی کرد. به این ترتیب تنها این کامپوننت خاص و فرزندان آن دسترسی به امکانات سرویس مدنظر را می‌یابند و نه تمام کامپوننت‌های دیگر تعریف شده‌ی در برنامه.
در ادامه هر کلاسی که به این سرویس نیاز دارد (با توجه به سلسه مراتب دسترسی ذکر شده)، تنها کافی است در سازنده‌ی خود، این وابستگی را اعلام کند تا توسط AngularJS 2.0 Injector تامین گردد.
نظرات مطالب
جایگزین کردن jQuery با JavaScript خالص - قسمت اول - یافتن عناصر
بوت استرپ 4.2 نیز وابستگی jQuery اش را حذف خواهد کرد.
We’ve been working on a huge v4.2 update for several months now. Our attention has largely been on advancing the project and simplifying it’s dependencies, namely by removing our jQuery dependency
مطالب دوره‌ها
بایدها و نبایدهای استفاده از IoC Containers
طوری با IoC Containers کار کنید که انگار وجود خارجی ندارند

تفاوت پایه‌ای که بین یک فریم ورک IoC و سایر فریم ورک‌ها وجود دارد، در معکوس شدن مسئولیت‌ها است. در اینجا لایه‌های مختلف برنامه شما نیستند که فریم ورک IoC را فراخوانی می‌کنند؛ بلکه این فریم ورک IoC است که از جزئیات ارتباطات و وابستگی‌های سیستم شما آگاه است و نهایتا کار کنترل وهله سازی اشیاء مختلف را عهده دار خواهد شد. طول عمر آن‌ها را تنظیم کرده یا حتی در بعضی از موارد مانند برنامه نویسی جنبه‌گرا یا AOP، نسبت به تزئین این اشیاء یا دخالت در مراحل مختلف فراخوانی متدهای آن‌ها نیز نقش خواهد داشت. نکته‌ی مهم در اینجا، نا آگاهی برنامه از حضور آن‌ها است.
بنابراین در پروژه شما اگر ماژول‌ها و لایه‌های مختلفی حضور دارند، تنها برنامه اصلی است که باید ارجاعی را به فریم ورک IoC داشته باشد و نه سایر لایه‌های سیستم. علت حضور آن در ریشه سیستم نیز تنها باید به اصطلاحا bootstrapping و اعمال تنظیمات مرتبط با آن خلاصه شود.
به عبارتی استفاده صحیح از یک فریم ورک IoC نباید به شکل الگوی Service Locator باشد؛ حالتی که در تمام قسمت‌های برنامه مدام مشاهده می‌کنید  resolver.Resolve،  resolver.Resolve و الی آخر. باید از این نوع استفاده از فریم ورک‌های IoC تا حد ممکن حذر شود و کدهای برنامه نباید وابستگی مستقیم ثانویه‌ای را به نام خود فریم ورک IoC پیدا کنند.
 var container = BootstrapContainer();

var finder = container.Resolve<IDuplicateFinder>();
var processor = container.Resolve<IArgumentsParser>();

Execute( args, processor, finder );
 
container.Dispose();
نمونه‌ای از نحوه صحیح استفاده از یک IoC Container را مشاهده می‌کنید. تنها در سه نقطه است که یک IoC container باید حضور پیدا کند:
الف) در آغاز برنامه برای اعمال تنظیمات اولیه و bootstrapping
ب) پیش از اجرای عملی جهت وهله سازی وابستگی‌های مورد نیاز
ج) پس از اجرای عمل مورد نظر جهت آزاد سازی منابع

نکته مهم اینجا است که در حین اجرای فرآیند، این فرآیند باید تا حد ممکن از حضور IoC container بی‌خبر باشد و کار تشکیل اشیاء باید خارج از منطق تجاری برنامه انجام شود: IoC container خود را صدا نزنید؛ او شما را صدا خواهد زد.
عنوان شد تا «حد ممکن». این تا حد ممکن به چه معنایی است؟ اگر کار وهله سازی اشیاء را می‌توانید تحت کنترل قرار دهید، مثلا آیا می‌توانید در نحوه وهله سازی کنترلرها در ASP.NET MVC دخل و تصرف کرده و در زمان وهله سازی، اینکار را به یک IoC Container واگذار کنید؟ اگر بلی، دیگر به هیچ عنوانی نباید داخل کلاس‌های فراخوانی شده و تزریق شده به کنترلرهای برنامه اثری از IoC Container شما مشاهده شود. زیرا این فریم ورک‌ها اینقدر توانمند هستند که بتوانند تا چندین لایه از سیستم را واکاوی کرده و وابستگی‌های لازم را وهله سازی کنند.
اگر خیر (نمی‌توانید کار وهله سازی اشیاء را مستقیما تحت کنترل قرار دهید)؛ مانند تهیه یک Role Provider سفارشی در ASP.NET MVC که کار وهله سازی این Role Provider راسا توسط موتور ASP.NET انجام می‌شود و در این بین امکان دخل و تصرفی هم در آن ممکن نیست، آنگاه مجاز است داخل این کلاس ویژه از متدهای container.Resolve استفاده کرد؛ چون چاره‌ی دیگری وجود ندارد و IoC Container نیست که کار وهله سازی ابتدایی آن‌را عهده دار شده است. باید دقت داشت به این حالت خاص دیگر تزریق وابستگی‌ها گفته نمی‌شود؛ بلکه نام الگوی آن Service locator است. در Service locator یک کامپوننت خودش به دنبال وابستگی‌های مورد نیازش می‌گردد. در حالت تزریق وابستگی‌ها، یک کامپوننت وابستگی‌های مورد نیاز را درخواست می‌کند.

یک مثال:
public class ExampleClass
{
    private readonly IService _service;

    public ExampleClass()
    {
        _service = Container.Resolve<IService>();
    }

    public void DoSomething(int id)
    {
        _service.DoSomething(id);
    }
}
کاری که در اینجا انجام شده است نمونه اشتباهی از استفاده از یک IoC Container می‌باشد. به صرف اینکه مشغول به استفاده از یک IoC Container  هستیم به این معنا نیست که واقعا الگوی معکوس سازی وابستگی‌ها را درست درک کرده‌ایم. در اینجا الگوی Service locator مورد استفاده است و نه الگوی تزریق وابستگی‌ها. به عبارتی در مثال فوق، کلاس ExampleClass وابسته است به یک وابستگی جدیدی به نام Container، علاوه بر وابستگی IService ایی که به او قرار است خدماتی را ارائه دهد.
نمونه اصلاح شده کلاس فوق، تزریق وابستگی‌ها در سازنده کلاس به نحو زیر است:
public class ExampleClass
{
    private IService _service;

    public ExampleClass(IService service)
    {
        _service = service;
    }

    public void DoSomething(int id)
    {
        _service.DoSomething(id);
    }
}
در اینجا این کلاس است که وابستگی‌های خود را درخواست می‌کند و نه اینکه خودش به دنبال آن‌ها بگردد.

نمونه دیگری از کلاسی که خودش به دنبال یافتن و وهله سازی وابستگی‌های مورد نیازش است مثال زیر می‌باشد:
public class Search
{
   IDinner _dinner;
   public Search(): this(new Dinner()) 
   { }

   public Search(IDinner dinner) 
   {
      _dinner = dinner;
   }
}
به این کار poor man's dependency injection هم گفته می‌شود؛ اولین سازنده از طریق یک default constructor سعی کرده است وابستگی‌های کلاس را، خودش تامین کند. باز هم کلاس می‌داند که به چه وابستگی خاصی نیاز دارد و عملا معکوس سازی وابستگی‌ها رخ نداده است. همچنین استفاده از این حالت زمانیکه کلاس Dinner خودش وابستگی به کلاس‌های دیگر داشته باشد، بسیار به هم ریخته و مشکل خواهد بود. مزیت استفاده از IoC Containers وهله سازی یک large object graph کامل است. به علاوه توسط IoC Containers مدیریت طول عمر اشیاء را نیز می‌توان تحت نظر قرار داد. برای مثال می‌توان به یک IoC Container گفت تنها یک وهله از DbContext را در طول یک درخواست ایجاد و آن‌را در اختیار لایه‌های مختلف برنامه قرار بده؛ چون نیاز داریم کاری که در طی یک درخواست انجام می‌شود، در داخل یک تراکنش انجام شده و همچنین بی‌جهت به ازای هر new DbConetxt جدید، یکبار اتصالی به بانک اطلاعاتی باز و بسته نشود (سرعت بیشتر، سربار کمتر).