مطالب
ارتباط بین Controller و Directive در AngularJs
در پست قبلی با کلیات مفاهیم دیرکتیو‌ها آشنا شدید. در این پست قصد داریم برخی توابع  کنترلرهای تعریف شده در Angular را به وسیله دیرکتیو‌های تعریف شده در ماژول فراخوانی نماییم. در ادامه این موضوع را طی یک مثال بررسی خواهیم کرد.
ابتدا View مورد نظر را به صور زیر ایجاد می‌کنیم:
<script type="text/javascript" src="~/scripts/Modules/module4.js"></script>

<div ng-app="myApp">
    <div ng-controller="myCtrl">
        <span enter>Load More Books</span>
    </div>
</div>
برنامه به این صورت است که با ورود نشانگر ماوس بر روی تگ span (فراخوانی رویداد mouseenter برای تگ هایی که دارای دیرکتیو enter باشند) یک تابع به نام loadMoreBook در کنترلر myCtrl فراخوانی می‌شود.
بک فایل جاوااسکریپتی به نام myModule بسازید و ماژول مورد نظر را ایجاد نمایید:
var app = angular.module('myApp', []);
کنترلر مورد نظر به همراه تابع loadMoreBook را به صورت زیر ایجاد می‌کنیم(البته در اینجا به جای لود واقعی داده از یک alert استفاده کردم):
app.controller('myCtrl', function ($scope) {
    $scope.loadMoreBook = function () {
        alert('Loading Books...');
    }
});
حال نوبت به دیرکتیو مورد بحث می‌رسد که به صورت زیر ایجاد می‌شود:
app.directive('enter', function () {
    return function (scope, element) {
        element.bind('mouseenter', function () {
            scope.loadMoreBook();
        })        
    }
});
اولین نکته این است که به در تابع سازنده دیرکتیو به جای برگشت آبجک مورد نظر یک تابع برگشت داه می‌شود. برای اینکه بتوان به توابع کنترلر محصور کننده دیرکتیو دسترسی داشت آرگومان اول تابع معادل scope مورد استفاده در کنترلر خواهد بود. آرگومان دوم معادل المانی است که  دارای دیرکتیو enter است. در این تابع ابتدا برای رویداد mouseenter رویدادگردان آن پیاده سازی شده است که در آن تابع loadMoreBook کنترلر مورد نظر فراخوانی می‌شود.
خروجی


حال فرض بر این است که در کنترلر بالا تابع دیگری به نام loadMoreAuthor برای فراخوانی نویسندگان نیز وجود دارد. به صورت زیر:
app.controller('myCtrl', function ($scope) {
    $scope.loadMoreBook = function () {
        alert('Loading Books...');
    }

    $scope.loadMoreAuthor = function () {
        alert('Loading Authors...');
    }
});
اما برای انعطاف پذیری بیشتر برنامه، قصد داریم دیرکتیو بالا را به گونه ای تغییر دهیم که نام تابع مورد نظر در کنترلر را به عنوان مقدار یک ویژگی دریافت کند. به صورت زیر:

<script type="text/javascript" src="~/scripts/Modules/module4.js"></script>

<div ng-app="myApp">
    <div ng-controller="myCtrl">
        <span enter="loadMoreBook()">Load More Book</span>         
        <hr>
       <span enter="loadMoreAuthor()">Load More Author</span>
    </div>
</div>
برای به دست آوردن مقدار دیرکتیوی که به عنوان ویژگی در المان تعیین شده، باید از آرگومان سوم در تابع سازنده دیرکتیو به صورت زیر استفاده کرد.
app.directive('enter', function () {
    return function (scope, element , attrs) {
        element.bind('mouseenter', function () {
            scope.$apply(attrs.enter);
        })        
    }   
});
در  کد‌های بالا، برای اینکه بتوان بر اساس نام یک تابع آن را فراخوانی کرد، از سرویس apply$ که به صورت توکار در angular تعبیه شده است استفاده کردم. برای به دست آوردن نام تابع، باید از آرگومان سوم تابع (attrs) به همراه نام دیرکتیو استفاده کرد. به دلیل اینکه نام دیرکتیو enter است باید پارامتر سرویس apply$ به صورت attrs.enter باشد. خروجی نیز مانند حالت قبل خواهد بود.
نظرات مطالب
مهارت‌های تزریق وابستگی‌ها در برنامه‌های NET Core. - قسمت دهم - پیاده سازی الگوی Decorator
متد الحاقی Decorate کتابخانه Structor، کلاس Decorate کننده (MyTaskServiceDecorator)  را با همان LifeTime کلاس Decorate شده (MyTaskService) ثبت میکند (که در مثال بالا Transient است)
حال فرض کنید کلاس MyTaskService به صورت Singleton ثبت شده است، آیا راهی وجود دارد که توسط Structor، کلاس MyTaskServiceDecorator را با LifeTime متفاوتی (مثلا Scoped) ثبت کرد یا باید به روش دستی factory registration عمل کرد ؟
نظرات مطالب
روش استفاده‌ی صحیح از HttpClient در برنامه‌های دات نت
هنوز هم می‌توانید از تک client استفاده کنید به شرطی‌که از متد ویژه‌ی زیر استفاده کنید:
public Task<HttpResponseMessage> SendAsync(HttpRequestMessage request)
در این متد می‌توان هدرها را به ازای هر درخواست مشخص، جداگانه تنظیم و ارسال کرد (بدون تاثیر گذاشتن بر روی سایر درخواست‌ها):
 var httpRequestMessage = new HttpRequestMessage(HttpMethod.Get, uri);
 httpRequestMessage.Headers.Authorization = ...
 httpClient.SendAsync(httpRequestMessage);
مطالب
ارسال ایمیل در ASP.NET Core
فضای نام System.Net.Mail در NET Core 1.2. که پیاده سازی netstandard2.0 است، ارائه خواهد شد. بنابراین فعلا (در زمان NET Core 1.1.) راه حل توکار و رسمی برای ارسال ایمیل در برنامه‌های مبتنی بر NET Core. وجود ندارد. اما می‌توان کتابخانه‌ی ثالثی را به نام MailKit، به عنوان راه‌حلی که .NET 4.0, .NET 4.5, .NET Core, Xamarin.Android, و Xamarin.iOS را پشتیبانی می‌کند، درنظر گرفت و توانمندی‌ها و پروتکل‌های پشتیبانی شده‌ی توسط آن، از System.Net.Mail توکار نیز بسیار بیشتر است.


افزودن وابستگی‌های MailKit به برنامه

برای شروع به استفاده‌ی از MailKit، می‌توان بسته‌ی نیوگت آن‌را به فایل project.json برنامه معرفی کرد:
{
    "dependencies": {
        "MailKit": "1.10.0"
    }
}


استفاده از MailKit جهت تکمیل وابستگی‌های ASP.NET Core Identity

قسمتی از ASP.NET Core Identity، شامل ارسال ایمیل‌های «ایمیل خود را تائید کنید» است که آن‌را می‌توان توسط MailKit به نحو ذیل تکمیل کرد:
using System.Threading.Tasks;
using ASPNETCoreIdentitySample.Services.Contracts.Identity;
using MailKit.Net.Smtp;
using MailKit.Security;
using MimeKit;
 
namespace ASPNETCoreIdentitySample.Services.Identity
{
    public class AuthMessageSender : IEmailSender, ISmsSender
    {
        public async Task SendEmailAsync(string email, string subject, string message)
        {
            var emailMessage = new MimeMessage();
 
            emailMessage.From.Add(new MailboxAddress("DNT", "do-not-reply@dotnettips.info"));
            emailMessage.To.Add(new MailboxAddress("", email));
            emailMessage.Subject = subject;
            emailMessage.Body = new TextPart(TextFormat.Html)
            {
                Text = message
            };
 
            using (var client = new SmtpClient())
            {
                client.LocalDomain = "dotnettips.info";
                await client.ConnectAsync("smtp.relay.uri", 25, SecureSocketOptions.None).ConfigureAwait(false);
                await client.SendAsync(emailMessage).ConfigureAwait(false);
                await client.DisconnectAsync(true).ConfigureAwait(false);
            }
        }
 
        public Task SendSmsAsync(string number, string message)
        {
            // Plug in your SMS service here to send a text message.
            return Task.FromResult(0);
        }
    }
}
در اینجا MimeMessage بیانگر محتوا و تنظیمات ایمیلی است که قرار است ارسال شود. ابتدا قسمت‌های From و To آن تنظیم می‌شوند تا مشخص باشد که ایمیل ارسالی از کجا ارسال شده و قرار است به چه آدرسی ارسال شود. در ادامه موضوع و عنوان ایمیل تنظیم شده‌است. سپس متن ایمیل، به خاصیت Body شیء MimeMessage انتساب داده خواهد شد. فرمت ایمیل ارسالی را نیز می‌توان در اینجا تنظیم کرد. برای مثال TextFormat.Html جهت ارسال پیام‌هایی حاوی تگ‌های HTML مناسب است و اگر قرار است صرفا متن ارسال شود، می‌توان TextFormat.Plain را انتخاب کرد.
در آخر، این پیام به SmtpClient جهت ارسال نهایی، فرستاده می‌شود. این SmtpClient هرچند هم نام مشابه آن در System.Net.Mail است اما با آن یکی نیست و متعلق است به MailKit. در اینجا ابتدا LocalDomain تنظیم شده‌است. تنظیم این مورد اختیاری بوده و صرفا به SMTP سرور دریافت کننده‌ی ایمیل‌ها، مرتبط است که آیا قید آن‌را اجباری کرده‌است یا خیر. تنظیمات اصلی SMTP Server در متد ConnectAsync ذکر می‌شوند که شامل مقادیر host ،port و پروتکل ارسالی هستند.


ارسال ایمیل به SMTP pickup folder

روشی که تا به اینجا بررسی شد، جهت ارسال ایمیل‌ها به یک SMTP Server واقعی کاربرد دارد. اما در حین توسعه‌ی محلی برنامه می‌توان ایمیل‌ها را در داخل یک پوشه‌ی موقتی ذخیره و آن‌ها را توسط برنامه‌ی Outlook (و یا حتی مرورگر Firefox) بررسی و بازبینی کامل کرد.
در این حالت تنها کاری را که باید انجام داد، جایگزین کردن قسمت ارسال ایمیل واقعی توسط SmtpClient در کدهای فوق، با قطعه کد ذیل است:
using (var stream = new FileStream($@"c:\smtppickup\email-{Guid.NewGuid().ToString("N")}.eml", FileMode.CreateNew))
{
   emailMessage.WriteTo(stream);
}
تولید فایل‌های eml جهت «شبیه سازی ارسال ایمیل در ASP.Net» بسیار مفید هستند.


FAQ و منبع تکمیلی
مطالب
طراحی و پیاده سازی ServiceLayer به همراه خودکارسازی Business Validationها

در این مطلب قصد داریم علاوه بر طراحی زیرساختی برای راه اندازی هرچه سریعتر ServiceLayer، طراحی ای برای مکانیزم Validation به عنوان یک Cross Cutting Concern، نیز ارائه داده و آن را پیاده سازی کنیم.

پیش نیازها:

 ServiceLayer در معماری لایه‌ای، در برگیرنده ApplicationService هایی می‌باشد که به عنوان مدخل ورودی (Entry Point) برنامه، در معرض دید لایه Presentation قرار گرفته و داده را به فرمت مورد نیاز Presentation در اختیارش قرار خواهند داد.
 این سرویس‌ها DTO‌ها را به عنوان پارامتر دریافت کرده و DTO هایی را به عنوان خروجی برگشت خواهند داد. مباحثی مانند Logging، Caching، Business Validation Authorization و مدیریت تراکنش‌ها را می‌توان در این لایه در نظر گرفت.

در ادامه اگر واژه «سرویس» به کار گرفته می‌شود منظور ما ApplicationServiceها می‌باشند.

کار را با ارائه یکسری واسط و کلاس پایه برای عملیات CRUD در سرویس‌ها به صورت زیر پیش می‌بریم.

قرار است به صورت قراردادی، تمام سرویس‌های ما واسط زیر را پیاده سازی کرده باشند. این مورد در مباحث تعریف Policy‌های مربوط به StructureMap مفید خواهد بود.

namespace MvcFramework.Framework.Application.Services
{
    public interface IApplicationService : ITransientDependency
    {
    }
}

دو واسط دیگر برای اعمال طول عمر اشیاء به صورت قراردادی در StructureMap به شکل زیر در نظر گرفته شده‌اند.

namespace MvcFramework.Framework.Dependency
{
    public interface ISingletonDependency
    {
    }
}
namespace MvcFramework.Framework.Dependency
{
    public interface ITransientDependency
    {
    }
}

و با پیاده سازی یک LifeCyclePolicy از دو واسط بالا به شکل زیر استفاده خواهیم کرد.

namespace MvcFramework.Framework.Dependency
{
    public class LifeCyclePolicy : IInstancePolicy
    {
        public void Apply(Type pluginType, Instance instance)
        {
            if (typeof(ISingletonDependency).IsAssignableFrom(instance.ReturnedType))
                instance.SetLifecycleTo<SingletonLifecycle>();
            else if (typeof(ITransientDependency).IsAssignableFrom(instance.ReturnedType))
                instance.SetLifecycleTo<TransientLifecycle>();
        }
    }
}

به این صورت تنظیم طول عمر اشیاء ساخته شده توسط StructureMap این بار به صورت قرادادی بوده و لازم به ذکر تک تک این موارد در تنظیمات اولیه مربوط به Container آن نیست.

کلاس پایه‌ای را که پیاده ساز واسط IApplicationService می‌باشد، برای مقابله با عدم نگارش پذیری واسط‌ها، به شکل زیر در نظر میگیریم. 

namespace MvcFramework.Framework.Application.Services
{
    public abstract class ApplicationService : IApplicationService
    {
    }
}

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

در ادامه واسط ICrudApplicationSevie را به شکل زیر طراحی خواهیم کرد.

namespace MvcFramework.Framework.Application.Services
{
    public interface ICrudApplicationService<TModel, TCreateModel, TEditModel, TDeleteModel> :
        ICrudApplicationService<TModel, TCreateModel, TEditModel, TDeleteModel, PagedListRequest,
            PagedListResponse<TModel, PagedListRequest>, DynamicListRequest>
        where TEditModel : class, IEditModel
        where TModel : class, IModel
        where TDeleteModel : class, IDeleteModel
    {
    }

    public interface ICrudApplicationService<TModel, TCreateModel, TEditModel, TDeleteModel, in TDynamicListRequest> :
        ICrudApplicationService<TModel, TCreateModel, TEditModel, TDeleteModel, PagedListRequest,
            PagedListResponse<TModel, PagedListRequest>, TDynamicListRequest>
        where TEditModel : class, IEditModel
        where TModel : class, IModel
        where TDeleteModel : class, IDeleteModel
        where TDynamicListRequest : DynamicListRequest
    {
    }

    public interface ICrudApplicationService<TModel, TCreateModel, TEditModel, TDeleteModel, in TPagedListRequest,
        TPagedListResponse> :
        ICrudApplicationService<TModel, TCreateModel, TEditModel, TDeleteModel, TPagedListRequest, TPagedListResponse,
            DynamicListRequest>
        where TEditModel : class, IEditModel
        where TModel : class, IModel
        where TDeleteModel : class, IDeleteModel
        where TPagedListRequest : PagedListRequest, new()
        where TPagedListResponse : PagedListResponse<TModel, TPagedListRequest>
    {
    }

    public interface ICrudApplicationService<TModel, TCreateModel, TEditModel, TDeleteModel, in TPagedListRequest,
        TPagedListResponse,
        in TDynamicListRequest> : IApplicationService
        where TEditModel : class, IEditModel
        where TModel : class, IModel
        where TDeleteModel : class, IDeleteModel
        where TPagedListRequest : PagedListRequest, new()
        where TPagedListResponse : PagedListResponse<TModel, TPagedListRequest>
        where TDynamicListRequest : DynamicListRequest
    {
        void Create(TCreateModel model);
        void Create(IList<TCreateModel> models);
        Task CreateAsync(TCreateModel model);
        Task CreateAsync(IList<TCreateModel> models);

        IList<TModel> GetList();
        DynamicListResponse GetDynamicList(TDynamicListRequest request);
        TPagedListResponse GetPagedList(TPagedListRequest request);
        IList<LookupItem> GetLookup();
        TModel GetById(long id);
        TEditModel GetForEdit(long id);
        bool Exists(long id);
        Task<IList<TModel>> GetListAsync();
        Task<DynamicListResponse> GetDynamicListAsync(TDynamicListRequest request);
        Task<TPagedListResponse> GetPagedListAsync(TPagedListRequest request);
        Task<IList<LookupItem>> GetLookupAsync();
        Task<TModel> GetByIdAsync(long id);
        Task<TEditModel> GetForEditAsync(long id);
        Task<bool> ExistsAsync(long id);

        void Edit(TEditModel model);
        void Edit(IList<TEditModel> models);
        Task EditAsync(TEditModel model);
        Task EditAsync(IList<TEditModel> models);
        
        void Delete(TDeleteModel model);
        void Delete(IList<TDeleteModel> models);
        Task DeleteAsync(TDeleteModel model);
        Task DeleteAsync(IList<TDeleteModel> models);
    }
}

سرویسی که نیاز دارد از عملیات CRUD نیز پشتیبانی داشته باشد، بهتر است واسط آن از یک چنین واسطی که در بالا معرفی شد، ارث بری کند. 

مدل‌ها و واسط‌های پیش فرضی را که در واسط بالا از آنها استفاده شده است، در زیر مشاهده می‌کنید:

 واسط IModel

namespace MvcFramework.Framework.Application.Models
{
    public interface IModel
    {
        long Id { get; set; }
    }
}

واسط IEditModel

namespace MvcFramework.Framework.Application.Models
{
    public interface IEditModel : IModel
    {
        byte[] RowVersion { get; set; }
    }
}

واسط IDeleteModel

namespace MvcFramework.Framework.Application.Models
{
    public interface IDeleteModel : IModel
    {
        byte[] RowVersion { get; set; }
    }
}

کلاس LookupItem

namespace MvcFramework.Framework.Application.Models
{
    public class LookupItem
    {
        public string Value { get; set; }
        public string Text { get; set; }
        public bool Selected { get; set; }
    }
}

کلاس PagedListRequest

namespace MvcFramework.Framework.Application.Models
{
    public class PagedListRequest : IShouldNormalize
    {
        public long TotalCount { get; set; }
        public int PageNumber { get; set; }
        public int PageSize { get; set; }

        /// <summary>
        ///     Sorting information.
        ///     Should include sorting field and optionally a direction (ASC or DESC)
        ///     Can contain more than one field separated by comma (,).
        /// </summary>
        /// <example>
        ///     Examples:
        ///     "Name"
        ///     "Name DESC"
        ///     "Name ASC, Age DESC"
        /// </example>
        public string SortBy { get; set; }

        public void Normalize()
        {
            if (PageNumber < 1)
                PageNumber = 1;

            if (PageSize < 0)
                PageSize = 10;

            if (SortBy.IsEmpty())
                SortBy = "Id DESC";
        }
    }
}

در این طراحی دو شکل از GetPagedList در نظر گرفته شده است؛ یکی با ورودی و خروجی داینامیک مثلا جهت استفاده برای نمایش اطلاعات در کندو گرید که در ادامه با آن بیشتر آشنا خواهید شد و دیگری هم برای زمانیکه نیاز دارید اطلاعات صفحه بندی شده‌ای را در اختیار داشته باشید. کلاس بالا برای پیاده سازی شکل دومی که صحبت شد، استفاده میشود. پیاده سازی واسط IShouldNormalize باعث خواهد شد که قبل از اجرای خود متد، این نوع پارامترها با استفاده از یک Interceptor شناسایی شده و متد Normalize آنها اجرا شود.


کلاس PagedListResponse

namespace MvcFramework.Framework.Application.Models
{
    public class PagedListResponse<TModel, TPagedListRequest>
        where TPagedListRequest : PagedListRequest, new()
        where TModel : IModel
    {
        public PagedListResponse()
        {
            Result = new List<TModel>();
            Request = new TPagedListRequest();
        }
        public IList<TModel> Result { get; set; }
        public TPagedListRequest Request { get; set; }
    }
}

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


کلاس‌های DynamicListRequest و DynamicListResponse برگرفته از کتابخانه Kendo.DynamicLinq می باشند.


کلاس Entity

namespace MvcFramework.Framework.Domain.Entities
{
    public abstract class Entity
    {
        #region Properties

        public long Id { get; set; }
        public byte[] RowVersion { get; set; }
        public EntityChangeState State { get; set; }

        #endregion

        #region Public Methods

        [SuppressMessage("ReSharper", "BaseObjectGetHashCodeCallInGetHashCode")]
        [SuppressMessage("ReSharper", "NonReadonlyMemberInGetHashCode")]
        public override int GetHashCode()
        {
            if (IsTransient())
                return base.GetHashCode();

            unchecked
            {
                var hash = this.GetRealType().GetHashCode();
                return (hash * 31) ^ Id.GetHashCode();
            }
        }

        public virtual bool IsTransient()
        {
            return Id == 0;
        }

        public override bool Equals(object obj)
        {
            var other = obj as Entity;
            if (ReferenceEquals(other, null)) return false;

            if (ReferenceEquals(this, other)) return true;

            var typeOfThis = this.GetRealType();
            var typeOfOther = other.GetRealType();

            if (typeOfThis != typeOfOther) return false;

            if (IsTransient() || other.IsTransient()) return false;

            return Id.Equals(other.Id);
        }

        public override string ToString()
        {
            return $"[{this.GetRealType().Name} : {Id}]";
        }

        #endregion

        #region Operators

        public static bool operator ==(Entity left, Entity right)
        {
            return Equals(left, right);
        }

        public static bool operator !=(Entity left, Entity right)
        {
            return !(left == right);
        }

        #endregion
    }
}

در این کلاس یکسری خصوصیات پایه ای مانند Id و متدهای مشترک بین Entityها قرار گرفته شده است. این کلاس پایه تمام Entity‌های سیستم می‌باشد.

پیاده سازی پیش فرض از واسط ICrudApplicationService به شکل زیر می‌باشد.

namespace MvcFramework.Framework.Application.Services
{
    public abstract class CrudApplicationService<TEntity, TModel, TCreateModel, TEditModel, TDeleteModel> :
        CrudApplicationService<TEntity, TModel, TCreateModel, TEditModel, TDeleteModel, PagedListRequest,
            PagedListResponse<TModel, PagedListRequest>, DynamicListRequest>
        where TEntity : Entity
        where TCreateModel : class
        where TEditModel : class, IEditModel
        where TModel : class, IModel
        where TDeleteModel : class, IDeleteModel
    {
        protected CrudApplicationService(IUnitOfWork unitOfWork, IMapper mapper) : base(unitOfWork, mapper)
        {
        }
    }

    public abstract class CrudApplicationService<TEntity, TModel, TCreateModel, TEditModel, TDeleteModel,
        TDynamicListRequest> :
        CrudApplicationService<TEntity, TModel, TCreateModel, TEditModel, TDeleteModel, PagedListRequest,
            PagedListResponse<TModel, PagedListRequest>, TDynamicListRequest>
        where TEntity : Entity
        where TCreateModel : class
        where TEditModel : class, IEditModel
        where TModel : class, IModel
        where TDeleteModel : class, IDeleteModel
        where TDynamicListRequest : DynamicListRequest
    {
        protected CrudApplicationService(IUnitOfWork unitOfWork, IMapper mapper) : base(unitOfWork, mapper)
        {
        }
    }

    public abstract class CrudApplicationService<TEntity, TModel, TCreateModel, TEditModel, TDeleteModel,
        TPagedListRequest,
        TPagedListResponse> :
        CrudApplicationService<TEntity, TModel, TCreateModel, TEditModel, TDeleteModel, TPagedListRequest,
            TPagedListResponse,
            DynamicListRequest>
        where TEntity : Entity
        where TCreateModel : class
        where TEditModel : class, IEditModel
        where TModel : class, IModel
        where TDeleteModel : class, IDeleteModel
        where TPagedListRequest : PagedListRequest, new()
        where TPagedListResponse : PagedListResponse<TModel, TPagedListRequest>, new()
    {
        protected CrudApplicationService(IUnitOfWork unitOfWork, IMapper mapper) : base(unitOfWork, mapper)
        {
        }
    }

    public abstract class CrudApplicationService<TEntity, TModel, TCreateModel, TEditModel, TDeleteModel,
        TPagedListRequest,
        TPagedListResponse, TDynamicListRequest> : ApplicationService,
        ICrudApplicationService<TModel, TCreateModel, TEditModel, TDeleteModel, TPagedListRequest, TPagedListResponse,
            TDynamicListRequest>
        where TEntity : Entity
        where TCreateModel : class
        where TEditModel : class, IEditModel
        where TModel : class, IModel
        where TDeleteModel : class, IDeleteModel
        where TPagedListRequest : PagedListRequest, new()
        where TPagedListResponse : PagedListResponse<TModel, TPagedListRequest>, new()
        where TDynamicListRequest : DynamicListRequest

    {
        #region Constructor

        protected CrudApplicationService(IUnitOfWork unitOfWork, IMapper mapper)
        {
            Guard.ArgumentNotNull(unitOfWork, nameof(unitOfWork));
            Guard.ArgumentNotNull(mapper, nameof(mapper));

            UnitOfWork = unitOfWork;
            Mapper = mapper;
            EntitySet = UnitOfWork.Set<TEntity>();
        }

        #endregion

        #region Properties

        protected IQueryable<TEntity> UnTrackedEntitySet => EntitySet.AsNoTracking();
        protected IUnitOfWork UnitOfWork { get; }
        protected IMapper Mapper { get; }
        protected IDbSet<TEntity> EntitySet { get; }

        #endregion

        #region ICrudApplicationService Members

        #region Methods

        [Transactional]
        public virtual void Create(TCreateModel model)
        {
            Guard.ArgumentNotNull(model, nameof(model));

            var entity = Mapper.Map<TEntity>(model);

            EntitySet.Add(entity);
            UnitOfWork.SaveChanges();
        }

        [Transactional]
        public virtual void Create(IList<TCreateModel> models)
        {
            Guard.ArgumentNotEmpty(models, nameof(models));

            var entities = Mapper.Map<IList<TEntity>>(models);

            UnitOfWork.AddRange(entities);
            UnitOfWork.SaveChanges();
        }

        [Transactional]
        public virtual Task CreateAsync(TCreateModel model)
        {
            Guard.ArgumentNotNull(model, nameof(model));

            var entity = Mapper.Map<TEntity>(model);

            EntitySet.Add(entity);
            return UnitOfWork.SaveChangesAsync();
        }

        [Transactional]
        public virtual Task CreateAsync(IList<TCreateModel> models)
        {
            Guard.ArgumentNotEmpty(models, nameof(models));

            var entities = Mapper.Map<IList<TEntity>>(models);

            UnitOfWork.AddRange(entities);
            return UnitOfWork.SaveChangesAsync();
        }


        [Transactional]
        public virtual void Edit(TEditModel model)
        {
            Guard.ArgumentNotNull(model, nameof(model));

            var entity = Mapper.Map<TEntity>(model);

            UnitOfWork.MarkAsChanged(entity);
            UnitOfWork.SaveChanges();
        }

        [Transactional]
        public virtual void Edit(IList<TEditModel> models)
        {
            Guard.ArgumentNotNull(models, nameof(models));
            Guard.ArgumentNotEmpty(models, nameof(models));

            var entities = Mapper.Map<IList<TEntity>>(models);

            UnitOfWork.UpdateRange(entities);
            UnitOfWork.SaveChanges();
        }

        [Transactional]
        public virtual Task EditAsync(TEditModel model)
        {
            Guard.ArgumentNotNull(model, nameof(model));

            var entity = Mapper.Map<TEntity>(model);

            UnitOfWork.MarkAsChanged(entity);
            return UnitOfWork.SaveChangesAsync();
        }

        [Transactional]
        public virtual Task EditAsync(IList<TEditModel> models)
        {
            Guard.ArgumentNotNull(models, nameof(models));
            Guard.ArgumentNotEmpty(models, nameof(models));

            var entities = Mapper.Map<IList<TEntity>>(models);

            UnitOfWork.UpdateRange(entities);
            return UnitOfWork.SaveChangesAsync();
        }


        public virtual IList<TModel> GetList()
        {
            return EntitySet.ProjectToList<TModel>(Mapper.ConfigurationProvider);
        }

        public virtual DynamicListResponse GetDynamicList(TDynamicListRequest request)
        {
            Guard.ArgumentNotNull(request, nameof(request));

            var query = ApplyFiltering(request);

            return query.ProjectTo<TModel>().ToListResponse(request);
        }

        public virtual TPagedListResponse GetPagedList(TPagedListRequest request)
        {
            Guard.ArgumentNotNull(request, nameof(request));

            var query = ApplyFiltering(request);

            request.TotalCount = query.LongCount();

            query = ApplySorting(query, request);
            query = ApplyPaging(query, request);

            var result = query.ProjectToList<TModel>(Mapper.ConfigurationProvider);

            return new TPagedListResponse
            {
                Result = result,
                Request = request
            };
        }

        public virtual IList<LookupItem> GetLookup()
        {
            return EntitySet.ProjectToList<LookupItem>(Mapper.ConfigurationProvider);
        }

        public virtual TModel GetById(long id)
        {
            Guard.ArgumentInRange(id, 1, long.MaxValue, nameof(id));

            var entity =
                EntitySet.Where(a => a.Id == id).ProjectToFirstOrDefault<TModel>(Mapper.ConfigurationProvider);

            if (entity == null)
                throw new EntityNotFoundException($"Couldn't Find Entity {id} When GetById");

            return entity;
        }

        public virtual TEditModel GetForEdit(long id)
        {
            Guard.ArgumentInRange(id, 1, long.MaxValue, nameof(id));

            var entity =
                EntitySet.Where(a => a.Id == id).ProjectToFirstOrDefault<TEditModel>(Mapper.ConfigurationProvider);

            if (entity == null)
                throw new EntityNotFoundException($"Couldn't Find Entity {id} When GetForEdit");

            return entity;
        }

        public virtual bool Exists(long id)
        {
            Guard.ArgumentInRange(id, 1, long.MaxValue, nameof(id));

            return EntitySet.Any(a => a.Id == id);
        }

        public virtual async Task<IList<TModel>> GetListAsync()
        {
            return await EntitySet.ProjectToListAsync<TModel>(Mapper.ConfigurationProvider);
        }

        public virtual Task<DynamicListResponse> GetDynamicListAsync(TDynamicListRequest request)
        {
            Guard.ArgumentNotNull(request, nameof(request));

            var query = ApplyFiltering(request);

            return query.ProjectTo<TModel>().ToListResponseAsync(request);
        }

        public virtual async Task<TPagedListResponse> GetPagedListAsync(TPagedListRequest request)
        {
            Guard.ArgumentNotNull(request, nameof(request));

            var query = ApplyFiltering(request);

            request.TotalCount = await query.LongCountAsync().ConfigureAwait(false);

            query = ApplySorting(query, request);
            query = ApplyPaging(query, request);

            var result = await query.ProjectToListAsync<TModel>(Mapper.ConfigurationProvider).ConfigureAwait(false);

            return new TPagedListResponse
            {
                Result = result,
                Request = request
            };
        }

        public virtual async Task<IList<LookupItem>> GetLookupAsync()
        {
            return await EntitySet.ProjectToListAsync<LookupItem>(Mapper.ConfigurationProvider);
        }

        public virtual async Task<TModel> GetByIdAsync(long id)
        {
            Guard.ArgumentInRange(id, 1, long.MaxValue, nameof(id));

            var entity = await UnTrackedEntitySet.Where(a => a.Id == id)
                .ProjectToFirstOrDefaultAsync<TModel>(Mapper.ConfigurationProvider);

            if (entity == null)
                throw new EntityNotFoundException($"Couldn't Find Entity {id} When GetByIdAsync");

            return entity;
        }

        public virtual async Task<TEditModel> GetForEditAsync(long id)
        {
            Guard.ArgumentInRange(id, 1, long.MaxValue, nameof(id));

            var entity = await UnTrackedEntitySet.Where(a => a.Id == id)
                .ProjectToFirstOrDefaultAsync<TEditModel>(Mapper.ConfigurationProvider);

            if (entity == null)
                throw new EntityNotFoundException($"Couldn't Find Entity {id} When GetForEditAsync");

            return entity;
        }

        public virtual Task<bool> ExistsAsync(long id)
        {
            Guard.ArgumentInRange(id, 1, long.MaxValue, nameof(id));

            return EntitySet.AnyAsync(a => a.Id == id);
        }


        [Transactional]
        public virtual void Delete(TDeleteModel model)
        {
            Guard.ArgumentNotNull(model, nameof(model));

            var entity = Mapper.Map<TEntity>(model);

            UnitOfWork.MarkAsDeleted(entity);
            UnitOfWork.SaveChanges();
        }

        [Transactional]
        public virtual void Delete(IList<TDeleteModel> models)
        {
            Guard.ArgumentNotEmpty(models, nameof(models));
            Guard.ArgumentNotEmpty(models, nameof(models));

            var entities = Mapper.Map<IList<TEntity>>(models);

            UnitOfWork.RemoveRange(entities);
            UnitOfWork.SaveChanges();
        }

        [Transactional]
        public virtual Task DeleteAsync(TDeleteModel model)
        {
            Guard.ArgumentNotNull(model, nameof(model));

            var entity = Mapper.Map<TEntity>(model);

            UnitOfWork.MarkAsDeleted(entity);
            return UnitOfWork.SaveChangesAsync();
        }

        [Transactional]
        public virtual Task DeleteAsync(IList<TDeleteModel> models)
        {
            Guard.ArgumentNotEmpty(models, nameof(models));
            Guard.ArgumentNotEmpty(models, nameof(models));

            var entities = Mapper.Map<IList<TEntity>>(models);

            UnitOfWork.RemoveRange(entities);
            return UnitOfWork.SaveChangesAsync();
        }

        #endregion

        #endregion

        #region Protected Methods

        /// <summary>
        ///     Apply Filtering To GetDynamicList
        /// </summary>
        /// <param name="request"></param>
        /// <returns></returns>
        protected virtual IQueryable<TEntity> ApplyFiltering(TDynamicListRequest request)
        {
            Guard.ArgumentNotNull(request, nameof(request));

            return UnTrackedEntitySet;
        }

        /// <summary>
        ///     Apply Filtering To GetPagedList and GetPagedListAsync
        /// </summary>
        /// <param name="request"></param>
        /// <returns></returns>
        protected virtual IQueryable<TEntity> ApplyFiltering(TPagedListRequest request)
        {
            Guard.ArgumentNotNull(request, nameof(request));

            return UnTrackedEntitySet;
        }

        /// <summary>
        ///     Apply Sorting To GetPagedList and GetPagedListAsync
        /// </summary>
        /// <param name="query">query</param>
        /// <param name="request">PagedListRequest</param>
        /// <returns></returns>
        protected virtual IQueryable<TEntity> ApplySorting(IQueryable<TEntity> query, TPagedListRequest request)
        {
            Guard.ArgumentNotNull(request, nameof(request));
            Guard.ArgumentNotNull(query, nameof(query));

            return !request.SortBy.IsEmpty() ? query.OrderBy(request.SortBy) : query.OrderByDescending(e => e.Id);
        }

        /// <summary>
        ///     Apply Paging To GetPagedList and GetPagedListAsync
        /// </summary>
        /// <param name="request">PagedListRequest</param>
        /// <param name="query">query</param>
        /// <returns></returns>
        protected virtual IQueryable<TEntity> ApplyPaging(IQueryable<TEntity> query, TPagedListRequest request)
        {
            Guard.ArgumentNotNull(request, nameof(request));
            Guard.ArgumentNotNull(query, nameof(query));

            return request != null
                ? query.Page((request.PageNumber - 1) * request.PageSize, request.PageSize)
                : query;
        }

        #endregion
    }
}

همه متد‌های این کلاس پایه، قابلیت override شدن را دارند. به عنوان مثال یکسری متد با دسترسی protected مثلا ApplyFiltering هم برای بازنویسی نحوه فیلتر کردن خروجی GetPagedList می‌توانند در SubClassها مورد استفاده قرار گیرند. برای مباحث مرتب سازی هم از کتابخانه System.Linq.Dynamic استفاده شده است. 

برای مکانیزم Validation خودکار هم از کتابخانه FluentValidatoin کمک گرفته شده است و با استفاده از Interceptor زیر در صورت یافتن Validator مربوط به Model ورودی، عملیات اعتبارسنجی انجام میگرد و در صورت معتبر نبودن، استثنایی صادر خواهد شد که حاوی اطلاعات مربوط به جزئیات خطاها نیز می‌باشد.

ValidatorInterceptor

namespace MvcFramework.Framework.Aspects.Validation
{
    public class ValidatorInterceptor : ISyncInterceptionBehavior
    {
        private readonly IValidatorFactory _validatorFactory;

        public ValidatorInterceptor(IValidatorFactory validatorFactory)
        {
            _validatorFactory = validatorFactory;
        }

        public IMethodInvocationResult Intercept(ISyncMethodInvocation methodInvocation)
        {
            var argumentValues = methodInvocation.Arguments.Select(a => a.Value).ToArray();

            var validator = new MethodInvocationValidator(_validatorFactory, methodInvocation.MethodInfo,
                argumentValues);

            validator.Validate();

            return methodInvocation.InvokeNext();
        }
    }
}

کتابخانه جانبی دیگری برای AOP توسط تیم StructureMap به نام StructureMap.DynamicInterception ارائه شده است. نمونه‌ی استفاده از آن، در بالا مشخص می‌باشد. در اینجا انتقال مسئولیت اعتبارسنجی پارامترهای متدی که قرار است Intercept شود، به کلاسی به نام MethodInvocationValidator سپرده شده‌است.

کلاس MethodInvocationValidator

namespace MvcFramework.Framework.Aspects.Validation
{
    internal class MethodInvocationValidator
    {
        #region Constructor

        public MethodInvocationValidator(IValidatorFactory validatorFactory, MethodInfo method,
            object[] parameterValues)
        {
            Guard.ArgumentNotNull(method, nameof(method));
            Guard.ArgumentNotNull(parameterValues, nameof(parameterValues));
            Guard.ArgumentNotNull(validatorFactory, nameof(validatorFactory));

            _method = method;
            _parameterValues = parameterValues;
            _validatorFactory = validatorFactory;
            _parameters = method.GetParameters();

            _parametersToBeNormalized = new List<IShouldNormalize>();
        }

        #endregion

        #region Public Methods

        public void Validate()
        {
            if (!CheckShouldBeValidate()) return;

            foreach (var parameterValue in _parameterValues)
                ValidateMethodParameter(parameterValue);

            foreach (var parameterToBeNormalized in _parametersToBeNormalized)
                parameterToBeNormalized.Normalize();
        }

        #endregion

        #region Fields

        private readonly MethodInfo _method;
        private readonly object[] _parameterValues;
        private readonly ParameterInfo[] _parameters;
        private readonly IValidatorFactory _validatorFactory;
        private readonly List<IShouldNormalize> _parametersToBeNormalized;

        #endregion

        #region Private Methods

        private bool CheckShouldBeValidate()
        {
            if (!_method.IsPublic)
                return false;

            if (IsValidationDisabled())
                return false;

            if (_parameters.IsNullOrEmpty())
                return false;

            if (_parameters.Length != _parameterValues.Length)
                throw new Exception("Method parameter count does not match with argument count!");

            return true;
        }

        private bool IsValidationDisabled()
        {
            if (_method.IsDefined(typeof(EnableValidationAttribute), true))
                return false;

            return ReflectionHelper
                       .GetSingleAttributeOfMemberOrDeclaringTypeOrDefault<DisableValidationAttribute>(_method) != null;
        }

        private void ValidateMethodParameter(object parameterValue)
        {
            if (parameterValue == null) return;

            var parameterValueList = parameterValue as IEnumerable<object>;
            if (parameterValueList != null)
            {
                var valueList = parameterValueList.ToList();

                ValidateMethodParameterValues(valueList);
            }
            else
            {
                ValidateMethodParameterValues(new List<object> { parameterValue });
            }

            if (parameterValue is IShouldNormalize)
                _parametersToBeNormalized.Add(parameterValue as IShouldNormalize);
        }

        private void ValidateMethodParameterValues(List<object> valueList)
        {
            var ruleSet = GetRuleSet(_method);

            var validator = _validatorFactory.GetValidator(valueList.First().GetType());
            if (validator == null) return;

            foreach (var item in valueList)
                ValidateWithReflection(validator, item, ruleSet);
        }

        private static string GetRuleSet(MemberInfo method)
        {
            const string @default = "default";

            var attribute = method.GetCustomAttribute<ValidateWithRuleAttribute>();

            if (attribute == null)
                return @default;

            var rules = new List<string> { @default };

            rules.AddRange(attribute.RuleSetNames);

            return string.Join(",", rules).TrimEnd(',');
        }

        private static void ValidateAndThrow<T>(IValidator<T> validator, T argument, string ruleSet)
        {
            validator.ValidateAndThrow(argument, ruleSet);
        }

        private void ValidateWithReflection(IValidator validator, object argument, string ruleSet)
        {
            GetType().GetMethod(nameof(ValidateAndThrow), BindingFlags.Static | BindingFlags.NonPublic)
                .MakeGenericMethod(argument.GetType())
                .Invoke(null, new[] { validator, argument, ruleSet });
        }

        #endregion
    }
}

در متد Validate آن ابتدا چک می‌شود که آیا اعتبارسنجی می‌بایستی انجام شود یا خیر. سپس تک تک آرگومان‌های ارسالی را با استفاده از متد ValidateMethodParameter وارد مکانیزم اعتبارسنجی می‌کند. در داخل این متد ابتدا نوع آرگومان تشخیص داده شده و این مقادیر به متد ValidateMethodParameterValues ارسال شده و داخل آن ابتدا Validator مرتبط را یافته و آن را به متد ValidateWithReflection ارسال می‌کند. در این بین متد GetRuleSets وظیفه واکشی اسامی RuleSet هایی که بر روی متد مورد نظر تنظیم شده اند را دارد؛ برای مواقعی که از یک ویومدل برای ویرایش، درج و حذف استفاده کنید، در این صورت با توجه به اینکه برای یک ویومدل یک Validator خواهید داشت، امکانات RuleSet مربوط به FluentValidation کارساز خواهند بود. به این صورت که برای هر کدام از عملیات حذف، ویرایش و درج، RuleSet مناسب را تعریف کرده و با استفاده از ValidateWithRuleAttribute برروی متدهای مورد نظر، این ruleها در سیستم اعتبارسنجی ارائه شده اعمال خواهند شد.

با توجه به اینکه متد ValidateAndThrow در واسط IValidator‎<T>‎ تعریف شده‌است و از آنجاییکه ما نوع داده مدل مورد نظر را هم نداریم لازم است با استفاده از MakeGenericMethod به صورت داینامیک نوع داده T را مشخص کنیم و فراخوانی متد استاتیک ValidatorWithThrow‎<T>‎ را با Reflection انجام دهیم.

در ادامه لازم است ValidatorInterceptor معرفی شده را به StructureMap نیز معرفی کنیم. برای این منظور به شکل زیر عمل خواهیم کرد.

namespace MvcFramework.Framework
{
    public class FrameworkRegistry : Registry
    {
        public FrameworkRegistry()
        {
            For<IValidatorFactory>().Singleton().Use<StructureMapValidatorFactory>();

            Scan(scan =>
            {
                scan.TheCallingAssembly();
                scan.WithDefaultConventions();
                scan.LookForRegistries();
            });

            Policies.Interceptors(new DynamicProxyInterceptorPolicy(f => typeof(IApplicationService).IsAssignableFrom(f), typeof(ValidatorInterceptor),typeof(TransactionInterceptor)));
        }
    }
}

در کد بالا با استفاده از DynamicProxyInterceptorPolicy، یک Policy را برای Intercept کردن متدهای مربوط به کلاس هایی که پیاده ساز IApplicationService می‌باشند، معرفی کرده‌ایم.

کار اعتبارسنجی هم به پایان رسید؛ در زیر استفاده از سرویس پایه معرفی شده را می‌توانید مشاهده کنید.

namespace MyApp.ServiceLayer.Roles
{
    public interface IRoleApplicationService :
        ICrudApplicationService<RoleViewModel, RoleCreateViewModel, RoleEditViewModel, RoleDeleteViewModel, RolePagedListRequest, RoleListViewModel>
    {
    }
}

namespace MyApp.ServiceLayer.Roles
{
    public class RoleApplicationService :
        CrudApplicationService<Role, RoleViewModel, RoleCreateViewModel, RoleEditViewModel, RoleDeleteViewModel, RolePagedListRequest, RoleListViewModel>,
        IRoleApplicationService
    {
        #region Constructor

        public RoleApplicationService(IUnitOfWork unitOfWork, IMapper mapper) : base(unitOfWork, mapper)
        {
        }

        #endregion
    }
}


نکته: در این لایه بندی نکات مربوط به مطلب «پیاده سازی ماژولار Autofac» نیز با استفاده از StructureMap اعمال شده است. بدین ترتیب در هر لایه یک Registry مربوط به StructureMap ایجاد شده است. به شکل زیر:

FrameworkRegistry

namespace MyApp.Framework
{
    public class FrameworkRegistry : Registry
    {
        public FrameworkRegistry()
        {
            For<IValidatorFactory>().Singleton().Use<StructureMapValidatorFactory>();

            Scan(scan =>
            {
                scan.TheCallingAssembly();
                scan.WithDefaultConventions();
                scan.AssembliesFromApplicationBaseDirectory();
                scan.AddAllTypesOf<IRunOnEndTask>();
                scan.AddAllTypesOf<IRunOnOwinStartupTask>();
                scan.AddAllTypesOf<IRunOnStartTask>();
                scan.AddAllTypesOf<IRunOnBeginRequestTask>();
                scan.AddAllTypesOf<IRunOnErrorTask>();
                scan.AddAllTypesOf<IRunOnEndRequestTask>();

                scan.LookForRegistries();
            });

            Policies.Interceptors(new DynamicProxyInterceptorPolicy(f => typeof(IApplicationService).IsAssignableFrom(f), typeof(ValidatorInterceptor)/*, typeof(TransactionInterceptor)*/));
        }
    }
}


DataLayerRegistry

namespace MyApp.DataLayer
{
    public class DataLayerRegistry : Registry
    {
        public DataLayerRegistry()
        {
            Scan(scan =>
            {
                scan.TheCallingAssembly();
                scan.WithDefaultConventions();
                scan.AssembliesFromApplicationBaseDirectory();
                scan.AddAllTypesOf<IRunOnStartTask>();
            });

            //todo:use container per request (Nested Containers) instead of HttpContextLifeCycle
            For<IUnitOfWork>().Use<ApplicationDbContext>();
        }
    }
}


ServiceLayerRegistry

namespace MyApp.ServiceLayer
{
    public class ServiceLayerRegistry : Registry
    {
        #region Constructor

        public ServiceLayerRegistry()
        {
            Scan(scan =>
            {
                scan.TheCallingAssembly();
                scan.WithDefaultConventions();
                scan.AssembliesFromApplicationBaseDirectory();
                scan.AddAllTypesOf<IRunOnEndTask>();
                scan.AddAllTypesOf<IRunOnOwinStartupTask>();
                scan.AddAllTypesOf<IRunOnStartTask>();
                scan.AddAllTypesOf<IRunOnBeginRequestTask>();
                scan.AddAllTypesOf<IRunOnErrorTask>();
                scan.AddAllTypesOf<IRunOnEndRequestTask>();

                scan.Assembly(typeof(DataLayerRegistry).Assembly);
                scan.LookForRegistries();

                scan.AddAllTypesOf<Profile>().NameBy(item => item.FullName);
                scan.AddAllTypesOf<IHaveCustomMappings>().NameBy(item => item.FullName);
            });

            FluentValidationConfig();
            AutoMapperConfig();
        }

        #endregion

        #region Private Methods

        private void AutoMapperConfig()
        {
            For<MapperConfiguration>().Singleton().Use("MapperConfig", ctx =>
            {
                var config = new MapperConfiguration(cfg =>
                {
                    cfg.CreateMissingTypeMaps = true;
                    AddProfiles(ctx, cfg);
                    AddIHaveCustomMappings(ctx, cfg);
                    AddMapFrom(cfg);
                });

                config.AssertConfigurationIsValid();

                return config;
            });

            For<IMapper>().Singleton().Use(ctx => ctx.GetInstance<MapperConfiguration>().CreateMapper(ctx.GetInstance));
        }

        private void FluentValidationConfig()
        {
            AssemblyScanner.FindValidatorsInAssembly(Assembly.GetExecutingAssembly())
                .ForEach(result =>
                {
                    For(result.InterfaceType)
                        .Singleton()
                        .Use(result.ValidatorType);
                });
        }

        private static void AddMapFrom(IProfileExpression cfg)
        {
            var types = typeof(RoleViewModel).Assembly.GetExportedTypes();
            var maps = (from t in types
                        from i in t.GetInterfaces()
                        where i.IsGenericType && i.GetGenericTypeDefinition() == typeof(IMapFrom<>) && !t.IsAbstract &&
                              !t.IsInterface
                        select new
                        {
                            Source = i.GetGenericArguments()[0],
                            Destination = t
                        }).ToArray();

            foreach (var map in maps)
                cfg.CreateMap(map.Source, map.Destination);
        }

        private static void AddProfiles(IContext ctx, IMapperConfigurationExpression cfg)
        {
            var profiles = ctx.GetAllInstances<Profile>().ToList();
            foreach (var profile in profiles)
                cfg.AddProfile(profile);
        }

        private static void AddIHaveCustomMappings(IContext ctx, IMapperConfigurationExpression cfg)
        {
            var mappings = ctx.GetAllInstances<IHaveCustomMappings>().ToList();
            foreach (var mapping in mappings)
                mapping.CreateMappings(cfg);
        }

        #endregion
    }
}


WebRegistry

namespace MyApp.Web
{
    public class WebRegistry : Registry
    {
        public WebRegistry()
        {
            Scan(scan =>
            {
                scan.TheCallingAssembly();
                scan.WithDefaultConventions();
                scan.AssembliesFromApplicationBaseDirectory();
                
                scan.AddAllTypesOf<IRunOnEndTask>();
                scan.AddAllTypesOf<IRunOnOwinStartupTask>();
                scan.AddAllTypesOf<IRunOnStartTask>();
                scan.AddAllTypesOf<IRunOnBeginRequestTask>();
                scan.AddAllTypesOf<IRunOnErrorTask>();
                scan.AddAllTypesOf<IRunOnEndRequestTask>();

                scan.Assembly(typeof(ServiceLayerRegistry).Assembly);
                scan.LookForRegistries();
            });
        }
    }
}

در این طراحی، لایه Web یا همان Presentation به DataLayer و DomainClasses هیچ ارجاعی ندارد.


در قسمت بعد استفاده از این سرویس را در یک برنامه ASP.NET MVC با هم بررسی خواهیم کرد. 

کدهای کامل این قسمت را می‌توانید از اینجا دریافت کنید.

مطالب
انجام اعمال ریاضی بر روی Generics
کامپایلر سی‌شارپ اگر نتواند نوع‌های عملوندها را در حین بکارگیری عملگرها تشخیص دهد، اجازه‌ی استفاده از عملگر را نخواهد داد و کار کامپایل، با یک خطا خاتمه می‌یابد. برای نمونه مثال زیر را در نظر بگیرید:
    public interface ICalculator<T>
    {
        T Add(T operand1, T operand2);
    }

    public class Calculator<T> : ICalculator<T>
    {
        public T Add(T operand1, T operand2)
        {
            return operand1 + operand2;
        }
    }
در اینجا چون کامپایلر نمی‌داند که عملگر + بر روی چه نوع‌هایی قرار است اعمال شود (به علت جنریک تعریف شدن این نوع‌ها و مشخص نبودن اینکه آیا این نوع، اصلا عملگر + دارد یا خیر)، با صدور خطای زیر، عملیات کامپایل را متوقف می‌کند:
 Operator '+' cannot be applied to operands of type 'T' and 'T'
برای حل این مساله، چندین روش مطرح شده‌است که در ادامه تعدادی از آن‌ها را مرور خواهیم کرد.


روش اول: واگذار کردن استراتژی عملیات ریاضی به یک کلاس خارجی

این راه حلی است که توسط اعضای تیم سی‌شارپ در روزهای ابتدایی معرفی جنریک‌ها مطرح شده‌است. فرض کنید می‌خواهیم لیستی از جنریک‌ها را با هم جمع بزنیم:
    public class Calculator2<T>
    {
        public T Sum(List<T> list)
        {
            T sum = 0;
            for (int i = 0; i < list.Count; i++)
                sum += list[i];
            return sum;
        }
    }
این کد نیز قابل کامپایل نبوده و امکان اعمال عملگر + بر روی نوع ناشناخته‌ی T میسر نیست.
    public interface ICalculator<T>
    {
        T Add(T operand1, T operand2);
    }

    public class Int32Calculator : ICalculator<int>
    {
        public int Add(int operand1, int operand2)
        {
            return operand1 + operand2;
        }
    }

    public class AlgorithmLibrary<T> where T : new() 
    {
        private readonly ICalculator<T> _calculator;
        public AlgorithmLibrary(ICalculator<T> calculator)
        {
            _calculator = calculator;
        }

        public T Sum(List<T> items)
        {
            var sum = new T();
            for (var i = 0; i < items.Count; i++)
            {
                sum = _calculator.Add(sum, items[i]);
            }
            return sum;
        }
    }
در راه حل ارائه شده، یک اینترفیس عمومی که متد جمع را تعریف کرده‌است، مشاهده می‌کنیم. سپس این اینترفیس در سازنده‌ی کتابخانه‌ی الگوریتم‌‌های برنامه تزریق شده‌است. اکنون کدهای AlgorithmLibrary بدون مشکل کامپایل می‌شوند. هر زمان که نیاز به استفاده از آن بود، بر اساس نوع T، پیاده سازی خاصی را باید ارائه داد. برای مثال در اینجا Int32Calculator پیاده سازی نوع int را انجام داده‌است. برای استفاده از آن نیز خواهیم داشت:
 var result = new AlgorithmLibrary<int>(new Int32Calculator()).Sum(new List<int> { 1, 2, 3 });

البته این نوع پیاده سازی را که کار اصلی آن واگذاری عملیات جمع، به یک کلاس خارجی است، توسط Func نیز می‌توان خلاصه‌تر کرد:
    public class Algorithms<T> where T : new() 
    {
        public T Calculate(Func<T, T, T> add, IEnumerable<T> numbers)
        {
            var sum = new T();
            foreach (var number in numbers)
            {
                sum = add(sum, number);
            }
            return sum;
        }
    }
استفاده از Action و Func نیز یکی دیگر از روش‌های تزریق وابستگی‌ها است که در اینجا بکار گرفته شده‌است. برای استفاده از آن خواهیم داشت:
 var result = new Algorithms<int>().Calculate((a, b) => a + b, new[] { 1, 2, 3 });
آرگومان اول روش جمع زدن را مشخص می‌کند و آرگومان دوم، لیستی است که باید اعضای آن جمع زده شوند.


روش دوم: استفاده از واژه‌ی کلیدی dynamic

با استفاده از واژه‌ی کلیدی dynamic می‌توان بررسی نوع داده‌ها را به زمان اجرا موکول کرد. به این ترتیب دیگر کامپایلر مشکلی با کامپایل قطعه کد ذیل نخواهد داشت:
    public class Calculator<T> : ICalculator<T>
    {
        public T Add(T operand1, T operand2)
        {
            return (dynamic)operand1 + operand2;
        }
    }
و مثال زیر نیز به خوبی کار می‌کند:
 var test = new Calculator<int>().Add(1, 2);
البته بدیهی است که نوع تعریف شده در اینجا باید دارای عملگر + باشد. در غیر اینصورت در زمان اجرا برنامه با یک خطا خاتمه خواهد یافت.
روش فوق نسبت به حالتی که بر اساس نوع T تصمیم‌گیری شود و از عملگر + متناظری استفاده گردد، خوانایی بهتری دارد:
public T Add(T t1, T t2)
{
    if (typeof(T) == typeof(double))
    {
        var d1 = (double)t1;
        var d2 = (double)t2;
        return (T)(d1 + d2);
    }
    else if (typeof(T) == typeof(int)){
        var i1 = (int)t1;
        var i2 = (int)t2;
        return (T)(i1 + i2);
    }
    else ...
}


روش سوم: استفاده از Expression Trees

روش زیر بسیار شبیه است به حالتیکه از Func در روش اول استفاده شد. در اینجا این Func به صورت پویا تولید و سپس صدا زده می‌شود:
using System;
using System.Linq.Expressions;

namespace GenericsArithmetic
{
    public class Solution3
    {
        public T Add<T>(T a, T b)
        {
            var paramA = Expression.Parameter(typeof(T), "a");
            var paramB = Expression.Parameter(typeof(T), "b");

            var body = Expression.Add(paramA, paramB);
            var add = Expression.Lambda<Func<T, T, T>>(body, paramA, paramB).Compile();
            return add(a, b);
        }
    }
}
البته این مثال، یک مثال ابتدایی در این مورد است. بر همین مبنا و ایده، یک کتابخانه‌ی با کارآیی بالا، تحت عنوان Generic Operators که جزو Misc utils می‌باشد، تهیه شده‌است.
به کمک کتابخانه‌ی Generic Operators، کدهای جمع زدن اعضای یک لیست جنریک به صورت ذیل خلاصه می‌شوند:
public static T Sum<T>(this IEnumerable<T> source)
{
    T sum = Operator<T>.Zero;
    foreach (T value in source)
    {
            sum = Operator.Add(sum, value);
    }
    return sum;
}
مطالب
ایجاد Helper سفارشی جهت نمایش ویدئو در ASP.NET MVC
معرفی HTML Helpers
در صورتی که در مورد Helper‌ها در ASP.NET MVC اطلاعات بیشتری نیاز دارید پیشنهاد می‌کنم ابتدا این مطلب + را مطالعه کنید.
 
ایجاد یک HTML Helper سفارشی برای نمایش ویدئو‌های سایت آپارات
قبل از ایجاد هر Helper ی باید با خروجی نهایی آن آشنا بود. پس از بررسی خروجی نهایی کافیست بخش هایی از آن را Optional کنیم تا در زمان استفاده از آن، نسبت به مقادیر این دسته از پارامترها تصمیم گیری کنیم.
سایت آپارات نمونه‌ی فارسی سایت YouTube است و امکان اشتراک گذاری فایل‌های ویدئویی را برای کاربران مهیا کرده است. در این سایت در صفحه نمایش هر ویدئو، بخشی با عنوان "دریافت کد ویدئو" وجود دارد. با کمی بررسی در کدهای نهایی ایجاد شده توسط دستورات ذکر شده در این بخش، کد نهایی برای نمایش یک ویدئو به صورت زیر خواهد بود:

<embed height="400" width="500" 
flashvars="config=http://www.aparat.com//video/video/config/videohash/BA9Md/watchtype/embed" 
allowfullscreen="true" 
quality="high" name="aparattv_BA9Md" id="aparattv_BA9Md" 
src="http://host10.aparat.com/public/player/aparattv" type="application/x-shockwave-flash">
برای تولید پویای این کد Helper زیر می‌تواند مفید باشد:
using System.Web.Mvc;

namespace MvcApplication1
{
    public static class AparatPlayerHelper
    {
        public static MvcHtmlString AparatPlayer(this HtmlHelper helper, string mediafile, int height, int width)
        {
            var player = @"<embed height=""{0}"" width=""{1}"" flashvars=""config=http://www.aparat.com//video/video/config/videohash/{2}/watchtype/embed"" 
                                allowfullscreen=""true"" 
                                quality=""high"" 
                                name=""aparattv_{2}"" id=""aparattv_{2}"""" src=""http://host10.aparat.com/public/player/aparattv"" 
                                type=""application/x-shockwave-flash"">";

            player = string.Format(player, height, width, mediafile);
            return new MvcHtmlString(player);
        }
    }
}
نحوه استفاده از این Helper:
@Html.AparatPlayer("BA9Md", 400, 500)
اگر به آدرس صفحات در سایت آپارات دقت کنید URL جاری به یک عبارت چند حرقی ختم می‌شود که از این عبارت به عنوان مقدار پارامتر mediafile و شناسه منحصر بفرد فایل ویدئو استفاده شده است.
ایجاد Helper سفارشی جهت نمایش ویدئو‌های YouTube:
با توجه به توضیحات فوق یک Helper سفارشی برای نمایش ویدئو‌های YouTube بصورت زیر است:
using System;
using System.Drawing;
using System.Web.Mvc;

namespace MvcApplication1
{

    public static class YouTubePlayerHelper
    {
        public static MvcHtmlString YouTubePlayer(this HtmlHelper helper, string playerId, string mediaFile, YouTubePlayerOption youtubePlayerOption)
        {

            const string baseURL = "http://www.youtube.com/v/";

            // YouTube Embedded Code
            var player = @"<div id=""YouTubePlayer_{7}""width:{1}px; height:{2}px;"">
                                 <object width=""{1}"" height=""{2}"">
                                 <param name=""movie"" value=""{6}{0}&fs=1&border={3}&color1={4}&color2={5}""></param>
                                 <param name=""allowFullScreen"" value=""true""></param>
                                 <embed src=""{6}{0}&fs=1&border={3}&color1={4}&color2={5}""
                                 type = ""application/x-shockwave-flash""
                                 width=""{1}"" height=""{2}"" allowfullscreen=""true""></embed>
                                 </object>
                             </div>";

            // Replace All The Value
            player = String.Format(player, mediaFile, youtubePlayerOption.Width, youtubePlayerOption.Height, (youtubePlayerOption.Border ? "1" : "0"), ConvertColorToHexa.ConvertColorToHexaString(youtubePlayerOption.PrimaryColor), ConvertColorToHexa.ConvertColorToHexaString(youtubePlayerOption.SecondaryColor), baseURL, playerId);

            //Retrun Embedded Code
            return new MvcHtmlString(player);
        }
    }

    public class YouTubePlayerOption
    {
        int _width = 425;
        int _height = 355;
        Color _color1 = Color.Black;
        Color _color2 = Color.Aqua;

        public YouTubePlayerOption()
        {
            Border = false;
        }

        public int Width { get { return _width; } set { _width = value; } }
        public int Height { get { return _height; } set { _height = value; } }
        public Color PrimaryColor { get { return _color1; } set { _color1 = value; } }
        public Color SecondaryColor { get { return _color2; } set { _color2 = value; } }
        public bool Border { get; set; }
    }

    public class ConvertColorToHexa
    {
        private static readonly char[] HexDigits =
            {
                '0', '1', '2', '3', '4', '5', '6', '7',
                '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'
            };

        public static string ConvertColorToHexaString(Color color)
        {
            var bytes = new byte[3];
            bytes[0] = color.R;
            bytes[1] = color.G;
            bytes[2] = color.B;
            var chars = new char[bytes.Length * 2];
            for (int i = 0; i < bytes.Length; i++)
            {
                int b = bytes[i];
                chars[i * 2] = HexDigits[b >> 4];
                chars[i * 2 + 1] = HexDigits[b & 0xF];
            }
            return new string(chars);
        }


    }        

}
نحوه استفاده:
@Html.YouTubePlayer("Casablanca", "iLdqKUkkM6w", new YouTubePlayerOption()
                                {
                                    Border = true
                                })
در اینجا جهت مقداردهی پارمتر‌های پخش کننده ویدئو، از کلاس YouTubePlayerOption استفاده شده است.
مطالب
قرار دادن نمودارهای MS Chart در گزارشات PdfReport
در حالت کلی، هر شیءایی را که بتوان تبدیل به تصویر کرد، قابلیت قرارگیری در یک فایل PDF را هم خواهد داشت. از این نمونه می‌توان به اشیاء MSChart اشاره کرد که از دات نت 4 جزئی از کتابخانه‌های اصلی دات نت شده‌اند و البته برای دات نت سه و نیم نیز به صورت جداگانه قابل دریافت است.
در ادامه مثالی را بررسی خواهیم کرد که بر اساس ردیف‌های گزارش آن، یک نمودار، به انتهای گزارش اضافه خواهد شد.
کدهای کامل این مثال را در ذیل مشاهده می‌کنید:
using System;
using System.Collections.Generic;
using PdfReportSamples.Models;
using PdfRpt.Core.Contracts;
using PdfRpt.Core.Helper;
using PdfRpt.FluentInterface;

namespace PdfReportSamples.ChartImage
{
    public class ChartImagePdfReport
    {
        public IPdfReportData CreatePdfReport()
        {
            var chart = new MSChartHelper
                {
                    AxisXTitle = "User",
                    AxisYTitle = "Balance",
                    ChartTitle = "Users Balance",
                    AxisTitleFont = new System.Drawing.Font("Tahoma", 12f),
                    LabelStyleFont = new System.Drawing.Font("Tahoma", 10f),
                    ChartTitleFont = new System.Drawing.Font("Arial", 16f, System.Drawing.FontStyle.Bold),
                    LegendsFont = new System.Drawing.Font("Tahoma", 12f)
                };

            return new PdfReport().DocumentPreferences(doc =>
            {
                doc.RunDirection(PdfRunDirection.RightToLeft);
                doc.Orientation(PageOrientation.Portrait);
                doc.PageSize(PdfPageSize.A4);
                doc.DocumentMetadata(new DocumentMetadata { Author = "Vahid", Application = "PdfRpt", Keywords = "Test", Subject = "Test Rpt", Title = "Test" });
            })
             .DefaultFonts(fonts =>
             {
                 fonts.Path(string.Format("{0}\\fonts\\irsans.ttf", AppPath.ApplicationPath),
                            string.Format("{0}\\fonts\\verdana.ttf", Environment.GetEnvironmentVariable("SystemRoot")));
             })
             .PagesFooter(footer =>
             {
                 footer.DefaultFooter(printDate: DateTime.Now.ToString("MM/dd/yyyy"));
             })
             .PagesHeader(header =>
             {
                 header.DefaultHeader(defaultHeader =>
                 {
                     defaultHeader.ImagePath(AppPath.ApplicationPath + "\\Images\\01.png");
                     defaultHeader.Message("گزارش جدید ما");
                 });
             })
             .MainTableTemplate(template =>
             {
                 template.BasicTemplate(BasicTemplate.ClassicTemplate);
             })
             .MainTablePreferences(table =>
             {
                 table.ColumnsWidthsType(TableColumnWidthType.Relative);
             })
             .MainTableDataSource(dataSource =>
             {
                 var listOfRows = new List<User>();
                 for (var i = 0; i < 7; i++)
                 {
                     listOfRows.Add(new User { Id = i, LastName = "نام خانوادگی " + i, Name = "نام " + i, Balance = (i * 50) + 1000 });
                 }
                 dataSource.StronglyTypedList(listOfRows);
             })
             .MainTableEvents(events =>
             {
                 events.DataSourceIsEmpty(message: "There is no data available to display.");
                 events.DocumentOpened(args =>
                 {
                     chart.ChartInit(width: (int)args.PdfWriter.PageSize.Width - 100, height: 250);
                 });
                 events.RowAdded(args =>
                 {
                     if (args.RowType == RowType.DataTableRow)
                     {
                         var name = args.TableRowData.GetValueOf<User>(x => x.Name);
                         if (name == null) return;

                         var balance = args.TableRowData.GetValueOf<User>(x => x.Balance);
                         if (balance == null) return;

                         chart.AddXY(name, balance);
                     }
                 });
                 events.DocumentClosing(args =>
                 {
                     chart.AddChartToPage(args.PdfDoc);
                     chart.FreeResources();
                 });
             })
             .MainTableSummarySettings(summary =>
             {
                 summary.OverallSummarySettings("جمع");
                 summary.PreviousPageSummarySettings("نقل از صفحه قبل");
             })
             .MainTableColumns(columns =>
             {
                 columns.AddColumn(column =>
                 {
                     column.PropertyName("rowNo");
                     column.IsRowNumber(true);
                     column.CellsHorizontalAlignment(HorizontalAlignment.Center);
                     column.IsVisible(true);
                     column.Order(0);
                     column.Width(1);
                     column.HeaderCell("ردیف", captionRotation: 90);
                 });

                 columns.AddColumn(column =>
                 {
                     column.PropertyName<User>(x => x.Id);
                     column.CellsHorizontalAlignment(HorizontalAlignment.Center);
                     column.IsVisible(true);
                     column.Order(1);
                     column.Width(2);
                     column.HeaderCell("شماره");
                 });

                 columns.AddColumn(column =>
                 {
                     column.PropertyName<User>(x => x.Name);
                     column.CellsHorizontalAlignment(HorizontalAlignment.Center);
                     column.IsVisible(true);
                     column.Order(2);
                     column.Width(2);
                     column.HeaderCell("نام");
                 });

                 columns.AddColumn(column =>
                 {
                     column.PropertyName<User>(x => x.LastName);
                     column.CellsHorizontalAlignment(HorizontalAlignment.Center);
                     column.IsVisible(true);
                     column.Order(3);
                     column.Width(3);
                     column.HeaderCell("نام خانوادگی");
                 });

                 columns.AddColumn(column =>
                 {
                     column.PropertyName<User>(x => x.Balance);
                     column.HeaderCell("موجودی");
                     column.ColumnItemsTemplate(template =>
                     {
                         template.TextBlock();
                         template.DisplayFormatFormula(obj => obj == null ? string.Empty : string.Format("{0:n0}", obj));
                     });
                     column.Width(2);
                     column.AggregateFunction(aggregateFunction =>
                     {
                         aggregateFunction.NumericAggregateFunction(AggregateFunction.Sum);
                         aggregateFunction.DisplayFormatFormula(obj => obj == null ? string.Empty : string.Format("{0:n0}", obj));
                     });
                     column.CellsHorizontalAlignment(HorizontalAlignment.Center);
                     column.IsVisible(true);
                     column.Order(4);
                 });
             })
             .Generate(data => data.AsPdfFile(AppPath.ApplicationPath + "\\Pdf\\RptChartSample.pdf"));
        }
    }
}
برای تهیه این گزارش و افزودن نمودار به آن، از کلاس کمکی ذیل استفاده شده است:
using System.Drawing;
using System.IO;
//It's part of the .NET 4.0+ now.
using System.Windows.Forms.DataVisualization.Charting;
using iTextSharp.text;
using iTextSharp.text.pdf;
using PdfRpt.Core.Helper;

namespace PdfReportSamples.ChartImage
{
    public class MSChartHelper
    {
        // MS Chart learning tutorials:
        // http://weblogs.asp.net/scottgu/archive/2010/02/07/built-in-charting-controls-vs-2010-and-net-4-series.aspx
        Chart _chart;

        public System.Drawing.Font AxisTitleFont { set; get; }

        public string AxisXTitle { set; get; }

        public string AxisYTitle { set; get; }

        public string ChartTitle { set; get; }

        public System.Drawing.Font ChartTitleFont { set; get; }

        public System.Drawing.Font LabelStyleFont { set; get; }

        public System.Drawing.Font LegendsFont { set; get; }

        public void AddChartToPage(Document pdfDoc,
                                   int scalePercent = 100,
                                   float spacingBefore = 20,
                                   float spacingAfter = 10,
                                   float widthPercentage = 80)
        {
            using (var chartimage = new MemoryStream())
            {
                _chart.SaveImage(chartimage, ChartImageFormat.Bmp); //BMP gives the best compression result

                var iTextSharpImage = PdfImageHelper.GetITextSharpImageFromByteArray(chartimage.GetBuffer());
                iTextSharpImage.ScalePercent(scalePercent);
                iTextSharpImage.Alignment = Element.ALIGN_CENTER;

                var table = new PdfPTable(1)
                {
                    WidthPercentage = widthPercentage,
                    SpacingBefore = spacingBefore,
                    SpacingAfter = spacingAfter
                };
                table.AddCell(iTextSharpImage);

                pdfDoc.Add(table);
            }
        }

        public void AddXY(object xValue, params object[] yValue)
        {
            _chart.Series[0].Points.AddXY(xValue, yValue);
        }

        public void ChartInit(int width, int height)
        {
            _chart = new Chart
            {
                Width = width,
                Height = height,
                AntiAliasing = AntiAliasingStyles.All,
                TextAntiAliasingQuality = TextAntiAliasingQuality.High,
                Palette = ChartColorPalette.BrightPastel,
                BackColor = ColorTranslator.FromHtml("#F3DFC1"),
                BackGradientStyle = GradientStyle.TopBottom
            };

            setBorder();
            setTitles();
            setChartAreas();
            setLegends();
            setSeries();
        }

        public void FreeResources()
        {
            if (_chart != null && !_chart.IsDisposed)
                _chart.Dispose();
        }        

        private void setBorder()
        {
            _chart.BorderSkin.SkinStyle = BorderSkinStyle.Emboss;
            _chart.BorderlineWidth = 2;
            _chart.BorderlineColor = Color.FromArgb(181, 64, 1);
            _chart.BorderlineDashStyle = ChartDashStyle.Solid;
        }

        private void setChartAreas()
        {
            _chart.ChartAreas.Add("ChartArea1");
            _chart.ChartAreas[0].AxisX.Title = AxisXTitle;
            _chart.ChartAreas[0].AxisY.Title = AxisYTitle;
            _chart.ChartAreas[0].AxisX.TitleFont = AxisTitleFont;
            _chart.ChartAreas[0].AxisY.TitleFont = AxisTitleFont;
            _chart.ChartAreas[0].AxisX.LabelStyle.Font = LabelStyleFont;
            _chart.ChartAreas[0].AxisX.LabelStyle.Angle = -90;
            _chart.ChartAreas[0].BackColor = Color.White;
            _chart.ChartAreas[0].AxisX.LineColor = Color.FromArgb(64, 64, 64);
            _chart.ChartAreas[0].AxisX.MajorGrid.LineColor = Color.FromArgb(64, 64, 64);
            _chart.ChartAreas[0].AxisY.LineColor = Color.FromArgb(64, 64, 64);
            _chart.ChartAreas[0].AxisY.MajorGrid.LineColor = Color.FromArgb(64, 64, 64);
        }

        private void setLegends()
        {
            _chart.Legends.Add("Default");
            _chart.Legends[0].LegendStyle = LegendStyle.Row;
            _chart.Legends[0].IsTextAutoFit = false;
            _chart.Legends[0].DockedToChartArea = "ChartArea1";
            _chart.Legends[0].Docking = Docking.Bottom;
            _chart.Legends[0].IsDockedInsideChartArea = false;
            _chart.Legends[0].BackColor = Color.Transparent;
            _chart.Legends[0].Font = LegendsFont;
        }

        private void setSeries()
        {
            _chart.Series.Add("");
            _chart.Series[0].ChartType = SeriesChartType.Column;
            _chart.Series[0].Palette = ChartColorPalette.EarthTones;
            _chart.Series[0].IsValueShownAsLabel = true;
            _chart.Series[0].IsVisibleInLegend = false;
        }

        private void setTitles()
        {
            _chart.Titles.Add(ChartTitle);
            _chart.Titles[0].Font = ChartTitleFont;
            _chart.Titles[0].TextStyle = TextStyle.Shadow;
            _chart.Titles[0].ShadowOffset = 3;
            _chart.Titles[0].ShadowColor = Color.FromArgb(32, 0, 0);
            _chart.Titles[0].Alignment = ContentAlignment.TopCenter;
            _chart.Titles[0].ForeColor = Color.FromArgb(26, 59, 105);
        }
    }
}

توضیحات:
- استفاده از MSChart در اینجا از این جهت مناسب است که فراگیری کار کردن با آن عمومی بوده و در پروژه‌های وب و ویندوز کاربرد دارد و احتمالا هم اکنون با نحوه کارکردن با آن آشنا هستید، زیرا از سال 2010 به دات نت اضافه شده است.
- در این بین تنها متد جدید و مهم کلاس MSChartHelper، متد AddChartToPage آن است. به کمک متد chart.SaveImage می‌توان تصویر نهایی معادل یک نمودار را در حافظه ذخیره کرد. سپس با استفاده از متد PdfImageHelper.GetITextSharpImageFromByteArray، این تصویر موجود در حافظه را به معادل قابل استفاده آن در iTextSharp تبدیل کرده و به صفحه اضافه خواهیم کرد.
- در کدهای اصلی تولید گزارش، مقدار دهی کلاس کمکی MSChartHelper در قسمت رخدادهای قابل استفاده PdfReport در متد MainTableEvents آن انجام شده است:
در روال رویدادگردان DocumentOpened، بر اساس عرض واقعی صفحه، عرض نمودار را مشخص می‌کنیم:
                 events.DocumentOpened(args =>
                 {
                     chart.ChartInit(width: (int)args.PdfWriter.PageSize.Width - 100, height: 250);

                 });
سپس در روال رویدادگردان RowAdded، فرصت خواهیم داشت به اطلاعات در حال افزوده شدن به گزارش دسترسی داشته باشیم. این اطلاعات را به متد افزودن XY نمودار ارسال خواهیم کرد:
                 events.RowAdded(args =>
                 {
                     if (args.RowType == RowType.DataTableRow)
                     {
                         var name = args.TableRowData.GetValueOf<User>(x => x.Name);
                         if (name == null) return;

                         var balance = args.TableRowData.GetValueOf<User>(x => x.Balance);
                         if (balance == null) return;

                         chart.AddXY(name, balance);
                     }
                 });
و در آخر، پیش از بسته شدن فایل PDF تولیدی (DocumentClosing)، نمودار نهایی را به صفحه اضافه کرده و منابع مرتبط با آن‌را آزاد خواهیم کرد:
                 events.DocumentClosing(args =>
                 {
                     chart.AddChartToPage(args.PdfDoc);
                     chart.FreeResources();
                 });
بنابراین اگر این سؤال عمومی وجود دارد که آیا می‌توان در این بین، به ابتدا و انتهای گزارش اشیایی را افزود، روش کلی آن‌را در روال‌های فوق ملاحظه می‌کنید. توسط args.PdfDoc و args.PdfWriter می‌توان به زیرساخت iTextSharp دسترسی یافت.
 

مطالب
بررسی تغییرات HttpClient در NET 5.0.
پیشتر بسته‌ی نیوگتی به نام Microsoft.AspNet.WebApi.Client وجود داشت/دارد که کار آن ارائه‌ی یک سری متد الحاقی کار با JSON، جهت HttpClient است. در نگارش 5 دات نت، تمام این متدهای الحاقی جزئی از دات نت استاندارد شده‌اند و برای کار با آن‌ها دیگر نیازی به استفاده‌ی از بسته‌های نیوگت خاصی نیست.


تغییرات API دات نت 5 از دیدگاه افزونه‌های HttpClient

در اینجا لیست کامل متدهای الحاقی اضافه شده‌ی به فضای نام جدید و استاندارد System.Net.Http.Json را مشاهده می‌کنید:
namespace System.Net.Http.Json {
    public static class HttpClientJsonExtensions {
        public static Task<object> GetFromJsonAsync(this HttpClient client, string requestUri, Type type, JsonSerializerOptions options, CancellationToken cancellationToken = default(CancellationToken));
        public static Task<object> GetFromJsonAsync(this HttpClient client, string requestUri, Type type, CancellationToken cancellationToken = default(CancellationToken));
        public static Task<object> GetFromJsonAsync(this HttpClient client, Uri requestUri, Type type, JsonSerializerOptions options, CancellationToken cancellationToken = default(CancellationToken));
        public static Task<object> GetFromJsonAsync(this HttpClient client, Uri requestUri, Type type, CancellationToken cancellationToken = default(CancellationToken));
        public static Task<TValue> GetFromJsonAsync<TValue>(this HttpClient client, string requestUri, JsonSerializerOptions options, CancellationToken cancellationToken = default(CancellationToken));
        public static Task<TValue> GetFromJsonAsync<TValue>(this HttpClient client, string requestUri, CancellationToken cancellationToken = default(CancellationToken));
        public static Task<TValue> GetFromJsonAsync<TValue>(this HttpClient client, Uri requestUri, JsonSerializerOptions options, CancellationToken cancellationToken = default(CancellationToken));
        public static Task<TValue> GetFromJsonAsync<TValue>(this HttpClient client, Uri requestUri, CancellationToken cancellationToken = default(CancellationToken));
        public static Task<HttpResponseMessage> PostAsJsonAsync<TValue>(this HttpClient client, string requestUri, TValue value, JsonSerializerOptions options = null, CancellationToken cancellationToken = default(CancellationToken));
        public static Task<HttpResponseMessage> PostAsJsonAsync<TValue>(this HttpClient client, string requestUri, TValue value, CancellationToken cancellationToken);
        public static Task<HttpResponseMessage> PostAsJsonAsync<TValue>(this HttpClient client, Uri requestUri, TValue value, JsonSerializerOptions options = null, CancellationToken cancellationToken = default(CancellationToken));
        public static Task<HttpResponseMessage> PostAsJsonAsync<TValue>(this HttpClient client, Uri requestUri, TValue value, CancellationToken cancellationToken);
        public static Task<HttpResponseMessage> PutAsJsonAsync<TValue>(this HttpClient client, string requestUri, TValue value, JsonSerializerOptions options = null, CancellationToken cancellationToken = default(CancellationToken));
        public static Task<HttpResponseMessage> PutAsJsonAsync<TValue>(this HttpClient client, string requestUri, TValue value, CancellationToken cancellationToken);
        public static Task<HttpResponseMessage> PutAsJsonAsync<TValue>(this HttpClient client, Uri requestUri, TValue value, JsonSerializerOptions options = null, CancellationToken cancellationToken = default(CancellationToken));
        public static Task<HttpResponseMessage> PutAsJsonAsync<TValue>(this HttpClient client, Uri requestUri, TValue value, CancellationToken cancellationToken);
    }

    public static class HttpContentJsonExtensions {
        public static Task<object> ReadFromJsonAsync(this HttpContent content, Type type, JsonSerializerOptions options = null, CancellationToken cancellationToken = default(CancellationToken));
        public static Task<T> ReadFromJsonAsync<T>(this HttpContent content, JsonSerializerOptions options = null, CancellationToken cancellationToken = default(CancellationToken));
    }

    public sealed class JsonContent : HttpContent {
        public Type ObjectType { get; }
        public object Value { get; }
        public static JsonContent Create(object inputValue, Type inputType, MediaTypeHeaderValue mediaType = null, JsonSerializerOptions options = null);
        public static JsonContent Create<T>(T inputValue, MediaTypeHeaderValue mediaType = null, JsonSerializerOptions options = null);
        protected override void SerializeToStream(Stream stream, TransportContext context, CancellationToken cancellationToken);
        protected override Task SerializeToStreamAsync(Stream stream, TransportContext context);
        protected override Task SerializeToStreamAsync(Stream stream, TransportContext context, CancellationToken cancellationToken);
        protected override bool TryComputeLength(out long length);
    }
}


متدهای الحاقی جدید کلاس HttpClientJsonExtensions

این متدها به صورت خلاصه شامل سه متد زیر می‌شوند:
- GetFromJsonAsync : یک درخواست Get را به آدرسی خاص ارسال کرده و خروجی JSON دریافتی را به کمک امکانات توکار System.Text.Json، پردازش و deserialize می‌کند.
- PostAsJsonAsync : یک درخواست POST را به آدرسی خاص، ارسال می‌کند. شیء ارسالی به آن به صورت خودکار به JSON تبدیل شده و سپس به سمت سرور ارسال می‌گردد.
- PutAsJsonAsync : یک درخواست PUT را به آدرسی خاص، ارسال می‌کند. شیء ارسالی به آن به صورت خودکار به JSON تبدیل شده و سپس به سمت سرور ارسال می‌گردد.

در ذیل چند مثال را در مورد نحوه‌ی کار با این متدهای الحاقی جدید فضای نام استاندارد System.Net.Http.Json، مشاهده می‌کنید:
var httpClient = new HttpClient();
httpClient.BaseAddress = new Uri("https://localhost:5000");

var profiles = await httpClient.GetFromJsonAsync<Profile[]>("api/users/profiles");

var profile = new Profile { FirstName = "User 1", LastName = "Name 1", Age = 25 };
using var response1 = await httpClient.PostAsJsonAsync("api/users/profiles", profile);
response1.EnsureSuccessStatusCode();


var updatedProfile = new Profile { FirstName = "User 2", LastName = "Name 2", Age = 40 };
using var response2 = await httpClient.PutAsJsonAsync("api/users/profiles", profile);
response2.EnsureSuccessStatusCode();

اگر می‌خواستیم یک چنین کارهایی را پیش از دات نت 5 انجام دهیم، می‌بایستی قسمت Serialize کردن و همچنین تنظیم content-type را دستی انجام می‌دادیم:
var profile = new Profile { FirstName = "User 1", LastName = "Name 1", Age = 25 };
var json = JsonSerializer.Serialize(profile);
var stringContent = new StringContent(json, Encoding.UTF8, "application/json");
using var response4 = await httpClient.PostAsync("api/users/profiles", stringContent);
response4.EnsureSuccessStatusCode();


متدهای الحاقی جدید کلاس HttpContentJsonExtensions

این کلاس، متد الحاقی جدید ReadFromJsonAsync را ارائه می‌دهد که کار آن، خواندن یک محتوای HTTP از نوع HttpContent و deserialize آن به صورت JSON است. یک مثال:
var httpClient = new HttpClient();
httpClient.BaseAddress = new Uri("https://localhost:5000");

var request = new HttpRequestMessage(HttpMethod.Get, "api/users/profiles");
using var response1 = await httpClient.SendAsync(request);
if (response1.IsSuccessStatusCode)
{
  var profiles = await response1.Content.ReadFromJsonAsync<Profile[]>();
}

انجام اینکار در نگارش‌های پیشین دات نت، نیاز به فراخوانی دستی JsonSerializer.DeserializeAsync را دارد:
var request = new HttpRequestMessage(HttpMethod.Get, "api/users/profiles");
using var response2 = await httpClient.SendAsync(request);
if (response2.IsSuccessStatusCode)
{
   using var streamResult = await response2.Content.ReadAsStreamAsync();
   var profiles = JsonSerializer.DeserializeAsync<Profile[]>(streamResult);
}


متدهای جدید کلاس JsonContent

روش‌های زیادی برای کار با HttpClient وجود دارند. یک روش آن، ساخت دستی HttpRequestMessage و سپس ارسال آن توسط متد SendAsync است؛ بجای استفاده از متد PostAsJsonAsync که بررسی شد. در این حالت با استفاده از متد جدید JsonContent.Create، می‌توان کار تبدیل یک شیء را به JSON و همچنین تنظیم content-type را به صورت خودکار انجام داد:
var httpClient = new HttpClient();
var uri = "https://localhost:5000";
httpClient.BaseAddress = new Uri(uri);

var requestMessage = new HttpRequestMessage(HttpMethod.Post, "https://localhost:5000")
{
   Content = JsonContent.Create(new Profile { FirstName = "User 1", LastName = "Name 1", Age = 25 })
};
using var reponse1 = await httpClient.SendAsync(requestMessage);
reponse1.EnsureSuccessStatusCode();
مطالب
تزریق وابستگی‌ها در ASP.NET Core - بخش 4 - طول حیات سرویس ها یا Service Lifetime
در قسمت‌های قبلی این سری، به ترتیب ابتدا در مورد مبحث تزریق وابستگی‌ها صبحت کردیم، بعد اولین سرویس‌مان را در ASP.NET Core ثبت و واکشی کردیم. در بخش سوم، تنظیمات را درون سامانه، ثبت و استفاده کردیم و حالا در این بخش می‌خواهیم به مبحث طول حیات سرویس‌ها بپردازیم.
همانطور که گفتیم، وظیفه‌ی DI Container، ایجاد یک نمونه از سرویس درخواست شده، تزریق آن به کلاس درخواست دهنده و در انتها از بین بردن یا Dispose شیء ایجاد شده از سرویس ثبت شده‌است. بنابراین ما باید در هنگام ثبت سرویس، بر اساس تحلیل و نیاز برنامه‌ی خودمان، طول عمر سرویس/Service Life Time را مشخص کنیم.

بصورت کلی در Microsoft Dependency Injection Container و اکثر DI Container‌های دیگر، 3 نوع کلی چرخه‌ی حیات وجود دارند که به ترتیب پایداری و طول عمر شیء ایجاد شده، در زیر آورده شده‌اند:
  •  Singleton
  •  Scoped
  •  Transient

Singleton (یگانه)

فقط و فقط یک شیء از سرویس ثبت شده با این طول عمر، در اولین درخواست ایجاد می‌شود و سپس در کل طول حیات برنامه، از همین شیء ایجاد شده، استفاده می‌گردد.  به همین دلیل به آن «یگانه» یا Singleton می‌گویند. هر زمانیکه این سرویس در خواست داده می‌شود، DI Container، همان یک شیء را در اختیار درخواست دهنده قرار می‌دهد و این شیء، هیچگاه از بین نمی‌رود.  به بیان دیگر، DI Container هیچگاه این شیء را از بین نمی‌برد. شیء ساخته شده از سرویس ثبت شده‌ی با حالت Singleton، بین تمامی استفاده کنندگان، به صورت اشتراکی استفاده می‌شود. این طول عمر تقریبا مشابه‌ی اشیاء ساخته شده توسط Singleton Pattern عمل می‌کند.
با توجه به مطالب گفته شده، ویژگی‌های سرویس‌های Singleton به شرح زیر هستند:
  •   در اولین درخواست به سرویس، یک نمونه از آن ساخته می‌شود و تا پایان برنامه در حافظه نگه داشته می‌شود.
  •   در سایر درخواست‌ها، همان یک نمونه‌ی ساخته شده‌ی از سرویس، ارائه داده می‌شود. 
  •   به علت موجود بودن در حافظه، معمولا دسترسی به آن‌ها و عملکرد آن‌ها سریعتر است.
  •   بار کاری بر روی Garbage Collector فریمورک را کاهش می‌دهند.

بنابراین در هنگام تعریف کردن یک سرویس به صورت Singleton باید نکات زیر را مد نظر قرار بدهید:
  • باید سرویس مورد نظر Thread Safe باشد .
  •  نباید استفاده کننده‌ی از این سرویس، امکان تغییر State آن را داشته باشد.
  •  اگر ساخت شیء‌ای از یک سرویس، هزینه‌ی زیادی را داشته باشد ، احتمالا Singleton کردن آن می‌تواند ایده‌ی خوبی باشد.
  •  شیء ساخته شده‌ی از این سرویس، تا زمان اجرای برنامه، بخشی از حافظه‌ی برنامه را اشغال می‌کند. پس باید حجم اشغالی در حافظه را نیز مد نظر قرار داد.
  •  تعداد دفعات استفاده را در برابر مصرف حافظه در نظر بگیرید.
معمولا سرویس‌هایی مثل تنظیمات برنامه، از این نوع تعریف می‌شوند.

برای ثبت یک سرویس به صورت Singleton می‌توانیم از متدهای توسعه‌ای با نام ()AddSingleton، با سربارهای مختلف بر روی IServiceCollection استفاده کنیم. علاوه بر این، در هنگام استفاده از Option Pattern، متد Configure، خودش سرویس مورد نظر را به صورت Singleton ثبت می‌کند.

خب، به روش زیر سرویس GuidProvider را بعنوان یک Singleton  تعریف می‌کنیم:
services.AddSingleton(services => new GuidProvider());
 اکنون این سرویس را درون اکشن Index  و کنترلر HomeController تزریق می‌کنیم:
        public HomeController(ILogger<HomeController> logger,
            IMessageServiceA messageService,
            LiteDbConfig liteDbConfig,
            GuidProvider guidHelper)
        {
            _logger = logger;
            _messageService = messageService;
            _messageService = new MessageServiceAA();
            _guidHelper = guidHelper;
        }

حالا اگر برنامه را اجرا کنیم، می‌بینید که با تازه سازی صفحه‌ی Home/Index ، همچنان Id، برابر با یک رشته‌ی یکسان است. حتی اگر تب دیگری را در مرورگر باز کنیم و دوباره به این صفحه برویم، می‌بینیم که Id برابر همان رشته‌ی قبلی است و دلیل این موضوع، ثبت سرویس Guid Service به صورت Singleton است.


Scoped ( محدود شده )

به ازای هر درخواست (در اینجا معمولا درخواست‌های Http مد نظر است) یک نمونه از این سرویس ساخته می‌شود و در طول حیات این درخواست، DI Container به هر کلاسی که به این سرویس نیاز دارد، همان یک نمونه را برگشت می‌دهد و این نمونه در کل طول اجرای این درخواست، بین تمامی سرویس گیرندگان، یکسان است. هر زمانی، درخواست به پایان برسد، نمونه‌ی ساخته شده از سرویس، Disposed می‌گردد و GC می‌تواند آن را از بین ببرد.

معمولا سرویس‌های اتصال به پایگاه داده‌ها و کار بر روی آنها که شامل خواندن، نوشتن، ویرایش، حذف می‌شوند را با طول حیات Scoped ، درون DI Container ثبت می‌کنند . EF Core به صورت پیش فرض ، Db Context را به صورت Scoped ثبت می‌کند.

سرویس‌های Scoped در محدوده‌ی درخواست، مانند  Singleton عمل می‌کنند و شیء ساخته شده و وضعیت آن در بین تمامی سرویس‌هایی  که به آن نیاز دارند، مشترک است. بنابراین باید به این نکته در هنگام تعریف سرویس به صورت Scoped ، توجه داشته باشید.

تمام Middleware ‌های ASP.NET Core هم فقط همان نمونه‌ی ایجاد شده از سرویس Scoped را در طی اجرای یک درخواست خاص، می‌گیرند.

هر سرویسی که به سرویس‌های Scoped نیاز دارد، یا باید به صورت Transient و یا باید به صورت Scoped ثبت شود، تا مانع از این شویم که شیء ساخته شده، فراتر از طول حیات موردنظرمان، در حافظه بماند و از آن استفاده شود .

برای ثبت یک سرویس به صورت Scoped می‌توانیم از متدهای توسعه‌ای با نام AddScoped() با سربارهای مختلف بر روی IServiceCollection استفاده کنیم. در اینجا از نسخه‌ای که دو پارامتر جنریک را می‌گیرد، برای ثبت یک سرویس به صورت Scoped استفاده می‌کنیم:

services.AddScoped<IMessageServiceB, MessageServiceBA>();

می توانیم سرویس GuidProvider را  به جای Signleton ، به صورت Scoped ثبت کنیم: 

services.AddScoped(services => new GuidProvider());
حال اگر برنامه را اجرا کنیم، می بینید که این بار با تازه سازی صفحه‌ی Home/Index، مقدار  Id برابر با یک رشته‌ی جدید است.  

 

Transient (گذرا)

به ازای هر درخواست دهنده‌ی جدید، یک نمونه‌ی جدید از سرویس، توسط DI Container ساخته می‌شود و در اختیار آن قرار می‌گیرد.

سرویس‌هایی را به این صورت ثبت کنید که:

  •   نیاز به Thread Safe بودن داشته باشند.
  • نمی توانید طول عمر سرویس را حدس بزنید.

سرویس‌های Transient ، کارآیی پائین‌تری دارند و سربار عملکردی زیادی بر روی Garbage Collector می گذارند؛ ولی به علت اینکه به ازای هر واکشی، یک نمونه‌ی جدید از آن‌ها ساخته می‌شود و State بین این اشیاء به اشتراک گذاشته نمی‌شود، امنیت بیشتری دارند و درک و خطایابی آنها ساده‌تر است.

برای ثبت سرویس‌های Transient از متد توسعه‌ای AddTransient() استفاده می‌کنیم. سربارهای این متد مانند سربارهای متدهای AddSingleton() و AddScoped() است:

services.AddTransient<IMessageServiceC, MessageServiceCA>();

 

وابستگی‌های محصور شده

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

برای مثال اگر درون سرویسی با طول حیات Singleton، از یک سرویس با طول حیات Transient استفاده کنیم، اینکار باعث می‌شود که یک نمونه از سرویس Transient در طول حیات برنامه، همیشه درون حافظه بماند و این ممکن است باعث خطاهای عجیبی در هنگام اجرا شود که معمولا خطایابی و رفع آن‌ها مشکل است.


اثرات جانبی وابستگی‌های محصور شده:

  • به اشتراک گذاری تصادفی وضعیت یک شیء بین Thread ‌ها درون سرویس‌هایی که Thread Safe نیستند.
  • اشیاء، بیش از زمان پیش بینی شده‌ی برایشان، درون حافظه باقی می‌مانند.


سرویس‌های Transient می‌توانند به سرویس‌هایی با طول حیات زیر وابستگی داشته باشند:

  •   Transient
  •   Scoped
  •   Singleton

 

سرویس‌های Scoped می‌توانند به سرویس‌هایی با طول حیات زیر وابستگی داشته باشند:

  • Transient
  •   Scoped


سرویس‌های Singleton می‌توانند به سرویس هایی با طول حیات زیر وابستگی داشته باشند:

Singleton  


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


Scope Validation 

این قابلیت که به صورت پیش فرض در حالت توسعه‌ی برنامه‌های ASP.NET Core فعال است، در زمان شروع برنامه و در Startup ، بررسی می‌کند که سرویس‌ها، وابستگی به سرویس‌هایی با طول حیاتی مناسب، داشته باشند.