۱۳۹۱/۰۲/۲۶

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 در اختيار برنامه قرار مي‌گيرد.

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