مطالب
SharePoint Client object Model
دو روش اصلی برای دسترسی به داده‌ها از طریق برنامه نویسی در SharePoint وجود دارند. روش اول استفاده از SharePoint API روی سرور است. زمانیکه شما کدی را مستقیم روی سرور SharePoint  اجرا می‌کنید، SharePoint API کنترل کامل تمام جنبه‌های شیرپوینت و داده‌ها را در اختیار شما می‌گذارد. اگر برنامه شما روی سرور اجرا نمی‌شود و نیاز به دسترسی به داده‌های شیرپوینت دارد، لازم است از SharePoint web services استفاده کنید. web services امکاناتی مشابه SharePoint  API را در اختیار شما می‌گذارد؛ هرچند همه امکانات را پوشش نمی‌دهد.

در SharePoint 2010 گزینه دیگری در برنامه نویسی، برای دسترسی به داده‌های SharePoint تدارک دیده شده است: Client Object Model. این یک روش جدید، در برنامه نویسی شیرپوینت است. اگرچه استفاده از web services، پوشش وسیعی از امکانات شیرپوینت را به شما می‌دهد، اما برنامه نویسی به روش Client Object Model و API با استفاده از web services بسیار متفاوت است. استفاده از web services کار را برای شما سخت خواهد کرد و لازم است دو روش برنامه نویسی کاملا مختلف را بیاموزید. همچنین فراخوانی web services با JavaScript پیچیده است و نیازمند ساخت و دستکاری XML‌های فراوان است. Client Object Model تمام این مسائل را حل و برنامه نویسی سمت client را راحت کرده است.

در واقع Client Object Model سه Object Model جدا از هم است:
 نسخه: .NET CLR برای ساخت WinForms, Windows Presentation Foundation (WPF), console applications
 نسخه Silverlight : برای کا با هر دو حالت داخل in-browser و out-of-browser Silverlight applications
 نسخه JavaScript : کدهای Ajax و jQuery را قادر می‌سازد تا داده‌های شیرپوینت را فراخوانی کنند

یکی از سوالاتی که در مورد Client Object Model پیش می‌آید، این است که چه کارهایی را با آن می‌شود انجام داد؟ Client Object Model امکان دسترسی به بیشتر اشیاء رایج را مانند sites, webs, content types, lists, folders, navigations فراهم می‌کند. این اشیا با اسم‌های مشابه در Client Object Model وجود دارند که در جدول زیر مشخص شده‌اند.



 در زیر یک مثال ساده از استفاده‌های Client Object Model را توضیح خواهم داد که لیست‌های موجود در سایت را در خروجی نمایش می‌دهد.
1- در Visual Studio یک پروژه Console application ایجاد کنید.
2- بر روی References کلیک راست کرده Add Reference را انتخاب کنید. از مسیر زیر
 C:\Program Files\Common Files\Microsoft Shared\Web Server Extensions\14\ISAPI
دو فایل زیر را اضافه کنید
 Microsoft.SharePoint.dll
Microsoft.SharePoint.Client.Runtime.dll

static void Main(string[] args)
        {
            var ctx = new ClientContext(@"http://localhost");
            var web = ctx.Web;
            var lists = web.Lists;
            ctx.Load(lists,
                l => l.Include
                    (list => list.Title).Where
                    (list => list.BaseType == BaseType.GenericList));
            ctx.ExecuteQuery();
            foreach (var list in lists)
                Console.WriteLine(list.Title);
            Console.ReadLine();
}
نظرات مطالب
مروری بر کاربردهای Action و Func - قسمت دوم
یک نکته‌ی تکمیلی: امضای نگارش‌های Task دار و Async این متدها

در حالت اول، Task فراخوانی شده یک خروجی را باز می‌گرداند و در حالت دوم، خروجی آن void است:
    private async Task<T> doSomethingAsync<T>(Func<Task<T>> task)
    {
        var result = default(T);
        try
        {
            result = await task();
        }
        catch (Exception ex)
        {
            // todo: log
        }
        return result;
    }

    private async Task doSomethingAsync(Func<Task> task)
    {
        await task();
    }
با یک چنین کاربردهای نمونه‌ای
    public async Task ExampleAsync()
    {
        await doSomethingAsync<string>(() => Task.FromResult("...."));
        await doSomethingAsync(() => Task.Delay(1000));
    }
مطالب
کوئری نویسی در EF Core - قسمت پنجم - اعمال تجمعی - بخش دوم
کوئری‌های تجمعی این قسمت، کمی پیچیده‌تر هستند و برای حل آن‌ها باید از window functions استفاده کرد و چون این مفهوم توسط EF-Core پشتیبانی نمی‌شود (منظور توسط LINQ to Entities آن است و نه SQL نویسی مستقیم)، در بعضی از موارد مجبور خواهیم شد اطلاعات مورد نیاز گزارش را از بانک اطلاعاتی دریافت کرده و سپس در سمت کلاینت توسط LINQ to Objects شکل دهی کنیم.


مثال 12: محاسبه کنید در سال 2012 و به ازای هر ماه مجزای آن، چه تعداد slots رزرو شده‌اند؛ قسمت دوم.

این مثال را در قسمت قبل (مثال 6 آن) نیز بررسی کردیم. در اینجا می‌خواهیم در گزارش نهایی تولید شده، پس از اتمام ردیف‌های یک ماه به ازای یک امکان خاص، جمع کل آن نیز درج شود و همچنین در پایان تمام ردیف‌ها، جمع کل نهایی ذکر شود؛ چیزی شبیه به تصویر زیر که در آن 910، جمع کل slots ماه 8 است و 9191، جمع کل سال.


روش پیشنهادی حل این مساله استفاده از مفهومی به نام «GROUP BY ROLLUP» است:
SELECT   facid,
         DATEPART(month, [StartTime]) AS month,
         sum(slots) AS slots
FROM     bookings
WHERE    starttime >= '2012-01-01'
         AND starttime < '2013-01-01'
GROUP BY ROLLUP(facid, DATEPART(month, [StartTime]))
ORDER BY facid, month;
یک چنین گروه بندی توسط LINQ to Entities پشتیبانی نمی‌شود. اما خلاصه‌ی این گزارش به این صورت است:
ابتدا جمع slots را گروه بندی شده بر اساس هر ماه سال محاسبه می‌کنیم. این قسمت توسط LINQ to Entities قابل انجام است؛ همان مثال 6 قسمت قبل است.
سپس این اطلاعات که اکنون در سمت کلاینت (یعنی برنامه‌ی ما) در حافظه موجود هستند، نیاز دارند به ازای هر گروه، یک جمع کل (sub total) و به ازای کل سال نیز یک جمع کل (grand total یا total) پیدا کنند.

ROLLUP(facid, month) اطلاعات تجمعی سلسه مراتبی پارامترهای ارسالی به آن را تولید می‌کند. یعنی (facid, month), (facid) و (). پیاده سازی LINQ to Objects این تابع را در اینجا می‌توانید مشاهده کنید: Utils\GroupingExtensions.cs

بنابراین راه حل این مساله به صورت زیر خواهد بود:
var date1 = new DateTime(2012, 01, 01);
var date2 = new DateTime(2013, 01, 01);

var facilities = context.Bookings
                                    .Where(booking => booking.StartTime >= date1
                                                        && booking.StartTime < date2)
                                    .GroupBy(booking => new { booking.FacId, booking.StartTime.Month })
                                    .Select(group => new
                                    {
                                        group.Key.FacId,
                                        group.Key.Month,
                                        TotalSlots = group.Sum(booking => booking.Slots)
                                    })
                                    .OrderBy(result => result.FacId)
                                        .ThenBy(result => result.Month)
                                    .ToList()
                            //This is new
                            .GroupByWithRollup(
                                item => item.FacId,
                                item => item.Month,
                                (primaryGrouping, secondaryGrouping) => new
                                {
                                    FacId = primaryGrouping.Key,
                                    Month = secondaryGrouping.Key,
                                    TotalSlots = secondaryGrouping.Sum(item => item.TotalSlots)
                                },
                                item => new
                                {
                                    FacId = item.Key,
                                    Month = -1,
                                    TotalSlots = item.SubTotal(subItem => subItem.TotalSlots)
                                },
                                items => new
                                {
                                    FacId = -1,
                                    Month = -1,
                                    TotalSlots = items.GrandTotal(subItem => subItem.TotalSlots)
                                });
تا جائیکه متد ToList فراخوانی شده، همان مثال 6 قسمت قبل است. پس از آن چون این لیست را درون حافظه داریم، اکنون متد الحاقی جدید GroupByWithRollup را به آن اعمال می‌کنیم تا اطلاعات گروه بندی اصلی، اطلاعات subTotal (همان ردیف اضافه‌ی تولید شده‌ی حاصل جمع هر گروه) و total (یا همان ردیف جمع کل گزارش) را تولید کند.
در اینجا سلول‌هایی که اطلاعاتی ندارند، با منهای یک مشخص شده‌اند؛ در گزارش اصلی با null مقدار دهی شده بودند.


مثال 13: به ازای نام هر کدام از امکانات موجود، جمع کل تعداد ساعات رزرو شده‌ی آن‌ها را محاسبه کنید.

هر slot تنها نیم ساعت است و گزارش نهایی باید به همراه ستون‌های facid, name, Total Hours باشد؛ مرتب شده بر اساس facid.
var items = context.Bookings
                                    .GroupBy(booking => new { booking.FacId, booking.Facility.Name })
                                    .Select(group => new
                                    {
                                        group.Key.FacId,
                                        group.Key.Name,
                                        TotalHours = group.Sum(booking => booking.Slots) / 2M
                                    })
                                    .OrderBy(result => result.FacId)
                                    .ToList();
در اینجا روش گروه بندی بر اساس FacId که از جدول Bookings تامین می‌شود و Facility.Name را که از جدول دیگری به نامFacilities  تامین می‌شود، ملاحظه می‌کنید که به صورت خودکار جوین لازم آن در کوئری نهایی تولید خواهد شد:



مثال 14: گزارشی را از اولین رزرو کاربران پس از September 1st 2012، تهیه کنید.

این گزارش باید به همراه ستون‌های surname, firstname, memid, starttime باشد؛ مرتب شده بر اساس memid.
var date1 = new DateTime(2012, 09, 01);
var items = context.Bookings
                                    .Where(booking => booking.StartTime >= date1)
                                    .GroupBy(booking => new
                                    {
                                        booking.Member.Surname,
                                        booking.Member.FirstName,
                                        booking.Member.MemId
                                    })
                                    .Select(group => new
                                    {
                                        group.Key.Surname,
                                        group.Key.FirstName,
                                        group.Key.MemId,
                                        StartTime = group.Min(booking => booking.StartTime)
                                    })
                                    .OrderBy(result => result.MemId)
                                    .ToList();
هدف از این مثال محاسبه‌ی حداقل StartTime‌ها به ازای اطلاعات گروه بندی شده‌ی بر اساس هر کاربر است که روش آن‌را با استفاده از متد group.Min مشاهده می‌کنید.



مثال 15: گزارشی را از کاربران تهیه کنید که هر ردیف آن، به همراه تعداد کل کاربران باشد.

این گزارش باید به همراه ستون‌های count, firstname, surname باشد؛ مرتب شده بر اساس joindate.
var members = context.Members
                        .OrderBy(member => member.JoinDate)
                        .Select(member => new
                        {
                            Count = context.Members.Count(),
                            member.FirstName,
                            member.Surname
                        })
                        .ToList();
EF-Core این گزارش به همراه یک sub-query را تبدیل به دو کوئری می‌کند؛ ابتدا مقدار ثابت تعداد اعضاء را محاسبه می‌کند و سپس این تعداد ثابت را در کوئری دوم بکار می‌گیرد:
SELECT COUNT(*)
FROM   [Members] AS [m];

SELECT   [m].[FirstName],
         [m].[Surname],
         @__Count_0 AS [Count]
FROM     [Members] AS [m]
ORDER BY [m].[JoinDate];


مثال 16:  گزارشی را از کاربران تهیه کنید که به همراه ستون شماره ردیف آن‌ها نیز باشد.

باید بخاطر داشت که ID کاربران پشت سرهم نیست و همچنین این گزارش باید به همراه ستون‌های row_number, firstname, surname باشد؛ مرتب شده بر اساس joindate.

هدف اصلی از این مثال، کار با مفهوم window function‌ها و تابع row_number است:
SELECT   row_number() OVER (ORDER BY joindate) AS row_number,
         firstname,
         surname
FROM     members
ORDER BY joindate;
اما چون چنین قابلیتی با LINQ to Entities قابل پیاده سازی نیست، در اینجا نیز ابتدا ردیف‌های گزارش را تولید می‌کنیم و سپس شماره ردیف را در سمت کلاینت (در سمت برنامه و توسط LINQ to Objects)، اضافه خواهیم کرد:
var members = context.Members
                        .OrderBy(member => member.JoinDate)
                        .Select(member => new
                        {
                            member.FirstName,
                            member.Surname
                        })
                        .ToList()
                        /*
                            SELECT [m].[FirstName], [m].[Surname]
                                FROM [Members] AS [m]
                                ORDER BY [m].[JoinDate]
                        */
                        // Now using LINQ to Objects
                        .Select((member, index) => new
                        {
                            RowNumber = index + 1,
                            member.FirstName,
                            member.Surname
                        })
                        .ToList();
تا قسمت ToList، یک کوئری LINQ to Entities استاندارد مشاهده می‌شود. پس از آن چون این اطلاعات درون حافظه هستند، می‌توان با استفاده از LINQ to Objects و قابلیت index ذاتی موجود در متد Select، شماره ردیف‌ها را که همان index + 1 هستند، تولید کرد.


مثال 17: کدامیک از امکانات موجود، بیشترین slots رزرو شده را دارد؟ قسمت دوم.

این مورد همان مثال 11 قسمت قبل است که پاسخ آن‌را یافتیم (و از تکرار مجدد آن صرفنظر می‌کنیم) و هدف اصلی آن رسیدن به کوئری window function دار زیر است که تنها از طریق اجرای یک raw sql در EF-Core قابل اجرا است:
SELECT facid,
       total
FROM   (SELECT   facid,
                 sum(slots) AS total,
                 rank() OVER (ORDER BY sum(slots) DESC) AS rank
        FROM     bookings
        GROUP BY facid) AS ranked
WHERE  rank = 1;


مثال 18: به کاربران بر اساس تعداد ساعات رزرو آن‌ها، امتیاز دهی (رتبه بندی) کنید.

این گزارش باید به همراه ستون‌های firstname, surname, hours, rank باشد؛ مرتب شده بر اساس rank, surname.

هدف اصلی از این مثال، رسیدن به کوئری rank دار زیر است:
SELECT   mems.firstname,
         mems.surname,
         ((sum(bks.slots) + 10) / 20) * 10 AS hours,
         rank() OVER (ORDER BY ((sum(bks.slots) + 10) / 20) * 10 DESC) AS rank
FROM     bookings AS bks
         INNER JOIN
         members AS mems
         ON bks.memid = mems.memid
GROUP BY mems.firstname,
         mems.surname
ORDER BY rank, mems.surname, mems.firstname;
هرچند نمی‌توان از window functions به همراه LINQ to Entities استفاده کرد، اما می‌توان نتیجه‌ای را که خواسته (تولید rank بر اساس تعداد ساعات استفاده شده) به صورت زیر نیز تولید کرد که شامل استفاده‌ی از LINQ to Objects هم نمی‌شود؛ یعنی برای تولید Rank، الزاما نیازی به Window Functions نیست:
var itemsQuery = context.Bookings
                                    .GroupBy(booking => new
                                    {
                                        booking.Member.FirstName,
                                        booking.Member.Surname
                                    })
                                    .Select(group => new
                                    {
                                        group.Key.FirstName,
                                        group.Key.Surname,
                                        Hours = (group.Sum(booking => booking.Slots) + 10) / 20 * 10
                                    })
                                    .OrderByDescending(result => result.Hours)
                                        .ThenBy(result => result.Surname)
                                        .ThenBy(result => result.FirstName);
                var rankedItems = itemsQuery.Select(thisItem => new
                {
                    thisItem.FirstName,
                    thisItem.Surname,
                    thisItem.Hours,
                    Rank = itemsQuery.Count(mainItem => mainItem.Hours > thisItem.Hours) + 1
                })
                .ToList();
در ابتدا یک کوئری متداول گروه بندی شده‌ی بر اساس کاربران را مشاهده می‌کنید که به ازای هر کاربر، جمع تعداد ساعات رزور شده‌ی او محاسبه شده‌است. البته itemsQuery یک IQueryable مرتب سازی شده‌است؛ یعنی چون هنوز ToList بر روی آن فراخوانی نشده، بر روی بانک اطلاعاتی اجرا نشده‌است و فقط یک LINQ Expression است. سپس این LINQ Expression را به صورت زنجیروار در یک کوئری دیگر استفاده کرده‌ایم که در آن sub-query دارای itemsQuery.Count، مقدار rank را تشکیل داده‌است. این ساب کوئری به این معنا است: چه تعداد ساعت حاصل از کوئری گروه بندی و مرتب شده، از مقدار ساعت ردیف جاری بیشتر است + 1 که رتبه‌ی هر ردیف را نسبت به ردیف‌های دیگر محاسبه می‌کند.

با این خروجی SQL نهایی:



مثال 19: سه امکانی را لیست کنید که بالاترین میزان فروش را داشته‌اند.

این گزارش باید به همراه ستون‌های name, rank باشد؛ مرتب شده بر اساس rank.

روش محاسبه‌ی این گزارش با مثال قبلی یکی است (البته اینبار رتبه بندی بر اساس TotalRevenue است) و فقط در انتهای آن یک Where(result => result.Rank <= 3) را بیشتر دارد:
var facilitiesQuery =
                            context.Bookings.Select(booking =>
                                new
                                {
                                    booking.Facility.Name,
                                    Revenue = booking.MemId == 0 ?
                                            booking.Slots * booking.Facility.GuestCost
                                            : booking.Slots * booking.Facility.MemberCost
                                })
                                .GroupBy(b => b.Name)
                                .Select(group => new
                                {
                                    Name = group.Key,
                                    TotalRevenue = group.Sum(b => b.Revenue)
                                })
                                .OrderBy(result => result.TotalRevenue);

                var rankedFacilities = facilitiesQuery.Select(thisItem => new
                {
                    thisItem.Name,
                    thisItem.TotalRevenue,
                    Rank = facilitiesQuery.Count(mainItem => mainItem.TotalRevenue > thisItem.TotalRevenue) + 1
                })
                .Where(result => result.Rank <= 3)
                .OrderBy(result => result.Rank)
                .ToList();
ابتدا به نحو متداولی گروه بندی بر اساس نام صورت گرفته و محاسبه‌ی میزان فروش هر گروه انجام شده‌است. سپس در کوئری زنجیروار دوم، ستون Rank، به نتیجه‌ی حاصل اضافه شده‌است و اگر این Rank کمتر از 3 باشد، پاسخ مساله‌است.



مثال 20: امکانات موجود را بر اساس میزان فروشی که دارند به گروه‌هایی با تعداد مساوی high, average, low تقسیم بندی کنید.

این گزارش باید به همراه ستون‌های name, revenue باشد؛ مرتب شده بر اساس revenue, name.

هدف اصلی از این گزارش کار با تابع ntile است که اطلاعات را بر اساس پارامتر ارسالی به آن تاجای ممکن به گروه‌های مساوی تقسیم می‌کند:
SELECT   name,
         CASE WHEN class = 1 THEN 'high' WHEN class = 2 THEN 'average' ELSE 'low' END AS revenue
FROM     (SELECT   facs.name AS name,
                   ntile(3) OVER (ORDER BY sum(CASE WHEN memid = 0 THEN slots * facs.guestcost ELSE slots * membercost END) DESC) AS class
          FROM     bookings AS bks
                   INNER JOIN
                   facilities AS facs
                   ON bks.facid = facs.facid
          GROUP BY facs.name) AS subq
ORDER BY class, name;
Ntile نیز در LINQ to Entities معادلی ندارد. بنابراین ابتدا رزروهای انجام شده را بر اساس نوع امکانات رزرو شده، گروه بندی کرده و میزان فروش هر گروه را پیدا می‌کنیم:
var facilities =
                            context.Bookings.Select(booking =>
                                new
                                {
                                    booking.Facility.Name,
                                    Revenue = booking.MemId == 0 ?
                                            booking.Slots * booking.Facility.GuestCost
                                            : booking.Slots * booking.Facility.MemberCost
                                })
                                .GroupBy(b => b.Name)
                                .Select(group => new
                                {
                                    Name = group.Key,
                                    TotalRevenue = group.Sum(b => b.Revenue)
                                })
                                .OrderByDescending(result => result.TotalRevenue)
                                .ToList();
که یک چنین SQL ای را تولید می‌کند:
SELECT   [f].[Name],
         SUM(CASE WHEN [b].[MemId] = 0 THEN CAST ([b].[Slots] AS DECIMAL (18, 6)) * [f].[GuestCost] ELSE CAST ([b].[Slots] AS DECIMAL (18, 6)) * [f].[MemberCost] END) AS [TotalRevenue]
FROM     [Bookings] AS [b]
         INNER JOIN
         [Facilities] AS [f]
         ON [b].[FacId] = [f].[FacId]
GROUP BY [f].[Name]
ORDER BY SUM(CASE WHEN [b].[MemId] = 0 THEN CAST ([b].[Slots] AS DECIMAL (18, 6)) * [f].[GuestCost] ELSE CAST ([b].[Slots] AS DECIMAL (18, 6)) * [f].[MemberCost] END) DESC;
سپس با استفاده از LINQ to Objects، تابع ntile را شبیه سازی می‌کنیم:
var n = 3;
var tiledFacilities = facilities.Select((item, index) =>
                                        new
                                        {
                                            Item = item,
                                            Index = (index / n) + 1
                                        })
                                        .GroupBy(x => x.Index)
                                        .Select(g =>
                                            g.Select(z =>
                                                new
                                                {
                                                    z.Item.Name,
                                                    z.Item.TotalRevenue,
                                                    Tile = g.Key,
                                                    GroupName = g.Key == 1 ? "High" : (g.Key == 2 ? "Average" : "Low")
                                                })
                                                .OrderBy(x => x.GroupName)
                                                    .ThenBy(x => x.Name)
                                        )
                                        .ToList();

var flatTiledFacilities = tiledFacilities.SelectMany(group => group)
                                        .Select(tile => new { tile.Name, Revenue = tile.GroupName })
                                        .ToList();
هدف از این گزارش این است که در نتیجه‌ی مرتب سازی شده‌ی بر اساس TotalRevenue، به سه تای اول، برچسب High را بدهیم، به سه تای دوم برچسب average و به مابقی برچسب low. به همین جهت ردیف‌های حاصل را بر اساس ستون جدیدی به نام Index که بیانگر شماره ردیف گروه‌های سه تایی است، گروه بندی می‌کنیم و به هر گروه برچسبی را انتساب می‌دهیم. حاصل آن، گروه‌های تو در تویی است که با SelectMany، نسبت به مسطح سازی آن‌ها اقدام شده‌است.


مثال 21: چندماه طول می‌کشد تا هر کدام از امکانات موجود بر اساس فروشی که دارند، هزینه‌ی مالکیت ابتدایی خود را کسب کنند.

این گزارش باید به همراه ستون‌های name, months باشد؛ مرتب شده بر اساس name.
var facilities =
                            context.Bookings.Select(booking =>
                                new
                                {
                                    booking.Facility.Name,
                                    booking.Facility.InitialOutlay,
                                    booking.Facility.MonthlyMaintenance,
                                    Revenue = booking.MemId == 0 ?
                                            booking.Slots * booking.Facility.GuestCost
                                            : booking.Slots * booking.Facility.MemberCost
                                })
                                .GroupBy(b => new
                                {
                                    b.Name,
                                    b.InitialOutlay,
                                    b.MonthlyMaintenance
                                })
                                .Select(group => new
                                {
                                    group.Key.Name,
                                    RepayTime =
                                        group.Key.InitialOutlay /
                                                ((group.Sum(b => b.Revenue) / 3) - group.Key.MonthlyMaintenance)
                                })
                                .OrderBy(result => result.Name)
                                .ToList();
ابتدا رزروهای انجام شده را بر اساس نوع امکانات رزرو شده گروه بندی کرده و میزان فروش هر گروه را پیدا می‌کنیم. سپس بر روی این حاصل، محاسبات خاص RepayTime را انجام داده و نتیجه را بازگشت می‌دهیم:



مثال 22: گزارش میانگین متحرک فروش کل هر کدام از روزهای August 2012 را برای یک بازه‌ی 15 روزه‌ی قبل، محاسبه کنید.

این گزارش باید به همراه ستون‌های date, revenue باشد؛ مرتب شده بر اساس date. در این گزارش روزهای ماه 8 میلادی ردیف شده و به ازای هر ردیف، میانگین فروش 15 روز قبل از آن تاریخ، نمایش داده می‌شود. به همین جهت به آن میانگین متحرک نیز می‌گویند.

هدف اصلی از این گزارش، استفاده از توابع avg(revdata.rev) over است. اما چون نمی‌توان از آن‌ها در LINQ to Entities استفاده کرد، از روش دیگری که شامل جوین یک جدول با خودش است، استفاده می‌کنیم:
var startDate = new DateTime(2012, 08, 1);
var endDate = new DateTime(2012, 08, 31);
var period = 14;

var dailyRevenueQuery =
                        context.Bookings
                                .Select(booking =>
                                new
                                {
                                    StartDate = booking.StartTime.Date, // How to group by date (or TruncateTime) in EF-Core
                                    Revenue = booking.MemId == 0 ?
                                                           booking.Slots * booking.Facility.GuestCost
                                                           : booking.Slots * booking.Facility.MemberCost
                                })
                                .GroupBy(b => b.StartDate)
                                .Select(group =>
                                new
                                {
                                    Date = group.Key,
                                    TotalRevenue = group.Sum(b => b.Revenue)
                                });
ابتدا میزان کل فروش‌ها را بر حسب تاریخ هر روز ماه 8 میلادی، محاسبه می‌کنیم. برای این گروه بندی خاص نیاز خواهیم داشت تا از زمان یک تاریخ صرفنظر کنیم (چون StartTime به همراه تاریخ و ساعت است). برای اینکار فقط کافی است بجای  booking.StartTime از booking.StartTime.Date استفاده شود تا نتیجه‌ی حاصل به CONVERT(date, [b0].[StartTime]) ترجمه شده و قسمت زمان تاریخ از کوئری نهایی حذف شود.
اکنون که میزان کل فروش روزها را داریم، می‌خواهیم میانگین فروش 15 روز قبل شروع شده‌ی از از ابتدای ماه 8، تا انتهای آن‌را محاسبه کنیم. برای اینکار نیاز است کوئری فوق را یکبار دیگر با خودش جوین کنیم تا از یک سر آن تاریخ هر روز و از طرف دیگر، میانگین 15 روز قبل، تولید شود:
var movingAvgs =
                        dailyRevenueQuery
                                .Select(dr1 =>
                                new
                                {
                                    dr1.Date,
                                    MovingAvg = dailyRevenueQuery
                                        .Where(dr2 => dr2.Date <= dr1.Date && dr2.Date >= dr1.Date.AddDays(-period))
                                        .Average(dr2 => dr2.TotalRevenue)
                                })
                                .Where(result => result.Date >= startDate && result.Date <= endDate)
                                .OrderBy(result => result.Date)
                                .ToList();



کدهای کامل این قسمت را در اینجا می‌توانید مشاهده کنید.
مطالب
مهارت‌های تزریق وابستگی‌ها در برنامه‌های NET Core. - قسمت اول - تزریق وابستگی‌ها در برنامه‌های کنسول
پیشتر با مقدمات تزریق وابستگی‌ها در برنامه‌های ASP.NET Core آشنا شده‌ایم:
در ادامه در طی چند مطلب می‌خواهیم نکات و سناریوهای تکمیلی مرتبط با امکانات تزریق وابستگی‌های توکار برنامه‌های مبتنی بر NET Core. را بررسی کنیم.


تزریق وابستگی‌ها در برنامه‌های کنسول مبتنی بر NET Core.

تزریق وابستگی‌ها، یکی از پرکاربردترین الگوهای طراحی برنامه‌های مدرن است. در نگارش‌های قبلی ASP.NET، به کمک DependencyResolver آن، کتابخانه‌های ثالث کمکی تزریق وابستگی‌ها می‌توانستند خودشان را به سیستم متصل کنند. اینبار ASP.NET Core به همراه IoC Container توکار خودش ارائه شده‌است که این کتابخانه، در خارج از آن، مانند برنامه‌های کنسول نیز قابل استفاده است.


سرویس نمونه‌‌ای برای تزریق آن در یک برنامه‌ی کنسول NET Core.

در پوشه‌ی جدید CoreIocServices، دستور dotnet new classlib را صادر می‌کنیم تا یک پروژه‌ی class library جدید را ایجاد کند. سپس اینترفیس ITestService و یک نمونه پیاده سازی آن‌را به این پروژه اضافه می‌کنیم تا در ادامه بتوانیم تنظیمات تزریق وابستگی‌های آن‌را در یک پروژه‌ی کنسول، ایجاد کنیم:
using System;
using Microsoft.Extensions.Logging;

namespace CoreIocServices
{
    public interface ITestService
    {
        void Run();
    }

    public class TestService : ITestService
    {
        private readonly ILogger<TestService> _logger;

        public TestService(ILogger<TestService> logger)
        {
            _logger = logger ?? throw new ArgumentNullException(nameof(logger));
        }

        public void Run()
        {
            _logger.LogWarning("A Warning from the TestService!");
        }
    }
}
در اینجا این سرویس نمونه نیز دارای یک وابستگی تزریق شده‌ی در سازنده‌ی آن است. این وابستگی، همان امکانات توکار logging مربوط به ASP.NET Core است که در برنامه‌های کنسول نیز قابل استفاده است. برای اینکه پروژه قابل کامپایل باشد، نیاز است وابستگی Microsoft.Extensions.Logging را نیز به آن افزود:
<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <TargetFramework>netstandard2.0</TargetFramework>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="Microsoft.Extensions.Logging" Version="2.2.0" />
  </ItemGroup>

</Project>


دسترسی به سرویس TestService از طریق تزریق وابستگی‌ها در یک برنامه‌ی کنسول

در ادامه، یک پوشه‌ی جدید را به نام CoreIocSample01 ایجاد کرده و دستور dotnet new console را در آن اجرا می‌کنیم تا یک برنامه‌ی کنسول جدید را ایجاد کند.
سپس اولین قدم برای استفاده‌ی از سرویس TestService از طریق تزریق وابستگی‌ها، افزودن وابستگی‌های مورد نیاز آن است:
<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>netcoreapp2.2</TargetFramework>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="2.2.0" />
  </ItemGroup>

  <ItemGroup>
    <ProjectReference Include="..\CoreIocServices\CoreIocServices.csproj" />
  </ItemGroup>

</Project>
در اینجا بسته‌ی Microsoft.Extensions.DependencyInjection جهت دسترسی به امکانات تزریق وابستگی‌های NET Core. به پروژه اضافه شده و همچنین ارجاعی نیز به پروژه‌ی class library که پیشتر ایجاد کردیم، افزوده شده‌است.
اکنون می‌توانیم همان روشی را که در یک برنامه‌ی ASP.NET Core با ارائه‌ی متد ConfigureServices به صورت از پیش آماده شده برای ما مهیا است، در اینجا نیز پیاده سازی کنیم:
using CoreIocServices;
using Microsoft.Extensions.DependencyInjection;

namespace CoreIocSample01
{
    class Program
    {
        static void Main(string[] args)
        {
            var serviceCollection = new ServiceCollection();
            ConfigureServices(serviceCollection);
            var serviceProvider = serviceCollection.BuildServiceProvider();

            var testService = serviceProvider.GetService<ITestService>();
            testService.Run();
        }

        private static void ConfigureServices(IServiceCollection services)
        {
            services.AddTransient<ITestService, TestService>();
        }
    }
}
کار با تعریف یک ServiceCollection جدید شروع می‌شود. سپس در متد ConfigureServices، همانند کاری که در برنامه‌های ASP.NET Core انجام می‌دهیم، ارتباطات اینترفیس‌ها و پیاده سازی‌های آن‌ها، به همراه طول عمر آن‌ها را تعریف می‌کنیم.
سپس نیاز است بر روی این ServiceCollection، متد BuildServiceProvider فراخوانی شود تا بتوانیم به IServiceProvider دسترسی پیدا کنیم. به آن Dependency Management Container نیز می‌گویند. این Container است که امکان دسترسی به وهله‌ای از ITestService و سپس فراخوانی متد Run آن‌را میسر می‌کند.


مشکل! برنامه‌ی کنسول اجرا نمی‌شود!

اگر سعی کنیم مثال فوق را اجرا کنیم، با استثنای زیر برنامه خاتمه می‌یابد:
Exception has occurred: CLR/System.InvalidOperationException
An unhandled exception of type 'System.InvalidOperationException' occurred in Microsoft.Extensions.DependencyInjection.dll:
'Unable to resolve service for type 'Microsoft.Extensions.Logging.ILogger`1[CoreIocServices.TestService]'
while attempting to activate 'CoreIocServices.TestService'.'
عنوان می‌کند که وابستگی تزریق شده‌ی در سازنده‌ی کلاس TestService را نمی‌تواند پیدا کند. علت اینجا است که هرچند ILogger را به سازنده‌ی کلاس سرویس خود اضافه کرده‌ایم، اما هنوز پیاده سازی کننده‌ی آن‌را مشخص نکرده‌ایم. به همین جهت امکان وهله سازی از این کلاس وجود ندارد. عموما در برنامه‌های ASP.NET Core نیازی به تنظیم زیر ساخت logging آن نیست؛ چون این مورد نیز به صورت پیش‌فرض انجام شده‌است. اما در اینجا خیر. به همین جهت دو وابستگی جدید Microsoft.Extensions.Logging.Console و Microsoft.Extensions.Logging.Debug را به پروژه‌ی کنسول اضافه می‌کنیم:
<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>netcoreapp2.2</TargetFramework>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="2.2.0" />
    <PackageReference Include="Microsoft.Extensions.Logging.Console" Version="2.2.0" />
    <PackageReference Include="Microsoft.Extensions.Logging.Debug" Version="2.2.0" />
  </ItemGroup>

  <ItemGroup>
    <ProjectReference Include="..\CoreIocServices\CoreIocServices.csproj" />
  </ItemGroup>

</Project>
پس از آن متد ConfigureServices ما جهت تعریف logging در دو حالت دیباگ و کنسول، به صورت زیر تغییر می‌کند:
private static void ConfigureServices(IServiceCollection services)
{
   services.AddLogging(configure => configure.AddConsole().AddDebug());
   services.AddTransient<ITestService, TestService>();
}
اکنون اگر برنامه را اجرا کنیم، خروجی زیر قابل مشاهده خواهد بود:
 CoreIocServices.TestService:Warning: A Warning from the TestService!


کدهای کامل این مثال را از اینجا می‌توانید دریافت کنید: CoreDependencyInjectionSamples-01.zip
مطالب
شروع به کار با DNTFrameworkCore - قسمت 1 - معرفی و نحوه استفاده از آن

پروژه DNTFrameworkCore  که قصد پشتیبانی از آن را دارم، یک زیرساخت سبک وزن و توسعه پذیر با پشتیبانی از طراحی چند مستاجری، با تمرکز بر کاهش زمان و افزایش کیفیت توسعه سیستم‌های تحت وب مبتنی بر ASP.NET Core، توسعه داده شده است. 


اهدافی که این زیرساخت دنبال می‌کند

  • ارائه ساختارهای مشترک بین پروژه‌های مختلف از جمله Cross-Cutting Concern‌ها و ...
  • دنبال کردن اصل DRY به منظور متمرکز شدن صرف برروی منطق تجاری سیستم نه انجام و حل یکسری مسائل تکراری
  • کاهش زمان توسعه و اختصاص زمان بیشتر برای نوشتن آزمون‌های واحد منطق تجاری
  • کاهش باگ و جلوگیری از پخش شدن باگ‌های پیاده سازی در سراسر سیستم
  • کاهش زمان آموزش نیروهای جدید برای ملحق شدن به تیم تولید شما با حداقل دانش طراحی و برنامه نویسی شیء گرا
  • ارائه راهکاری یکپارچه برای توسعه پذیر بودن منطق تجاری پیاده سازی شده از طریق در معرض دید قرار دادن یکسری «Extensibility Point» با استفاده از رویکرد Event-Driven


امکانات این زیرساخت در زمان نگارش مطلب جاری


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

PM> Install-Package DNTFrameworkCore -Version 1.0.0
 services.AddDNTFramework()
     .AddDataAnnotationValidation()
     .AddModelValidation()
     .AddValidationOptions(options =>
     {
         /*options.IgnoredTypes.Add(typeof());*/
     })
     .AddMemoryCache()
     .AddAuditingOptions(options =>
     {
         // options.Enabled = true;
         // options.EnabledForAnonymousUsers = false;
         // options.IgnoredTypes.Add(typeof());
         // options.Selectors.Add(new NamedTypeSelector("SelectorName", type => type == typeof()));
     }).AddTransactionOptions(options =>
     {
         // options.Timeout=TimeSpan.FromMinutes(3);
         //options.IsolationLevel=IsolationLevel.ReadCommitted;
     });

متدهای الحاقی بالا برای ثبت سرویس‌ها و تنظیمات مرتبط با مکانیزم‌های اعتبارسنجی خودکار، مدیریت تراکنش‌ها، لاگ آماری، Eventing و سایر امکانات ذکر شده، در IoC Container  توکار ASP.NET Core استفاده خواهند شد.

PM> Install-Package DNTFrameworkCore.EntityFramework -Version 1.0.0
services.AddDNTUnitOfWork<ProjectDbContext>();

‎بسته نیوگت بالا شامل پیاده سازی مبتنی بر EF Core برای واسط‌های تعریف شده در بسته نیوگت DNTFrameworkCore، می‌باشد؛ از جمله آن می‌توان به CrudService پایه اشاره کرد.  متد الحاقی AddDNTUnitOfWork برای ثبت و معرفی واسط‌های IUnitOfWork و ITransactionProvider به عنوان مهیا کننده تراکنش به همراه پیاده سازهای آنها و همچنین ثبت یک سری Hook تعریف شده برای ردیابی تغییرات، در سیستم تزریق وابستگی، استفاده خواهد شد.

همچنین با نصب بسته بالا، امکان استفاده از مهیا کننده Logging با امکان ذخیره سازی در بانک اطلاعاتی را خواهید داشت:

 public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
            WebHost.CreateDefaultBuilder(args)
                .UseDefaultServiceProvider((context, options) =>
                {
                    options.ValidateScopes = context.HostingEnvironment.IsDevelopment();
                })
                .ConfigureLogging((hostingContext, logging) =>
                {
                    logging.AddConfiguration(hostingContext.Configuration.GetSection("Logging"));
                    logging.AddConsole();
                    logging.AddDebug();
                    logging.AddEntityFramework<ProjectDbContext>(options => options.MinLevel = LogLevel.Warning);
                })
                .UseStartup<Startup>();

متد جنریک الحاقی AddEntityFramework برای ثبت مهیا کننده مذکور استفاده می‌شود. 

PM> Install-Package DNTFrameworkCore.Web -Version 1.0.0

بسته نیوگت بالا شامل یکسری سرویس برای اعمال دسترسی‌های پویا، CrudController مبتنی بر ASP.NET Core Web API، فیلتر مدیریت سراسری خطاهای برنامه و سایر امکاناتی که در ادامه مقالات با جزئیات بیشتری بررسی خواهیم کرد، می‌باشد. برای ثبت سرویس‌های تعریف شده می‌توانید از متد الحاقی AddDNTCommonWeb و به منظور تغییر محل ذخیره سازی کلیدهای موقت رمزنگاری مرتبط با Data Protection API و انتقال آنها به بانک اطلاعاتی، استفاده کنید. 

services.AddDNTCommonWeb()
    .AddDNTDataProtection();

نکته: برای انتقال کلیدهای موقت رمزنگاری به بانک اطلاعاتی، نیاز است تا از متد الحاقی زیر که در بسته نیوگت DNTFrameworkCore.EntityFramework موجود می‌باشد، به شکل زیر استفاده کنید:

services.AddDNTProtectionRepository<ProjectDbContext>();

‎‎

اگر نیاز به شماره گذاری خودکار دارید، بسته زیر را نیز می‌بایست نصب کنید:
PM> Install-Package DNTFrameworkCore.EntityFramework.SqlServer -Version 1.0.0

بسته بالا از امکانات مخصوص SqlServer برای اعمال قفل منطقی برای مدیریت مباحث همزمانی استفاده می‌کند؛ همچنین PreUpdateHook مرتبط با تولید خودکار کد منحصر به فرد، در این کتابخانه پیاده سازی شده است. به شکل زیر می‌توانید سرویس‌های مرتبط با آن را به سیستم تزریق وابستگی‌های معرفی کنید:

services.AddDNTNumbering(options =>
{
    options.NumberedEntityMap[typeof(Task)] = new NumberedEntityOption
    {
        Start = 100,
        Prefix = "Task-",
        IncrementBy = 5
    };
});

‎‎به عنوان مثال برای شماره گذاری موجودیت Task، لازم است تنظیمات مرتبط آن را به شکل بالا به سیستم شماره گذاری معرفی کنید.

اگر قصد استفاده از کتابخانه FluentValidation را داشته باشید، می‌بایست بسته زیر را نیز نصب کنید:

PM> Install-Package DNTFrameworkCore.FluentValidation -Version 1.0.0  

برای ثبت و معرفی Adapter مرتبط، به سیستم اعتبارسنجی خودکار معرفی شده، لازم است از طریق متد الحاقی AddFluentModelValidation به شکل زیر اقدام کنید:

 services.AddDNTFramework()
     .AddDataAnnotationValidation()
     .AddModelValidation()
     .AddFluentModelValidation()
     .AddValidationOptions(options =>
     {
         /*options.IgnoredTypes.Add(typeof());*/
     })
     .AddMemoryCache()
     .AddAuditingOptions(options =>
     {
         // options.Enabled = true;
         // options.EnabledForAnonymousUsers = false;
         // options.IgnoredTypes.Add(typeof());
         // options.Selectors.Add(new NamedTypeSelector("SelectorName", type => type == typeof()));
     }).AddTransactionOptions(options =>
     {
         // options.Timeout=TimeSpan.FromMinutes(3);
         //options.IsolationLevel=IsolationLevel.ReadCommitted;
     });

‎‎

برای شروع پروژه جدید، نصب این بسته‌ها کفایت می‌کند. اگر نیاز به طراحی MultiTenancy دارید، بسته زیر را برای شناسایی Tenant جاری و از این قبیل کارها نیز می‌بایست نصب کنید:

PM> Install-Package DNTFrameworkCore.Web.MultiTenancy -Version 1.0.0
برای ثبت و معرفی ITenantResolver شخصی سازی شده خود، می‌توانید از متد الحاقی زیر استفاده کنید:
services.AddMultiTenancy<TenantResolver>();
همچنین نیاز است با استفاده از متد الحاقی زیر، Middleware تعریف شده در کتابخانه را به «HTTP Request Pipeline» سیستم معرفی کرده و ثبت کنید:
app.UseMultiTenancy();
نکته: بسته نیوگت DNTFrameworkCore.Web.MultitTenancy به منظور مهیا کردن طول عمر Tenant-Singleton، وابستگی به کتابخانه StructureMap نیز دارد. البته این بسته نیاز به بهبود هم دارد که در ادامه اعمال خواهد شد. همچنین امضای متدهای الحاقی بالا، در انتشارهای بعدی به AddDNTMultiTenancy و UseDNTMultiTenancy تغییر خواهند کرد.
در نهایت به منظور تزئین خودکار و پویای سرویس‌های برنامه برای اعمال یکسری Cross-Cutting Concern معرفی شده در بالا، از جمله اعتبارسنجی ورودی‌ها، مدیریت تراکنش‌ها و ... می‌توانید پس از ثبت و معرفی سرویس‌های خود به سیستم تزریق وابستگی توکار، با استفاده از Interceptor‌های پیاده سازی شده در زیرساخت، به شکل زیر اقدام کنید:
services.Scan(scan => scan
    .FromCallingAssembly()
    .AddClasses(classes => classes.AssignableTo<ISingletonDependency>())
    .AsMatchingInterface()
    .WithSingletonLifetime()
    .AddClasses(classes => classes.AssignableTo<IScopedDependency>())
    .AsMatchingInterface()
    .WithScopedLifetime()
    .AddClasses(classes => classes.AssignableTo<ITransientDependency>())
    .AsMatchingInterface()
    .WithTransientLifetime()
    .AddClasses(classes => classes.AssignableTo(typeof(IDomainEventHandler<>)))
    .AsImplementedInterfaces()
    .WithTransientLifetime());

foreach (var descriptor in services.Where(s => typeof(IApplicationService).IsAssignableFrom(s.ServiceType))
    .ToList())
{
    services.Decorate(descriptor.ServiceType, (target, serviceProvider) =>
        ProxyGenerator.CreateInterfaceProxyWithTargetInterface(
            descriptor.ServiceType,
            target, serviceProvider.GetRequiredService<ValidationInterceptor>(),
            (IInterceptor) serviceProvider.GetRequiredService<TransactionInterceptor>()));
}
به عنوان مثال در اینجا از ValidationInterceptor و TransactionInterceptor استفاده شده است.

پروژه نمونه‌ای هم برای نمایش امکانات زیرساخت را از اینجا می توانید دریافت کنید.
آزمون‌های واحد مرتبط با قسمت هایی از این زیرساخت را نیز می‌توانید در اینجا مشاهده کنید.
مطالب
الگوی طراحی Null Object

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

الگوی طراحی Null Object این مشکل را حل می‌کند که جای پاس دادن شیء Null Reference بجای شیء ای که واقعا به آن وابستگی وجود دارد و باید هر بار قبل استفاده‌ی از آن بررسی کنیم که آیا آن شیء ای که داریم با آن کار می‌کنیم نال است یا خیر، کلاسی خاصی را بسازیم که یک وابستگی غیر کاربردی است. به این معنا که قرار نیست هیچ کاری را انجام دهد و عملا یک non-functional Dependency است. این کلاس یا یک اینترفیس خاصی را پیاده سازی می‌کند و یا اینکه از یک کلاس انتزاعی ارث بری خواهد کرد؛ ولی هیچ عملکرد خاصی را نخواهد داشت. به این معنا که متدها و پراپرتی‌های این کلاس کاری را انجام نداده و یک مقدار پیشفرض و یا یک مقدار خاصی را برگشت خواهند داد. این روش به ساده سازی کد کمک خواهد کرد، چون می‌توان بدون انجام پیش شرط‌هایی مانند بررسی نال بودن یا نبودن یک شیء وابسته، از آن استفاده کرد.

این الگوی طراحی معمولا همراه با دیگر الگوهای طراحی مورد استفاده قرار می‌گیرد. بهینه‌تر است که خود کلاس Null Object به صورت Singleton پیاده سازی شود. مزیت این کار در این است که چون شیء ساخته شده از این کلاس، نه کار خاصی را انجام می‌دهد و نه حالت خاصی را نگه می‌دارد، پس ساختن شیءای از آن عملا ضرورتی نداشته و هیچگونه ارزشی ندارد و فقط سرباری را بر روی نرم افزار قرار می‌دهد. پس سزاوار است فقط به یک شیء از این کلاس اکتفا کرد و هر بار همان شیء را برگشت داد. الگوی دیگری که غالبا از الگوی Null Object در آن استفاده می‌شود، الگوی Strategy است. زمانیکه یکی از استراتژی‌ها این باشد که کار خاصی را انجام نداد و یا استراتژی مورد نظر عملکردی نداشته باشد، از الگوی Null Object استفاده می‌کنیم. الگوی دیگری که از الگوی Null Object زیاد استفاده می‌کند، الگوی Factory است. برای مثال هنگامیکه بخواهیم بر طبق شرایط برنامه یک شیء Null Reference را بسازیم و برگردانیم، از الگوی Null Object استفاده خواهیم کرد.

فرض کنید می‌خواهیم ماژولی را توسعه دهیم که وظیفه‌ی آن گزارش دادن وضعیت وقوع رخدادها است و می‌خواهیم پیام‌های وضعیت، به روش‌های مختلفی مانند ارسال ایمیل و یا ثبت لاگ در سرورهای راه دور که برای لاگ گیری تعبیه شده‌اند، انجام گیرد و در بعضی از مواقع هم می‌خواهیم برای برخی از رخداد‌ها نیاز به گزارش نباشد. در این مواقع برای استراتژی سوم از الگوی طراحی Null Object استفاده می‌کنند.


پیاده سازی الگوی طراحی Null Object

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

Client : این کلاس دارای یک وابستگی به یک کلاس دیگر است که در بعضی مواقع نیازی به این وابستگی پیدا نمی‌کند و در صورتیکه به کارکرد اصلی وابستگی نیاز پیدا نکند، متدهای داخل کلاس Null Object را اجرا می‌کند.

DependencyBase : این قسمت کلاس پایه‌ای است که به صورت Abstract بوده و شامل همه وابستگی‌هایی است که ممکن است Client به آن وابسته باشد. همچنین این بخش، کلاس پایه‌ی کلاس Null Object هم است. شایان ذکر است که بجای استفاده از کلاس Abstract می‌توان از یک Interface هم استفاده کرد؛ چون این کلاس هیچ عملکرد مشترکی را برای زیر کلاس‌هایش پیاده سازی نمی‌کند.

Dependency : این کلاس یک عملکرد واقعی از یک وابستگی است که Client به آن وابسته است.

NullObject : این همان کلاس Null Object است که به عنوان یک وابستگی توسط Client مورد استفاده قرار می‌گیرد. این کلاس هیچ عملکرد مشخصی را ندارد ولی باید تمام اعضای کلاس پایه، یعنی DependencyBase را پیاده سازی کند.

مثال زیر کدهای اصلی پیاده سازی الگوی طراحی Null Object را نشان خواهد داد که با زبان سی شارپ نوشته شده‌است. کلاس Client، وابستگی‌های خود را از طریق سازنده دریافت خواهد کرد که به آن Constructor injection گفته می‌شود. همانطور که می‌بینید در کلاس NullObject، تنها متد Operation بازنویسی شده است و داخل آن هیچ عملکرد خاصی پیاده سازی نشده است؛ زیر تنها به وجود آن نیاز است و نه عملکرد داخلی آن.

public class Client
{
    DependencyBase _dependency;
 
    public void Client(DependencyBase dependency)
    {
        _dependency = dependency;
    }
 
    public void DoSomething()
    {
        _dependency.Operation();
    }
}
 
 
public abstract class DependencyBase
{
    public abstract void Operation();
}
 
 
public class Dependency : DependencyBase
{
    public override void Operation()
    {
        Console.WriteLine("Dependency.Operation() executed");
    }
}
 
 
public class NullObject : DependencyBase
{
    public override void Operation() { }
}


یک نمونه واقعی از الگوی طراحی Null Object

در این بخش قصد داریم مثالی از الگوی استراتژی را ارائه دهیم که در یکی از استراتژی‌هایش از کلاس Null Object استفاده خواهد کرد. در این مثال کلاسی وجود دارد به نام StatusMonitor که پس از انجام کارهایی، وضعیت انجام آن را اعلام می‌کند. ۳ نوع استراتژی برای اعلام وضعیت انجام کارها متصور است که بسته به موقعیت‌های مختلف، یکی از آنها انتخاب خواهد شد. استراتژی‌های اعلام وضعیت شامل ارسال ایمیل، ارسال وضعیت به یک وب سرویس و یا اصلا اعلام نکردن وضعیت هستند. زمانیکه قصد داریم هیچگونه وضعیتی اعلام نشود، از نمونه‌ای از کلاس Null Object استفاده خواهد شد که در این مثال کلاس NullStatusReporter این وابستگی را تامین می‌کند. همه کلاس‌های استراتژی که بیان شد تنها شامل یک متد هستند که از آن برای گزارش پیام وضعیت استفاده خواهیم کرد.

کلاس‌های EmailStatusReporter و WebServiceStstusReporter در صورتیکه بتوانند به درستی پیام‌ها را گزارش دهند، مقدار true را برگشت خواهند داد و در غیر اینصورت مقدار false برگشت داده می‌شود. اما کلاس Null Object هیچ کاری را انجام نمی‌دهد و چیزی را گزارش نمی‌دهد و تنها مقدار true را برگشت خواهد داد. اینکه این کلاس چه مقداری را برگشت دهد، قراردادی است که بین Client و Dependency انجام می‌گیرد. به این نکته هم توجه بفرمایید که کلاس NullStatusReporter به صورت Singleton پیاده سازی شده است.

public class StatusMonitor
{
    StatusReporterBase _reporter;
 
    public StatusMonitor(StatusReporterBase reporter)
    {
        _reporter = reporter;
    }
 
    public void CheckStatus()
    {
        // Do something to check status
        if (!_reporter.Report("Everything's OK"))
        {
            Console.WriteLine("Failed to report status.");
        }
    }
}
 
 
public abstract class StatusReporterBase
{
    public abstract bool Report(string message);
}
 
 
public class EmailStatusReporter : StatusReporterBase
{
    public override bool Report(string message)
    {
        try
        {
            Console.WriteLine("Emailed '{0}'.", message);
            return true;
        }
        catch
        {
            return true;
            throw;
        }
    }
}
 
 
public class WebServiceStatusReporter : StatusReporterBase
{
    public override bool Report(string message)
    {
        try
        {
            Console.WriteLine("Sent '{0}' to web service.", message);
            return true;
        }
        catch
        {
            return true;
            throw;
        }
    }
}
 
 
public class NullStatusReporter : StatusReporterBase
{
    private static NullStatusReporter _instance;
    private static object _lock = new object();
 
    private NullStatusReporter() { }
 
    public static NullStatusReporter GetReporter()
    {
        lock (_lock)
        {
            if (_instance == null) _instance = new NullStatusReporter();
        }
 
        return _instance;
    }
 
    public override bool Report(string message)
    {
        return true;
    }
}


تست کلاس Null Object

برای تست کلاس StatusMonitor باید یکی از انواع استرتژی‌ها را برایش تعیین و آن را به سازنده کلاس تزریق کرد و با آن استراتژی، کلاس را تست نمود. در کد زیر از استراتژی NullObject استفاده شده‌است. پس یک نمونه‌ی آن ساخته شده و از طریق سازنده به کلاس StatusMonitor فرستاده می‌شود. سپس متد CheckStatus فراخوانی می‌گردد. اما این متد کاری را انجام نمی‌دهد و تنها مقدار true  برگشت داده می‌شود. بررسی روش‌های دیگر را به خودتان واگذار می‌کنم.

StatusReporterBase reporter = NullStatusReporter.GetReporter();
StatusMonitor monitor = new StatusMonitor(reporter);
monitor.CheckStatus();


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

1:data pages اساسی‌ترین واحد نگهداری داده در اس کیوال سرور، صفحه نام دارد. فضای دیسک اختصاص یافته به فایل داده بانک، برای یک بانک اطلاعاتی به صورت منطقی به صفحات پیوسته از صفر تا n تقسیم بندی می‌شود. همچنین لازم به ذکر است عملیات خواندن و یا نوشتن در دیسک، در سطح این صفحه‌ها صورت می‌گیرد که در تصویر زیر قابل مشاهده است:


 لازم به ذکر است در sql server هر صفحه، 8 کیلوبایت است. این مورد به این معنی است که هر بانک اطلاعاتی، دارای 128 صفحه به ازای هر یک مگابایت است. هر صفحه دارای 96 بایت با عنوان header یا سرصفحه است که شامل اطلاعات سیستمی در مورد صفحه است. این اطلاعات سیستمی شامل مواردی چون page number یا شماره صفحه و نوع صفحه یا page type و مقدار فضای خالی آن صفحه و شماره شناسایی یک واحد اختصاص یافته یا به اختصار allocation unit id و.... هستند می‌باشد. نکته جالب و قابل توجه این است که فایل‌های ثبت وقایع یا Log files از صفحه استفاده نمی‌کنند؛ بلکه شامل یکسری رکورد log هستند.
 برای بدست آوردن اطلاعات در مورد فایلهای دیتابیس می‌توانید از کد زیر استفاده نمایید SELECT * FROM sys.database_files که خروجی زیر را به شما نشان می‌دهد:


 extents: به ابتدایی‌ترین قسمتی که sql server امکان مدیریت بر آن را دارد extent گویند. هر extent شامل 8 صفحه‌ی به هم پیوسته است. لازم به ذکر است که sql server هر 1 مگابایت را به شانزده extent اختصاص می‌دهد. sql server شامل دونوع extent است که عبارتند از : uniform,mixed uniform extent متعلق به یک شیء است و هر هشت صفحه‌ی آن فقط توسط یک شیء قابل استفاده‌است. mixed extent می‌تواند حداکثر بین هشت شیء به اشتراک گذاشته شود؛ به نحوی که هر یک از هشت صفحه می‌توانند متعلق به یک شیء باشند. همانطور که در شکل زیر می‌بینید به طور پیش فرض با ایجاد یک جدول، یک mixed extent به آن اختصاص داده می‌شود. در صورتیکه این شیء به اندازه‌ی هشت صفحه رشد کند، به آن یک uniform extent اختصاص داده میشود.


فایلهای بانک اطلاعاتی
هر بانک اطلاعاتی در sql server دارای سه نوع فایل است
فایلهای داده اولیه یا به اختصار primary data files
فایلهای داده‌های ثانویه یا به اختصار secondary data files
فایلهای ثبت وقایع یا به اختصار log file
فایل ثبت وقایع برای نگهداری و ثبت وقایع که برای عملیات recovery مورد نیاز است. معمولا یک بانک اطلاعاتی یک log file دارد؛ ولی می‌تواند بیشتر هم داشته باشد. پسوند این نوع فایلها ldf است .

مطالب
مهارت‌های تزریق وابستگی‌ها در برنامه‌های NET Core. - قسمت هشتم - ساده سازی معرفی سرویس‌ها توسط Scrutor
قابلیت‌های قرار گرفته‌ی در اسمبلی Microsoft.Extensions.DependencyInjection که پایه‌ی تزریق وابستگی‌های برنامه‌های مبتنی بر NET Core. را ارائه می‌دهد، برای پیاده سازی اکثر پروژه‌ها کافی است. اما اگر از نگارش‌های پیشین ASP.NET MVC به ASP.NET Core مهاجرت کرده باشید، حتما با قابلیت‌های ویژه‌ی اسکن اسمبلی‌های موجود در IoC Containers ثالث، جهت ساده سازی معرفی سرویس‌های برنامه به سیستم تزریق وابستگی‌ها، آشنایی دارید. برای مثال StructureMap قابلیت اسکن اسمبلی‌های موجود در برنامه و معرفی اینترفیس‌ها و سرویس‌های موجود در آن‌را به Container خود دارد:
var container = new Container(x =>
            {
                x.Scan(scanner =>
                {
                    scanner.AssemblyContainingType<IOrderHandler>();
                    // connects `IAccounting` to `Accounting` and `ISales` to `Sales` automatically.
                    scanner.WithDefaultConventions();
                });
            });
و یا AutoFac نیز به همین صورت:
builder.RegisterAssemblyTypes(myAssembly)
    .Where(t => t.IsAssignableTo<IMyInterface>())
    .AsImplementedInterfaces();
البته می‌توان مجددا به تمام این قابلیت‌ها رسید؛ به شرطی‌که سیستم تزریق وابستگی‌های پایه‌ی NET Core. را با یکی از IoC Containers ثالث به طور کامل تعویض کنیم. اگر قصد چنین تعویض پایه‌ای را ندارید و هنوز قصد دارید از همان Microsoft.Extensions.DependencyInjection استفاده کنید، اما تعدادی متد الحاقی جدید تعریف شده‌ی بر فراز آن، کار اسکن کردن اسمبلی‌ها را مانند قبل انجام دهند، می‌توان از کتابخانه‌ی کمکی Scrutor استفاده کرد. این کتابخانه، جایگزین سیستم تزریق وابستگی‌های توکار برنامه‌های NET Core. نیست؛ بلکه صرفا مکمل آن است.


دریافت و نصب کتابخانه‌ی کمکی Scrutor

کتابخانه‌ی کمکی Scrutor سورس باز بوده و بسته‌ی NuGet آن توسط یکی از دستورات زیر به پروژه افزوده می‌شود:
> Install-Package Scrutor
> dotnet add package Scrutor
و یا به صورت مدخلی جدید در فایل csproj:
<Project Sdk="Microsoft.NET.Sdk.Web">
  <ItemGroup>
    <PackageReference Include="Scrutor" Version="3.0.2" />
  </ItemGroup>
</Project>


ثبت و معرفی ساده‌تر سرویس‌ها بر اساس قواعد نامگذاری آن‌ها توسط Scrutor

فرض کنید تعدادی سرویس را به صورت زیر تعریف کرده‌اید:
namespace CoreIocServices
{
    public interface IFoo
    {
        void Run();
    }

    public class Foo : IFoo
    {
        public void Run()
        {
            throw new System.NotImplementedException();
        }
    }

    public interface IBar
    {
        void Add();
    }

    public class Bar : IBar
    {
        public void Add()
        {
            throw new System.NotImplementedException();
        }
    }


    public interface IBaz
    {
        void Stop();
    }

    public class Baz : IBaz
    {
        public void Stop()
        {
            throw new System.NotImplementedException();
        }
    }
}
روش متداول معرفی آن‌ها به IoC Container برنامه به صورت زیر است:
services.AddScoped<IFoo, Foo>();
services.AddScoped<IBar, Bar>();
services.AddScoped<IBaz, Baz>();
و هرچقدر تعداد سرویس‌های برنامه بیشتر شود، سطرهای فوق نیز بیشتر خواهند شد.
در اینجا در حین تعریف سرویس‌های فوق این روش نامگذاری رعایت شده‌است: هر اینترفیس، نامش یک I بیشتر از نام کلاس مشتق شده‌ی از آن دارد؛ مانند اینترفیس IFoo و کلاس Foo. کتابخانه‌ی StructureMap که در ابتدای بحث معرفی شد، کار اسکن و اتصال یک چنین سرویس‌هایی را با تعریف scanner.WithDefaultConventions انجام می‌دهد. معادل آن با Scrutor به صورت زیر است:
namespace CoreIocSample02
{
    public class Startup
    {
        public void ConfigureServices(IServiceCollection services)
        {
            services.Scan(scan =>
                //scan.FromCallingAssembly()
                scan.FromAssemblyOf<IFoo>()
                    .AddClasses()
                    .AsMatchingInterface()
                    .WithScopedLifetime());
تعریف فوق به این معنا است:
- scan.FromAssemblyOf کار اسکن اسمبلی را انجام می‌دهد که نوع IFoo در آن قرار دارد. اگر از scan.FromCallingAssembly استفاده کنیم، به این معنا است که کار اسکن را دقیقا از همین اسمبلی فراخوان کدهای جاری، شروع کن. اما چون IFoo تعریف شده، در یک پروژه و اسمبلی دیگر قرار دارد، به همین جهت نیاز به ذکر صریح اسمبلی آن نیز هست.
- AddClasses یعنی تمام کلاس‌های public, non-abstract را به لیست services اضافه کن.
- AsMatchingInterface یعنی بر اساس قرارداد نامگذاری IClassName و ClassName، اتصالات سرویس‌ها را انجام بده.
بجای آن می‌توان از AsImplementedInterfaces نیز استفاده کرد. این حالت برای زمانی مناسب است که یک کلاس، چندین اینترفیس را پیاده سازی کند (مثلا کلاس TestService اینترفیس‌های ITestService و IService را پیاده سازی کرده باشد) و علاقمند باشید به ازای هر اینترفیس، یکبار سرویس آن نیز ثبت شود؛ کاری مانند تنظیمات زیر:
services.AddScoped<ITestService, TestService>();
services.AddScoped<IService, TestService>();
یا حتی می‌توان از متد ()<As<T نیز استفاده کرد. در اینجا به Scrutor گفته می‌شود که تمام کلاس‌های یافت شده را بر اساس نوع سرویس T ثبت و معرفی کن. البته اگر کلاسی نتواند نوع اینترفیس T را پیاده سازی کند، در زمان اجرا با استثناء مواجه خواهید شد.
- WithScopedLifetime نیز طول عمر این سرویس‌های اضافه شده را مشخص می‌کند. در اینجا می‌توان WithTransientLifetime و WithSingletonLifetime را نیز ذکر کرد.

بنابراین همانطور که ملاحظه می‌کنید، هنوز هم همان سیستم Microsoft.Extensions.DependencyInjection برقرار است؛ اما با وجود متد الحاقی جدید Scan، کار تعاریف سرویس‌های برنامه به شدت ساده می‌شود.


کار با وهله‌های کلاس‌های سرویس‌ها بجای اینترفیس‌های آن توسط Scrutor

می‌خواهیم مثال سوم قسمت ششم «چگونه بجای اینترفیس‌ها، یک وهله از کلاسی مشخص را از سیستم تزریق وابستگی‌ها درخواست کنیم؟» را توسط Scrutor پیاده سازی کنیم:
namespace CoreIocServices
{
    public interface IService { }
    public class Service1 : IService { }
    public class Service2 : IService { }
    public class Service : IService { }
}
در حالت متداول آن می‌توان از روش زیر نیز استفاده کرد:
services.AddTransient<Service1>();
services.AddTransient<Service2>();
services.AddTransient<Service>();
که با افزایش تعداد کلاس‌های سرویس برنامه به همین نحو نیز افزایش خواهند یافت. معادل این تنظیمات با Scrutor به صورت زیر است:
namespace CoreIocSample02
{
    public class Startup
    {
        public void ConfigureServices(IServiceCollection services)
        {
            services.Scan(scan =>
              //scan.FromCallingAssembly()
              scan.FromAssemblyOf<IService>()
                  .AddClasses()
                  .AsSelf()
                  .WithTransientLifetime());
در اینجا اسمبلی حاوی IService اسکن خواهد شد و سپس تمام کلاس‌های public, non-abstract آن AsSelf (ثبت پیاده سازی خود کلاس به عنوان سرویس) با طول عمر Transient به لیست services اضافه می‌شوند و یا اگر صرفا تعدادی سرویس مشخص مد نظر بود می‌توان به صورت زیر عمل کرد:
services.Scan(scan =>
               scan.AddTypes(new[] { typeof(Service1), typeof(Service2) })
                   .AsSelf()
                   .WithTransientLifetime());
متدهایی که در Scrutor، یک پیاده سازی را به عنوان سرویس معرفی می‌کنند، شامل این موارد هستند:
AsSelf: معادل ()<services.AddTransient<TestService است. در این حالت کلاس‌هایی که اینترفیسی را پیاده سازی نمی‌کنند و یا در کل مایل هستید که از طریق تزریق وابستگی‌ها در دسترس باشند، می‌توان توسط متد AsSelf به سیستم معرفی کرد.
AsSelfWithInterfaces: معادل تنظیمات زیر است:
services.AddSingleton<TestService>();
services.AddSingleton<ITestService>(x => x.GetRequiredService<TestService>());
services.AddSingleton<IService>(x => x.GetRequiredService<TestService>());
فرض کنید کلاس TestService اینترفیس‌های ITestService و IService را پیاده سازی کرده باشد. با استفاده از AsSelfWithInterfaces، یکبار پیاده سازی خود سرویس به سیستم معرفی می‌شود، سپس به ازای هر اینترفیس، از همان وهله‌ی TestService برای وهله سازی سرویس‌های ITestService و IService نیز استفاده می‌شود.


روش‌های متفاوت اسکن اسمبلی‌ها در Scrutor

Scrutor به همراه روش‌های متعددی برای تعریف اسمبلی یا اسمبلی‌هایی است که باید اسکن شوند و نمونه‌ای از آن‌را با FromAssemblyOf بررسی کردیم:
services.Scan(scan =>
              //scan.FromCallingAssembly()
              scan.FromAssemblyOf<IService>()
سایر موارد آن به شرح زیر هستند:
الف) FromAssemblyOf<>, FromAssembliesOf : اسمبلی یا اسمبلی‌هایی که نوع یا نوع‌های تعیین شده را به همراه دارند، اسکن می‌کند.
ب) FromCallingAssembly, FromExecutingAssembly, FromEntryAssembly کار اسکن اسمبلی‌های فراخوان، اسمبلی که هم اکنون در حال اجرا است و اسمبلی آغازین برنامه را انجام می‌دهند.
ج) FromAssemblyDependencies: تمام اسمبلی‌هایی را که وابسته‌ی به اسمبلی معرفی شده‌ی به آن هستند، اسکن می‌کند.
د) FromApplicationDependencies, FromDependencyContext: تمام اسمبلی‌هایی را که توسط برنامه، ارجاعی به آن‌ها وجود دارند، اسکن می‌کند.


انتخاب دقیق‌تر کلاس‌ها و سرویس‌های مدنظر توسط Scrutor

شاید عملکرد کلی متد AddClasses مدنظر شما نباشد و نیاز به انتخاب دقیق‌تری از سرویس‌های اسکن شده را داشته باشید؛ برای این مورد نیز Scrutor روش‌های زیر را ارائه می‌دهد. برای مثال خود کلاس AddClasses دارای overloadهای زیر نیز هست:
    public interface IImplementationTypeSelector : IAssemblySelector, IFluentInterface
    {
        IServiceTypeSelector AddClasses();
        IServiceTypeSelector AddClasses(bool publicOnly);
        IServiceTypeSelector AddClasses(Action<IImplementationTypeFilter> action);
        IServiceTypeSelector AddClasses(Action<IImplementationTypeFilter> action, bool publicOnly);
    }
حالت پیش‌فرض آن انتخاب تمام کلاس‌های public, non-abstract است. اگر پارامتر publicOnly را با false مقدار دهی کنید، internal/private nested classes را نیز انتخاب می‌کند. پارامتر action ای که در اینجا درنظر گرفته شده، جهت فیلتر کردن سرویس‌های انتخابی است که تعدادی از مثال‌های آن‌را در زیر بررسی می‌کنیم:
services.Scan(scan => scan
              .FromAssemblyOf<IService>()
                .AddClasses(classes => classes.AssignableTo<IService>())
// .AddClasses(classes => classes.InNamespaces("MyApp")) 
// .AddClasses(classes => classes.Where(type => type.Name.EndsWith("Repository")) 
                    .AsImplementedInterfaces()
                    .WithTransientLifetime());
در اینجا در حالت اول، کلاس‌هایی که صرفا اینترفیس IService را پیاده سازی کرده باشند، انتخاب می‌شوند. حالت دوم آن، انتخاب‌ها را به یک فضای نام محدود می‌کند و حالت سوم اگر نام کلاسی به Repository ختم شود، آن‌را به عنوان سرویس انتخاب خواهد کرد.


مدیریت جایگزینی سرویس‌ها توسط Scrutor

یکی از مزیت‌های طراحی یک برنامه با درنظر گرفتن الگوی تزریق وابستگی‌ها، امکان جایگزین کردن سرویس‌های پیش‌فرض آن با سرویس‌های دیگری است. فرض کنید کتابخانه‌ای ارائه شده و از الگوریتم هش کردن X استفاده کرده‌است؛ اما شما علاقمندید تا از الگوریتم Y بجای آن استفاده کنید. اگر این کتابخانه وهله‌ی الگوریتم هش کردن را از طریق تزریق وابستگی‌ها تامین کرده باشد، فقط کافی است در ابتدای معرفی تنظیمات تزریق وابستگی‌های آن، سرویس الگوریتم هش کردن موجود را با نمونه‌ی خاص خودتان جایگزین کنید.
اکنون فرض کنید پیش از استفاده‌ی از Scrutor، تعدادی سرویس را به روش متداولی ثبت و معرفی کرده‌اید:
services.AddTransient<ITransientService, TransientService>();
services.AddScoped<IScopedService, ScopedService>();
حال که قرار است متد Scan آن، سرویس‌های یک اسمبلی را به لیست موجود اضافه کند، به سرویس‌های زیر می‌رسد:
public class TransientService : IFooService {}
public class AnotherService : IScopedService {}
 رفتار آن با سرویس‌های معادلی که از پیش ثبت شده‌اند چگونه باید باشد؟ برای مدیریت این مساله، متد UsingRegistrationStrategy پیش بینی شده‌است:
services.Scan(scan =>
                scan.FromAssemblyOf<IFoo>()
                    .AddClasses()
                    .UsingRegistrationStrategy(RegistrationStrategy.Skip)
                    .AsMatchingInterface()
                    .WithScopedLifetime());
و پارامتر دریافتی آن یک چنین امضایی را دارد:
namespace Scrutor
{
    public abstract class RegistrationStrategy
    {
        public static readonly RegistrationStrategy Skip;
        public static readonly RegistrationStrategy Append;
        protected RegistrationStrategy();
        public static RegistrationStrategy Replace();
        public static RegistrationStrategy Replace(ReplacementBehavior behavior);
        public abstract void Apply(IServiceCollection services, ServiceDescriptor descriptor);
    }
}
- حالت Append آن که حالت پیش‌فرض نیز هست، تمام سرویس‌های یافت شده را به لیست IServiceCollection اضافه می‌کند؛ صرفنظر از اینکه پیشتر ثبت شده‌است یا خیر.
- حالت Skip آن، سرویسی را تکراری ثبت نمی‌کند. یعنی اگر سرویسی پیشتر در مجموعه‌ی IServiceCollection موجود بود، مجددا آن‌را ثبت نمی‌کند.

سپس نوبت به متدهای Replace می‌رسد که یک چنین پارامتری را قبول می‌کنند:
namespace Scrutor
{
    [Flags]
    public enum ReplacementBehavior
    {
        Default = 0,
        ServiceType = 1,
        ImplementationType = 2,
        All = 3
    }
}
- در حالت استفاده‌ی از Replace(​ReplacementBehavior.​ServiceType)، اگر سرویسی پیشتر در لیست IServiceCollection ثبت شده باشد، آن‌را حذف کرده و سپس نمونه‌ی جدید را ثبت می‌کند (ثبت سرویس بر اساس اینترفیس و پیاده سازی آن).
- در حالت استفاده‌ی از Replace(​ReplacementBehavior.​ImplementationType)، اگر پیاده سازی کلاسی پیشتر در لیست IServiceCollection ثبت شده باشد، آن‌را حذف کرده و سپس نمونه‌ی جدید را ثبت می‌کند (ثبت سرویس صرفا بر اساس نام کلاس آن).
- حالت Replace(​ReplacementBehavior.All) هر دو حالت قبل را با هم شامل می‌شود.


امکان ترکیب چندین استراتژی جستجو با هم توسط Scrutor

در یک برنامه‌ی واقعی غیرممکن است که بخواهید تمام کلاس‌ها را با یک طول عمر، اسکن و ثبت کنید. برای این منظور می‌توان از قابلیت فیلتر کردن کلاس‌ها که در مورد آن بحث شد و همچنین امکان ترکیب زنجیر وار حالت‌های مختلف اسکن، استفاده کرد:
services.Scan(scan => scan 
  .FromAssemblyOf<CombinedService>() 
    .AddClasses(classes => classes.AssignableTo<ICombinedService>()) // Filter classes 
      .AsSelfWithInterfaces() 
      .WithSingletonLifetime() 
 
    .AddClasses(x=> x.AssignableTo(typeof(IOpenGeneric<>))) // Can close generic types 
      .AsMatchingInterface() 
 
    .AddClasses(x=> x.InNamespaceOf<MyClass>()) 
      .UsingRegistrationStrategy(RegistrationStrategy.Replace()) // Defaults to ReplacementBehavior.ServiceType 
      .AsMatchingInterface() 
      .WithScopedLifetime() 
 
  .FromAssemblyOf<DatabaseContext>()   // Can load from multiple assemblies within one Scan() 
    .AddClasses()  
      .AsImplementedInterfaces() 
);
مطالب
نگاشت خودکار اشیاء توسط AutoMapper و Reflection - ایده شماره 1
آموزش کامل AutoMapper قبلا در سایت ارائه شده است. در این مقاله می‌خواهیم Mapping نوع‌های مختلف بین Dto و Entity‌های پروژه را توسط Reflection به صورت خودکار انجام دهیم. سورس کامل مثال را می‌توانید در این ریپازیتوری مشاهده کنید.
در این روش ما یک کلاس جنریک را به نام BaseDto داریم که تمام Dto‌های ما برای نگاشت خودکار باید از آن ارث بری کنند. در مثال زیر کلاس PostDto لازم است به کلاس Post نگاشت شود. پس خواهیم داشت :
public class PostDto : BaseDto<PostDto, Post, long>
{
    public string Title { get; set; }
    public string Text { get; set; }
    public int CategoryId { get; set; }

    public string CategoryName { get; set; } //=> Category.Name
}
  • کلاس PostDto خودش را به عنوان اولین پارامتر جنریک BaseDto معرفی می‌کند.
  • به عنوان پارامتر دوم، باید کلاس Entity ایی که قرار است به آن نگاشت شود (Post) را معرفی کنیم.
  • پارامتر سوم، نوع فیلد Id است که در اینجا خاصیت Id کلاس‌های Post و PostDto ما، از نوع long است.
  • نهایتا خواصی را که برای نگاشت لازم داریم، تعریف میکنیم مثل Title و...
  • همچنین می‌توانیم خواصی برای نگاشت با خواص Navigation Property‌های Post هم تعریف کنیم؛ مانند CategoryName که به خاصیت Name از Category پست مربوطه اشاره میکند و AutoMapper به صورت هوشمندانه آن‌ها را به هم نگاشت می‌کند.
تعریف کلاس جنریک BaseDto هم به نحو زیر است.
public abstract class BaseDto<TDto, TEntity, TKey>
        where TDto : class, new()
        where TEntity : BaseEntity<TKey>, new()
{
    [Display(Name = "ردیف")]
    public TKey Id { get; set; }

    public TEntity ToEntity()
    {
        return Mapper.Map<TEntity>(CastToDerivedClass(this));
    }

    public TEntity ToEntity(TEntity entity)
    {
        return Mapper.Map(CastToDerivedClass(this), entity);
    }

    public static TDto FromEntity(TEntity model)
    {
        return Mapper.Map<TDto>(model);
    }

    protected TDto CastToDerivedClass(BaseDto<TDto, TEntity, TKey> baseInstance)
    {
        return Mapper.Map<TDto>(baseInstance);
    }
}
  • نوع TDto به کلاس Dto ما اشاره میکند؛ مثلا PostDto
  • نوع TEntity به کلاس Entity ما اشاره میکند؛ مثلا Post
  • نوع TKey به نوع خاصیت Id اشاره میکند.
  • شرط لازم برای نوع TEntity این است که از <BaseEntity<TKey ارث بری کرده باشد (نوع پایه‌ای که تمام Entity‌های ما از آن ارث بری می‌کنند).
  • متد‌های کمکی ToEntity و FromEntity، کار نگاشت اشیاء را برای ما راحت‌تر می‌کنند.
پیاده سازی کلاس BaseEntity و Post نیز به شرح زیر است.
public abstract class BaseEntity<TKey>
{
    public TKey Id { get; set; }
}

public class Post : BaseEntity<long>
{
    public string Title { get; set; }
    public string Text { get; set; }
    public int CatgeoryId { get; set; }

    public Category Category { get; set; }
}

توضیح متد های ToEntity  و  FromEntity 
متد ToEntity شی Dto جاری را به Entity مربوطه نگاشت کرده و یک وهله از آن را باز میگرداند. پس بجای استفاده دستی از Api‌های AutoMapper مانند Mapper.Map<Post>(postDto)  کافی است متد ToEntity را فراخوانی کنیم؛ مثال:
var postDto = new PostDto();
var post = postDto.ToEntity();
متد بالا برای اکشن Create مناسب است؛ ولی برای اکشن Update خیر. چرا که برای Update نباید نگاشت بر روی وهله جدیدی از Post انجام شود؛ بلکه باید بر روی وهله‌ای از قبل موجود (همان post ایی که بر اساس id واکشی کرده‌ایم) نگاشت انجام شود، تا تغییرات لازم، بر روی همان وهله تاثیر کند. در غیر این صورت اگر وهله جدیدی از post ایجاد شود، چون توسط EF ChangeTracker ردیابی نمی‌شود، به‌روز رسانی هم انجام نخواهد شد.
بنابراین برای نگاشت postDto به یک شیء Post از پیش موجود (post یافت شده توسط id) خواهیم داشت:
var post = // finded by id
var updatePost = postDto.ToEntity(post);
همچنین برای نگاشت از یک Entity به Dto (عکس قضیه بالا: مثلا نگاشت یک postDto به post) کافی است متد ایستای FromEntity را خوانی کنیم. مثال :
var postDto = PostDto.FromEntity(post);

کانفیگ خودکار Mapping توسط Reflection
در ادامه می‌خواهیم کانفیگ Mapping بین Dto‌های پروژه به Entity‌های مربوطه (مثلا PostDto به Dto و برعکس) را به صورت خودکار توسط Reflection پیاده سازی و اعمال کنیم. این کار توسط کلاس AutoMapperConfiguration به نحو زیر انجام می‌شود.
public static class AutoMapperConfiguration
{
    public static void InitializeAutoMapper()
    {
        Mapper.Initialize(configuration =>
        {
            configuration.ConfigureAutoMapperForDto();
        });

        //Compile mapping after configuration to boost map speed
        Mapper.Configuration.CompileMappings();
    }

    public static void ConfigureAutoMapperForDto(this IMapperConfigurationExpression config)
    {
        config.ConfigureAutoMapperForDto(Assembly.GetEntryAssembly());
    }

    public static void ConfigureAutoMapperForDto(this IMapperConfigurationExpression config, params Assembly[] assemblies)
    {
        var dtoTypes = GetDtoTypes(assemblies);

        var mappingTypes = dtoTypes
            .Select(type =>
            {
                var arguments = type.BaseType.GetGenericArguments();
                return new
                {
                    DtoType = arguments[0],
                    EntityType = arguments[1]
                };
            }).ToList();

        foreach (var mappingType in mappingTypes)
            config.CreateMappingAndIgnoreUnmappedProperties(mappingType.EntityType, mappingType.DtoType);
    }

    public static void CreateMappingAndIgnoreUnmappedProperties(this IMapperConfigurationExpression config, Type entityType, Type dtoType)
    {
        var mappingExpression = config.CreateMap(entityType, dtoType).ReverseMap();

        //Ignore mapping to any property of source (like Post.Categroy) that dose not contains in destination (like PostDto)
        //To prevent from wrong mapping. for example in mapping of "PostDto -> Post", automapper create a new instance for Category (with null catgeoryName) because we have CategoryName property that has null value
        foreach (var property in entityType.GetProperties())
        {
            if (dtoType.GetProperty(property.Name) == null)
                mappingExpression.ForMember(property.Name, opt => opt.Ignore());
        }
    }

    public static IEnumerable<Type> GetDtoTypes(params Assembly[] assemblies)
    {
        var allTypes = assemblies.SelectMany(a => a.ExportedTypes);

        var dtoTypes = allTypes.Where(type =>
                type.IsClass && !type.IsAbstract && type.BaseType != null && type.BaseType.IsGenericType &&
                (type.BaseType.GetGenericTypeDefinition() == typeof(BaseDto<,>) ||
                type.BaseType.GetGenericTypeDefinition() == typeof(BaseDto<,,>)));

        return dtoTypes;
    }
}
عملیات با فراخوانی متد ایستا InitializeAutoMapper شروع می‌شود و باید این متد فقط یکبار در اجرای پروژه فراخوانی شود. (مثلا در سازنده کلاس Startup.cs)
public class Startup
{
    public Startup(IConfiguration configuration)
    {
        Configuration = configuration;
        AutoMapperConfiguration.InitializeAutoMapper();
    }
- درون این متد کانفیگ، Mapping نوع‌های مختلف قابل نگاشت برای AutoMapper توسط Mapper.Initialize انجام می‌شود.
- متد ConfigureAutoMapperForDto متد دیگری را به همین نام، فراخوانی می‌کند؛ با این تفاوت که Assembly ورودی پروژه را توسط متد ()Assembly.GetEntryAssembly، یافته و به آن پاس میدهد.
- EntryAssembly به اسمبلی ای که به عنوان نقطه ورود برنامه است، اشاره می‌کند. در این سورس کد چون پروژه ما از نوع ASP.NET Core است، اسمبلی این پروژه به عنوان EntryAssmebly شناخته می‌شود؛ یعنی همان لایه‌ای که کلاس‌های Dto ما (مانند PostDto) داخل آن تعریف شده‌است. ما به این اسمبلی از این جهت نیاز داریم که می‌خواهیم توسط Reflection، تمام نوع‌هایی که از BaseDto ارث بری می‌کنند (مانند PostDto) را یافته و Mapping آنها را به AutoMapper معرفی و اعمال کنیم.
نکته : اگر در پروژه شما Dto‌ها در لایه/لایه‌های دیگری تعریف شده‌اند باید اسمبلی آن لایه‌ها را به آن پاس دهید.
در این مرحله توسط متد GetDtoTypes کار یافتن نوع‌های Dto موجود در اسمبلی/اسمبلی‌های مشخص شده انجام می‌شود.
public static IEnumerable<Type> GetDtoTypes(params Assembly[] assemblies)
{
    var allTypes = assemblies.SelectMany(a => a.ExportedTypes);

    var dtoTypes = allTypes.Where(type =>
            type.IsClass && !type.IsAbstract && type.BaseType != null && type.BaseType.IsGenericType &&
            (type.BaseType.GetGenericTypeDefinition() == typeof(BaseDto<,>) ||
            type.BaseType.GetGenericTypeDefinition() == typeof(BaseDto<,,>)));

    return dtoTypes;
}
  • در خط اول ابتدا تمامی نوع‌های قابل دسترس از بیرون (ExportedTypes) از assembly‌های دریافتی واکشی می‌شود.
  • سپس توسط Where، نوع‌هایی که کلاس بوده، abstract نیستند و از BaseDto ارث بری کرده‌اند، فیلتر شده و بازگردانده می‌شوند.
در ادامه، از لیست نوع‌های Dto یافت شده، پارامتر‌های جنریک TDto و TEntity به ازای هر نوع استخراج می‌شوند.
public static void ConfigureAutoMapperForDto(this IMapperConfigurationExpression config, params Assembly[] assemblies)
{
var dtoTypes = GetDtoTypes(assemblies);

var mappingTypes = dtoTypes
.Select(type =>
{
var arguments = type.BaseType.GetGenericArguments();
return new
{
DtoType = arguments[0],
EntityType = arguments[1]
};
}).ToList();

foreach (var mappingType in mappingTypes)
config.CreateMappingAndIgnoreUnmappedProperties(mappingType.EntityType, mappingType.DtoType);
}

در آخر بر روی لیست یافت شده، گردش می‌کنیم (foreach) و دو نوع DtoType و EntityType (مانند postDto و post) را که باید به یکدیگر نگاشت شوند، به متد CreateMappingAndIgnoreUnmappedProperties ارسال می‌کنیم. کار این متد، معرفی/اعمال Mapping بین نوع‌ها به کانفیگ AutoMapper می‌باشد. همچنین خواصی را که نباید نگاشت شوند، به طور خودکار یافته و Ignore می‌کند.
در مثال جاری، خاصیت CategoryName کلاس PostDto برای خواندن و select از دیتابیس لازم است زیرا می‌خواهیم هر postDto، شامل نام دسته بندی هر پست نیز باشد، ولی این ویژگی برای افزودن یا به‌روزرسانی مدنظر ما نیست؛ چرا که کلاینت ما به هنگام فراخوانی اکشن Create، فقط مقادیر خواص Post (مانند Title, Text و CategoryId) را ارسال می‌کند و نه CategoryName را. در نتیجه CatgoryName همیشه null است. اما مشکلی که ایجاد می‌کند این است که AutoMapper به هنگام نگاشت یک PostDto به Post، چون خاصیت CategoryName با (مقدار null)  وجود دارد، یک وهله جدید (با مقادیر پیشفرض) را برای Category ایجاد می‌کند که خاصیت Name آن برابر با null است و قطعا این مدنظر ما نیست. پس جهت جلوگیری از این مشکل لازم است خواصی از Entity که در Dto موجود نیستند (مانند Category) را Ignore کنیم و این دقیقا همان کاری است که متد CreateMappingAndIgnoreUnmappedProperties انجام می‌دهد. 
public static void CreateMappingAndIgnoreUnmappedProperties(this IMapperConfigurationExpression config, Type entityType, Type dtoType)
{
    var mappingExpression = config.CreateMap(entityType, dtoType).ReverseMap();

    //Ignore mapping to any property of entity (like Post.Categroy) that dose not contains in dto (like PostDto.CategoryName)
    //To prevent from wrong mapping. for example in mapping of "PostDto -> Post", automapper create a new instance for Category (with null catgeoryName) because we have CategoryName property that has null value
    foreach (var property in entityType.GetProperties())
    {
        if (dtoType.GetProperty(property.Name) == null)
            mappingExpression.ForMember(property.Name, opt => opt.Ignore());
    }
}
البته اساسا استفاده از یک Dto هم برای Create/Update و هم برای Select اصولی نیست و بهتر است دو Dto جداگانه که صرفا خواص مورد نیاز را دارند، داشته باشیم که در این صورت مشکل بالا نیز اصلا رخ نخواهد داد. راه حل مورد استفاده کنونی صرفا مرهمی برای یک استفاده غیر اصولی است!
در آخر می‌توان گفت تنها ایراد کوچک ایده‌ی فوق، استفاده از Api‌های استاتیک AutoMapper در کلاس BaseDto است (متد Mapper.Map)  که باعث می‌شود نتوانیم به هنگام تست نویسی، سرویس AutoMapper را با پیاده سازی دیگری (Fake) جایگزین و آن را Mock کنیم. البته این کار برای AutoMapper زیاد معمول هم نبوده و در مقابل مزایای این ایده، به نظرم ارزش استفاده را خواهد داشت.
در قسمت بعدی همین ایده را توسعه خواهیم داد و قابلیت سفارشی سازی Mapping را برای آن فراهم خواهیم کرد.
مطالب
Asp.Net Identity #3
در مقاله‌ی  پیشین  نگاهی داشتیم به نحوه‌ی برپایی سیستم Identity. در این مقاله به نحوه‌ی استفاده از این سیستم به منظور طراحی یک سیستم مدیریت کاربران خواهیم پرداخت و انشالله در مقاله‌های بعدی این سیستم را تکمیل خواهیم نمود. کار را با اضافه کردن یک کنترلر جدید به پروژه آغاز می‌کنیم.
using System.Web;
using System.Web.Mvc;
using Microsoft.AspNet.Identity.Owin;
using Users.Infrastructure;

namespace Users.Controllers
{
    public class HomeController : Controller
    {
        private AppUserManager UserManager
        {
            get { return HttpContext.GetOwinContext().GetUserManager<AppUserManager>(); }
        }
        // GET: Home
        public ActionResult Index()
        {
            return View(UserManager.Users);

        }

}
در خط 10 یک پروپرتی از نوع AppUserManager (کلاسی که مدیریت کاربران را برعهده دارد) ایجاد می‌کنیم. اسمبلی Microsoft.Owin.Host.SystemWeb یک سری متدهای الحاقی را به کلاس HttpContext اضافه می‌کند که یکی از آنها متد GetOwinContext می‌باشد. این متد یک شیء Per-Request Context را از طریق رابط IOwinContext به OwinApi ارسال می‌کند؛ با استفاده از متد الحاقی <GetUserManager<T که T همان کلاس AppUserManager می‌باشد. حال که نمونه‌ای از کلاس AppUserManager را بدست آوردیم، می‌توانیم درخواستهایی را به جداول کاربران بدهیم. مثلا در خط 17 با استفاده از پروپرتی Users میتوانیم لیست کاربران موجود را بدست آورده و آن را به ویو پاس دهیم.
@using Users.Models
@model IEnumerable<AppUser>
@{
    ViewBag.Title = "Index";
}
<div class="panel panel-primary">
    <div class="panel-heading">
        User Accounts
    </div>
    <table class="table table-striped">
        <tr><th>ID</th><th>Name</th><th>Email</th></tr>
        @if (!Model.Any())
        {
            <tr><td colspan="3" class="text-center">No User Accounts</td></tr>
        }
        else
        {
            foreach (AppUser user in Model)
            {
                <tr>
                    <td>@user.Id</td>
                    <td>@user.UserName</td>
                    <td>@user.Email</td>
                </tr>
            }
        }
    </table>
</div>
@Html.ActionLink("Create", "CreateUser", null, new { @class = "btn btn-primary" })

نحوه‌ی ساخت یک کاربر جدید
ابتدا در پوشه Models یک کلاس ایجاد کنید : 
 namespace Users.Models
    {
        public class CreateModel
        {
            [Required]
            public string Name { get; set; }
            [Required]
            public string Email { get; set; }
            [Required]
            public string Password { get; set; }
        }
    }
فقط دوستان توجه داشته باشید که در پروژه‌های حرفه‌ای و تجاری هرگز اطلاعات مهم مربوط به مدل‌ها را در پوشه‌ی Models قرار ندهید. ما در اینجا صرف آموزش و برای جلوگیری از پیچیدگی مثال این کار را انجام میدهیم. برای اطلاعات بیشتر به این مقاله مراجعه کنید.
حال در کنترلر برنامه کدهای زیر را اضافه می‌کنیم:
 public ActionResult CreateUser()
        {
            return View();
        }

        [HttpPost]
        public async Task<ActionResult> CreateUser(CreateModel model)
        {
            if (!ModelState.IsValid)
                return View(model);

            var user = new AppUser { UserName = model.Name, Email = model.Email };
            var result = await UserManager.CreateAsync(user, model.Password);

            if (result.Succeeded)
            {
                return RedirectToAction("Index");
            }

            foreach (var error in result.Errors)
            {
                ModelState.AddModelError("", error);
            }
            return View(model);
        }
در اکشن CreateUser ابتدا یک شیء از کلاس AppUser ساخته و پروپرتی‌های مدل را به پروپرتی‌های کلاس AppUser انتساب می‌دهیم. در مرحله‌ی بعد یک شیء از کلاس IdentityResult به نام result ایجاد کرده و نتیجه‌ی متد CreateAsync را درون آن قرار می‌دهیم. متد CreateAsync از طریق پروپرتی از نوع AppUserManager قابل دسترسی است و دو پارامتر را دریافت می‌کند. پارامتر اول یک شیء از کلاس AppUser و پارامتر دوم یک رشته‌ی حاوی Password می‌باشد و خروجی متد یک شیء از کلاس IdentityResult است. در مرحله‌ی بعد چک می‌کنیم اگر Result، مقدار Succeeded را داشته باشد (یعنی نتیجه موفقیت آمیز بود) آن‌وقت ... در غیر اینصورت خطاهای موجود را به ModelState اضافه نموده و به View می‌فرستیم.
@model Users.ViewModels.CreateModel

@Html.ValidationSummary(false)

@using (Html.BeginForm())
{
    <div class="form-group">
        <label>Name</label>
        @Html.TextBoxFor(x => x.UserName, new { @class = "form-control" })
    </div>
    <div class="form-group">
        <label>Email</label>
        @Html.TextBoxFor(x => x.Email, new { @class = "form-control" })
    </div>

    <div class="form-group">
        <label>Password</label>
        @Html.PasswordFor(x => x.Password, new { @class = "form-control" })
    </div>
    <button type="submit" class="btn btn-primary">Create</button>
    @Html.ActionLink("Cancel", "Index", null, new { @class = "btn btn-default" })
}

اعتبار سنجی رمز
عمومی‌ترین و مهمترین نیازمندی برای هر برنامه‌ای، اجرای سیاست رمزگذاری می‌باشد؛ یعنی ایجاد یک سری محدودیتها برای ایجاد رمز است. مثلا رمز نمی‌تواند از 6 کاراکتر کمتر باشد و یا باید حاوی حروف بزرگ و کوچک باشد و ... . برای اجرای سیاست‌های رمزگذاری از کلاس PasswordValidator استفاده میشود. کلاس PasswordValidator برای اجرای سیاستهای رمزگذاری از پروپرتی‌های زیر استفاده می‌کند.

var manager = new AppUserManager(new UserStore<AppUser>(db))
            {
                PasswordValidator = new PasswordValidator
                {
                    RequiredLength = 6,
                    RequireNonLetterOrDigit = false,
                    RequireDigit = false,
                    RequireLowercase = true,
                    RequireUppercase = true
                }
            };

فقط دوستان توجه داشته باشید که کد بالا را در متد Create از کلاس AppUserManager استفاده کنید.


اعتبار سنجی نام کاربری

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

manager.UserValidator = new UserValidator<AppUser>(manager)
            {
                AllowOnlyAlphanumericUserNames = true,
                RequireUniqueEmail = true
            };

کد بالا را نیز در متد Create  از کلاس AppUserManager قرار می‌دهیم.