سناریو تزریق وابستگی در MVC

سه شنبه 21 دی 1395

در این مقاله قصد داریم شما را با dependency injection در web api و کنترلر های MVC آشناکنیم که دراین مقاله برای شما توضیح داده ایم.

سناریو تزریق وابستگی در MVC

تزریق وابستگی یک نوع برنامه نویسی  و برنامه کاربردی design pattern است که برای  توسعه دهندگان استفاده میشود.

این الگو به عنوان یک نحوه برنامه نویسی پیش فرض از فریمورک های زیادی مثل angular می آید که ما باید به طور منظم از آن چارچوب استفاده کنیم.

نکته : در مهندسی نرم افزار ، تزریق وابستگی یک نرم افزار طراحی الگو(design pattern) است که به صورت کنترل معکوس برای حل مشکل وابستگی پیاده سازی میشود.وابستگی یک شی است که میتواند از آن استفاده کرد. یک تزریق پاس دادن وابستگی به  یک شی وابسته است که یک کلاینت میتواند از آن استفاده کند.

تزریق وابستگی اتصال سست بین ماژول های برنامه و provider های ما را افزایش میدهد. این یعنی کد های ماژول ما بدون نیاز به build دوباره کل برنامه یا بخشی از آن کار میکند.

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

ما میتوانیم برای پیاده سازی یک سناریو کد DI فریمورک خودمان را میسازیم.

اما در ASP.NET MVC  ما باید  رویکرد NET. را دنبال کنیم.این میتواند به وسیله پیاده سازی اینترفیس ساخته شده در کلاسها و نوشتن ان ها در بالای فایل Global.asax زمانی که برنامه اجرا میشود، انجام شود.

ما در اینجا فرض میکنیم که شما از تزریق وابستگی به عنوان الگو طراحی و به عنوان یک مفهوم برنامه نویسی از قبل آشنا هستید.در این مقاله ما مثال هایی از DI در area های مختلف در MVC را به شما نشان میدهیم.

استفاده از کد :

ساخت یک برنامه  empty Asp.Net Mvc .اضافه کردن یک homecontroller و index view و یک Web Api Controller.

کنترلر کلاس dependency injection :

کلاس های کنترلر در ASP.NET MVC به طور گسترده ای از بازگشت نمایش HTML در مرورگر با روش های GET و POST در سمت کلاینت استفاده میشوند.

در بسیاری از مواقع ما از یک کلاس کنترلر به چیزهایی مانند  data accessو  error logging و caching و... نیاز داریم.

در این جا چند راه برای انجام این کار است. استاندارد ترین کار این است که یک Object خارج از کلاس manager بسازیم که شامل منطق data access باشد و به طور مستقیم از ان استفاده کنیم.

اما مسائل مختلفی در این رویکرد وجود دارد.

کلاس manager شدیدا به کلاس کنترلر محدود خواهد شد ، به منظور پیاده سازی متفاوت کلاس manager ما باید در کد کنترلر تغییراتی را ایجاد کنیم و پس از build برنامه آن را دوباره توسعه میدهیم.

میتواند چندین کلاس وجود داشته باشد که کلاس کنترلر میتواند نیاز به انجام عملیات ضروری منطق کسب و کار باشد که این به معنی تغییرات بیشتر است.

کلاس data manager :

namespace DIScenarios.Managers
{
    public class DataManager
    {
        public Object GetData()
        {
            throw new NotImplementedException();
        }
    }
}

این میتواند به صورت پویا کلاس کنترلر وابستگی های آن را زمانی که شی خارج از آن تعریف شده مدیریت کند

برای این کار ، وابستگی های مورد نیاز خود را به سازنده (constructor) کلاس کنترلر تزریق میکنیم.

 در اینجا سوالی مطرح میشود که کجا ما میتوانیم تزریق وابستگی کنیم؟  

برای انجام این کار ما نیاز به پیاده سازی کلاس System.Web.Mvc.defaultControllerFactory  که از اینترفیس IControllerFactrory مشتق شده است داریم.

DefaultIControllerFactrory در کلاس IControllerFactrory گسترش (Extend) داده میشود.

در ControllerFactrory مانیاز به پیاده سازی متد ()GetControllerInstance nhvdl  داریم. برمبنای نوع کنترلر مورد تقاضا ما می توانیم شی کنترل مناسب تزریق وابستگی مورد نیاز به متد سازنده بازگشت دهیم.

namespace DIScenarios.Controllers
{
    public class HomeController : Controller
    {
        private DataManager _manager;
        public HomeController(DataManager manager) 
        {
            _manager = manager;
        }

        /// <summary>
        /// Returns the Index view.
        /// </summary>
        /// <returns></returns>
        public ActionResult Index()
        {
            //using the injected manager dependency to return the object model to the view.
            return View(_manager.GetData());
        }
    }
}

حال این سوال پیش می آید که چگونه ControllerFactrory ، وابستگی که بعدا در کنترلر وارد میشود را دریافت میکند؟

برای این ما دوباره از الگوی  constructor injection استفاده میکنیم.

ساخت یک سازنده ControllerFactrory با داشتن چند ارگومان از کلاس manager.

constructor (سازنده) پارامتر های دریافتی را در عضو های private کلاس manager قرار میدهد.

namespace DIScenarios
{
    public class ControllerFactory : DefaultControllerFactory
    {
        private DataManager _manager;
        public ControllerFactory(DataManager manager)
        {
            _manager = manager;
        }

        protected override IController GetControllerInstance(System.Web.Routing.RequestContext requestContext, Type controllerType)
        {
            return controllerType == typeof(HomeController) ? new HomeController(_manager) : null;
        }
    }
}

ControllerFactrory  میتواند در آخر در فایل Global.asax استفاده شود. ما مجبور خواهیم بود از کلاس ControllerBuilder برای ساخت پویا کنترلر ها و تزریق وابستگی های مورد نیاز در انها استفاده کنیم.

بعد از آن که تمام کار ها را کردید ما میتوانیم درآخر برنامه را اجرا کنیم ومیتوانیم شی dependency تزریق شده را در کلاس کنترلر ببینیم.

namespace DIScenarios
{
    public class MvcApplication : System.Web.HttpApplication
    {
        protected void Application_Start()
        {
            AreaRegistration.RegisterAllAreas();

            WebApiConfig.Register(GlobalConfiguration.Configuration);
            FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
            RouteConfig.RegisterRoutes(RouteTable.Routes);

            DataManager dataManager = new DataManager();

            #region Controller Dependency Injection

            ControllerBuilder.Current.SetControllerFactory(new ControllerFactory(dataManager));

            #endregion Controller Dependency Injection            
        }
    }
}

ControllerFactrory عموما در وسط شی Request و response کنترلر عمل میکنند. این به ما اجازه انجام Load موارد دیگر را قبل از هرچیزی به ما میدهد

در حقیقت مامیتوانیم شی کلاس کنترلر را ایجاد کنیم و آن را return دهیم.

همه ی این ها در رابطه با تزریق وابستگی در کنترلر ها بود. برای تزریق در محیط های دیگر ما نیاز به پیروی کردن از تمرینات NET. داریم که در انجا در رابطه با تزریق وابستگی در WebAPI صحبت شده است. تزریق وابستگی در کنترلر و webAPI  مانند یکدیگرند.

تزریق به کلاس کنترل webAPI :

برای پاس دادن وابستگی در کنترل Web API ما مجبور به پیروی کردن از الگوی طراحی قبل  هستیم و این برای انتقال وابستگی به تابع constructor انجام میشود.

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

namespace DIScenarios.ApiControllers
{
    public class ValuesController : ApiController
    {
        private DataManager _manager;
        public ValuesController(DataManager manager)
        {
            _manager = manager;
        }

        // GET api/<controller>
        public IEnumerable<string> Get()
        {
            return new string[] { "value1", "value2" };
        }

        // GET api/<controller>/5
        public string Get(int id)
        {
            return "value";
        }

        // POST api/<controller>
        public void Post([FromBody]string value)
        {
        }

        // PUT api/<controller>/5
        public void Put(int id, [FromBody]string value)
        {
        }

        // DELETE api/<controller>/5
        public void Delete(int id)
        {
        }
    }
}

ما از پیاده سازی اینترفیس IControllerFactory برای پاس دادن وابستگی به کنترلر زمانی که برنامه اجرا میشود استفاده میکنیم.

برای web api ما نیاز به پیاده سازی اینترفیس IDependencyResolver و متد ()GetService داریم.

ما همچنین باید سناریو بررسی نوع را در اینجا تکرار کنیم و این باید یک متد سازنده برای دریافت وابستگی ها داشته باشد.

namespace DIScenarios
{
    public class WebApiDependencyResolver : IDependencyResolver
    {
        private DataManager _manager;

        public WebApiDependencyResolver(DataManager manager)
        {
            _manager = manager;
        }

        public Object GetService(Type serviceType)
        {
            return serviceType == typeof(ValuesController) ? new ValuesController(_manager) : null;
        }

        public IEnumerable<Object> GetServices(Type serviceType)
        {
            return new List<Object>();
        }

        public IDependencyScope BeginScope()
        {
            return this;
        }

        public void Dispose()
        {

        }
    }
}

در بالا، کد اول نوع درخواست شی کنترلر را بررسی میکند و این یک تزریق وابستگی به سازنده manager را انجام میدهد. 

به مانند WebApiDependencyResolver ،  ControllerFactory نیز در وسط درخواست کنترل api کار میکند.

ما مجبور به پیاده سازی کلاس WebApiDependencyResolver  در فایل Global.asax هستیم.

برای این کار ما از شی GlobalConfiguration  استفاده میکنیم.ما همچنین میتوانیم خصوصیت   DependencyResolver از  GlobalConfiguration  با api  سفارشی خودمان برای برطرف کردن وابستگی تنظیم کنیم.که در کد زیر همین کارها را کرده است:

namespace DIScenarios
{
    public class MvcApplication : System.Web.HttpApplication
    {
        protected void Application_Start()
        {
            AreaRegistration.RegisterAllAreas();

            WebApiConfig.Register(GlobalConfiguration.Configuration);
            FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
            RouteConfig.RegisterRoutes(RouteTable.Routes);
            
            DataManager dataManager = new DataManager();
            #region Web Api Dependency Injection

            GlobalConfiguration.Configuration.DependencyResolver = new WebApiDependencyResolver(dataManager);

            #endregion Web Api Dependency Injection
        }
    }
}

هنگامی که برنامه اجرا میشود کنترل api از سمت کلاینت فراخوانی میشود و در وابستگی خودمان استفاده میشود برای ساختن شی کنترلر api و سپس میتوان وابستگی مورد نیاز را در شی کلاس کنترلر تزریق کرد.

کنترل تزریق با استفاده از تنظیمات پیکربندی :

در قسمت بالا دیدیم که چگونه ما میتوانیم وابستگی را در کنترلر mvc و کنترلر api تزریق کنیم. اما همانطور که در بالا دیدید پیاده سازی ها dynamic (پویا) نبود.

فرض کنید ما نیاز به بروزرسانی نوع پیاده سازی کلاس manager استفاده شده داریم، سپس  به تغییر کد در چند مکان برای پیاده سازی جدید نیاز پیدا خواهیم کرد. این یک شکست بزرگ برای ما اطلاق میشود به دلیل اینکه ما هر زمان کد را تغییر میدهیم و نیاز به rebuild  و توسعه دوباره برنامه داریم.

این مسئله میتواند به وسیله خواندن اطلاعاتی از پیکربندی برنامه حل شود.

این پیکربندی میتواند(configuration ) در یک فایلconfig خارجی یا داخل دیتا بیس باشد.در هرمورد اگر ما تصمیم به تغییر اطلاعات وابستگی بگیریم ، سپس برنامه داخلی نیاز به restart دارد اما این مخرب است پس برنامه رادوباره توسعه میدهیم.

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

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

در  configuration نام کلاس manager قابل تزریق در یک فایل text  ذخیره میشود.

<?xml version="1.0" encoding="utf-8"?>
<configuration>
  <appSettings>
    ...
    <add key="DataManagerClass" value="DIScenarios.Managers.DataManagerMySQL" />
  </appSettings>

  <system.web>    
    ...
  </system.web>

  <system.webServer>
    ...
  </system.webServer>
</configuration>

حال در controller و  ControllerFactory و Global.asax ما نیاز به استفاده از یک اینترفیس داریم که پیاده سازی را در همه کلاس های manager یکسان میکند. ما وابستگی را تزریق میکنیم در یک فرم اینترفیس بنابراین ما دیگر بابت نوع کلاس manager دغدغه ای نداریم.

برای این کار ما یک اینترفیس اضافه میکنیم که برای تزریق وابستگی استفاده میشود.

IDataManager

namespace DIScenarios.Interfaces
{
    public interface IDataManager
    {
        Object GetData();
    }
}

همچنین ما یک کلاس دیگر manager میسازیم که ما میتوانیم به هر کدام که نیاز داشتیم در فایل کانفیگ switch کنیم.

DatamanagerMySQL :

namespace DIScenarios.Managers
{
    public class DataManagerMySQL : IDataManager
    {
        public Object GetData()
        {
            throw new NotImplementedException();
        }
    }
}

تغییرات زیر باید در کلاس ها اعمال شود.

namespace DIScenarios.Managers
{
    public class DataManager : IDataManager
    {
        public Object GetData()
        {
            throw new NotImplementedException();
        }
    }
}

DataManager  استاندارد به سادگی از اینترفیس IDataManager  مشتق نمیشود.

HomeControllre :

public class HomeController : Controller
{
    private IDataManager _manager;
    public HomeController(IDataManager manager)
    {
        _manager = manager;
    }
}

ControllerFactory :

public class ControllerFactory : DefaultControllerFactory
{
    private IDataManager _manager;
    public ControllerFactory(IDataManager manager)
    {
        _manager = manager;
    }
}

ValueController :

namespace DIScenarios.ApiControllers
{
public class ValuesController : ApiController
{
    private IDataManager _manager;
    public ValuesController(IDataManager manager)
    {
        _manager = manager;
    }

}

WebApiDependencyResolver :

public class WebApiDependencyResolver : IDependencyResolver
{
    private IDataManager _manager;

    public WebApiDependencyResolver(IDataManager manager)
    {
        _manager = manager;
    }
}

همانطور که در کد بالا میبینید ما الان از اینترفیس IDataManager به جای کلاس DataManager  استفاده کرده ایم.

ما میتوانیم هر زمانی بین DataManager  و DataManagerMySql  در فایل config سوئیچ کنیم.

در فایل global.asax شی dependency  و به روش زیر ساخته میشود :

Global.asax.cs :

namespace DIScenarios
{
    public class MvcApplication : System.Web.HttpApplication
    {
        protected void Application_Start()
        {
            AreaRegistration.RegisterAllAreas();

            WebApiConfig.Register(GlobalConfiguration.Configuration);
            FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
            RouteConfig.RegisterRoutes(RouteTable.Routes);

            #region Figure out the type of dependency to use from the application configuration file

            String type = WebConfigurationManager.AppSettings["DataManagerClass"];
            IDataManager dataManager = Activator.CreateInstance(System.Reflection.Assembly.GetExecutingAssembly().GetName().Name, type).Unwrap() as IDataManager;

            #endregion Figure out the type of dependency to use from the application configuration file

            #region Controller Dependency Injection

            ControllerBuilder.Current.SetControllerFactory(new ControllerFactory(dataManager));

            #endregion Controller Dependency Injection

            #region Web Api Dependency Injection

            GlobalConfiguration.Configuration.DependencyResolver = new WebApiDependencyResolver(dataManager);

            #endregion Web Api Dependency Injection
        }
    }
}

در کد بالا Activator.CreateInstance() برای ساخت شی  کلاس manager استفاده شده است.

نوع شی ساخته شده هیچ تاثیری برروی تابع هایی که ما در اینترفیس به جای کلاس داخل کنترلر ها استفاده کرده ایم، ندارد.

آموزش asp.net mvc

برنامه نویسان

نویسنده 3355 مقاله در برنامه نویسان

کاربرانی که از نویسنده این مقاله تشکر کرده اند

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