مطالب
SQL Indexing

دلیل استفاده از ایندکس چیست؟

این سوالی است که ممکن است هر توسعه دهنده‌ای به آن در ابتدا پاسخ دهد: «جهت بالابردن سرعت و کارآیی!» حال اگر بپرسیم چگونه؟ توضیحات چندان دقیقی ارائه نمی‌شود.

ایندکس چیست؟

ایندکس شیءای از دیتابیس است می‌تواند برروی یک یا چند ستون ایجاد شود (تا 16 ستون). هنگامیکه ایندکسی ایجاد می‌گردد، ساختار داده‌ای (BTree) جهت بهینه سازی عملیات مقایسه نیز ایجاد می‌شود. اس کیو ال سرور بدون داشتن ایندکس، برای دریافت اطلاعات درخواستی مجبور است کل ردیف‌های جدول را جستجو نماید. این کار مانند این است که شما بدون اطلاع از شماره صفحه (محل) عنوان درخواستی، به دنبال آن در صفحات یک کتاب باشید. حال اگر به ایندکس (فهرست) کتاب مراجعه کنید به سرعت و حداقل اتلاف وقت می‌توانید محل یا شماره صفحه‌ی عنوان مورد نظر را، بدون جستجوی کلیه‌ی صفحات کتاب، پیدا کنید و به آن مراجعه کنید. ایندکس جدول نیز اجازه می‌دهد بدون جستجوی کلیه رکوردها، رکورد مورد نظر را دریافت نمایید.
مثال:
SELECT [computer_id],[nic_device_id],[nic_vendor_id],[nic_desc]
FROM [eXpress].[dbo].[nics]

فرض کنید در جدول بالا ایندکس گذاری انجام نشده باشد و قصد داشته باشید رکوردهایی را دریافت نمایید که در آن‌ها computer_id>5100 باشد. اس کیو ال سرور مجبور است کلیه رکوردهای جدول را جهت اعمال شرط بررسی نماید.

حال، برروی ستون computer_id ایندکسی را اعمال می‌نماییم و شرط computer_id>5100 را مجدد بررسی می‌کنیم. اس کیو ال از محل رکوردهای با مقادیر بزرگتر از 5100 اطلاع دارد و از جستجوی کل جدول اجتناب می‌کند. چرا؟ بدلیل اینکه براساس این ستون مرتب شده است.

انواع ایندکس

دو نوع ایندکس اصلی وجود دارد: ایندکس خوشه‌ای و ایندکس غیرخوشه‌ای

ایندکس خوشه‌ای

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

بوسیله ایندکس زیر تمام رکوردها براساس ستون computer_id مرتب شده ذخیره می‌گردند.
CREATE CLUSTERED INDEX [IX_CLUSTERED_COMPUTER_ID] 
ON [dbo].[nics] ([computer_id] ASC)

همانطور که اشاره شد، رکوردها بصورت مرتب شده براساس ستون انتخاب شده‌ی در جدول نگهداری می‌شوند. اما این مرتب سازی توسط ساختار BTree به‌شرح زیر انجام خواهد شد. جدول زیر را در نظر داشته باشید:

فرض کنید بعد ایندکس گذاری ستون StudId جدول فوق، درخت BTree زیر ایجاد می‌گردد که این ساختار به‌صورت جداگانه‌ای بر روی دیسک ذخیره می‌گردد. در این درخت، مقدار گره سمت چپ ریشه از آن کمتر و مقدار گره سمت راست ریشه از آن بیشتر است (البته عکس این فرض نیز امکان پذیر است).

و سپس کوئری‌های زیر را صادر می‌کنید:

Select * from student where studid = 103;
Select * from student where studid = 107;
بدون ایندکس گذاری، کوئری اول، بعد از 3 عمل مقایسه و کوئری دوم بعد از 8 عمل مقایسه پیدا می‌شود.
با ایندکس گذاری، کوئری اول، بعد از اولین عمل مقایسه و کوئری دوم بعد از 3 عمل مقایسه پیدا می‌شود؛ به‌شرح زیر:
  1. مقایسه 107 با 103 و انتقال به گره سمت راست
  2. مقایسه 107 با 106 و انتقال به گره سمت راست
  3. مقایسه 107 با 107 و یافتن مقدار درخواستی و بازگشت رکورد

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

ایندکس غیرخوشه‌ای

این نوع ایندکس، تغییری در نحوه‌ی ذخیره سازی رکوردها انجام نمی‌دهند. ولی شیء دیگری را که شامل ستون/هایی که قرار است ایندکس شوند و اشاره‌گر به رکورد (RID) هستند، در جدول ایجاد می‌کند. برای مثالی از ایندکس غیرخوشه‌ای در دنیای واقعی، می‌توان به فهرست انتهای کتاب‌ها که شامل عناوین و شماره صفحه‌ی مربوطه می‌باشد، اشاره کرد.

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

برای درک بهتر به سناریوی زیر دقت کنید:

کتابی داریم که شامل 1200 صفحه می‌باشد و فهرست مطالب آن شامل عناوین و شماره صفحات عناوین می‌باشد. حال اگر عنوان درخواستی A در صفحات 700، 300، 800 قرار داشته باشد، برای رفتن به این صفحات، مراحل زیر را برای هر یک طی خواهید کرد:

  1. یافتن شماره صفحه عنوان درخواستی با مراجعه به فهرست انتهای کتاب.
  2. در ادامه شما صفحه‌ای را در میانه‌ی کتاب، باز می‌کنید؛ چون عدد 700 مقداری از نصف 1200 برزگتر است.
  3. چند صفحه به جلو رفته، شماره صفحه 750 خواهد بود و هنوز به شرط مورد نظر نرسیده‌اید.
  4. پس مجددا چند صفحه به عقب بازگشته تا به صفحه‌ی مورد نظر، 700، برسید.

مراحل فوق برای یافتن عنوان A واقع شده‌ی در صفحه 700 انجام شد که همین مراحل نیز برای سایر صفحات می‌تواند انجام شود. در این مثال، صفحه فهرست مطالب کتاب،  به ایندکس غیرخوشه‌ای تعبیر خواهد شد.

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

A: 300, 700, 800
B: 100, 110
C: 600, 1200

ایندکس غیرخوشه ای توسط دستور زیر ایجاد می‌گردد:

CREATE NONCLUSTERED INDEX [IX_NONCLUSTERED_COMPUTER_ID] 
ON [dbo].[nics] ([computer_id] ASC)

نکته: یک جدول می‌تواند بیش از یک ایندکس غیرخوشهای و فقط و فقط یک ایندکس خوشهای داشته باشد.

ارتباط ایندکس خوشه‌ای و غیر خوشه‌ای

اشاره‌گر به رکورد (RID) در یک جدول دارای ایندکس خوشه‌ای، کلید ایندکس خوشه‌ای خواهد بود.

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

مزایا:
جدولی بدون ایندکس خوشه‌ای، heap table شناخته می‌شود. یک جدول هیپ، داده‌ی مرتب شده نخواهد داشت و به منظور دریافت اطلاعات، اس کیو ال سرور مجبور است کل ردیف‌های جدول را بررسی نماید که این عملیات Scan نامیده می‌شود. ولی در صورت استفاده از ایندکس خوشه‌ای برروی یک ستون، اس کیو ال، جهت یافتن اطلاعات مورد جستجو با توجه به BTree عملیات جستجو را از ریشه شروع، از شاخه‌ها عبور کرده و به برگ که همان اطلاعات درخواستی است می‌رسد که این عملیات Seek نامیده می‌شود. عملیات Seek طبیعتا از Scan سریعتر است.
ایندکس غیرخوشه‌ای، شامل مجموعه‌ای از ستون‌ها و ارجاعاتی به رکوردها یا کلید ایندکس خوشه‌ای است (ارتباط بین ایندکس غیر خوشه‌ای با خوشه‌ای). به‌دلیل حجم کم این نوع ایندکس، می‌تواند ردیف‌ها یا کلیدهای ایندکس خوشه ای بیشتری در صفحه‌ی ایندکس وجود داشته باشد که باعث افزایش کارآیی I/O می‌گردد.

معایب:
ایندکس گذاری، در طی عملیات درج، ویرایش و حذف، باعث سربار می‌گردد. هنگامیکه تغییری بر روی رکوردهای جدول انجام می‌شود، سبب تغییراتی نیز بر روی ایندکس‌ها می‌گردد (هنگامیکه برگه‌ای از کتابی جدا شود، نیاز است شماره صفحات و فهرست انتهایی کتاب مجددا به‌روز گردد) که این تغییرات باعث ایجاد هزینه می‌شود. بنابراین خیلی اهمیت دارد که هنگام طراحی ایندکس گذاری به سربارها نیز توجه کنید. به‌عنوان مثال هنگامیکه توسط دستور Delete رکوردی را از جدولی حذف نمایید، نیاز است رکوردها مجددا مرتب شوند که این یک سربار است.
ایندکس گذاری ، سرباری بنام bookmark lookup دارد. bookmark lookup فرآیندی جهت یافتن سایر ستون‌هایی است که در ایندکس گذاری وجود ندارند و براساس RID هستند.
مطالب
پیاده سازی CQRS توسط MediatR - قسمت چهارم
در این قسمت قصد داریم به بررسی Behavior‌ ها در فریمورک MediatR بپردازیم. کدهای این قسمت به‌روزرسانی و از این ریپازیتوری قابل دسترسی است.

با استفاده از Behavior‌ها امکان پیاده سازی AOP را براحتی خواهید داشت. Behavior‌ها، مانند Filter‌‌ ها در ASP.NET MVC هستند. همانطور که با استفاده از متدهای OnActionExecuting و OnActionExecuted میتوانستیم اعمالی را قبل و بعد از اجرای یک اکشن‌متد انجام دهیم، چنین قابلیتی را با Behavior‌ها در MediatR نیز خواهیم داشت. مزیت اینکار این است که شما میتوانید کدهای Cross-Cutting-Concern خود را یکبار نوشته و چندین بار بدون تکرار مجدد، از آن استفاده کنید.


Performance Counter Behavior

فرض کنید میخواهید زمان انجام کار یک متد را اندازه گیری کرده و در صورت طولانی بودن زمان انجام آن، لاگی را مبنی بر کند بودن بیش از حد مجاز این متد، ثبت کنید. شاید اولین راهی که برای انجام اینکار به ذهنتان بیاید این باشد که داخل تمام متدهایی که میخواهیم زمان انجام آنها را  محاسبه کنیم، چنین کدی را تکرار کنیم:
public class SomeClass
{
    private readonly ILogger _logger;

    public SomeClass(ILogger logger)
    {
        _logger = logger;
    }

    public void SomeMethod()
    {
        Stopwatch stopwatch = new Stopwatch();
        stopwatch.Start();

        // TODO: Do some work here

        stopwatch.Stop();

        if (stopwatch.ElapsedMilliseconds > TimeSpan.FromSeconds(5).Milliseconds)
        {
            // This method has taken a long time, So we log that to check it later.
            _logger.LogWarning($"SomeClass.SomeMethod has taken {stopwatch.ElapsedMilliseconds} to run completely !");
        }
    }
}
در این صورت تمام متدهایی که نیاز به محاسبه زمان پردازش را دارند، باید به کلاسشان Logger تزریق شود. Stopwatch باید ایجاد، Start و Stop شود و در نهایت، بررسی کنیم که آیا زمان انجام این متد از حداکثری که برای آن مشخص کرده‌ایم گذشته است یا خیر.

علاوه بر این تصور کنید روزی تصمیم بگیرید که حداکثر زمان برای Log کردن را از 5 ثانیه به 10 ثانیه تغییر دهید. در این صورت بدلیل اینکه در همه متدها این قطعه کد تکرار شده‌است، مجبور به تغییر تمام کدهای برنامه برای اصلاح این بخش خواهید شد. در اینجا اصل DRY نقض شده‌است.

 

برای حل این مشکل از Behavior‌ها استفاده میکنیم. برای پیاده سازی Behavior‌ها داخل MediatR، کافیست از interface ای بنام IPipelineBehavior ارث بری کنیم:
public class RequestPerformanceBehavior<TRequest, TResponse> :
    IPipelineBehavior<TRequest, TResponse>
{
    private readonly ILogger<RequestPerformanceBehavior<TRequest, TResponse>> _logger;

    public RequestPerformanceBehavior(ILogger<RequestPerformanceBehavior<TRequest, TResponse>> logger)
    {
        _logger = logger;
    }

    public async Task<TResponse> Handle(TRequest request, CancellationToken cancellationToken, RequestHandlerDelegate<TResponse> next)
    {
        Stopwatch stopwatch = new Stopwatch();
        stopwatch.Start();

        TResponse response = await next();

        stopwatch.Stop();

        if (stopwatch.ElapsedMilliseconds > TimeSpan.FromSeconds(5).Milliseconds)
        {
            // This method has taken a long time, So we log that to check it later.
            _logger.LogWarning($"{request} has taken {stopwatch.ElapsedMilliseconds} to run completely !");
        }

        return response;
    }
}
همانطور که میبینید منطق کد ما تغییری نکرده‌است. از IPipelineBehavior ارث بری کرده و متد Handle آن را پیاده سازی کرده‌ایم. همانند Middleware‌ ها در ASP.NET Core، در اینجا نیز یک RequestHandlerDelegate بنام next داریم که با اجرا و return آن، روند اجرای بقیه Command/Query‌ها ادامه پیدا خواهد کرد.

سپس باید Behavior‌های خود را از طریق DI به MediatR معرفی کنیم. داخل Startup.cs به این صورت RequestPerformanceBehavior خود را Register میکنیم:
services.AddScoped(typeof(IPipelineBehavior<,>), typeof(RequestPerformanceBehavior<,>));

در نهایت برای تست کارکرد این Behavior، در کوئری GetCustomerByIdQueryHandler خود 5 ثانیه Delay ایجاد میکنیم تا طول اجرای آن، از Maximum زمان مشخص شده بیشتر و Log انجام شود:
public class GetCustomerByIdQueryHandler : IRequestHandler<GetCustomerByIdQuery, CustomerDto>
{
    private readonly ApplicationDbContext _context;
    private readonly IMapper _mapper;

    public GetCustomerByIdQueryHandler(ApplicationDbContext context, IMapper mapper)
    {
        _context = context;
        _mapper = mapper;
    }

    public async Task<CustomerDto> Handle(GetCustomerByIdQuery request, CancellationToken cancellationToken)
    {
        Customer customer = await _context.Customers
            .FindAsync(request.CustomerId);

        if (customer == null)
        {
            throw new RestException(HttpStatusCode.NotFound, "Customer with given ID is not found.");
        }

        // For testing PerformanceBehavior
        await Task.Delay(5000, cancellationToken);

        return _mapper.Map<CustomerDto>(customer);
    }
}

پس از اجرای برنامه و فراخوانی GetCustomerById ، داخل Console این پیغام را خواهید دید:



Transaction Behavior


یکی دیگر از استفاده‌های Behavior‌ها میتواند پیاده سازی Transaction و Rollback باشد. فرض کنید میخواهیم افزودن یک مشتری به دیتابیس فقط زمانی صورت گیرد که تمام کارهای داخل Command با موفقیت و بدون رخ دادن Exception انجام شود. برای انجام اینکار میتوان یک TransactionBehavior نوشت تا بدنه Command‌ها را داخل یک TransactionScope قرار دهد و در صورت وقوع Exception ، عمل Rollback صورت گیرد :
public class TransactionBehavior<TRequest, TResponse> :
    IPipelineBehavior<TRequest, TResponse>
{
    public async Task<TResponse> Handle(TRequest request, CancellationToken cancellationToken, RequestHandlerDelegate<TResponse> next)
    {
        var transactionOptions = new TransactionOptions
        {
            IsolationLevel = IsolationLevel.ReadCommitted,
            Timeout = TransactionManager.MaximumTimeout
        };

        using (var transaction = new TransactionScope(TransactionScopeOption.Required, transactionOptions,
            TransactionScopeAsyncFlowOption.Enabled))
        {
            TResponse response = await next();

            transaction.Complete();

            return response;
        }
    }
}

سپس این Behavior را داخل DI Container خود Register میکنیم :
services.AddScoped(typeof(IPipelineBehavior<,>), typeof(TransactionBehavior<,>));

در نهایت متد Handle در CreateCustomerCommandHandler را که در قسمت‌های قبل ایجاد کردیم، تغییر داده و بعد از SaveChanges مربوط به Entity Framework، یک Exception را صادر میکنیم:
public class CreateCustomerCommandHandler : IRequestHandler<CreateCustomerCommand, CustomerDto>
{
    readonly ApplicationDbContext _context;
    readonly IMapper _mapper;
    readonly IMediator _mediator;

    public CreateCustomerCommandHandler(ApplicationDbContext context,
        IMapper mapper,
        IMediator mediator)
    {
        _context = context;
        _mapper = mapper;
        _mediator = mediator;
    }

    public async Task<CustomerDto> Handle(CreateCustomerCommand createCustomerCommand, CancellationToken cancellationToken)
    {
        Domain.Customer customer = _mapper.Map<Domain.Customer>(createCustomerCommand);

        await _context.Customers.AddAsync(customer, cancellationToken);
        await _context.SaveChangesAsync(cancellationToken);

        throw new Exception("======= MY CUSTOM EXCEPTION =======");

        // Raising Event ...
        await _mediator.Publish(new CustomerCreatedEvent(customer.FirstName, customer.LastName, customer.RegistrationDate), cancellationToken);

        return _mapper.Map<CustomerDto>(customer);
    }
}

اگر برنامه را اجرا کنید خواهید دید با اینکه Exception ما بعد از SaveChanges رخ داده است، اما بدلیل استفاده از Transaction Behavior ای که نوشتیم، عملیات Rollback صورت گرفته و داخل دیتابیس رکوردی ثبت نشده‌است.

* نکته : قبل از استفاده از Transaction‌ها حتما این مطالب ( 1 , 2 ) را مطالعه کنید.

MediatR دارای 2 اینترفیس IRequestPreProcessor و IRequestPostProcessor نیز هست که اگر نیاز داشته باشید یک عمل فقط قبل یا بعد از انجام یک Command/Query صورت گیرد، میتوانید از آنها استفاده کنید.

همچنین پیاده سازی‌های پیشفرضی از این 2 اینترفیس با نام‌های RequestPreProcessorBehavior و RequestPostProcessorBehavior داخل فریمورک، بطور پیشفرض وجود دارد که قبل و بعد از تمامی Handler‌ها اجرا خواهند شد.
مطالب
ارتقاء به ASP.NET Core 1.0 - قسمت 11 - بررسی بهبودهای Razor
زبان Razor نیز در ASP.NET Core به همراه بهبودها و اضافات قابل توجهی است که در این قسمت تعدادی از آن‌ها را مانند امکان ارث بری و تزریق وابستگی‌ها، بررسی خواهیم کرد.

نحوه‌ی سفارشی سازی کلاس پایه‌ی تمام Viewهای برنامه و معرفی inherits@

در نگارش‌های پیشین ASP.NET MVC، امکان تعویض کلاس پایه‌ی Viewها، در فایل web.config واقع در پوشه‌ی ریشه‌ی Views وجود داشت. با حذف این فایل و ساده سازی و محول کردن مسئولیت‌های آن به فایل جدید view imports، اینبار برای تعریف کلاس پایه‌ی viewها می‌توان به صورت ذیل عمل کرد:
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc.Razor;
 
namespace Core1RtmEmptyTest.StartupCustomizations
{
    public abstract class MyCustomBaseView<TModel> : RazorPage<TModel>
    {
        public bool IsAuthenticated()
        {
            return Context.User.Identity.IsAuthenticated;
        }
 
#pragma warning disable 1998
        public override async Task ExecuteAsync()
        {
        }
#pragma warning restore 1998
    }
}
به صورت پیش فرض تمام viewهای برنامه از کلاس <RazorPage<T ارث بری می‌کنند؛ که در اینجا T، نوع مدلی است که توسط model@ تنظیم می‌شود. اگر نیاز به سفارشی سازی این کلاس وجود داشت، برای مثال بجای اینکه هربار در viewها مقدار Context.User.Identity.IsAuthenticated را جهت نمایش قسمتی از صفحه، به کاربران اعتبارسنجی شده بررسی کنیم، می‌توان این قطعه کد را به یک کلاس پایه‌ی سفارشی منتقل و از آن در تمام Viewها استفاده کرد که نمونه‌ای از آن‌را در کدهای فوق مشاهده می‌کنید.
پس از تعریف این کلاس، برای ثبت و معرفی آن به فایل ViewImports.cshtml_ مراجعه کنید و این یک سطر را به ابتدای آن اضافه نمائید:
@inherits Core1RtmEmptyTest.StartupCustomizations.MyCustomBaseView<TModel>
inherits@ از تازه‌های razor بوده و جهت تعریف ارث بری‌ها کاربرد دارد. البته ممکن است در حین تعریف فوق، TModel را قرمز رنگ مشاهده کنید که مهم نیست و بیشتر مشکل ReSharper است و برنامه بدون مشکل اجرا می‌شود.
برای نمونه پس از سفارشی سازی صفحه‌ی پایه‌ی تمام Viewها، اکنون یک سطر ذیل را در هر view ایی می‌توان تعریف و استفاده کرد:
 Is Current User Authenticated? @IsAuthenticated()


معرفی functions@

دایرکتیو جدید functions@، بسیار شبیه است به دایرکتیو قدیمی و حذف شده‌ی helper@، که در نگارش‌های پیشین Razor معرفی شده بود:
@functions
 {
    public string Test()
    {
        return message;
    }
 
    readonly string message = "test";
}
ASP.NET Core در حین پردازش یک View، کدهای آن‌را تبدیل به یک کلاس می‌کند و در اینجا تمام کدهای داخل بدنه‌ی functions@ را نیز به صورت اعضای این کلاس تعریف خواهد کرد. به این ترتیب یک چنین فراخوانی‌هایی در View میسر می‌شوند:
 @Test()
<br />
@message


معرفی inject@

توسط دایرکتیو جدید inject@، یک خاصیت عمومی به ASP.NET Core اعلام می‌شود و سپس مقدار دهی آن بر اساس تنظیمات IoC Container برنامه به صورت خودکار صورت خواهد گرفت. برای مثال زمانیکه می‌خواهیم به سرویس توکار HostingEnvironment در یک  View دسترسی پیدا کنیم، می‌توان در ابتدای آن نوشت:
 @inject Microsoft.AspNetCore.Hosting.IHostingEnvironment Host;
در این حالت کد فوق از دیدگاه ASP.NET Core به صورت ذیل تفسیر می‌شود:
 [Microsoft.AspNetCore.Mvc.Razor.Internal.RazorInjectAttribute]
public Microsoft.AspNetCore.Hosting.IHostingEnvironment Host { get; private set; }
این خاصیت عمومی نیز با توجه به از پیش ثبت شده بودن سرویس IHostingEnvironment  و مشخص شدن توسط RazorInjectAttribute، توسط IoC Container آن شناسایی شده و تامین می‌شود.
اکنون برای استفاده‌ی از آن خواهیم داشت:
 <div>
 Running in @Host.EnvironmentName
</div>
البته استفاده‌ی از inject@ شاید به نوعی سؤ استفاده‌ی از الگوی MVC به شما رود؛ از این جهت که اطلاعات مورد نیاز یک View، باید از طریق کنترلر آن تامین شود و خود View نباید به صورت مستقیم درخواست تامین آ‌ن‌ها را بدهد. اما باید دقت داشت که در نهایت View نیاز دارد تا کدها را اجرا کرده و خروجی را تولید کند و برای این منظور، در پشت صحنه سرویس‌های زیادی مانند IUrlHelper ، IViewComponentHelper ، IHtmlHelper و غیره به همین ترتیب در اختیار آن قرار می‌گیرند. به علاوه استفاده‌ی از تزریق وابستگی‌ها بهتر است از روش ارث بری صفحات پایه، از این جهت که انتخاب composition همواره مقدم است بر inheritance و سبب انعطاف پذیری بیشتری نسبت به قبل می‌گردد. داشتن یک صفحه‌ی پایه که بتواند تمام نیازهای انواع و اقسام Viewها را تامین کند، دور از انتظار و گاهی از اوقات، سبب سنگینی بیش از حد پردازش تمام Viewها خواهد شد. اما تزریق سرویس‌هایی اینچنینی جهت تامین نیازهای اولیه و تکراری یک یا چند View خاص، کل برنامه را سنگین نکرده و همچنین انعطاف پذیری بیشتری را در جهت تامین آن‌ها فراهم می‌کند.
به علاوه باید دقت داشت اگر تعریف inject@ فوق را در فایل view import قرار دهیم، این سرویس در اختیار تمام Viewهای برنامه قرار خواهد گرفت و دیگر نیازی به قرار دادن آن در یک کلاس پایه‌ی سفارشی نیست.
یکی از مفیدترین استفاده‌های از قابلیت تزریق سرویس‌ها در Viewها می‌تواند دسترسی به سرویس تامین تنظیمات برنامه باشد (که در مورد نحوه‌ی تامین آن در مطلب «ارتقاء به ASP.NET Core 1.0 - قسمت 7 - کار با فایل‌های config» بیشتر بحث شد):
 @inject IOptions<SmtpConfig> Settings;
نظرات مطالب
اعتبارسنجی در فرم‌های ASP.NET MVC با Remote Validation
اگر به فایل jquery.validate.js مراجعه کنید، در قسمت remote آن، متد startRequest پیش از شروع عملیات Ajax و متد stopRequest پس از پایان کار فراخوانی می‌شوند.
prototype: {
startRequest: function( element ) {
  //...
},
stopRequest: function( element, valid ) {
  //...
},
 این دو متد را باید برای نمایش loading بازنویسی کرد. برای مثال:
   
var originalStartRequest = $.validator.prototype.startRequest;
$.validator.prototype.startRequest = function (element) {
    // یافتن عنصر در حال بررسی
    var container = $('form').find("[data-valmsg-for='" + element.name + "']");
// افزودن کلاس نمایش منتظر بمانید 
    container.addClass('loading');      
    
// فراخوانی متد اصلی برای انجام کارهای درونی افزونه
    originalStartRequest.apply(this, arguments);
};

var originalStopRequest = $.validator.prototype.stopRequest;
$.validator.prototype.stopRequest = function (element) {
    // یافتن عنصر در حال بررسی
    var container = $('form').find("[data-valmsg-for='" + element.name + "']");
// حذف  کلاس نمایش منتظر بمانید 
    container.removeClass('loading');

// فراخوانی متد اصلی برای انجام کارهای درونی افزونه
    originalStopRequest.apply(this, arguments);
};
در اینجا loading به span مخفی data-valmsg-for اضافه می‌شود.
<span class="field-validation-valid" data-valmsg-replace="true" data-valmsg-for="Url"></span>

نمونه‌ی این بازنویسی در مطلب « اعتبار سنجی سمت کاربر wysiwyg-editor‌ها در ASP.NET MVC  » هم انجام شده‌است.
مطالب
چگونه از SVN جهت به روز رسانی یک سایت استفاده کنیم؟

این سناریو رو در نظر بگیرید:
وب سرور ما در همان محلی قرار دارد که SVN Server نصب شده است.
می‌خواهیم به ازای هربار Commit تیم به مخزن SVN ما، سایت ارائه شده توسط وب سرور نیز به صورت خودکار به روز شود.
چه باید کرد؟!

احتمالا خیلی‌ها تصور می‌کنند که امکان پذیر نیست؛ چون مخزن SVN موجود در سرور، ساختار خودش را دارد و همانند فایل‌های یک پروژه معمولی نگهداری نمی‌شود.
برای انجام اینکار چندین روش موجود است، که تمام آن‌ها به مفهوم hooks در SVN گره خورده است. هرچند hook به معنای قلاب است، اما در اینجا معنای تریگر را دارد. شبیه به تریگرهای SQL Server : پیش یا پس از انجام کار یا رخداد مشخصی، فلان کار را انجام بده. (برای اطلاعات بیشتر می‌توانید به فصل hooks در این کتابچه مراجعه کنید: (+))
در میان این قلاب‌های موجود، می‌توان از قلاب post-commit جهت به روز رسانی یک سایت پس از هر هماهنگ سازی با مخزن SVN استفاده کرد. پیشنهاد من به تمام کسانی که می‌خواهند کار با SVN را شروع کنند استفاده از برنامه رایگان Visual SVN Server است. این برنامه سازگاری فوق العاده‌ای با محیط ویندوز دارد (از لحاظ تعریف سطح دسترسی‌ها). همچنین تعریف hooks را هم به شدت ساده کرده است. فقط کافی است روی یک مخزن کد تعریف شده در Visual SVN Server کلیک راست کرده و در برگه‌ی باز شده، تنظیمات سطوح دسترسی یا تعاریف Hooks را اضافه نمود (در اینجا اعمال سطوح دسترسی روی پوشه‌ها یا روی فایل‌ها نیز به همان شکل با کلیک راست و کم و زیاد کردن کاربران میسر است؛ همانند دادن دسترسی بر اساس امکانات NTFS و اکتیودایرکتوری).

بنابراین به صورت خلاصه:
  • فرض بر این است که مخزن کد SVN ایی را بر روی سرور راه اندازی کرده‌اید. همچنین پوشه‌ای را که می‌خواهید ریشه سایت باشد، مثلا در مسیر دلخواه C:\path\www قرار دارد.
  • برای شروع کار، check out باید صورت گیرد. یا می‌توان از TortoiseSVN استفاده کرد یا چون مخزن کد در همان سرور است، دستور زیر نیز کار می‌کند:
svn checkout file:///c:/svn/MyRepository/trunk C:\path\www
  • سپس یک فایل bat باید درست کنید با محتوای زیر:
svn update file:///c:/svn/MyRepository/trunk C:\path\www

این فایل bat باید در همان قسمت تعریف post-commit hook استفاده شود.
به این معنا که پس از هر commit ، لطفا مسیر C:\path\www را بر اساس آخرین به روز رسانی‌های مخزن کد به صورت خودکار به روز کن. در این حالت اگر فایلی حذف شده باشد، به صورت خودکار از ریشه سایت شما حذف می‌شود و اگر فایل یا فایل‌هایی تغییر کرده باشند نیز سریعا به روز رسانی آن‌ها انجام خواهد شد.
در روش svn update ، پوشه‌های مخفی svn نیز در ریشه سایت حضور خواهند داشت. وجود آن‌ها هم الزامی است زیرا update بر همین اساس کار می‌کند.
  • اگر می‌خواهید این پوشه‌های مخفی وجود نداشته باشند از دستور svn export استفاده کنید. فقط دقت کنید که در این حالت اگر فایلی از مخزن کد حذف شده باشد، باز هم در ریشه سایت وجود خواهد داشت. راه حلی هم که توصیه شده، این است که در همان bat فایلی که درست می‌کنید ابتدا دستور حذف محتویات پوشه ریشه را صادر کنید و بعد svn export . البته بدیهی است این روش نسبت به svn update کندتر است و svn update به شدت بهینه و سریع می‌باشد.
  • یا راه دیگر بجای حذف کردن پوشه موجود و بعد export به آن، استفاده از برنامه‌هایی مانند Robocopy است که می‌توانند عملیات همگام سازی را هم انجام دهند. در این حالت محتوای فایل bat شما شبیه به دستورات زیر خواهد شد:
svn checkout file:///c:/svn/MyRepository/trunk C:\temp\Site1 >> output.log
robocopy C:\temp\Site1 C:\path\www *.* /S /XF *.cs *.tmp *.sln *.csproj *.webinfo /XD .svn _svn /PURGE >> output.log

به این معنا که پس از هر commit‌ به مخزن کد (با توجه به تعریف قلاب ذکر شده)، ابتدا یک svn checkout در یک پوشه موقتی (خارج از ریشه اصلی سایت) انجام گردیده و سپس برنامه robocopy یا موارد مشابه آن وارد عمل شده و تغییرات را با ریشه اصلی هماهنگ می‌کنند (در اینجا می‌توان مشخص کرد چه فایل‌هایی با پسوندهای مشخص، با ریشه سایت هماهنگ نشوند).

در کل همان روش svn update به نظر سریعتر و مقرون به صرفه‌تر است. اگر از IIS استفاده می‌کنید، به صورت پیش فرض کسی نمی‌تواند محتوای پوشه‌ای را با وارد کردن آدرس آن در مرورگر بررسی کند، همچنین IIS فایل‌هایی را که نمی‌شناسد (پسوند از پیش تعریف شده‌ای در بانک اطلاعاتی آن ندارند)، سرو نمی‌کند و در صورت درخواست آن‌ها، خطای 404 یا "پیدا نشد" به کاربر نهایی ارائه خواهد شد.

مطالب
یک سرویس (میکروسرویس) چیست؟ و چگونه آن را مستند کنیم؟ (قسمت اول)
معماری میکروسرویس (یا به اختصار: میکروسرویس) یک سبک معماری نرم افزار می‌باشد که در آن یک نرم افزار، به مجموعه‌ای از سرویس‌ها خرد می‌شود؛ به نحوی که هر سرویس مسئولیت انجام بخشی از منطق کسب و کار را به عهده داشته باشد.
این تقسیم بندی مزایای متعددی را به همراه دارد که نهایتا پیاده سازی و توسعه راحت‌تر نرم افزار‌های بزرگ و پیچیده را ممکن می‌نماید. از جمله مزایای این معماری می‌توان به راحت‌تر شدن مباحث continuous delivery/deployment، مقیاس پذیری بهتر، تحمل خطا، مهاجرت به (و یا استفاده از) تکنولوژی‌های جدید در بخش‌های مختلف نرم افزار و ... اشاره نمود.

مهم‌ترین بخش و تصمیمات شما به عنوان یک معمار نرم افزار، هنگام طراحی با استفاده از این معماری، شناسایی بخش‌های مختلف کسب و کار، جدا سازی و مرزبندی نمودن آنها و نهایتا طراحی سرویس‌ها و تعیین نحوه همکاری آنها با یکدیگر می‌باشد. لذا در هنگام استفاده از معماری میکروسرویس، مرکز توجهات باید کسب و کار باشد و نه مسائل تکنیکال و موضوعاتی مانند Docker, Kubernetes , Serverless و ... . (DDD می‌تواند به شما جهت مرزبندی بخش‌های مختلف کسب و کار و شناسایی سرویس‌ها کمک نماید)

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

مشخصات یک سرویس
هر سرویس در معماری میکروسرویس دارای چندین ویژگی اصلی به شرح زیر می‌باشد:
- Loosely coupled with other services - باید به طور مستقل از سایر سرویس‌ها عمل کند. به این معنا که تغییر و توسعه سایر سرویس‌ها موجب اختلالی در عملکرد این سرویس نگردد و برعکس، تغییر و توسعه این سرویس نباید عملکرد سایر سرویس‌ها را مختل نماید.
- Independently deployable - تیم توسعه دهنده سرویس قادر باشد تا بدون نیاز به هماهنگی با سایر تیم‌ها، خدمات خود (شامل ویژگی‌های جدید و تغییرات) را مستقر (Deploy) نماید.
- Capable of being developed by a small team – سرویس، امکان توسعه توسط یک تیم کوچک را داشته باشد. این مورد به جهت جلوگیری از سربار زیاد ناشی از هماهنگی در تیم‌های بزرگ، ضرورت دارد.
- Highly maintainable and testable – سرویس بسیار قابل نگهداری و قابل آزمایش باشد؛ امکان توسعه، تست و استقرار سریع را داشته باشد.

ساختار یک سرویس
حال که با ویژگی‌ها و مشخصات اصلی یک سرویس آشنا شدیم، در دیاگرام زیر، ساختار درونی یک سرویس را که از معماری هگزاگون (hexagonal architecture) استفاده می‌نماید، بررسی میکنیم. در این معماری، هسته سرویس، منطق کسب کار (Business logic) می‌باشد که توسط چندین آداپتور (جهت ارتباط با سایر سرویس‌ها) احاطه شده است.

بیایید با دقت به هر یک از بخش‌های یک سرویس (با توجه به دیاگرام فوق) نگاه کنیم

هر سرویس  احتمالا دارای یک یا چندین API می‌باشد
از دید مصرف کنندگان یک سرویس (Consumers)، تنها مورد با اهمیت یک سرویس، APIهای آن سرویس می‌باشد. APIهای یک سرویس نیز (با توجه به تصویر فوق) شامل عملیات یا Operations و وقایع منتشر شده یا Published events می‌باشند. که در ادامه این انواع را بررسی میکنیم.

- عملیات (Operations)
به صورت کلی و همانطور که در دیاگرام فوق قابل مشاهده می‌باشد، عملیات به دو نوع دستورات (Commands) و جستارها (Queries) تقسیم می‌شوند. دستورات نوعی از عملیات می‌باشند که موجب تغییر داده‌ها می‌شود؛ اما در مقابل جستارها، عملیاتی در جهت واکشی داده‌ها می‌باشند. برای مثال یک سرویس  ثبت سفارش (OrderService) را در نظر بگیرید. عملیاتی مانند ثبت سفارش ()CreateOrder، انصراف از سفارش ثبت شده  ()CancelOrder و ... عملیاتی از نوع دستورات هستند و عملیاتی مانند یافتن یک سفارش خاص ()FindOrder که هیچ دیتایی را تغییر نمیدهد، از نوع جستارها می‌باشند.
عملیات ارائه شده توسط یک سرویس میتواند از ترکیبی از پروتکل‌های همزمان (Synchronous protocols) مانند REST یا gRPC و پروتکل‌های غیر همزمان (Asynchronous protocols) مانند messaging باشند.
پروتکل‌های همزمان، به ویژه REST، بیشتر در مواردی که قصد ارائه API به کلاینت‌های خارجی (External clients) را داریم، مانند موبایل اپلیکیشن‌ها و یا نرم افزارهای تک صفحه‌ای (SPA) کاربرد دارند.
از پروتکل‌های غیر همزمان مانند messaging نیز بیشتر در مواردی که میخواهیم الگوی ساگا (SAGA) را پیاده سازی نماییم و به روز نگه داشتن داده‌ها را بین سرویس‌های مختلف حفظ کنیم، نیاز به استفاده داریم. برای مثال در همان سیستم ثبت سفارش، عملیات ()CreateOrder به صورت Rest و با متد Post در Endpoint ای مانند /Order پیاده سازی می‌شود و پس از فراخوانی، یک عملیات غیرهمزمان مانند CreateOrderSaga را نیز به صورت messaging آغاز میکند.

- وقایع (Events)
سرویس‌ها، اغلب وقایعی (Event) را نیز منتشر میکنند. منظور از وقایع یا events معمولا همان مفهوم domain event درDDD می‌باشد که در همان ادبیات DDD وقایع توسط aggregate‌ها در زمان هایی مانند ایجاد، ویرایش، حذف و یا سایر مفاهیم موجود در منطق کسب و کار منتشر می‌شوند. سرویس نیز معمولا این وقایع را روی یک کانال ارتباطی (message channel) که توسط یک message broker (مانند RabbitMQ, Apache Kafka, ActiveMQ و ...) پیاده سازی شده است، منتشر میکند. و علاقمندان به دریافت این وقایع می‌توانند وقایع را پس از انتشار، بر روی کانال ارتباطی دریافت نمایند.


منطق کسب و کار (Business Logic)
منطق کسب و کار، قلب هر سرویس و دلیل وجود آن سرویس می‌باشد که API هایی را در قالب عملیات (Opertaions) پیاده سازی و همچنین مواردی را در قالب وقایع (Events) منتشر می‌نماید. همچنین منطق کسب و کار می‌تواند بنا بر نیاز خود، عملیات مربوط به سایر سرویس‌ها را فراخوانی و یا در کانال‌های ارتباطی (channels) مربوط به وقایع آنها، مشترک (Subscribes) شود و نهایتا داده‌ها را در دیتابیس خود نگهداری نماید.

نحوه همکاری سرویس‌ها با یکدیگر (Services Collaborations)
با توجه به مفاهیم فوق، زمانی که صحبت از همکاری (collaborate) بین سرویس‌ها می‌شود، معمولا منظور، ارتباط آنها از طریق APIهای یکدیگر (شامل عملیات و وقایع که پیش‌تر توضیح داده شد) می‌باشد (به جای خواندن و نوشتن مستقیم در دیتابیس‌های مربوط به یکدیگر می‌باشد).
برای مثال یک سرویس ممکن است عملیات مربوط به ایجاد سفارش ()CreateOrder را از سرویس ثبت سفارش (OrderService) فراخوانی نماید و یا برعکس خود سرویس ثبت سفارش (OrderService) ممکن است بر حسب نیاز منطق کسب و کار خود، عملیات ارائه شده توسط سرویس انبار را فراخوانی نماید.
همچنین یک سرویس جهت همکاری با دیگر سرویس‌ها میتواند در وقایع منتشر شده (Published events) توسط آنها مشترک (Subscribes) شود. برای مثال سرویس ثبت سفارش احتمالا در وقایع منتشر شده از سوی سرویس رستوران مشترک می‌شود.

دیتابیس اختصاصی
معمولا هر سرویس دارای یک یا چند دیتابیس می‌باشد که دیتای اختصاصی مربوط به منطق کسب و کار خود و در مواردی بخشی از دیتای مربوط به سایر سرویس‌ها را در آن‌(ها) نگهداری میکند. برای مثال اطلاعات سفارش‌ها را هم سرویس ثبت سفارش و هم سرویس رستوران، هر دو نگهداری میکنند و عملا این دیتا ابتدا در سرویس رستوران و سپس در سرویس ثبت سفارش، مجددا نگهداری می‌شود و به نوعی دیتای فوق Replicate شده و تکراری می‌باشد. اما به جهت اطمینان از کاهش وابستگی (loose coupling) این تکرار داده‌ها انجام می‌شود. در مجموع استفاده از یک دیتابیس مشترک (منظور table مشترک می‌باشد) بین سرویس‌ها ایده‌ی بدی می‌باشد و سرویس‌ها باید از طریق API‌های یکدیگر باهم همکاری نمایند.

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

منابع
برگرفته شده از مقاله آقای ریچاردسون (whats-a-service
مطالب
آشنایی با CLR: قسمت دهم
در سلسله مقالات قبلی ما فصل اول از بخش اول را به پایان بردیم و مبحث آشنایی با CLR و نحوه‌ی اجرای برنامه را یاد گرفتیم. در این سلسله مقالات که مربوط به فصل دوم از بخش اول است، در مورد نحوه‌ی ساخت و توزیع برنامه صحبت می‌کنیم.

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

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

سومین مورد امنیت هست. موقعی که کاربر برنامه‌ای را نصب می‌کند انواع فایل‌ها از شرکت و تولید کننده‌های مختلف روی سیستم نصب می‌شوند. گاهی اوقات برنامه‌ها بعضی از فایل هایشان را از روی اینترنت دریافت می‌کنند و کاربر اصلا متوجه موضوع نمی‌شود و این فایل‌ها می‌توانند هر کاری از حذف فایل از روی سیستم گرفته تا ارسال ایمیل را انجام بدهند که این موارد باعث وحشت کاربرها از نصب یک برنامه‌ی جدید می‌شود که این مورد را با قرار دادن یک سیستم امنیت داخلی با اجازه و عدم اجازه کاربر می‌شود تا حدی رفع کرد.

دات نت فریمورک هم این معضل را به طور عادی در زمینه‌ی DLL hellدارد که در فصل آتی حل آن بررسی خواهد شد. ولی بر خلاف COM، نوع‌های موجود در دات نت نیازی به ذخیره تنظیمات در ریجستری ندارند؛ ولی متاسفانه لینک‌های میانبر هنوز وجود دارند. در زمینه امنیت دات نت شامل یک مدل امنیتی به نام Code Access security می‌باشد؛ از آنجا که امنیت ویندوز بر اساس هویت کاربر تامین می‌شود. code access security به برنامه‌های میزبان مثل sql server اجازه می‌دهد که مجوز مربوطه را خودشان بدهند تا بدین صورت بر اعمال کامپوننت‌های بار شده نظارت داشته باشند که البته این مجوز‌ها در حد معمولی و اندک هست. ولی اگر برنامه خود میزبان که به طور محلی روی سیستم نصب می‌شوند، باشد دسترسی کاملب به مجوزها را دارد. پس بدین صورت کاربر این اجازه را دارد که بر آن چیزی که روی سیستم نصب یا اجرا می‌شود، نظارت داشته باشه تا کنترل سیستم به طور کامل در اختیار او باشد.
در قسمت بعدی با نحوه توزیع برنامه آشنا خواهیم شد.
مطالب
Angular CLI - قسمت سوم - تولید کد
پس از ایجاد ساختار اولیه‌ی یک برنامه‌ی Angular توسط Angular CLI، امکان تولید کدهای کامپوننت‌ها، ماژول‌ها، سرویس‌ها و ... نیز در این ابزار پیش بینی شده‌است. کدهای تولید شده‌ی آن بر اساس یک سری blueprint (و یا همان مفهوم قالب‌های از پیش آماده در سایر ابزارهای مشابه) ایجاد می‌شوند و فرمت کلی آن نیز به صورت ذیل است:
> ng generate <blueprint> <options>


ایجاد کامپوننت‌های جدید توسط Angular CLI

دستور ایجاد یک کامپوننت جدید توسط Angular CLI به نحو زیر است:
> ng generate component customer
این دستور اندکی طولانی به نظر می‌رسد. به همین جهت برای خلاصه نویسی آن می‌توان از مفهومی به نام Alias استفاده کرد. میانبر generate در اینجا g است و میانبر component، معادل c می‌باشد. به این صورت می‌توان دستور فوق را به این شکل، خلاصه و بازنویسی کرد:
> ng g c customer


گزینه‌های ایجاد کدهای جدید در Angular CLI

اگر به اولین دستور بحث جاری دقت کنید، قسمت <options> نیز برای آن درنظر گرفته شده‌است. تعدادی از مهم‌ترین گزینه‌هایی را که در اینجا می‌توان ذکر کرد به شرح زیر هستند:

گزینه 
Alias (میانبر/نام مستعار)      توضیح 
 flat--
    آیا باید برای آن پوشه‌ای ایجاد نشود؟ (flat = بدون پوشه در اینجا)
 (پیش فرض آن ایجاد یک پوشه‌ی جدید است). اگر می‌خواهیم ایجاد نشود، باید flat true-- را ذکر کرد. 
inline-template--  it-  آیا قالب کامپوننت، درون فایل ts. آن قرار گیرد؟ (پیش فرض آن، false است) 
 inline-style--  is-  آیا شیوه نامه‌ی کامپوننت، داخل فایل ts. آن قرار گیرد؟ (پیش فرض آن، false است) 
 spec--    آیا فایل spec نیز تولید شود؟
(پیش فرض آن true است) اگر می‌خواهیم این فایل ایجاد نشود باید spec false-- را ذکر کرد. 
 view-encapsulation--
 ve-  تعیین نوع استراتژی view encapsulation مورد استفاده (مانند Emulated). 
 change-detection--  cd-  تعیین استراتژی change detection مورد استفاده (مانند OnPush). 
 dry-run--  d-  گزارش فایل‌های تولیدی، بدون نوشتن و تولید آن‌ها (پیش فرض آن false است) 
 prefix--    تعیین صریح prefix مورد استفاده‌ی در حین مقدار دهی selectorها که در قسمت قبل در مورد آن بحث شد. 

برای مثال اگر خواستیم کامپوننتی را به همراه قالب‌ها و شیوه‌نامه‌های inline (قرار گرفته‌ی داخل فایل ts. آن) تولید کنیم، می‌توان از دستور ذیل کمک گرفت:
>ng generate component customer --inline-template --inline-style
که خلاصه شده‌ی آن با توجه به Alias‌های ذکر شده به صورت ذیل است:
>ng g c customer –it -is

اگر صرفا دستور ng generate component customer را اجرا کنیم (بدون هیچ گزینه‌ی اضافه‌تری)، فایل‌های ts (کلاس کامپوننت)، css (فایل شیوه نامه)، html (فایل قالب) و spec (فایل آزمون واحد کامپوننت) به صورت خودکار تولید خواهند شد.

همانطور که پیشتر نیز عنوان شد، اگر مطمئن نیستید که دستور درحال فراخوانی، چه فایل‌ها و پوشه‌هایی را ایجاد می‌کند، با ذکر پرچم dry-run-- و یا به صورت خلاصه d-، دستور مدنظر را شبیه سازی کنید تا صرفا گزارشی را از فایل‌هایی که قرار است تولید شوند، ارائه دهد.

نکته‌ی مهم دیگری که به همراه دستورات Angular CLI هستند، به روز رسانی خودکار فایل app.module.ts است:
>ng g c customer
installing component
  create src\app\customer\customer.component.css
  create src\app\customer\customer.component.html
  create src\app\customer\customer.component.spec.ts
  create src\app\customer\customer.component.ts
  update src\app\app.module.ts
برای نمونه زمانیکه دستور تولید یک کامپوننت را به نحوی که ملاحظه می‌کنید صادر کنیم، علاوه بر ایجاد 4 فایل مرتبط با آن کامپوننت، سطر به روز رسانی فایل app.module.ts را نیز در انتها ذکر کرده‌است. در اینجا تغییرات صورت گرفته را ملاحظه می‌کنید:
 import { CustomerComponent } from './customer/customer.component';

@NgModule({
  declarations: [
   AppComponent,
   CustomerComponent
]})
ابتدا به صورت خودکار سطر import این کامپوننت جدید ذکر شده‌است و سپس قسمت declarations ماژول را نیز با تعریف CustomerComponent به روز رسانی کرده‌است. بنابراین کار با Angular CLI فراتر است از صرفا کار با تعدادی قالب از پیش آماده‌ی کامپوننت‌ها و سرویس‌ها.


مشاهده‌ی تغییرات انجام شده‌ی توسط Angular CLI به کمک سورس کنترل

همانطور که در قسمت قبل نیز عنوان شد، دستور ng new، کار آغاز یک مخزن Git را نیز به صورت خودکار انجام می‌دهد. در اینجا هر دستوری که توسط Angular CLI اجرا شود، به این مخزن کد commit خواهد شد.


برای مثال اگر کل پوشه‌ی برنامه را توسط VSCode باز کنیم (کلیک راست در داخل ریشه‌ی اصلی پروژه و انتخاب گزینه‌ی Open With Code)، با مراجعه‌ی به لیست تغییرات و بررسی diff آن‌ها، به سادگی می‌توان تشخیص داد که چه تغییراتی بر روی فایل‌ها اعمال شده‌اند.


ایجاد سایر اجزای جدید برنامه توسط Angular CLI

نام جزء
 Alias  دستور
 service   s   ng g service customer-data 
 pipe  p   ng g pipe init-caps 
 class   cl   ng g class customer-model 
 directive   d   ng g directive search 
 interface   i   ng g interface orders 
 enum  e   ng g enum gender 
 module  m   ng generate module sales 

نکات تکمیلی
 - در حین ایجاد یک directive جدید، پوشه‌ای را برای آن ایجاد نمی‌کند. اگر می‌خواهید اینکار به صورت flat (بدون پوشه در اینجا) انجام نشود، گزینه‌ی flat false-- را نیز قید کنید.
 - در حین ایجاد یک سرویس جدید، اخطار «WARNING Service is generated but not provided, it must be provided to be used» را دریافت خواهید کرد. علت اینجا است که Angular CLI نمی‌داند که این سرویس را باید به کامپوننت خاصی اضافه کند یا به ماژول برنامه. به همین جهت یا باید به صورت دستی فایل src\app\app.module.ts را ویرایش و قسمت providers آن‌را بر اساس نام این سرویس جدید تکمیل کرد و یا توسط سوئیچ m می‌توان ماژول مدنظر را دقیقا ذکر کرد:
> ng g s sales -m app.module
در اینجا عنوان شده‌است که پس از ایجاد سرویس جدید sales، قسمت providers ماژول src\app\app.module.ts نیز به روز رسانی شود.
این نکته در مورد تمام اجزایی که فایل app.module را به روز رسانی می‌کنند نیز صادق است. اگر برای مثال کامپوننتی قرار است ماژول جدید دیگری را به روز رسانی کند، می‌توانید به صورت صریح نام ماژول آن‌را قید کنید؛ در غیراینصورت از همان app.module پیش فرض استفاده خواهد شد.
 - همانطور که مشاهده می‌کنید امکان تولید کلاس، اینترفیس و enum تایپ‌اسکریپتی نیز در اینجا پیش بینی شده‌است. اگر خواستید کلاسی را درون پوشه‌ی خاصی قرار دهید می‌توانید محل پوشه‌ی آن‌را دقیقا ذکر کنید (در مورد اینترفیس‌ها و enums و سایر اجزاء نیز به همین صورت):
> ng g cl models/customer
به این ترتیب فایل کلاس customer.ts درون پوشه‌ی arc/app/models تشکیل می‌شود. پوشه‌ی models نیز در صورت عدم وجود به صورت خودکار ایجاد خواهد شد.


تغییر تنظیمات پیش فرض تولید کد پروژه‌ی جاری

در قسمت قبل «تغییر پیش فرض‌های عمومی Angular CLI» را بررسی کردیم. در اینجا نیز می‌توان یکسری از خواص فایل angular-cli.json. را بازنویسی کرد؛ در قسمت defaults آن:
"defaults": {
    "styleExt": "css",
    "component": {}
}
یا از طریق خط فرمان
> ng set defaults.component.flat false
> ng set defaults.directive.flat false
> ng set defaults.styleExt sass
و یا با ویرایش فایل json تنظیمات cli به صورت مستقیم:
  "defaults": {
    "styleExt": "sass",
    "component": {
      "flat": false
    },
    "directive": {
      "flat": false
    }
  }
به این ترتیب دیگر نیازی نخواهد بود تا هربار به ازای ایجاد یک دایرکتیو جدید، پرچم flat نبودن آن‌را مقدار دهی کرد؛ چون از فایل angular-cli.json. تنظیمات خودش را دریافت می‌کند.


و اگر VSCode استفاده می‌کنید، به همراه intellisense کاملی در مورد اجزای مختلف این فایل json است (این intellisense را به صورت خودکار بر اساس اسکیمای این فایل و سرویس زبان Angular تهیه می‌کند).
مطالب
مهارت‌های تزریق وابستگی‌ها در برنامه‌های NET Core. - قسمت ششم - دخالت در مراحل وهله سازی اشیاء توسط IoC Container
روش متداول کار با تزریق وابستگی‌های برنامه‌های مبتنی بر NET Core.، عموما با ثبت و معرفی یک سرویس به صورت زیر، توسط متدهای AddTransient، AddSingleton و AddScoped است:
public class Startup 
{ 
    public void ConfigureServices(IServiceCollection services) 
    { 
        // ... 
         
        services.AddTransient<ICustomerService, DefaultCustomerService>(); 
         
        // ... 
    } 
}
و سپس استفاده‌ی از این سرویس، با تزریق آن در سازنده‌ی یک کنترلر که نمونه‌های بیشتری از آن‌را در قسمت چهارم بررسی کردیم:
public class SupportController 
{ 
    // DefaultCustomerService will be injected here: 
    public SupportController(ICustomerService customerService) 
    { 
        // ... 
    } 
}
در اینجا کار وهله سازی DefaultCustomerService به صورت خودکار و راسا توسط IoC Container توکار برنامه صورت می‌گیرد و ما هیچگونه دخالتی را در آن نداریم. اما اگر در این بین نیاز باشد پس از وهله سازی DefaultCustomerService، یک خاصیت آن نیز بر اساس شرایط جاری مقدار دهی شود و حاصل نهایی در اختیار SupportController فوق قرار گیرد چه باید کرد؟
برای سفارشی سازی مراحل وهله سازی اشیاء توسط IoC Container توکار برنامه و امکان دخالت در آن، قابلیتی تحت عنوان «factory registration» نیز پیش بینی شده‌است که در ادامه آن‌را بررسی می‌کنیم.


Factory Registration چیست؟

اگر در اسمبلی Microsoft.Extensions.DependencyInjection.Abstractions و فضای نام Microsoft.Extensions.DependencyInjection آن به کلاس ServiceCollectionServiceExtensions که متدهای الحاقی مانند AddScoped را ارائه می‌کند، بیشتر دقت کنیم، تک تک این متدها امضاهای دیگری را نیز دارند:
namespace Microsoft.Extensions.DependencyInjection
{
    public static class ServiceCollectionServiceExtensions
    {
        public static IServiceCollection AddScoped<TService>(
     this IServiceCollection services) where TService : class;
        public static IServiceCollection AddScoped(
     this IServiceCollection services, Type serviceType, Type implementationType);
        public static IServiceCollection AddScoped(
     this IServiceCollection services, Type serviceType, 
 Func<IServiceProvider, object> implementationFactory);
        public static IServiceCollection AddScoped<TService, TImplementation>(this IServiceCollection services)
        public static IServiceCollection AddScoped(
     this IServiceCollection services, Type serviceType);
        public static IServiceCollection AddScoped<TService>(
     this IServiceCollection services, 
 Func<IServiceProvider, TService> implementationFactory) where TService : class;
        public static IServiceCollection AddScoped<TService, TImplementation>(
     this IServiceCollection services, 
 Func<IServiceProvider, TImplementation> implementationFactory)
// ...
    }
}
همانطور که ملاحظه می‌کنید، امضای تعدادی از این overloadها، دارای پارامترهایی از نوع Func نیز هست و هدف آن‌ها فراهم آوردن روشی برای سفارشی سازی مراحل وهله سازی سرویسی‌های بازگشتی از طریق سیستم تزریق وابستگی‌های برنامه است. توسط این پارامتر، پیش از وهله سازی سرویس درخواستی، IServiceProvider جاری یا همان root container را در اختیار شما قرار می‌دهد (اطلاعات بیشتر در مورد IServiceProvider را در قسمت دوم بررسی کردیم) و توسط آن می‌توان ابتدا وهله‌ای از سرویس یا سرویس‌های خاصی را دریافت کرد و پس از ترکیب و سفارشی سازی آن‌ها، در آخر یک object را بازگشت داد که در نهایت به عنوان وهله‌ی اصلی این سرویس درخواستی، در سراسر برنامه مورد استفاده قرار می‌گیرد. در ادامه با مثال‌هایی، کاربردهای این پارامتر از نوع Func، یا Implementation Factory را بررسی می‌کنیم.


مثال 1 : تزریق وابستگی‌ها در حالتیکه کلاس سرویس مدنظر دارای تعدادی پارامتر ثابت است

IoC Container توکار برنامه‌های NET Core.، به صورت خودکار وابستگی‌های تزریق شده‌ی در سازنده‌های سرویس‌های مختلف را تا هر چند سطح ممکن، به صورت خودکار وهله سازی می‌کند؛ به شرطی‌که این وابستگی‌های تزریق شده نیز خودشان سرویس بوده باشند و در تنظیمات ابتدایی آن ثبت و معرفی شده باشند. به عبارتی زمانیکه با سیستم تزریق وابستگی‌ها کار می‌کنیم، مهم نیست که نگران مقدار دهی پارامترهای سازنده‌ی تزریق شده‌ی در سازنده‌های سرویسی خاص باشیم. اما ... برای نمونه سرویس زیر را که یک رشته را در سازنده‌ی خود دریافت می‌کند درنظر بگیرید:
namespace CoreIocServices
{
    public interface IParameterizedService
    {
        string GetConstructorParameter();
    }

    public class ParameterizedService : IParameterizedService
    {
        private readonly string _connectionString;

        public ParameterizedService(string connectionString)
        {
            _connectionString = connectionString;
        }

        public string GetConstructorParameter()
        {
            return _connectionString;
        }
    }
}
اینبار دیگر نمی‌توان این سرویس را از طریق متداول زیر ثبت و معرفی کرد:
services.AddTransient<IParameterizedService, ParameterizedService>();
چون IoC Container نمی‌داند که چگونه و از کجا باید پارامتر رشته‌ای درخواستی در سازنده‌ی کلاس ParameterizedService را تامین کند. همچنین ثبت سرویس‌ها نیز در کلاس ServiceCollectionServiceExtensions معرفی شده‌ی در ابتدای بحث، به قید «where TService : class» محدود شده‌است. اینجا است که روش factory registration به کمک ما خواهد آمد تا بتوانیم مراحل وهله سازی این سرویس را سفارشی سازی کنیم:
services.AddTransient<IParameterizedService>(serviceProvider =>
{
   return new ParameterizedService("some value ....");
});
البته چون بدنه‌ی این Func، صرفا از یک return تشکیل شده‌است، معادل ساده شده‌ی زیر را هم می‌تواند داشته باشد:
services.AddTransient<IParameterizedService>(serviceProvider => new ParameterizedService("some value ...."));
اینبار در سراسر برنامه اگر سرویس IParameterizedService درخواست شود، وهله‌ای از کلاس ParameterizedService را با پارامتر سازنده‌ی "some value ...."، دریافت خواهد کرد.

در اینجا چون serviceProvider نیز در اختیار ما است، حتی می‌توان این مقدار را از سرویسی دیگر دریافت کرد و سپس مورد استفاده قرار داد:
services.AddTransient<IParameterizedService>(serviceProvider =>
{
   var config = serviceProvider.GetRequiredService<ITestService>().GetConfigValue();
   return new ParameterizedService(config);
});

نمونه‌ی دیگری از این دست، کار با IUrlHelper توکار ASP.NET Core است. این سرویس برای اینکه پاسخ درستی را ارائه دهد، نیاز به ActionContext جاری را دارد تا بتواند از طریق آن به تمام جزئیات اکشن متد یک کنترلر و درخواست رسیده دسترسی داشته باشد. در این حالت برای ساده سازی کار با آن، بهتر است تامین وابستگی‌های لحظه‌ای این سرویس را با سفارشی سازی نحوه‌ی وهله سازی آن، انجام دهیم، تا اینکه این قطعه کد تکراری را در هر جائیکه به IUrlHelper نیاز است، تکرار کنیم:
services.AddScoped<IUrlHelper>(serviceProvider =>
{
   var actionContext = serviceProvider.GetRequiredService<IActionContextAccessor>().ActionContext;
   var urlHelperFactory = serviceProvider.GetRequiredService<IUrlHelperFactory>();
   return urlHelperFactory.GetUrlHelper(actionContext);
});
اکنون اگر IUrlHelper را به سازنده‌ی یک کنترلر تزریق کنیم، دیگر نیازی به سه سطر نوشته‌ی تامین factory و action context آن نخواهد بود.


مثال 2: وهله سازی در صورت نیاز به وابستگی‌های یک سرویس، به کمک Lazy loading

فرض کنید دو سرویس را در سازنده‌ی سرویس دیگری تزریق کرده‌اید:
namespace Services
{
    public class OrderHandler : IOrderHandler
    {
        private readonly IAccounting _accounting;
        private readonly ISales _sales;
        public OrderHandler(IAccounting accounting, ISales sales)
        {
بعد در این کلاس، در یک متد، از سرویس accounting استفاده می‌شود و در متدی دیگر از سرویس sales. یعنی هرچند در زمان وهله سازی شیء OrderHandler هر دو وابستگی تزریق شده‌ی در سازنده‌ی آن نیز وهله سازی خواهند شد، اما در بسیاری از شرایط، بسته به متد مورد استفاده، فقط از یکی از آن‌ها استفاده می‌کنیم. اکنون این سؤال مطرح می‌شود که آیا می‌توان سربار وهله سازی تمام سازنده‌های این کلاس را به زمان استفاده‌ی از آن‌ها منتقل کرد؟ یعنی سرویس accounting تزریق شده فقط زمانی وهله سازی شود که واقعا قرار است از آن استفاده کنیم.
روش انجام یک چنین کارهایی با استفاده از کلاس Lazy اضافه شده‌ی به NET 4x. قابل انجام است:
   public class OrderHandlerLazy : IOrderHandler
    {
        public OrderHandlerLazy(Lazy<IAccounting> accounting, Lazy<ISales> sales)
        {
 و برای معرفی آن در اینجا می‌توان از روش factory registration استفاده کرد:
services.AddTransient<IOrderHandler, OrderHandlerLazy>();
services.AddTransient<IAccounting, Accounting>()
            .AddTransient(serviceProvider => new Lazy<IAccounting>(() => serviceProvider.GetRequiredService<IAccounting>()));
services.AddTransient<ISales, Sales>()
           .AddTransient(serviceProvider => new Lazy<ISales>(() => serviceProvider.GetRequiredService<ISales>()));
- در اینجا در ابتدا تمام سرویس‌ها (حتی آن‌هایی که قرار است به صورت Lazy استفاده شوند) یکبار به صورت متداولی معرفی می‌شوند.
- سپس سرویس‌هایی که قرار است به صورت Lazy نیز واکشی شوند، بار دیگر توسط روش factory registration با وهله سازی new Lazy از نوع سرویس مدنظر و فراهم آوردن پیاده سازی آن با استفاده از serviceProvider.GetRequiredService، مجددا معرفی خواهند شد.

پس از این تنظیمات، اگر سرویس IOrderHandler را از طریق سیستم تزریق وابستگی‌ها درخواست کنید، وابستگی‌های تزریق شده‌ی در سازنده‌ی آن فقط زمانی و در محلی وهله سازی می‌شوند که از طریق خاصیت Value شیء Lazy آن‌ها مورد استفاده قرار گرفته شده باشند.
مثال کامل IOrderHandler را از فایل پیوستی انتهای مطلب می‌توانید دریافت. اگر آن‌را اجرا کنید (برنامه‌ی کنسول آن‌را)، در خروجی آن، فقط اجرا شدن سازنده‌ی سرویسی را مشاهده می‌کنید که مورد استفاده قرار گرفته و نه وابستگی دومی که تزریق شده، اما استفاده نشده‌است.


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

فرض کنید سرویسی را به صورت زیر به سیستم تزریق وابستگی‌ها معرفی کرده‌اید:
services.AddTransient<IMyDisposableService, MyDisposableService>();
در ادامه اگر سرویس IMyDisposableService را از این سیستم درخواست کنیم، برنامه بدون مشکل اجرا می‌شود؛ اما اگر خود MyDisposableService را تزریق کنیم چطور؟
public class AnotherController 
{ 
    public AnotherController(MyDisposableService customerService) 
    { 
        // ... 
    } 
}
در این حالت برنامه با استثنای زیر متوقف می‌شود و عنوان می‌کند که نمی‌داند چگونه باید این وابستگی تزریق شده را تامین کند:
An unhandled exception occurred while processing the request. 
InvalidOperationException: Unable to resolve service for type ‘MyDisposableService’ while attempting to activate ‘AnotherController’. 
Microsoft.Extensions.DependencyInjection.ActivatorUtilities.GetService(IServiceProvider sp, Type type, Type requiredBy, bool isDefaultParameterRequired)
این مورد را نیز می‌توان توسط factory registration به نحو زیر مدیریت کرد:
services.AddTransient<IMyDisposableService, MyDisposableService>();
services.AddTransient<MyDisposableService>(serviceProvider =>
serviceProvider.GetRequiredService<IMyDisposableService>() as MyDisposableService);
هر زمانیکه وهله‌ای از کلاس MyDisposableService درخواست شود، وهله‌ای از سرویس IMyDisposableService را بازگشت می‌دهیم.


کدهای کامل این قسمت را از اینجا می‌توانید دریافت کنید: CoreDependencyInjectionSamples-06.zip
مطالب
استفاده از Unity در پیاده سازی الگوی Service locator
یکی از راهکارهای پیاده سازی IOC یا همان Inversion Of Control در پروژه‌های MVC استفاده از Unity و معرفی آن به DependencyResolver خود دات نت است.
برای آشنایی با Unity و قابلیت‌های آن میتوانید به اینجا و اینجا سر بزنید.
اما برای استفاده از Unity در پروژه‌های MVC کافی است در Global یا فایل راه انداز (bootstrapper ) تک تک انتزاع‌ها (Interface) را به کلاس‌های مرتبط شان معرفی کنید.
var container = new UnityContainer();  
container.RegisterType<ISomeService, SomeService>(new PerRequestLifetimeManager());
container.RegisterType<ISomeBusiness, SomeBusiness>(new PerRequestLifetimeManager());
container.RegisterType<ISomeController, SomeController>(new PerRequestLifetimeManager());
و بعد از ایجاد container از نوع UnityContainer میتوانیم آنرا به MVC معرفی کنیم:
DependencyResolver.SetResolver(new UnityDependencyResolver(container));
تا به اینجا به‌راحتی میتوانید از سرویس‌های معرفی شده در پروژه MVC استفاده کنید.
var someService=(ISomeService)DependencyResolver.Current.GetService(typeof(ISomeService));
var data=someService.GetData();
اما اگر بخواهیم از کلاس‌های معرفی شده در Unity در لایه‌های دیگر (مثلا Business) استفاده کنیم چه باید کرد؟
برای هر این مشکل راهکارهای متفاوتی وجود دارد. من در لایه سرویس از Service locator بهره برده ام. برای آشنایی با این الگو اینجا را بخوانید. اکثر برنامه نویسان الگوهای IOC و Service Locator را با هم اشتباه میگیرند یا آنها را اشتباها بجای هم بکار میبرند.
برای درک تفاوت الگوی IOC و Service locator اینجا را بخوانید.

در لایه سرویس یک کلاس Service Factory داریم که قرار است همه سرویس‌ها، برای برقراری ارتباط با یکدیگر از آن استفاده کنند.این کلاس معمولا در لایه سرویس به اشکال گوناگونی پیاده سازی میشود که کارش وهه سازی از Interface‌های درخواستی است. اما برای یکپارچه کردن آن با Unity من آنرا به شکل زیر پیاده سازی کرده ام

public class ServiceFactory : MarshalByRefObject
    {
        static IUnityContainer uContainer = new UnityContainer();
        public static Type DataContextType { get; set; }

        public static void Initialise(IUnityContainer unityContainer, Type dbContextType)
        {
            uContainer = unityContainer;
            DataContextType = dbContextType;
            uContainer.RegisterType(typeof(BaseDataContext), DataContextType, new HierarchicalLifetimeManager());
        }
        public static T Create<T>()
        {
            return (T)Activator.CreateInstance<T>();
        }
        public static T Create<T>(string fullTypeName)
        {
            return (T)System.Reflection.Assembly.GetExecutingAssembly().CreateInstance(fullTypeName);
        }
        public static T Create<T>(Type entityType)
        {
            return (T)Activator.CreateInstance(entityType);
        }
        public static dynamic Create(Type entityType)
        {
            return Activator.CreateInstance(entityType);
        }

        public static T Get<T>()
        {
            return uContainer.Resolve<T>();
        }
        public static object Get(Type type)
        {
            return uContainer.Resolve(type);
        }
    }

در این کلاس ما بجای ایجاد داینامیک آبجکت‌ها، از Unity استفاده کرده‌ایم. در همان ابتدا که برنامه‌ی وب ما برای اولین بار اجرا میشود و بعد از Register کردن کلاس‌ها، می‌توانیم container را به صورت پارامتر سازنده به کلاس Service Factory ارسال کنیم. به این ترتیب برای استفاده از سرویس‌ها در لایه Business از Unity بهره میبریم.

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