بازخوردهای دوره
مدیریت استثناءها در حین استفاده از واژه‌های کلیدی async و await
- در مورد EF و متدهای Async آن مطلب جداگانه‌ای تهیه شده: «پردازش‌های Async در Entity framework 6»
- در مورد ASP.NET MVC و متدهای Async هم یک مطلب اختصاصی تهیه شده: «استفاده از Async و Await در برنامه‌های ASP.NET MVC »
- مثال دوم شما async نیست چون از متد Wait استفاده کرده‌اید (این متد، یک متد blocking است و ترد جاری را قفل می‌کند). این مثال با نمونه‌ی همزمان تقریبا یکسان عمل می‌کند.
همچنین در این مثال استفاده از Task.Factory.StartNew به معنای async تقلبی است و اصلا توصیه نمی‌شود. برای EF متدهای Async واقعی وجود دارند.
- هدف از بکارگیری متدهای async الزاما سریعتر کردن اجرای عملیات مورد نظر نیست. هدف خالی کردن ترد جاری و امکان استفاده‌ی مجدد از آن برای پاسخ دهی به یک کاربر دیگر است؛ با توجه به اینکه هزینه ایجاد تردهای جدید بالا است و همچنین نهایتا بر اساس مشخصات و منابع سرور، این تعداد محدود است. هدف بالا بردن میزان مقیاس پذیری یک برنامه است با تعداد کاربران بالا.
مطالب
نمایش بلادرنگ اعلامی به تمام کاربران در هنگام درج یک رکورد جدید به صورت notification
در ادامه می‌خواهیم مثالی را که در این مطلب مورد بررسی قرار گرفت، به صورتی تغییر دهیم که با ثبت یک آیتم جدید درون دیتابیس، یک notification، به تمامی کاربران متصل به هاب ارسال شود. همچنین با کلیک بر روی Notification سطر جدید نیز بلافاصله نمایش داده شود:

در این مثال برای نمایش پیام به صورت notification، از کتابخانه toastr استفاده می‌کنیم که از طریق nuget می‌توانید آن را به پروژه اضافه کنید:
PM> Install-Package toastr
کار با این کتابخانه خیلی ساده است؛ کافی است فایل‌های js و css آن را به فایل layout اضافه کرده و به این صورت از آن استفاده کنیم:
toastr.info("نمایش یک پیام - info");
toastr.success("نمایش یک پیام - success");
toastr.error("نمایش یک پیام - error");
toastr.warning("نمایش یک پیام - warning");
دستورات فوق خروجی‌های زیر را نمایش می‌دهد:

برای پیام‌های فوق نیز می‌توانید عنوانی را انتخاب کنید:
toastr.success("نمایش یک پیام - success", "عنوان");
اگر به فایل js این کتابخانه مراجعه کنید، می‌توانید مقادیر پیش‌فرض آن را برای نمایش یک پیام مشاهده کنید. برای سفارشی‌سازی آن نیز می‌توانید به این صورت عمل کنید:
toastr.options = {
            tapToDismiss: true,
            toastClass: 'toast',
            containerId: 'toast-container',
            debug: false,

            showMethod: 'fadeIn', //fadeIn, slideDown, and show are built into jQuery
            showDuration: 300,
            showEasing: 'swing', //swing and linear are built into jQuery
            onShown: undefined,
            hideMethod: 'fadeOut',
            hideDuration: 1000,
            hideEasing: 'swing',
            onHidden: undefined,

            extendedTimeOut: 1000,
            iconClasses: {
                error: 'toast-error',
                info: 'toast-info',
                success: 'toast-success',
                warning: 'toast-warning'
            },
            iconClass: 'toast-info',
            positionClass: 'toast-top-right',
            timeOut: 5000, // Set timeOut and extendedTimeOut to 0 to make it sticky
            titleClass: 'toast-title',
            messageClass: 'toast-message',
            target: 'body',
            closeHtml: '<button>&times;</button>',
            newestOnTop: true,
            preventDuplicates: false,
            progressBar: false
};
اکنون برای نمایش این نوع پیام‌ها در زمان اتصال به هاب (در واقع در زمان ثیت یک رکورد جدید) نیاز به ارسال پارامتر خاصی به سرور (از سمت کلاینت) نمی‌باشد. تنها باید کدهای سمت سرور یعنی هاب را به گونه‌ایی تغییر دهیم تا به محض فراخوانی SendNotification، آخرین رکورد ثبت شده در دیتابیس را به تمامی کلاینت‌های متصل به هاب ارسال کند:
public class NotificationHub : Hub
{
        private readonly IProductService _productService;

        public NotificationHub(IProductService productService)
        {
            _productService = productService;
        }

        public void SendNotification()
        {
            Clients.Others.ShowNotification(_productService.GetLastProduct());
        }
}
در سمت کلاینت نیز کدها همانند مثال قبل هستند؛ با این تفاوت که در متد سمت کلاینت باید اطلاعات ارسال شده از سمت سرور را با نمایش یک notification به کاربران اطلاع دهیم:
var notify = $.connection.notificationHub;
notify.client.showNotification = function (data) {
toastr.info("رکورد جدیدی ثبت گردید جهت نمایش اینجا کلیک کنید");
};
$.connection.hub.start().done(function () {
            @{
                if (ViewBag.NotifyUsers)
                {
                    <text>notify.server.sendNotification();</text>
                }
            }
});
تا اینجا همانند مثال قبلی عمل کردیم. یعنی به جای نمایش یک alert بوت‌استرپ، از کتابخانه toastr استفاده گردید. در مثال قبلی کاربر برای دیدن تغییرات می‌بایستی یکبار صفحه را ریفرش کند، اکنون می‌خواهیم کاربر بعد از کلیک بر روی پیام، بلافاصله سطر جدید را نیز مشاهده کند:
var positionClasses = {
            topRight: 'toast-top-right',
            bottomRight: 'toast-bottom-right',
            bottomLeft: 'toast-bottom-left',
            topLeft: 'toast-top-left',
            topCenter: 'toast-top-center',
            bottomCenter: 'toast-bottom-center'
        };
        var notify = $.connection.notificationHub;
        notify.client.showNotification = function (data) {
            toastr.options = {
                showDuration: 300,
                positionClass: positionClasses.bottomRight,
                onclick: function () {
                    $('#table tr:last').after("<tr>" +
                    "<td>" + data.Title + "</td>" +
                    "<td>" + data.Description + "</td>" +
                    "<td>" + data.Price + "</td>" +
                    "<td>" + data.Category + "</td>" +
                    "<td>  </td>" +
                    "</tr>");

                }
            };
            toastr.info("رکورد جدیدی ثبت گردید جهت نمایش اینجا کلیک کنید");
            
            
        };
        $.connection.hub.start().done(function () {
            @{
                if (ViewBag.NotifyUsers)
                {
                    <text>notify.server.sendNotification();</text>
                }
            }
});
همانطور که مشاهده می‌کنید از onClick برای toastr استفاده کرده‌ایم. با این callback گفته‌ایم که اگر بر روی پیام کلیک شد، اطلاعات را به صورت یک سطر جدید به جدول اضافه کن:
onclick: function () {
                    $('#table tr:last').after("<tr>" +
                    "<td>" + data.Title + "</td>" +
                    "<td>" + data.Description + "</td>" +
                    "<td>" + data.Price + "</td>" +
                    "<td>" + data.Category + "</td>" +
                    "<td>  </td>" +
                    "</tr>");

}
مقادیر به صورت یک شیء جاوااسکریپتی برگردانده خواهند شد:
data {Id: 12, Title: "Item1", Description: "Des", Price: 100000, Category: 0}
که توسط data می‌توانیم به هر کدام از فیلدها، جهت نمایش در خروجی، دسترسی داشته باشیم.
دریافت سورس مثال جاریShowAlertSignalR 
مطالب
راهی از گوگل ریدر به گوگل پلاس و سپس به دنیای خارج!

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

اما ... در ادامه ببینیم که چطور می‌توان از گوگل ریدر جدید، راهی به خارج باز کرد!

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


ب) اکنون به اکانت گوگل پلاس خود مراجعه کنید. در قسمت پروفایل، به لینک بالای صفحه دقت نمائید. چیزی است شبیه به:

https://plus.google.com/u/0/userId/posts 

این userId برای ادامه کار مهم است.

ج) از این userId برای فراخوانی لینک زیر استفاده خواهیم کرد (userId آن باید جایگزین شود):

https://plus.google.com/_/stream/getactivities/?&sp=[1,2,"userId",null,null,null,null,"social.google.com",[]]


خروجی آن طبق معمول متداول گوگل، فرمت JSON است و می‌توان از آن لیست اشتراک‌های عمومی را استخراج کرد. برای نمونه کلاس GooglePlusJsonParser تهیه شده زیر می‌تواند این عملیات را انجام دهد:

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Net;
//needs a ref. to System.Web.Extensions asm.
using System.Web.Script.Serialization;

namespace GooglePlus
{
[DebuggerDisplay("{DateTime}-{Title}-{Comment}-{Url}")]
public class GooglePlusItem
{
public DateTime DateTime { set; get; }
public string Url { set; get; }
public string Title { set; get; }
public string Comment { set; get; }
}

public static class Utils
{
public static DateTime ConvertFromUnixTimestamp(this long data)
{
return new DateTime(1970, 1, 1, 0, 0, 0, 0).AddSeconds(data);
}

public static string ToSafeString(this object data)
{
return data == null ? string.Empty : data.ToString();
}
}

public class GooglePlusJsonParser
{
public static IList<GooglePlusItem> GetMyPublicItemsList(string userId)
{
var url = string.Format("https://plus.google.com/_/stream/getactivities/?&sp=[1,2,\"{0}\",null,null,null,null,\"social.google.com\",[]]", userId);
var jsonData = new WebClient().DownloadString(url);
return ParsePublicData(jsonData);
}

public static IList<GooglePlusItem> ParsePublicData(string jsonData)
{
jsonData = removeRedundantData(jsonData);
var posts = parseJson(jsonData);
return createItemsList(posts);
}

private static IList<GooglePlusItem> createItemsList(object[] posts)
{
var result = new List<GooglePlusItem>();
foreach (object[] post in posts)
{
var items = (object[])((object[])post[66])[0];
result.Add(new GooglePlusItem
{
DateTime = (((Int64)post[5]) / 1000).ConvertFromUnixTimestamp(),
Title = items[3].ToSafeString(),
Url = items[1].ToSafeString(),
Comment = post[4].ToSafeString()
});
}
return result;
}

private static object[] parseJson(string jsonData)
{
var jsonObject = new JavaScriptSerializer().Deserialize<object[]>(jsonData);
var posts = (object[])(((object[])jsonObject[1])[0]);
return posts;
}

private static string removeRedundantData(string jsonData)
{
return jsonData.Replace(")]}'", string.Empty)
.Replace("[,", "[\"\",")
.Replace(",,", ",\"\",")
.Replace(",,", ",\"\",");
}
}
}

نحوه استفاده از آن هم می‌تواند فراخوانی متد GooglePlusJsonParser.GetMyPublicItemsList به همراه userId یاده شده باشد.



ایده اصلی از:
Getting the Google+ Feed for any profile in JSON


مطالب
مروری بر کاربردهای Action و Func - قسمت اول
delegate‌ها، نوع‌هایی هستند که ارجاعی را به یک متد دارند؛ بسیار شبیه به function pointers در C و CPP هستند، اما برخلاف آن‌ها، delegates شی‌ء‌گرا بوده، به امضای متد اهمیت داده و همچنین کد مدیریت شده و امن به شمار می‌روند.
سیر تکاملی delegates را در مثال ساده زیر می‌توان ملاحظه کرد:
using System;

namespace ActionFuncSamples
{
    public delegate int AddMethodDelegate(int a);
    public class DelegateSample
    {
        public void UseDelegate(AddMethodDelegate addMethod)
        {
            Console.WriteLine(addMethod(5));
        }
    }

    public class Helper
    {
        public int CustomAdd(int a)
        {
            return ++a;
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            Helper helper = new Helper();

            // .NET 1            
            AddMethodDelegate addMethod = new AddMethodDelegate(helper.CustomAdd);
            new DelegateSample().UseDelegate(addMethod);

            // .NET 2, anonymous delegates
            new DelegateSample().UseDelegate(delegate(int a) { return helper.CustomAdd(a); });

            // .NET 3.5
            new DelegateSample().UseDelegate(a => helper.CustomAdd(a));
        }
    }
}
معنای کلمه delegate، واگذاری مسئولیت است. به این معنا که ما در متد UseDelegate، نمی‌دانیم addMethod به چه نحوی تعریف خواهد شد. فقط می‌دانیم که امضای آن چیست.
در دات نت یک، یک وهله از شیء AddMethodDelegate ساخته شده و سپس متدی که امضایی متناسب و متناظر با آن را داشت، به عنوان متد انجام دهنده مسئولیت معرفی می‌شد. در دات نت دو، اندکی نحوه تعریف delegates با ارائه delegates بی‌نام، ساده‌تر شد و در دات نت سه و نیم با ارائه lambda expressions ، تعریف و استفاده از delegates باز هم ساده‌تر و زیباتر گردید.
به علاوه در دات نت 3 و نیم، دو Generic delegate به نام‌های Action و Func نیز ارائه گردیده‌اند که به طور کامل جایگزین تعریف طولانی delegates در کدهای پس از دات نت سه و نیم شده‌اند. تفاوت‌های این دو نیز بسیار ساده است:
اگر قرار است واگذاری قسمتی از کد را به متدی محول کنید که مقداری را بازگشت می‌دهد، از Func و اگر این متد خروجی ندارد از Action استفاده نمائید:
Action<int> example1 = x => Console.WriteLine("Write {0}", x);
example1(5);

Func<int, string> example2 = x => string.Format("{0:n0}", x);
Console.WriteLine(example2(5000));
در دو مثال فوق، نحوه تعریف inline یک Action و یا Func را ملاحظه می‌کنید. Action به متدی اشاره می‌کند که خروجی ندارد و در اینجا تنها یک ورودی int را می‌پذیرد. Func در اینجا به تابعی اشاره می‌کند که یک ورودی int را دریافت کرده و یک خروجی string را باز می‌گرداند.

پس از این مقدمه، در ادامه قصد داریم مثال‌های دنیای واقعی Action و Func را که در سال‌های اخیر بسیار متداول شده‌اند، بررسی کنیم.


مثال یک) ساده سازی تعاریف API ارائه شده به استفاده کنندگان از کتابخانه‌های ما
عنوان شد که کار delegates، واگذاری مسئولیت انجام کاری به کلاس‌های دیگر است. این مورد شما را به یاد کاربردهای interfaceها نمی‌اندازد؟
در interfaceها نیز یک قرارداد کلی تعریف شده و سپس کدهای یک کتابخانه، تنها با امضای متدها و خواص تعریف شده در آن کار می‌کنند و کتابخانه ما نمی‌داند که این متدها قرار است چه پیاده سازی خاصی را داشته باشند.
برای نمونه طراحی API زیر را درنظر بگیرید که در آن یک interface جدید تعریف شده که تنها حاوی یک متد است. سپس کلاس Runner از این interface استفاده می‌کند:
using System;

namespace ActionFuncSamples
{
    public interface ISchedule
    {
        void Run();
    }

    public class Runner
    {
        public void Exceute(ISchedule schedule)
        {
            schedule.Run();
        }
    }

    public class HelloSchedule : ISchedule
    {
        public void Run()
        {
            Console.WriteLine("Just Run!");
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            new Runner().Exceute(new HelloSchedule());
        }
    }
}
در اینجا ابتدا باید این interface را در طی یک کلاس جدید (مثلا HelloSchedule) پیاده سازی کرد و سپس حاصل را در کلاس Runner استفاده نمود.
نظر شما در مورد این طراحی ساده شده چیست؟
using System;

namespace ActionFuncSamples
{
    public class Schedule
    {
        public void Exceute(Action run)
        {
            run();
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            new Schedule().Exceute(() => Console.WriteLine("Just Run!"));
        }
    }
}
با توجه به اینکه هدف از معرفی interface در طراحی اول، واگذاری مسئولیت نحوه تعریف متد Run به کلاسی دیگر است، به همین طراحی با استفاده از یک Action delegate نیز می‌توان رسید. مهم‌ترین مزیت آن، حجم بسیار کمتر کدنویسی استفاده کننده نهایی از API تعریف شده ما است. به علاوه امکان inline coding نیز فراهم گردیده است و در همان محل تعریف Action، بدنه آن‌را نیز می‌توان تعریف کرد.
بدیهی است delegates نمی‌توانند به طور کامل جای interfaceها را پر کنند. اگر نیاز است قرارداد تهیه شده بین ما و استفاده کنندگان از کتابخانه، حاوی بیش از یک متد باشد، استفاده از interfaceها بهتر هستند.
از دیدگاه بسیاری از طراحان API، اشیاء delegate معادل interface ایی با یک متد هستند و یک وهله از delegate معادل وهله‌ای از کلاسی است که یک interface را پیاده سازی کرده‌است.
علت استفاده بیش از حد interfaceها در سایر زبان‌ها برای ابتدایی‌ترین کارها، کمبود امکانات پایه‌ای آن زبان‌ها مانند نداشتن lambda expressions، anonymous methods و anonymous delegates هستند. به همین دلیل مجبورند همیشه و در همه‌جا از interfaceها استفاده کنند.

ادامه دارد ...

 
مطالب
نحوه اضافه کردن Auto-Complete به جستجوی لوسین در ASP.NET MVC و Web forms
پیشنیازها:
چگونه با استفاده از لوسین مطالب را ایندکس کنیم؟
چگونه از افزونه jQuery Auto-Complete استفاده کنیم؟
نحوه استفاده صحیح از لوسین در ASP.NET


اگر به جستجوی سایت دقت کرده باشید، قابلیت ارائه پیشنهاداتی به کاربر توسط یک Auto-Complete به آن اضافه شده‌است. در مطلب جاری به بررسی این مورد به همراه دو مثال Web forms و MVC پرداخته خواهد شد.


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

الف) دریافت لوسین
از طریق NuGet آخرین نگارش را دریافت و به پروژه خود اضافه کنید. همچنین Lucene.NET Contrib را نیز به همین نحو دریافت نمائید.

ب) ایجاد ایندکس
کدهای این قسمت با مطلب برجسته سازی قسمت‌های جستجو شده، یکی است:
using System.Collections.Generic;
using System.IO;
using Lucene.Net.Analysis.Standard;
using Lucene.Net.Documents;
using Lucene.Net.Index;
using Lucene.Net.Store;
using LuceneSearch.Core.Model;
using LuceneSearch.Core.Utils;

namespace LuceneSearch.Core
{
    public static class CreateIndex
    {
        static readonly Lucene.Net.Util.Version _version = Lucene.Net.Util.Version.LUCENE_30;

        public static Document MapPostToDocument(Post post)
        {
            var postDocument = new Document();
            postDocument.Add(new Field("Id", post.Id.ToString(), Field.Store.YES, Field.Index.NOT_ANALYZED));
            var titleField = new Field("Title", post.Title, Field.Store.YES, Field.Index.ANALYZED, Field.TermVector.WITH_POSITIONS_OFFSETS);
            titleField.Boost = 3;
            postDocument.Add(titleField);
            postDocument.Add(new Field("Body", post.Body.RemoveHtmlTags(), Field.Store.YES, Field.Index.ANALYZED, Field.TermVector.WITH_POSITIONS_OFFSETS));
            return postDocument;
        }

        public static void CreateFullTextIndex(IEnumerable<Post> dataList, string path)
        {
            var directory = FSDirectory.Open(new DirectoryInfo(path));
            var analyzer = new StandardAnalyzer(_version);
            using (var writer = new IndexWriter(directory, analyzer, create: true, mfl: IndexWriter.MaxFieldLength.UNLIMITED))
            {
                foreach (var post in dataList)
                {
                    writer.AddDocument(MapPostToDocument(post));
                }

                writer.Optimize();
                writer.Commit();
                writer.Close();
                directory.Close();
            }
        }
    }
}
تنها تفاوت آن اضافه شدن titleField.Boost = 3 می‌باشد. توسط Boost به لوسین خواهیم گفت که اهمیت عبارات ذکر شده در عناوین مطالب، بیشتر است از اهمیت متون آن‌ها.


ج) تهیه قسمت منبع داده Auto-Complete

namespace LuceneSearch.Core.Model
{
    public class SearchResult
    {
        public int Id { set; get; }
        public string Title { set; get; }
    }
}

using System.Collections.Generic;
using System.IO;
using Lucene.Net.Index;
using Lucene.Net.Search;
using Lucene.Net.Store;
using LuceneSearch.Core.Model;
using LuceneSearch.Core.Utils;

namespace LuceneSearch.Core
{
    public static class AutoComplete
    {
        private static IndexSearcher _searcher;

        /// <summary>
        /// Get terms starting with the given prefix
        /// </summary>
        /// <param name="prefix"></param>
        /// <param name="maxItems"></param>
        /// <returns></returns>
        public static IList<SearchResult> GetTermsScored(string indexPath, string prefix, int maxItems = 10)
        {
            if (_searcher == null)
                _searcher = new IndexSearcher(FSDirectory.Open(new DirectoryInfo(indexPath)), true);

            var resultsList = new List<SearchResult>();
            if (string.IsNullOrWhiteSpace(prefix))
                return resultsList;

            prefix = prefix.ApplyCorrectYeKe();

            var results = _searcher.Search(new PrefixQuery(new Term("Title", prefix)), null, maxItems);
            if (results.TotalHits == 0)
            {
                results = _searcher.Search(new PrefixQuery(new Term("Body", prefix)), null, maxItems);
            }

            foreach (var doc in results.ScoreDocs)
            {
                resultsList.Add(new SearchResult
                {
                    Title = _searcher.Doc(doc.Doc).Get("Title"),
                    Id = int.Parse(_searcher.Doc(doc.Doc).Get("Id"))
                });
            }

            return resultsList;
        }
    }
}
توضیحات:
برای نمایش Auto-Complete نیاز به منبع داده داریم که نحوه ایجاد آن‌را در کدهای فوق ملاحظه می‌کنید. در اینجا توسط جستجوی سریع لوسین و امکانات PrefixQuery آن، به تعدادی مشخص (maxItems)، رکوردهای یافت شده را بازگشت خواهیم داد. خروجی حاصل لیستی است از SearchResultها شامل عنوان مطلب و Id آن. عنوان را به کاربر نمایش خواهیم داد؛ از Id برای هدایت او به مطلبی مشخص استفاده خواهیم کرد.


د) نمایش Auto-Complete در ASP.NET MVC

using System.Text;
using System.Web.Mvc;
using LuceneSearch.Core;
using System.Web;

namespace LuceneSearch.Controllers
{
    public class HomeController : Controller
    {
        static string _indexPath = HttpRuntime.AppDomainAppPath + @"App_Data\idx";

        public ActionResult Index(int? id)
        {
            if (id.HasValue)
            {
                //todo: do something
            }
            return View(); //Show the page
        }

        public virtual ActionResult ScoredTerms(string q)
        {
            if (string.IsNullOrWhiteSpace(q))
                return Content(string.Empty);

            var result = new StringBuilder();
            var items = AutoComplete.GetTermsScored(_indexPath, q);
            foreach (var item in items)
            {
                var postUrl = this.Url.Action(actionName: "Index", controllerName: "Home", routeValues: new { id = item.Id }, protocol: "http");
                result.AppendLine(item.Title + "|" + postUrl);
            }

            return Content(result.ToString());
        }
    }
}

@{
    ViewBag.Title = "جستجو";
    var scoredTermsUrl = Url.Action(actionName: "ScoredTerms", controllerName: "Home");
    var bulletImage = Url.Content("~/Content/Images/bullet_shape.png");
}
<h2>
    جستجو</h2>

<div align="center">
    @Html.TextBox("term", "", htmlAttributes: new { dir = "ltr" })
    <br />
    جهت آزمایش lu را وارد نمائید
</div>

@section scripts
{
    <script type="text/javascript">
        EnableSearchAutocomplete('@scoredTermsUrl', '@bulletImage');
    </script>
}

function EnableSearchAutocomplete(url, img) {
    var formatItem = function (row) {
        if (!row) return "";
        return "<img src='" + img + "' /> " + row[0];
    }

    $(document).ready(function () {
        $("#term").autocomplete(url, {
            dir: 'rtl', minChars: 2, delay: 5,
            mustMatch: false, max: 20, autoFill: false,
            matchContains: false, scroll: false, width: 300,
            formatItem: formatItem
        }).result(function (evt, row, formatted) {
            if (!row) return;
            window.location = row[1];
        });
    });
}
توضیحات:
- ابتدا ارجاعاتی را به jQuery، افزونه Auto-Complete و اسکریپت سفارشی تهیه شده، در فایل layout پروژه تعریف خواهیم کرد.
در اینجا سه قسمت را مشاهده می‌کنید: کدهای کنترلر، View متناظر و اسکریپتی که Auto-Complete را فعال خواهد ساخت.
- قسمت مهم کدهای کنترلر، دو سطر زیر هستند:
result.AppendLine(item.Title + "|" + postUrl);
return Content(result.ToString());
مطابق نیاز افزونه انتخاب شده در مثال جاری، فرمت خروجی مدنظر باید شامل سطرهایی حاوی متن قابل نمایش به همراه یک Id (یا در اینجا یک آدرس مشخص) باشد. البته ذکر این Id اختیاری بوده و در اینجا جهت تکمیل بحث ارائه شده است.
return Content هم سبب بازگشت این اطلاعات به افزونه خواهد شد.
- کدهای View متناظر بسیار ساده هستند. تنها نام TextBox تعریف شده مهم می‌باشد که در متد جاوا اسکریپتی EnableSearchAutocomplete استفاده شده است. به علاوه، نحوه مقدار دهی آدرس دسترسی به اکشن متد ScoredTerms نیز مهم می‌باشد.
- در متد EnableSearchAutocomplete نحوه فراخوانی افزونه autocomplete را ملاحظه می‌کنید.
جهت آن، به راست به چپ تنظیم شده است. با 2 کاراکتر ورودی فعال خواهد شد با وقفه‌ای کوتاه. نیازی نیست تا انتخاب کاربر از لیست ظاهر شده حتما با عبارت جستجو شده صد در صد یکی باشد. حداکثر 20 آیتم در لیست ظاهر خواهند شد. اسکرول بار لیست را حذف کرده‌ایم. عرض آن به 300 تنظیم شده است و نحوه فرمت دهی نمایشی آن‌را نیز ملاحظه می‌کنید. برای این منظور از متد formatItem استفاده شده است. آرایه row در اینجا در برگیرنده اعضای Title و Id ارسالی به افزونه است. اندیس صفر آن به عنوان دریافتی اشاره می‌کند.
همچنین نحوه نشان دادن عکس العمل به عنصر انتخابی را هم ملاحظه می‌کنید (در متد result مقدار دهی شده).  window.location را به عنصر دوم آرایه row هدایت خواهیم کرد. این عنصر دوم مطابق کدهای اکشن متد تهیه شده، به آدرس یک صفحه اشاره می‌کند.


ه) نمایش Auto-Complete در ASP.NET WebForms

قسمت عمده مطالب فوق با وب فرم‌ها نیز یکی است. خصوصا توضیحات مرتبط با متد EnableSearchAutocomplete ذکر شده.
<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="Default.aspx.cs" Inherits="LuceneSearch.WebForms.Default" %>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width" />
    <title>جستجو</title>
    <link href="Content/Site.css" rel="stylesheet" type="text/css" />
    <script src="Scripts/jquery-1.7.1.min.js" type="text/javascript"></script>
    <script src="Scripts/jquery.autocomplete.js" type="text/javascript"></script>
    <script src="Scripts/custom.js" type="text/javascript"></script>
</head>
<body dir="rtl">
    <h2>
        جستجو</h2>
    <form id="form1" runat="server">
    <div align="center">
        <asp:TextBox runat="server" dir="ltr" ID="term"></asp:TextBox>
        <br />
        جهت آزمایش lu را وارد نمائید
    </div>
    </form>
    <script type="text/javascript">
        EnableSearchAutocomplete('Search.ashx', 'Content/Images/bullet_shape.png');
    </script>
</body>
</html>

using System.Text;
using System.Web;
using LuceneSearch.Core;

namespace LuceneSearch.WebForms
{
    public class Search : IHttpHandler
    {
        static string _indexPath = HttpRuntime.AppDomainAppPath + @"App_Data\idx";

        public void ProcessRequest(HttpContext context)
        {
            string q = context.Request.QueryString["q"];
            if (string.IsNullOrWhiteSpace(q))
            {
                context.Response.Write(string.Empty);
                context.Response.End();
            }

            var result = new StringBuilder();
            var items = AutoComplete.GetTermsScored(_indexPath, q);
            foreach (var item in items)
            {
                var postUrl = "Default.aspx?id=" + item.Id;
                result.AppendLine(item.Title + "|" + postUrl);
            }

            context.Response.ContentType = "text/plain";
            context.Response.Write(result.ToString());
            context.Response.End();
        }

        public bool IsReusable
        { get { return false; } }
    }
}

در اینجا بجای Controller از یک Generic handler استفاده شده است (Search.ashx).
result.AppendLine(item.Title + "|" + postUrl);
context.Response.Write(result.ToString());
در آن، عنوان مطالب یافت شده به همراه یک آدرس مشخص، تهیه و در Response نوشته خواهند شد.


کدهای کامل مثال فوق را از اینجا می‌توانید دریافت کنید:
همچنین باید دقت داشت که پروژه MVC آن از نوع MVC4 است (VS2010) و فرض براین می‌باشد که IIS Express 7.5 را نیز پیشتر نصب کرده‌اید.
کلمه عبور فایل: dotnettips91
 
مطالب
Blazor 5x - قسمت 24 - تهیه API مخصوص Blazor WASM - بخش 1 - ایجاد تنظیمات ابتدایی
تا اینجا با اصول توسعه‌ی برنامه‌های مبتنی بر Blazor Server آشنا شدیم. در ادامه‌ی این سری، روش توسعه برنامه‌های مبتنی بر Blazor WASM را بررسی خواهیم کرد و پیش از شروع آن، باید بتوان امکانات سمت سرور مورد نیاز این نوع برنامه‌های سمت کلاینت را از طریق یک Web API تامین کرد که شامل دریافت و ارائه‌ی اطلاعات و همچنین اعتبارسنجی و احراز هویت مبتنی بر JWT یکپارچه‌ی با ASP.NET Core Identity است.


ایجاد پروژه‌ی ASP.NET Core Web API

برای تامین اطلاعات برنامه‌ی سمت کلاینت Blazor WASM و همچنین فراهم آوردن زیرساخت اعتبارسنجی کاربران آن، نیاز به یک پروژه‌ی ASP.NET Core Web API داریم که آن‌را با اجرای دستور dotnet new webapi در یک پوشه‌ی خالی، برای مثال به نام BlazorWasm.WebApi ایجاد می‌کنیم.
البته این پروژه، از زیرساختی که در برنامه‌ی Blazor Server بررسی شده‌ی تا این قسمت، ایجاد کردیم نیز استفاده خواهد کرد. همانطور که پیشتر نیز عنوان شد، هدف از قسمت Blazor Server مثال این سری، آشنایی با مدل برنامه نویسی خاص آن بود؛ وگرنه می‌توان کل این پروژه را با Blazor Server و یا کل آن‌را با Web API + Blazor WASM نیز پیاده سازی کرد. در این مثال، قسمت‌های مدیریتی برنامه‌ی مدیریت هتل را توسط Blazor Server (مانند قسمت‌های تعریف اتاق‌ها و امکانات رفاهی هتل) و قسمت مخصوص کاربران آن‌را مانند رزرو کردن اتاق‌ها، توسط Blazor WASM پیاده سازی می‌کنیم. به همین جهت قسمت‌هایی از این دو پروژه، مانند سرویس‌های استفاده شده‌ی در پروژه‌ی Blazor server، در پروژه‌ی Web API مکمل Blazor WASM، قابلیت استفاده‌ی مجدد را دارند.


افزودن سرویس‌های آغازین مورد نیاز، به پروژه‌ی Web API

در فایل آغازین BlazorWasm\BlazorWasm.WebApi\Startup.cs، برای شروع به تکمیل Web API، نیاز به این سرویس‌ها را داریم:
namespace BlazorWasm.WebApi
{
    public class Startup
    {
        //...

        public void ConfigureServices(IServiceCollection services)
        {
            services.AddAutoMapper(typeof(MappingProfile).Assembly);

            services.AddScoped<IHotelRoomService, HotelRoomService>();
            services.AddScoped<IAmenityService, AmenityService>();
            services.AddScoped<IHotelRoomImageService, HotelRoomImageService>();

            var connectionString = Configuration.GetConnectionString("DefaultConnection");
            services.AddDbContext<ApplicationDbContext>(options => options.UseSqlServer(connectionString));

            services.AddIdentity<IdentityUser, IdentityRole>()
                .AddEntityFrameworkStores<ApplicationDbContext>()
                .AddDefaultTokenProviders();

            //...
در اینجا سرویس‌های AutoMapper، تنظیمات ابتدایی DbContext برنامه، به همراه سرویس‌های Identity (بدون UI آن) و افزودن سرویس‌های اتاق‌ها و امکانات رفاهی هتل را نیاز داریم. به همین جهت ارجاعات و وابستگی‌های زیر را به فایل csproj جاری اضافه می‌کنیم تا پروژه‌های DataAccess ،Services و Mappings قابل دسترسی و استفاده شوند:
<Project Sdk="Microsoft.NET.Sdk.Web">
  <PropertyGroup>
    <TargetFramework>net5.0</TargetFramework>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="AutoMapper.Extensions.Microsoft.DependencyInjection" Version="8.1.1" />
    <PackageReference Include="Swashbuckle.AspNetCore" Version="5.6.3" />
  </ItemGroup>

  <ItemGroup>
    <ProjectReference Include="..\..\BlazorServer\BlazorServer.DataAccess\BlazorServer.DataAccess.csproj" />
    <ProjectReference Include="..\..\BlazorServer\BlazorServer.Services\BlazorServer.Services.csproj" />
    <ProjectReference Include="..\..\BlazorServer\BlazorServer.Models.Mappings\BlazorServer.Models.Mappings.csproj" />
  </ItemGroup>
</Project>
همچنین در این پروژه نیز از همان بانک اطلاعاتی پروژه‌ی Blazor Server که تاکنون تکمیل کردیم، استفاده می‌کنیم. بنابراین محتوای فایل BlazorWasm\BlazorWasm.WebApi\appsettings.json آن نیز مشابه‌است:
{
  "ConnectionStrings": {
    "DefaultConnection": "Server=(localdb)\\mssqllocaldb;Database=HotelManagement;Trusted_Connection=True;MultipleActiveResultSets=true"
  }
}


تعریف کنترلر HotelRoom

در ادامه کدهای اولین کنترلر Web API را مشاهده می‌کنید که مرتبط است با بازگشت اطلاعات تمام اتاق‌های ثبت شده و یا بازگشت اطلاعات یک اتاق ثبت شده:
using BlazorServer.Models;
using BlazorServer.Services;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using System.Collections.Generic;
using System.Threading.Tasks;

namespace BlazorWasm.WebApi.Controllers
{
    [Route("api/[controller]")]
    public class HotelRoomController : ControllerBase
    {
        private readonly IHotelRoomService _hotelRoomService;

        public HotelRoomController(IHotelRoomService hotelRoomService)
        {
            _hotelRoomService = hotelRoomService;
        }

        [HttpGet]
        public IAsyncEnumerable<HotelRoomDTO> GetHotelRooms()
        {
            return _hotelRoomService.GetAllHotelRoomsAsync();
        }

        [HttpGet("{roomId}")]
        public async Task<IActionResult> GetHotelRoom(int? roomId)
        {
            if (roomId == null)
            {
                return BadRequest(new ErrorModel
                {
                    Title = "",
                    ErrorMessage = "Invalid Room Id",
                    StatusCode = StatusCodes.Status400BadRequest
                });
            }

            var roomDetails = await _hotelRoomService.GetHotelRoomAsync(roomId.Value);
            if (roomDetails == null)
            {
                return BadRequest(new ErrorModel
                {
                    Title = "",
                    ErrorMessage = "Invalid Room Id",
                    StatusCode = StatusCodes.Status404NotFound
                });
            }

            return Ok(roomDetails);
        }
    }
}
- این کنترلر، از سرویس IHotelRoomService که در قسمت‌های قبل تکمیل کردیم، استفاده می‌کند.
- ErrorModel آن‌را در همان پروژه‌ی قبلی مدل‌ها، در فایل BlazorServer\BlazorServer.Models\ErrorModel.cs به صورت زیر ایجاد کرده‌ایم:
namespace BlazorServer.Models
{
    public class ErrorModel
    {
        public string Title { get; set; }

        public int StatusCode { get; set; }

        public string ErrorMessage { get; set; }
    }
}
در این حالت اگر برنامه‌ی Web API را اجرا کنیم، به خروجی Swagger زیر می‌رسیم که جزئیات این فناوری را در سری «مستند سازی ASP.NET Core 2x API توسط OpenAPI Swagger» پیشتر بررسی کردیم:


یکی از مزایای آن، امکان آزمایش API تهیه شده، بدون نیاز به تهیه‌ی هیچ نوع کلاینت خاصی است. برای مثال اگر بر روی api​/hotelroom آن کلیک کنیم، گزینه‌ی «try it out» آن ظاهر شده و با کلیک بر روی آن، اینبار دکمه‌ی execute ظاهر می‌شود. در ادامه با کلیک بر روی دکمه‌ی اجرای آن، اکشن متد GetHotelRooms اجرا شده و خروجی زیر ظاهر می‌شود:


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



انجام تنظیمات ابتدایی CORS و خروجی JSON برنامه

قرار است این API را از طریق پروژه‌ی Blazor سمت کلاینت خود استفاده کنیم که آدرس آن، با آدرس API یکی نیست. به همین جهت نیاز است تنظیمات CORS را به صورت زیر اضافه کنیم:
namespace BlazorWasm.WebApi
{
    public class Startup
    {
        public void ConfigureServices(IServiceCollection services)
        {
         // ... 

            services.AddCors(o => o.AddPolicy("HotelManagement", builder =>
            {
                builder.AllowAnyOrigin().AllowAnyMethod().AllowAnyHeader();
            }));

            services.AddControllers()
                    .AddJsonOptions(options =>
                    {
                        options.JsonSerializerOptions.PropertyNamingPolicy = null;
                        // To avoid `JsonSerializationException: Self referencing loop detected error`
                        options.JsonSerializerOptions.ReferenceHandler = ReferenceHandler.Preserve;
                    });
         // ... 
        }

        public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
        {
            // ... 

            app.UseCors("HotelManagement");
            app.UseRouting();

            app.UseAuthentication();
            // ...
        }
    }
}
در اینجا علاوه بر تنظیمات CORS، تنظیمات JsonSerializer را هم تغییر داده‌ایم تا خطاهای Self referencing loop را در حین ارائه‌ی خروجی‌های Web API، مشاهده نکنیم (همان نکته‌ی «تهیه خروجی JSON از مدل‌های مرتبط، بدون Stack overflow»).


کدهای کامل این مطلب را از اینجا می‌توانید دریافت کنید: Blazor-5x-Part-24.zip
مطالب
پیاده سازی Option یا Maybe در #C

Options یا Maybe در یک زبان تابعی مثل #F، نشان دهنده‌ی این است که شیء (Object) ممکن است وجود نداشته باشد(Null Reference) که یکی از مهمترین ویژگی‌های یک زبان شیءگرا مثل #C و یا Java محسوب می‌شودما برنامه نویس‌ها (اغلب) از هرچیزی که باعث کرش برنامه می‌شود، بیزاریم و برای اینکه برنامه کرش نکند، مجبور میشویم تمام کد‌های خود  را از Null Reference محافظت کنیم. تمام این مشکلات توسط Tony Hoare مخترع ALOGL است که تنها دلیل وجود Null References را سادگی پیاده سازی آن می‌داند و او این مورد را یک «خطای  میلیون دلاری» نامیده‌است. 

به این مثال توجه بفرمایید: 

public class User
    {
        public int Id { get; set; }
        public string Name { get; set; }
    }

public class UserService : IUserService
    {
        private IList<User> _userData;

        public UserService()
        {
            _userData = new List<User>
            {
                new User {Id = 1,Name = "ali"},
                new User {Id = 2,Name = "Karim"}
            };
        }

        public User GetById(int id)
        {
            return _userData.FirstOrDefault(x => x.Id == id);
        }
    }  

public class UserController : Controller
    {
        private readonly IUserService _userService;

        public UserController(IUserService  userService)
        {
            _userService = userService;
        }
        public ActionResult Details(int id)
        {
            var user=_userService.GetById(3); // این متد ممکن است مقداری برگرداند و یا مقدار نال برگرداند                           
            if( user == null)
                 return HttpNotFound();    
            return View(user);  
        }
    }

این کدی است که ما برنامه نویسان به صورت متداولی با آن سروکار داریم. اما چه چیزی درباره این کد اشکال دارد؟

مشکل از آن جایی هست که ما نمی‌دانیم متد GetById مقداری را برمیگرداند و یا Null را بر می‌گرداند. این متد هرگاه که امکان برگرداندن Null وجود داشته باشد، خطای  NullReferenceException را در زمان اجرا بر می‌گرداند و همان طور که میدانید، به ازای هر شرطی که به برنامه اضافه میکنیم، پیچیدگی برنامه هم افزایش می‌یابد و کد خوانایی خود را از دست می‌دهد. تصور کنید دنیایی بدون NullReferenceException چه دنیایی زیبایی می‌بود؛ ولی متاسفانه این مورد از ویژگی‌های زبان #C است. خوشبختانه راه‌حل‌های برای حل NRE ارائه شده‌اند که در ادامه به آن‌ها می‌پردازیم.

ما می‌خواهیم متد GetById همیشه چیزی غیر از نال را برگرداند و یکی از راه‌هایی که ما را به این هدف می‌رساند این است که این متد یک توالی را برگرداند.

به نگاری جدید کد توجه بفرمایید:
public class UserService : IUserService
    {
        private IList<User> _userData;

        public UserService()
        {
            _userData = new List<User>
            {
                new User {Id = 1,Name = "ali"},
                new User {Id = 2,Name = "Karim"}
            };
        }

        public IEnumerable<User> GetById(int id)
        {
            var user = _userData.FirstOrDefault(x => x.Id == id);
            if (user == null) return new User[0];
            return new[] { user };
        }
    } 

اگر به امضای متد GetById توجه کنید، به جای اینکه User را برگرداند، این متد یک توالی از User را بر می‌گرداند و اگر در اینجا کاربری یافت شد، این توالی دارای یک المان خواهد بود و در غیر این صورت اگر User یافت نشد، این متد یک توالی را بر می‌گرداند که دارای هیچ المانی نیست. در ادامه اگر کلاینت بخواهد از متد GetById استفاده کند، به صورت زیر خواهد بود:

 public ActionResult Details(int id)
        {
            var user = _userService
                            .GetById(3)
                            .DefaultIfEmpty(new User())
                            .Single();
            return View(user);
        }

 متد GetById دارای دو وجه است و وجه مثبت آن این است که اگر مجموعه دارای مقداری باشد، هیچ مشکلی نیست؛ ولی اگر مجموعه دارای المانی نباشد، باید یک شیء را به صورت پیش فرض به آن اختصاص دهیم که این کار را با استفاده از متد DefualtIfEmpty انجام داده‌ایم. 

 در اول مقاله هم اشاره کردیم که  Maybe یا Options، مجموعه‌ای است که دارای یک المان و یا هیچ المانی است. اگر به امضای متد GetById توجه کنید، متوجه خواهید شد که این متد می‌تواند مجموعه‌ای را برگرداند و نمی‌تواند گارانتی کند که حتما مجموعه‌ای را بر می‌گرداند که دارای یک المان و یا هیچ باشد. برای حل این مشکل می‌توانیم از کلاس Option استفاده کنیم:

public class Option<T> : IEnumerable<T>
    {
        private readonly T[] _data;

        private Option(T[] data)
        {
            _data = data;
        }

        public static Option<T> Create(T element) => new Option<T>(new[] { element });

        public static Option<T> CreateEmpty() => new Option<T>(new T[0]);

        public IEnumerator<T> GetEnumerator() => ((IEnumerable<T>) _data).GetEnumerator();

        IEnumerator IEnumerable.GetEnumerator() => this.GetEnumerator();
    }

تنها دلیل استفاده از متد‌های Create و CreateEmpty این است که به خوانایی برنامه کمک کنیم؛ نه بیشتر. در ادامه اگر بخواهیم از کلاس option استفاده کنیم، به صورت زیر خواهد بود:

 public class UserService : IUserService
    {
       ...
       ...
       public Option<User> GetById(int id)
        {
            var user = _userData.FirstOrDefault(x => x.Id == id);
            return user == null ? Option<User>.CreateEmpty() : Option<User>.Create(user);
        }
    }

 public class UserController : Controller
    {
       ...
       ...
       public ActionResult Details(int id)
        {
            var user = _userService
                            .GetById(3)
                            .DefaultIfEmpty(new User())
                            .Single();
            return View(user);
        }
    }


چکیده:

مدیریت کردن References کار بسیار پیچیده‌ای است. قبل از آن که تلاش کنیم مقداری را برگردانیم و یا عملیاتی را بر روی آن انجام دهیم، اول باید مطمئن شویم که این شیء به جایی اشاره می‌کند. نمونه‌های متفاوتی از Option و یا Maybe را می‌توانید در اینترنت پیدا کنید که هدف نهایی آن‌ها، حذف NullReferenceException است و آشنایی با این ایده، شما را به دنیای برنامه نویسی تابعی در#C هدایت می‌کند.

مطالب
پیاده سازی یک سیستم دسترسی Role Based در Web API و AngularJs - بخش سوم (پایانی)
در بخش پیشین  به بررسی جزئی‌تر ایجاد پایگاه داده و همچنین توسعه Custom Filter Attribute پرداختیم که وظیفه تایید صلاحیت کاربر جاری و بررسی دسترسی وی به API Method مورد نظر را بررسی می‌کرد. در این مقاله به این بحث می‌پردازیم که در Filter Attribute توسعه داده شده، قصد داریم یک سرویس Access Control ایجاد نماییم.
این سرویس وظیفه تمامی اعمال مربوط به نقش‌ها و دسترسی‌های کاربر را بر عهده خواهد داشت. این سرویس به صورت زیر تعریف می‌گردد:
public class AccessControlService
{
        private DbContext db;

        public AccessControlService()
        {
            db = new DbContext();
        }

        public IEnumerable<Permission> GetUserPermissions(string userId)
        {
            var userRoles = this.GetUserRoles(userId);
            var userPermissions = new List<Permission>();
            foreach (var userRole in userRoles)
            {
                foreach (var permission in userRole.Permissions)
                {
                    // prevent duplicates
                    if (!userPermissions.Contains(permission))
                        userPermissions.Add(permission);
                }
            }
            return userPermissions;
        }
        public IEnumerable<Role> GetUserRoles(string userId)
        {
            return db.Users.FirstOrDefault(x => x.UserId == userId).Roles.ToList();
        }

        public bool HasPermission(string userId, string area, string control)
        {
            var found = false;
            var userPermissions = this.GetUserPermissions(userId);
            var permission = userPermissions.FirstOrDefault(x => x.Area == area && x.Control == control);
            if (permission != null)
                found = true;
            return found;
        }
{
همانطور که ملاحظه می‌کنید، ما سه متد GetUserPermissions، GetUserRoles و HasPermission را توسعه داده‌ایم. حال اینکه بر حسب نیاز، می‌توانید متدهای بیشتری را نیز به این سرویس اضافه نمایید. متد اول، وظیفه‌ی واکشی تمامی permissionهای کاربر را عهده دار می‌باشد. متد GetUserRoles نیز تمامی نقش‌های کاربر را در سیستم، بازمی‌گرداند و در نهایت متد سوم، همان متدی است که ما در Filterattribute از آن استفاده کرد‌ه‌ایم. این متد با دریافت پارامترها و بازگردانی یک مقدار درست یا نادرست، تعیین می‌کند که کاربر جاری به آن محدوده دسترسی دارد یا خیر.
تمامی حداقل‌هایی که برای نگارش سمت سرور نیاز بود، به پایان رسید. حال به بررسی این موضوع خواهیم پرداخت که چگونه این سطوح دسترسی را با سمت کاربر همگام نماییم. به طوری که به عنوان مثال اگر کاربری حق دسترسی به ویرایش مطالب یک سایت را ندارد، دکمه مورد نظر که او را به آن صفحه هدایت می‌کند نیز نباید به وی نشان داده شود. سناریویی که ما برای این کار در نظر گرفته‌ایم، به این گونه می‌باشد که در هنگام ورود کاربر، لیستی از تمامی دسترسی‌های او به صورت JSON به سمت کلاینت ارسال می‌گردد. حال وظیفه مدیریت نمایش یا عدم نمایش المان‌های صفحه، بر عهده زبان سمت کلاینت، یعنی AngularJs خواهد بود. بنابراین در ابتدایی‌ترین حالت، ما نیاز به یک کنترلر و متد Web API داریم تا لیست دسترسی‌های کاربر را بازگرداند. این کنترلر در حال حاضر شامل یک متد است. اما بر حسب نیاز، می‌توانید متدهای بیشتری را به کنترلر اضافه نمایید.
    [RoutePrefix("َAuth/permissions")]
    public class PermissionsController : ApiController
    {
        private AccessControlService _AccessControlService = null;
        public PermissionsController()
        {
            _AccessControlService = new AccessControlService();
        }

        [Route("GetUserPermissions")]
        public async Task<IHttpActionResult> GetUserPermissions()
        {
            if (!User.Identity.IsAuthenticated)
            {
                return Unauthorized();
            }
            return Ok(_AccessControlService.GetPermissions(User.Identity.GetCurrentUserId()));
        }        
    }
در متد فوق ما از متد سرویس Access Control که لیست تمامی permissionهای کاربر را باز می‌گرداند، کمک گرفتیم. متد GetUserPermissions پس از ورود کاربر توسط کلاینت فراخوانی می‌گردد و لیست تمامی دسترسی‌ها در سمت کلاینت، در rootScope انگیولار ذخیره سازی می‌گردد. حال نوبت به آن رسیده که به بررسی عملیات سمت کلاینت بپردازیم.

توسعه سرویس‌ها و فرآیندهای سمت وب کلاینت AngularJS

در ابتدا در سمت کلاینت نیاز به سرویسی داریم که دسترسی‌های سمت سرور را دریافت نماید. از این رو ما نام این سرویس را permissionService می‌نامیم.
'use strict';
angular.module('App').factory('permissionService', ['$http', '$q', function ($http, $q) {

    var _getUserPermissions = function () {
        return $http.get(serviceBaseUrl + '/api/permissions/GetUserPermissions/');
    }

    var _isAuthorize = function (area, control) {
        return _.some($scope.permissions, { 'area': area, 'control': control });
    }

    return {
        getUserPermissions: _getUserPermissions,
        isAuthorize: _isAuthorize
    };
}]);
اگر بخواهیم مختصری درباره‌ی این سرویس صحبت کنیم، متد اول که یک دستور GET ساده است و لیست دسترسی‌ها را از PermissionController دریافت می‌کند. متد بعدی که در آینده بیشتر با آن آشنا می‌شویم، عملیات تایید صلاحیت کاربر را به ناحیه مورد نظر، انجام می‌دهد (همان مثال دسترسی به دکمه ویرایش مطلب در یک صفحه). در این متد برای جستجوی لیست permissions از کتابخانه‌ای با نام Lodash کمک گرفته‌ایم. این کتابخانه کاری شبیه به دستورات Linq را در کالکشن‌ها و آرایه‌های جاوااسکریپتی، انجام می‌دهد. متد some یک مقدار درست یا نادرست را بازمی‌گرداند. بازگردانی مقدار درست، به این معنی است که کاربر به ناحیه‌ی مورد نظر اجازه‌ی دسترسی را خواهد داشت.
حال باید متد‌های این سرویس را در کنترلر لاگین فراخوانی نماییم. در این مرحله ما از rootScope dependency استفاده می‌کنیم. برای نحوه‌ی عملکرد rootScope میتوانید به مقاله‌ای در این زمینه در وب سایت toddomotto مراجعه کنید. در این مقاله ویژگی‌ها و اختلاف‌های scope و rootScope به تفصیل بیان شده است. مقاله‌ای دیگر در همین زمینه نوشته شده است که در انتهای مقاله به بررسی چند نکته در مورد کدهای مشترک پرداخته شده‌است. تکه کد زیر، متد login را نمایش می‌دهد که در loginController قرار گرفته است و ما در آن از نوشتن کل بلاک loginController چشم پوشی کرده‌ایم. متد savePermissions تنها یک کار را انجام می‌دهد و آن هم این است که در ابتدا، به سرویس permissionService متصل شده و تمامی دسترسی‌ها را واکشی می‌نماید و پس از آن، آنها را درون rootScope قرار می‌دهد تا در تمامی کنترلرها قابل دسترسی باشد.
    $scope.login = function () {
        authService.login($scope.loginData).then(function (response) {
            savePermissions();
            $location.path('/userPanel');
        },
         function (err) {
             $scope.message = err.error_description;
         });
    };

    var savePermissions = function () {
        permissionService.getUserPermissions().then(function (response) {
            $rootScope.permissions = response.data;
        },
        function (err) {
        });
    }
}

   حال تمامی اطلاعات دسترسی، در سمت کلاینت نیز قابل دسترسی می‌باشد. تنها کاری که نیاز است، broadCast کردن متد isAuthorize است که آن هم باید در rootScope قرار بگیرد. ما برای این انتساب یک راهکار را ارائه کرده‌ایم. معماری سیستم کلاینت به این صورت است که تمامی کنترلرها درون یک parentController قرار گرفته‌اند. از این رو می‌توان در parentController این انتساب (ایجاد دسترسی عمومی برایisAuthorize) صورت گیرد. برای این کار در parentController تغییرات زیر صورت می‌گیرد:
App.controller('parentController', ['$rootScope', '$scope', 'authService', 'permissionService', function ($rootScope, $scope, authService, permissionService) {
        $scope.authentication = authService.authentication;

        // isAuthorize Method
        $scope.isAuthorize = permissionService.isAuthorize();

        // rest of codes
}]);
در کد فوق ما isAuthorize را درون scope قرار داده‌ایم. دلیل آن هم این است که هر چه که در scope قرار بگیرد، تمامی کنترلر‌های child نیز به آن دسترسی خواهند داشت. البته ممکن است که این بهترین نوع پیاده سازی برای به اشتراک گذاری یک منبع نباشد.
 در گام بعدی کافیست المان‌های صفحه را بر اساس همین دسترسی‌ها فعال یا غیر فعال کنیم. برای این کار از دستور ng-if میتوان استفاده کرد. برای این کار به مثال زیر توجه کنید:
<div ng-controller="childController">
    <div ng-if="isAuthorize('articles', 'edit')" >
    <!-- the block that we want to not see unauthorize person -->
    </div>
</div>
همانطور که مشاهده می‌کنید، تمامی المان‌ها را می‌توان با دستور ساده ng-if، از دید کاربران بدون صلاحیت، پنهان نمود. البته توجه داشته باشید که شما نمی‌توانید تنها به پنهان کردن این اطلاعات اکتفا کنید. بلکه باید تمامی متدهای کنترلرهای سمت سرور را هم با همین روش (فیلتر کردن با Filter Attribute) بررسی نمایید. به ازای هر درخواست کاربر باید بررسی شود که او به منبع مورد نظر دسترسی دارد یا خیر.
تنها نکته‌ای که باقی می‌ماند این است که طول عمر scope و rootScope چقدر است؟! برای پاسخ به این سوال باید بگوییم هر بار که صفحه refresh می‌شود، تمامی مقادیر scope و rootScope خالی می‌شوند. برای این کار هم یک راهکار خیلی ساده (و شاید کمی ناشیانه) در نظر گرفته شده‌است. میتوان بلاک مربوط به پر کردن rootScope.permissions را که در loginController نوشته شده بود، به درون parentController انتقال داد و آن را با استفاده از emit اجرا کرد و در حالت عادی، در هنگام refresh شدن صفحه نیز چون parentController در اولین لحظه اجرا می‌شود، میتوان تمامی مقادیر rootScope.permissions را دوباره از سمت سرور دریافت کرد.
نظرات مطالب
مستند سازی ASP.NET Core 2x API توسط OpenAPI Swagger - قسمت سوم - تکمیل مستندات یک API با کامنت‌ها
فقط کافی است از آخرین نگارش Swashbuckle.AspNetCore استفاده کنید که به صورت توکار از IFormFile برای آپلود فایل‌ها پشتیبانی می‌کند. در نگارش‌های قبل از آن (نگارش‌های 4x) نیاز به پیاده سازی یک IOperationFilter سفارشی برای اینکار بود، که دیگر نیازی به آن نیست.
برای نمونه اکشن متد زیر
[HttpPost("two-files")]
public async Task Upload(IFormFile file1, IFormFile file2)
{
    // validate the files, scan virus, save them to a file storage
}
چنین خروجی را پیدا می‌کند:

و یا حالت پیچیده‌تر آن

/// <summary>
/// Submit a form which contains a key-value pair and a file.
/// </summary>
/// <param name="id">Student ID</param>
/// <param name="form">A form which contains the FormId and a file</param>
/// <returns></returns>
[HttpPost("{id:int}/forms")]
[ProducesResponseType(typeof(FormSubmissionResult), StatusCodes.Status201Created)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
public async Task<ActionResult<FormSubmissionResult>> SubmitForm(int id, [FromForm] StudentForm form)
{
 // ...
}

public class StudentForm
{
    [Required] public int FormId { get; set; }
    [Required] public IFormFile StudentFile { get; set; }
}
این خروجی را ایجاد می‌کند: