اشتراک‌ها
همه چیز درباره الگوی طراحی Singleton

Singleton design pattern is one of the simplest design patterns: it involves only one class throughout the application which is responsible to instantiate itself, to make sure it creates not more than one instance; in the same time it provides a global point of access to that instance. In this case the same instance can be used from everywhere, being impossible to invoke directly the constructor each time. 

همه چیز درباره الگوی طراحی Singleton
اشتراک‌ها
1.Visual Studio 2017 15.7 منتشر شد

These are the customer-reported issues addressed in 15.7.1:

  • This release includes a fix that reduces memory usage and GC pressure during solution load.

Microsoft Security Advisory for .NET Core Denial Of Service Vulnerability

CVE-2018-0765

Microsoft is releasing this security advisory to provide information about a vulnerability in .NET Core and .NET native version 2.0. This advisory also provides guidance on what developers can do to update their applications to remove this vulnerability.

Microsoft is aware of a denial of service vulnerability that exists when .NET Framework and .NET Core improperly process XML documents. An attacker who successfully exploited this vulnerability could cause a denial of service against a .NET Framework, .NET Core, or .NET native application.

The update addresses the vulnerability by correcting how .NET Framework, .NET Core, and .NET native applications handle XML document processing.

If your application is an ASP.NET Core application, developers are also advised to update to ASP.NET Core 2.0.8. 

1.Visual Studio 2017 15.7 منتشر شد
اشتراک‌ها
بررسی معادل‌های SQL در MongoDB

The following table presents the various SQL statements related to inserting records into tables and the corresponding MongoDB statements. 

بررسی معادل‌های SQL در MongoDB
اشتراک‌ها
معرفی QuickGrid برای Blazor

A simple but effective data grid for Blazor
QuickGrid is an experimental grid component built by the Blazor team .  

معرفی QuickGrid برای Blazor
مطالب
روش‌های مختلف انجام چند کار به صورت همزمان در C# .NET - قسمت دوم
در قسمت قبل دیدیم که انجام کارهای همزمان، با Objectهایی که به اصطلاح Thread Safe نیستند (مانند DbContext) خروجی چندان جالبی ندارد و برای مثال اگر در یک Service یک DbContext را Inject کنیم (مثلا با Constructor injection) و از آن در متدی استفاده کنیم که آن متد یا با TPL یا RX و ... به صورت چندتایی و همزمان اجرا شود، DbContext به مشکل می‌خورد؛ یعنی نمی‌توان یک وهله از DbContext را بین چند Thread همزمان پردازش موازی، به اشتراک گذاشت.
در کدهای فرضی مثال‌های قسمت قبل، متدی داشتیم با نام DoSomethingWithCustomer که مثلا همان متدی بود که قرار است همزمان اجرا شود. یکی از ساده‌ترین کارهایی که برای رفع این مشکل می‌توان انجام داد، نوشتن چنین کدی است:
public async Task DoSomethingWithCustomer(Customer customer)
{
    using var dbContext = new AppDbContext();

    // ...
}
در این حالت، اگر متد DoSomethingWithCustomer به صورت همزمان اجرا شود، به ازای هر بار اجرا، یک DbContext جدید ساخته میشود؛ پس از یک DbContext مشترک، به صورت همزمان توسط چندین Thread پردازش موازی، استفاده نخواهد شد. ولی مشکل اینجاست که از Dependency Injection برای ساختن DbContext استفاده نشده‌است و ما خودمان، هم new کرده‌ایم و هم Dispose!

این روش ابدا توصیه نمی‌شود؛ برای اینکه Dependency Injection این روزها مسائل خیلی زیادی را مدیریت می‌کند. مثلا DbContext در EF Core وقتی با Dependency Injection ساخته شود، Logging اش هم فعال می‌شود و یا مثلا اگر از متد زیر
services.AddDbContextPool<AppDbContext>();
برای Register کردن DbContext استفاده کرده باشیم، تعدادی DbContext ساخته می‌شود و در پروژه به صورت امن از همان چندتا استفاده می‌شود؛ بجای اینکه Objectها مدام ساخته و از بین برده شوند که این مهم، روی کارآیی تاثیر گذار است.
و یا مثلا برای HttpClient فقط در همین سایت نزدیک به یک دوجین مقاله توضیح داده‌اند که چرا new کردن و Dispose کردن HTTP Client مناسب نیست و بهتر است برای Register کردن HttpClient، از services.AddHttpClient استفاده و از IHttpClientFactory و سایر روشها برای Resolve کردن HttpClient استفاده کنیم که اینها نیز توسط Dependency Injection قابل استفاده هستند. از مسائلی مانند Polly و ... نیز می‌گذریم.
راه حل ایجاد یک Context جدید، تقریبا در تمامی کتابخانه‌های Dependency Injection دیده شده‌است و آن ساختن یک Child Scope است. در ادامه با Microsoft.Extensions.DependencyInjection یک پیاده سازی آن‌را خواهیم داشت؛ ولی مشابه این روش در سایر کتابخانه‌ها همچون Autofac نیز شدنی است.

برای شروع System.IServiceProvider را inject کنید. سپس کد قبلی را به این شکل بنویسید:
(نیاز به ;using Microsoft.Extensions.DependencyInjection در بالای فایل کد است)
public async Task DoSomethingWithCustomer(Customer customer)
{
    using var scope = _serviceProvider.CreateScope();
    var dbContext = scope.ServiceProvider.GetRequiredService<AppDbContext>();
    var httpClient = scope.ServiceProvider.GetRequiredService<IHttpClientFactory>().CreateClient();
    // ...
}
در این روش فقط نیاز به using نوشتن برای خط اول است؛ یعنی scope که Dispose شود، Objectهایی که به وسیله آن scope ساخته شده‌اند، آزاد خواهند شد. تمام آن چیزهایی را که قبلا با Constructor یا Property injection می‌توانستید بگیرید را اکنون می‌توانید با متد GetRequiredService بگیرید.
همچنین می‌توانید برای داشتن کدی بهتر، یک interface و class را ایجاد کنید و logic مربوطه را در آن سرویس قرار دهید و در آن سرویس با constructor یا property injection از DbContext و HttpClient و سایر سرویس‌ها استفاده کنید و در نهایت آن interface/class را رجیستر کنید و در DoSomethingWithCustomer به کمک child scope، یک object از آن سرویس بسازید و متدش را فراخوانی کنید. برای مثال اگر هدف ساختن Excel تاریخچه خریدهای مشتری است، داریم:
public interface IOrderHistoryService
{
    Task BuildCustomerHistory();
}

public class OrderHistoryService : IOrderHistoryService
{
    private AppDbContext _dbContext;

    public OrderHistoryService(AppDbContext dbContext)
    {
        _dbContext = dbContext;
    }

    public async Task BuildCustomerHistory()
    {
        // ...
    }
}
سپس اگر جایی لازم شد روی لیستی از مشتری‌ها این متد اجرا شود، از TPL استفاده می‌کنیم و متدی را فراخوانی می‌کنیم که در آن child scope می‌سازیم و scope.GetRequiredService را برای گرفتن IOrderHistoryService استفاده می‌کنیم. در این روش هم مشکلی در استفاده از Object هایی که thread safe نیستند (مانند DbContext) رخ نخواهد داد و از Dependency injection و مزیت‌های بی‌شمار آن نیز بهره برده‌ایم.
مطالب
استفاده از Interop.word برای جایگزین کردن مقادیر در تمامی فایل (Footer - Header - ... )
یکی از متداول‌ترین کارهایی که با اسناد می‌توان انجام داد، تهیه خروجی pdf از word و پر کردن یک فایل word با مقادیر ورودی است که سعی داریم یک نمونه از آن را اینجا بررسی کنیم. کد عمومی برای جایگزین کردن:
public void MsInteropReplace(Microsoft.Office.Interop.Word.Application doc, object findText, object replaceWithText)
        {
            object matchCase = false;
            object matchWholeWord = true;
            object matchWildCards = false;
            object matchSoundsLike = false;
            object matchAllWordForms = false;
            object forward = true;
            object format = false;
            object matchKashida = false;
            object matchDiacritics = false;
            object matchAlefHamza = false;
            object matchControl = false;
            object read_only = false;
            object visible = true;
            object replace = 2;
            object wrap = 1;
            //execute find and replace
            doc.Selection.Find.Execute(ref findText, ref matchCase, ref matchWholeWord,
                ref matchWildCards, ref matchSoundsLike, ref matchAllWordForms, ref forward, ref wrap, ref format, ref replaceWithText, ref replace,
                ref matchKashida, ref matchDiacritics, ref matchAlefHamza, ref matchControl);
        }
 و یا این مورد:
private static void MsInteropReplace2()
        {
            var doc = new Microsoft.Office.Interop.Word.Application().Documents.Open(@"D:\temp\te1.docx");

            doc.Content.Find.Execute("@levelOrder", false, true, false, false, false, true, 1, false, "12345", 2,
            false, false, false, false);
            object missing = System.Reflection.Missing.Value;
            doc.SaveAs(@"D:\temp\out.docx", ref missing, ref missing, ref missing, ref missing
                , ref missing, ref missing, ref missing, ref missing, ref missing, ref missing
                , ref missing, ref missing, ref missing, ref missing, ref missing);
}

که هر دو مورد را در stackoverflow میتوانید پیدا کنید. به شخصه از این مورد برای replace کردن مقادیر در یک فایل template.docx استفاده میکردم؛ ولی بعد از مدتی فهمیدم که Footer‌ها و Header را نمیتواند پردازش کند. کد زیر در تمامی قسمت‌هایی که در یک فایل word می‌توان متغیر تعریف کرد را گشته و عمل پر کردن مقادیر را بر روی فایل نمونه، انجام می‌دهد و شامل سه متد ذیل است:
private static void repAll()
        {
            object Missing = System.Reflection.Missing.Value;

            Application app = null;
            Microsoft.Office.Interop.Word.Document doc = null;
            try
            {
                app = new Microsoft.Office.Interop.Word.Application();

                doc = app.Documents.Open(@"D:\temp\te1.docx", Missing, Missing, Missing, Missing, Missing, Missing, Missing, Missing, Missing);

                FindReplaceAnywhere(app, "@levelOrder", "محرمانه");

                doc.SaveAs(@"D:\temp\out.docx", Missing, Missing, Missing, Missing, Missing, Missing, Missing, Missing, Missing);
            }
            finally
            {
                try
                {
                    if (doc != null) ((Microsoft.Office.Interop.Word._Document)doc).Close(true, Missing, Missing);
                }
                finally { }
                if (app != null) ((Microsoft.Office.Interop.Word._Application)app).Quit(true, Missing, Missing);
            }
        }

        private static void searchAndReplaceInStory(Microsoft.Office.Interop.Word.Range rngStory, string strSearch, string strReplace)
        {
            rngStory.Find.ClearFormatting();
            rngStory.Find.Replacement.ClearFormatting();
            rngStory.Find.Text = strSearch;
            rngStory.Find.Replacement.Text = strReplace;
            rngStory.Find.Wrap = WdFindWrap.wdFindContinue;
            object Missing = System.Reflection.Missing.Value;

            object arg1 = Missing; // Find Pattern
            object arg2 = Missing; //MatchCase
            object arg3 = Missing; //MatchWholeWord
            object arg4 = Missing; //MatchWildcards
            object arg5 = Missing; //MatchSoundsLike
            object arg6 = Missing; //MatchAllWordForms
            object arg7 = Missing; //Forward
            object arg8 = Missing; //Wrap
            object arg9 = Missing; //Format
            object arg10 = Missing; //ReplaceWith
            object arg11 = WdReplace.wdReplaceAll; //Replace
            object arg12 = Missing; //MatchKashida
            object arg13 = Missing; //MatchDiacritics
            object arg14 = Missing; //MatchAlefHamza
            object arg15 = Missing; //MatchControl

            rngStory.Find.Execute(ref arg1, ref arg2, ref arg3, ref arg4, ref arg5, ref arg6, ref arg7, ref arg8, ref arg9, ref arg10, ref arg11, ref arg12, ref arg13, ref arg14, ref arg15);
        }

        // Main routine to find text and replace it,
        //   var app = new Microsoft.Office.Interop.Word.Application();
        public static void FindReplaceAnywhere(Microsoft.Office.Interop.Word.Application app, string findText, string replaceText)
        {
            // http://forums.asp.net/p/1501791/3739871.aspx
            var doc = app.ActiveDocument;

            // Fix the skipped blank Header/Footer problem
            //    http://msdn.microsoft.com/en-us/library/aa211923(office.11).aspx
            Microsoft.Office.Interop.Word.WdStoryType lngJunk = doc.Sections[1].Headers[WdHeaderFooterIndex.wdHeaderFooterPrimary].Range.StoryType;

            // Iterate through all story types in the current document
            foreach (Microsoft.Office.Interop.Word.Range rngStory in doc.StoryRanges)
            {

                // Iterate through all linked stories
                var internalRangeStory = rngStory;

                do
                {
                    searchAndReplaceInStory(internalRangeStory, findText, replaceText);

                    try
                    {
                        //   6 , 7 , 8 , 9 , 10 , 11 -- http://msdn.microsoft.com/en-us/library/aa211923(office.11).aspx
                        switch (internalRangeStory.StoryType)
                        {
                            case Microsoft.Office.Interop.Word.WdStoryType.wdEvenPagesHeaderStory: // 6
                            case Microsoft.Office.Interop.Word.WdStoryType.wdPrimaryHeaderStory:   // 7
                            case Microsoft.Office.Interop.Word.WdStoryType.wdEvenPagesFooterStory: // 8
                            case Microsoft.Office.Interop.Word.WdStoryType.wdPrimaryFooterStory:   // 9
                            case Microsoft.Office.Interop.Word.WdStoryType.wdFirstPageHeaderStory: // 10
                            case Microsoft.Office.Interop.Word.WdStoryType.wdFirstPageFooterStory: // 11

                                if (internalRangeStory.ShapeRange.Count > 0)
                                {
                                    foreach (Microsoft.Office.Interop.Word.Shape oShp in internalRangeStory.ShapeRange)
                                    {
                                        if (oShp.TextFrame.HasText != 0)
                                        {
                                            searchAndReplaceInStory(oShp.TextFrame.TextRange, findText, replaceText);
                                        }
                                    }
                                }
                                break;

                            default:
                                break;
                        }
                    }
                    catch
                    {
                        // On Error Resume Next
                    }

                    // ON ERROR GOTO 0 -- http://www.harding.edu/fmccown/vbnet_csharp_comparison.html

                    // Get next linked story (if any)
                    internalRangeStory = internalRangeStory.NextStoryRange;
                } while (internalRangeStory != null); // http://www.harding.edu/fmccown/vbnet_csharp_comparison.html
            }

        }

برای تهیه pdf نیز می‌توانید به کد زیر مراجعه کنید:
public static void getFileDocxInPdf()
        {
            // Create a new Microsoft Word application object
            Microsoft.Office.Interop.Word.Application word = new Microsoft.Office.Interop.Word.Application();

            // C# doesn't have optional arguments so we'll need a dummy value
            object oMissing = System.Reflection.Missing.Value;

            // Get list of Word files in specified directory
            DirectoryInfo dirInfo = new DirectoryInfo(@"D:\temp");
            FileInfo[] wordFiles = dirInfo.GetFiles("*.docx");

            word.Visible = false;
            word.ScreenUpdating = false;

            foreach (FileInfo wordFile in wordFiles)
            {
                // Cast as Object for word Open method
                Object filename = (Object)wordFile.FullName;

                // Use the dummy value as a placeholder for optional arguments
                Microsoft.Office.Interop.Word.Document doc = word.Documents.Open(ref filename, ref oMissing,
                    ref oMissing, ref oMissing, ref oMissing, ref oMissing, ref oMissing,
                    ref oMissing, ref oMissing, ref oMissing, ref oMissing, ref oMissing,
                    ref oMissing, ref oMissing, ref oMissing, ref oMissing);
                doc.Activate();

                object outputFileName = wordFile.FullName.Replace(".docx", ".pdf");
                object fileFormat = WdSaveFormat.wdFormatPDF;

                // Save document into PDF Format
                doc.SaveAs(ref outputFileName,
                    ref fileFormat, ref oMissing, ref oMissing,
                    ref oMissing, ref oMissing, ref oMissing, ref oMissing,
                    ref oMissing, ref oMissing, ref oMissing, ref oMissing,
                    ref oMissing, ref oMissing, ref oMissing, ref oMissing);

                // Close the Word document, but leave the Word application open.
                // doc has to be cast to type _Document so that it will find the
                // correct Close method.                
                object saveChanges = WdSaveOptions.wdDoNotSaveChanges;
                ((_Document)doc).Close(ref saveChanges, ref oMissing, ref oMissing);
                doc = null;
            }

            // word has to be cast to type _Application so that it will find
            // the correct Quit method.
            ((_Application)word).Quit(ref oMissing, ref oMissing, ref oMissing);
            word = null;
        }
مطالب
شروع به کار با 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 تامین گردد.