# برنامه نویسی با محصولات مایکروسافت > برنامه نویسی مبتنی بر Microsoft .Net Framework > ASP.NET MVC > آموزش: ایجاد یک پروژه MVC با به کارگیری الگوی Unit of work با استفاده از کتابخانه Structuremap چند لایه

## ali_autumnal

با سلام

*کلیه مطالب برگرفته از مطالب ارائه شده از مهندس وحید نصیری می باشد البته با کمی دخل و تصرف!* 


من بخش به بخش سعی می کنم مطالب رو بنویسم تا دوستان استفاده کنند. هر سوالی در این مورد داشتید در این تاپیک بپرسید. تا جایی که اطلاع داشته باشم پاسخ خواهم داد.

یه خواهش: برای ابراز احساسات از گزینه تشکر استفاده نمائید خواهشا از پست ها جهت ابراز احساسات استفاده نکنید!

بدون مقدمه یه راست سر اصل مطلب:

برای ایجاد پروژه مراحل زیر را انجام می دهیم:

با فرض اینکه نام پروژه اصلی MVCProject هست

New=> Project=> ASP.Net MVC 4 Web Application


پس از OK اول کادری باز شده در آن سومین گزینه از چپ یعنی Internet appllication
A default ASP.NET MVC 4 project with an account controller that uses forms authentication.را انتخاب کنید
View engin را هم Razor انتخاب شود. 

پس از ایجاد پروژه اصلی  Class Library های زیر را در ریشه پروژه اصلی ایجاد کنید
کلیک راست بروی ریشه اصلی پروژه و گزینه add سپس New Project


MVCProject.DataLayer
MVCProject.DomainClasses
MVCProject.ServiceLayer
MVCProject.Models	


سپس با دستور 

Install-Package EntityFramework


در Package Manager Console،  EntityFramework  را  در پروژه های 

Project
		DataLayer
		DomainClasses
		ServiceLayer
		Models


نصب کنید.

سپس در DataLayer توسط دستور زیر Migrations رو نصب کنید

Install-Package EntityFramework.Migrations -Version 0.9.0.0 

در Web.config در بخش Connection نام دیتابیس خود را بنویسید. درواقع DefaultConnection رو به "your db name" تغییر نام دهید
سپس در DataLayer با نام دیتابیس تون یه کلاس اضافه کنید
از طرفی باید به References این پروژه DomainClasses رو اضافه کنید
Interface زیر رو قبل از کلاس (نام دیتابیستون) تایپ کنید

public interface IUnitOfWork
    {
        IDbSet<TEntity> Set<TEntity>() where TEntity : class;
        DbEntityEntry<TEntity> Entry<TEntity>(TEntity entity) where TEntity : class;
        int SaveChanges();
    }


سپس کلاس اصلی رو به شکل زیر تغییر دهید:

public class "your db name" : DbContext, IUnitOfWork
    {
        public "your db name"()
            : base("name=DefaultConnection")
        {
        }

        public new IDbSet<TEntity> Set<TEntity>() where TEntity : class
        {
            return base.Set<TEntity>();
        }
	
	public DbSet<UserProfile> UserProfiles { set; get; }

	and other table...

	protected override void OnModelCreating(DbModelBuilder modelBuilder)
	{
		تنظیم کلیدهای خارجی
		تنظیم کلیدهای چندتایی
		تنظیم حذف خودکار فرزند
		
		base.OnModelCreating(modelBuilder);
	}
    }

به پروژه اصلی رفته و references های زیر رو اضافه کنید:

DataLayer
		DomainClasses
		ServiceLayer
		Models

سپس Build Solution
سپس تو DataLayer دستور ریر رو اجرا کنید

Enable-Migrations -ContextTypeName "your db name"

پس از اجرای دستور و نصب Migrations داخل پوشه Migrations کلاس Configuration رو باز کرده و کدهای زیر را به آن اضافه کنید:

public Configuration()
	{
    		AutomaticMigrationsEnabled = true;
    		AutomaticMigrationDataLossAllowed = true; // <-- THIS LINE
	}


مدل های اصلی پروژه رو به DomainClasses اضافه کنید 
سپس در صورت طی همه مراحل پس از اجرای دستور زیر باید دیتابیس شما ایجاد بشه

update-database

سپس تو Project دستورات زیر رو اجرا کنید تا structuremap نصب شود

Install-Package structuremap
	Install-Package StructureMap.MVC4




ادامه دارد...

----------


## مهدی هادیان2

بسم الله الرحمن الرحیم
با سلام



> Install-Package EntityFramework


با نصب این مورد اعلام میکنه که  EntityFramework به تمام  پروژه ها اضافه شده است ولی تنها در رفرنس پروژه اصلی دیده می شود !!!!!!
با دستور Install-Package EntityFramework ServiceLayer می خوام به صورت دستی به بقیه ها پروژه ها هم اضافه کنم با کمال تعجب میگه که قبلا مورد مذکور رو اضافه کرده است:


```
'EntityFramework 6.0.2' already installed.
Successfully added 'EntityFramework 6.0.2' to ServiceLayer.
```

و این بار به رفرنس پروژه مورد نظر هم افزوده شده است.
موضوع چیه؟ :متعجب: 
با سپاس فراوان

----------


## helpsos

با سلام
میشه این قطعه کد رو توضیح بدین؟
public interface IUnitOfWork
    {
        IDbSet<TEntity> Set<TEntity>() where TEntity : class;
        DbEntityEntry<TEntity> Entry<TEntity>(TEntity entity) where TEntity : class;
        int SaveChanges();
    }

----------


## helpsos

> بسم الله الرحمن الرحیم
> با سلام
> 
> با نصب این مورد اعلام میکنه که  EntityFramework به تمام  پروژه ها اضافه شده است ولی تنها در رفرنس پروژه اصلی دیده می شود !!!!!!
> با دستور Install-Package EntityFramework ServiceLayer می خوام به صورت دستی به بقیه ها پروژه ها هم اضافه کنم با کمال تعجب میگه که قبلا مورد مذکور رو اضافه کرده است:
> 
> 
> ```
> 'EntityFramework 6.0.2' already installed.
> ...


برای نصب در هر پروژه باید به صورت زیر عمل نمایید(با تشکر از وحید نصیری)

----------


## ali_autumnal

> با سلام
> میشه این قطعه کد رو توضیح بدین؟
> public interface IUnitOfWork
>     {
>         IDbSet<TEntity> Set<TEntity>() where TEntity : class;
>         DbEntityEntry<TEntity> Entry<TEntity>(TEntity entity) where TEntity : class;
>         int SaveChanges();
>     }


در این تایپیک من قصد دارم درمورد نحوه ایجاد پروژه توضیحاتی بدم. در نتیجه جهت درک بهتر مطلب توصیه میکنم اصل منبع رو مطالعه فرمائید.

----------


## ali_autumnal

> با نصب این مورد اعلام میکنه که EntityFramework به تمام پروژه ها اضافه شده است ولی تنها در رفرنس پروژه اصلی دیده می شود !!!!!!


به همان روشی که helpsos عزیز پاسخ دادند عمل کنید.

----------


## ali_autumnal

ادامه مطلب:

به DomainClasses رفته و کلاس های مورد نیاز پروژه خود را اضافه کنید
به لایه سرویس رفته و references زیر را اضافه کنید:

DataLayer
		DomainClasses
		Models


سرویس های مورد نیاز پروژه را به لایه سرویس اضافه کنید.

به لایه Models رفته و references  زیر را اضافه کنید:

DomainClasses
		Models

مدل های پروژه را در این لایه اضافه کنید.

تغییرات زیر را در Global.asax انجام دهید:

add using:

		using StructureMap;
		using Project.DataLayer;
		using Project.ServiceLayer;


add:	
	protected void Application_Start()
        {
		...
		initStructureMap();
	}


private static void initStructureMap()
        {
            ObjectFactory.Initialize(x =>
            {
                x.For<IUnitOfWork>().HttpContextScoped().Use(() => new DbContext());
		x.For<ISampleService>().Use<EfSampleService>();
		type all service in use project
		...
	    });
	    //Set current Controller factory as StructureMapControllerFactory
            ControllerBuilder.Current.SetControllerFactory(new StructureMapControllerFactory());		
	}

	protected void Application_EndRequest(object sender, EventArgs e)
        {
            ObjectFactory.ReleaseAndDisposeAllHttpScopedObject  s();
        }

کلاس زیر را در Global.asax تایپ کنید:

and add this class:

	public class StructureMapControllerFactory : DefaultControllerFactory
        {
        	protected override IController GetControllerInstance(RequestContext requestContext, Type controllerType)
        	{
            		return ObjectFactory.GetInstance(controllerType) as Controller;
        	}
        }

پس از اتمام کار  Build Solution کنید.

----------


## مهدی هادیان2

بسم الله الرحمن الرحیم
با سلام



> Enable-Migrations -ContextTypeName "your db name"


با اجرای این فرمان در پروژه DataLayer خطای زیر رو میده:


```
enable migrations parameter cannot be found that matches parameter name
```

با سپاس فراوان

----------


## helpsos

> بسم الله الرحمن الرحیم
> با سلام
> 
> با اجرای این فرمان در پروژه DataLayer خطای زیر رو میده:
> 
> 
> ```
> enable migrations parameter cannot be found that matches parameter name
> ```
> ...


سلام
ببینید هر جایی که_your db name  هست رو فکر کنم باید خودتون یه نام دلخواه بدون داشتن فاصله بدهید ببینید مشکل حل میشه!_

----------


## مهدی هادیان2

بسم الله الرحمن الرحیم



> سلام
> ببینید هر جایی که_your db name  هست رو فکر کنم باید خودتون یه نام دلخواه بدون داشتن فاصله بدهید ببینید مشکل حل میشه!_


با سلام
به جای  _your db name_ اسم بانکم رو گذاشتم:
PM> Enable-Migrations -MVCProjectContext "MVCProject"


نمی دونم مسئله از کجاست؟
با سپاس

[/CSHARP]

----------


## ali_autumnal

بجای این



> PM> Enable-Migrations -MVCProjectContext "MVCProject"


تایپ کنید

PM> Enable-Migrations -MVCProjectContext MVCProject

----------


## مهدی هادیان2

بسم الله الرحمن الرحیم



> بجای این
> 
> 
> تایپ کنید
> 
> PM> Enable-Migrations -MVCProjectContext MVCProject


با سلام
متاسفانه بازهم خطا میده:
Enable-Migrations : A parameter cannot be found that matches parameter name 'MVCProjectContext'.
At line:1 char:37
+ Enable-Migrations -MVCProjectContext <<<<  MVCProject
    + CategoryInfo          : InvalidArgument: (:) [Enable-Migrations], ParameterBindingException
    + FullyQualifiedErrorId : NamedParameterNotFound,Enable-Migrations
 
با سپاس

----------


## helpsos

> بسم الله الرحمن الرحیم
> 
> با سلام
> متاسفانه بازهم خطا میده:
> Enable-Migrations : A parameter cannot be found that matches parameter name 'MVCProjectContext'.
> At line:1 char:37
> + Enable-Migrations -MVCProjectContext <<<<  MVCProject
>     + CategoryInfo          : InvalidArgument: (:) [Enable-Migrations], ParameterBindingException
>     + FullyQualifiedErrorId : NamedParameterNotFound,Enable-Migrations
> ...


سلام
در کنسول پاورشل اول از قسمت default project پروژه Datalayer را انتخاب کن و بعد تایپ کن Enable-Migrations و enter کن

----------


## hp1361

با سلام

به نظرم اگه در مورد مفاهیم بحث بشه خیلی بهتره! این کدها که توی اون سایت هست. اما اگه بشه در مورد این مسائل سوال پرسید و پاسخی هم داده بشه خیلی کاربردی تر خواهد بود.

مثلا اینکه Unit of work چیه اساساً، و چرا ازش استفاده میکنیم! یا خیلی ساده تر چرا DataLayer
DomainClasses
ServiceLayer
Models    رو می سازیم؟!

----------


## hakim22

به نظر من وقتی Ninject هست دلیلی برای استفاه از StructureMap نیست. من برای اولین بار Ninject رو در 1 ساعت پیاده کردم و هیچوقت مشکلی باهاش نداشتم.

البته مسئله ربطی به ابزاری که از استفاده می کنیم نداره ، به اینکه چرا داریم ازش استفاده می کنیم ربط داره و کلا باید با مفهوم Unit Of Work و IoC آشنا بود تا این ابزار و اینکه چه پارامترهایی برای راه اندازی و تنظیم نیاز دارند برای ما قابل درک باشه.

----------


## hp1361

سلام

سوالی که برای من پیش آمده اینه که در کدام لایه باید به یک وب سرویس متصل شد و چطور باید خطاهای وب سرویس رو به کاربر نمایش داد؟

----------


## ali_autumnal

توضیحاتی در رابطه با لایه ها:

لایه ServiceLayer پروژه همه کارها رو واستون انجام خواهد داد. 

به ازای هر مدل اصلی DomainClasses یک سرویس بنویسید

در واقع بهتره برای هر کنترلر یک Service بنویسید در این صورت می تونید از سرویس هایی مختلف در یک سرویس Main که واسه کنترلر اختصاص داده اید بهره ببرید
مثلا از 10 سرویس اصلی در یک سرویسی که برای کنترلر نوشته اید استفاده کنید

لایه DataLayer فقط برای DbContext  هست

لایه Models کلا برای دیتاهایی که بین Sevice<=>Controller<=>View انتقال یا ارسال می شود کابرد دارد

فقط و فقط لایه Service پروژه با DomainClasses در ارتباط هست

در واقع Model ارسال شده از سمت View در Controller اعتبارسنجی خواهد شد در صورت اعتبار به لایه سرویس ارسال خواهد شد در لایه سرویس مدل دریافت شده به مدل اصلی(DomainClasses) تبدیل می شود و به سرویس مربوطه ارسال خواهد شد. سرویس اصلی مدل دریافت شده را در دیتابیس ذخیره خواهد کرد.

----------


## helpsos

با سلام
آیا نحوه کار با structuremap در MVC5 با مطالب گفته شده در بالا متفاوت است؟
آخه من StructureMap را نصب نمودم ولی چون از نسخه mvc5 استفاده نمودم چیزی برای آن نبود تا نصب کنم و بعد هم  در فایل global نیز کدهای شما را وارد نمودم ولی در این خط  x.For<IUnitOfWork>().HttpContextScoped().Use(() => new Sample07Context());

به HttpContextScoped و در این خط   ObjectFactory.ReleaseAndDisposeAllHttpScopedObject  s();

به ReleaseAndDisposeAllHttpScopedObjects گیر میده.

----------


## hp1361

سلام

StructureMap.Web رو به پروژه اضافه کنید.

بجای دستور 

            ObjectFactory.ReleaseAndDisposeAllHttpScopedObject  s();


بنویسید

            StructureMap.Web.Pipeline.HttpContextLifecycle.Dis  poseAndClearAll();


موفق باشیم

----------


## helpsos

سلام
من کدهای زیر را در global نوشتم ولی یک خطا میده که عکس اون در زیر است

کد ها         private static void InitStructureMap()        {
            ObjectFactory.Initialize(x =>
            {
                x.For<IUnitOfWork>().HttpContextScoped().Use(() => new PartakContext());
                x.For<IPhoneTypeService>().Use<EFPhoneTypeService>  ();


            });
            //Set current Controller factory as StructureMapControllerFactory
            ControllerBuilder.Current.SetControllerFactory(new StructureMapControllerFactory());
        }


        protected void Application_EndRequest(object sender, EventArgs e)
        {
            HttpContextLifecycle.DisposeAndClearAll();
        }
    public class StructureMapControllerFactory : DefaultControllerFactory
    {
        protected override IController GetControllerInstance(RequestContext requestContext, Type controllerType)
        {
            return ObjectFactory.GetInstance(controllerType) as Controller;
        }
    }




 ولی این خطا رو میده
Untitled.jpg

کسی می تونه کمکی بکنه؟

----------


## ali_autumnal

> سلام
>  من کدهای زیر را در global نوشتم ولی یک خطا میده که عکس اون در زیر است


در واقع شما از سرویسی استفاده کرده اید که در Initialize برای اون کلاس مربوطه رو تعریف نکرده اید.

----------


## helpsos

> در واقع شما از سرویسی استفاده کرده اید که در Initialize برای اون کلاس مربوطه رو تعریف نکرده اید.


سلام
من  InitStructureMap();  رو هم در Application_start نوشتم آیا باید کار دیگری انجام بدهم؟

----------


## ali_autumnal

شما کلا چندتا سرویس نوشته اید؟ 

آیا فقط سوریس به نام IPhoneTypeService نوشته اید؟

در صورت پاسخ منفی باید کلیه سرویس ها و کلاس های مورد نظر رو در زیر x.For<IPhoneTypeService>().Use<EFPhoneTypeService>  (); بنویسید.

----------


## helpsos

سلام
نه من همه سرویس هام رو نوشتم . ولی نمیدونم مشکلش چیه؟
البته این رو هم بگم که از MVC5 و
<package id="structuremap" version="3.0.0.108" targetFramework="net45" />  <package id="StructureMap.MVC4" version="2.6.4.3" targetFramework="net45" />
  <package id="structuremap.web" version="3.0.0.108" targetFramework="net45" />
رو هم نصب کردم.
آیا میتونه به StructureMap.MVC4 ربط داشته باشه؟

----------


## ali_autumnal

نه ربطی به MVC5 نداره. احتمال زیاد سرویس ها در ارتباط با کنترلرها مشکل دارند. شاید مقدار دهی نشده اند و...
اگه میتونی پروژه رو بزار چک کنم.

----------


## helpsos

زمانی که از این مدل لایه بندی استفاده کردیم حالا برای احراز هویت کاربران اگه از Asp.Net Identity پیش فرض vs2013 استفاده کنیم چطوری باید Context در لایه DataLayer رو بنویسیم و اینکه تکلیف لایه Service برای کار با Identity چی میشه?
من تست کردم ولی به نتیجه نرسیدم حالا کسی راه حلی نداره؟
با تشکر

----------


## ali_autumnal

احراز هویت میسپریم به دست ASP و خودش همه کارهای لازم رو تو AccountController انجام میده. (منظور از همه کارها: Login,LogOff,Register) برای UserProfile هم یه سرویس مینویسیم هر دیتایی که لازم داشتیم می خونیم آپدیت می کنیم و...

Context رو هم همون Context اصلی رو بهش معرفی کنه حله.

----------


## helpsos

سلام
خیلی ممنون ولی نگرفتم چی شد؟
میشه یکمی بیشتر توضیح بدین؟

----------


## aroshanzamir

سلام وقت شما بخیر

چرا زیر ObjectFactory خط سبز میکشه می نویسه منسوخ شده 
چکار باید بکنم

مرسی

----------


## ali_autumnal

واقعا شرمندم که چک نکرده بودم این چند وقت.

برای اینکه در نسخه جدید Structuremap حذف شده. Structuremap رو از رفرنس حذف کنید و این فایل رو بجای اون Add کنید

----------


## assari

تنظبمات Global.asax


public class MvcApplication : System.Web.HttpApplication
    {


        protected void Application_Start()
        {
            AreaRegistration.RegisterAllAreas();
            FilterConfig.RegisterGlobalFilters(GlobalFilters.F  ilters);
            RouteConfig.RegisterRoutes(RouteTable.Routes);
            BundleConfig.RegisterBundles(BundleTable.Bundles);


            initStructureMap();
        }




        private static void initStructureMap()
        {
            ObjectFactory.Container.GetInstance<IUnitOfWork>()  .ForceDatabaseInitialize();


            ControllerBuilder.Current.SetControllerFactory(new StructureMapControllerFactory());
        }




        protected void Application_EndRequest(object sender, EventArgs e)
        {
            HttpContextLifecycle.DisposeAndClearAll();
        }
    }




    public class StructureMapControllerFactory : DefaultControllerFactory
    {
        protected override IController GetControllerInstance(RequestContext requestContext, Type controllerType)
        {
            return ObjectFactory.Container.GetInstance(controllerType  ) as Controller;
        }
    }


}




نمونه ApplicationDataContext

public class DataContext : DbContext, IUnitOfWork    {
        public DataContext():base("DefaultConnection")
        {


        }
        public DbSet<Product> Product { get; set; }
        public DbSet<ProductGroup> ProductGroup { get; set; }


        public override int SaveChanges()
        {
            return base.SaveChanges();
        }




        public void RejectChanges()
        {
            foreach (var entry in this.ChangeTracker.Entries())
            {
                switch (entry.State)
                {
                    case EntityState.Modified:
                        entry.State = EntityState.Unchanged;
                        break;


                    case EntityState.Added:
                        entry.State = EntityState.Detached;
                        break;
                }
            }
        }


        public void ForceDatabaseInitialize()
        {
            Database.Initialize(true);
        }


        #region IUnitOfWork Members
        public new IDbSet<TEntity> Set<TEntity>() where TEntity : class
        {
            return base.Set<TEntity>();
        }
        #endregion
    }

نمونه ObjectFactoty.cs
 public static class ObjectFactory    {
        private static readonly Lazy<Container> ContainerBuilder =
            new Lazy<Container>(DefaultContainer, LazyThreadSafetyMode.ExecutionAndPublication);


        public static IContainer Container
        {
            get { return ContainerBuilder.Value; }
        }


        private static Container DefaultContainer()
        {
            var container = new Container(x =>
            {
                x.For<IUnitOfWork>().HybridHttpOrThreadLocalScoped  ().Use(() => new DataContext());
                //x.For<DataContext>().HybridHttpOrThreadLocalScoped  ().Use(context => (DataContext)context.GetInstance<IUnitOfWork>());
                //x.For<DbContext>().HybridHttpOrThreadLocalScoped()  .Use(context => (DataContext)context.GetInstance<IUnitOfWork>());


               
                x.For<IProductService>().Use<ProductService>();
                x.For<IProductGroupService>().Use<ProductGroupServ  ice>();


            });


            return container;
        }




    }

----------

