نظرات مطالب
طراحی جدول فایل‌های پیوستی پایگاه داده
با تشکر.
به غیر از روش دوم و  حالت آخر در تمام مواردی که فرمودید ، نیاز است ابتدا به عنوان مثال مقاله درج شود و سپس بتوان مشخصات فایل را در جدول مورد نظر ثبت کرد ؛ یک حالت دیگر هم در صورتی که نیاز به Privacy نباشد ، میتوان در نظر گرفت که احتمال میدم در سایت جاری هم به این شکل استفاده میشود ، آن هم داشتن یک جدول فارغ از جداول دیگر  و تنها ارتباطی با جدول کاربران داشته باشد که مدلی به شکل زیر دارد : 
 public class Attachment
    {

        #region Properties
        /// <summary>
        /// sets or gets identifier for attachment
        /// </summary>
        public virtual Guid Id { get; set; }
        /// <summary>
        /// sets or gets name for attachment
        /// </summary>
        public virtual string FileName { get; set; }
        /// <summary>
        /// sets or gets type of attachment
        /// </summary>
        public virtual string ContentType { get; set; }
        /// <summary>
        /// sets or gets size of attachment
        /// </summary>
        public virtual long Size { get; set; }
        /// <summary>
        /// sets or gets Extention of attachment
        /// </summary>
        public virtual string Extensions { get; set; }
        /// <summary>
        /// sets or gets Creation Date
        /// </summary>
        public virtual DateTime CreatedOn { get; set; }
        /// <summary>
        /// gets or sets counts of download this file
        /// </summary>
        public virtual long DownloadsCount { get; set; }

        public virtual DateTime ModifiedOn { get; set; }
        #endregion

        #region NavigationProperties
        /// <summary>
        /// sets or gets identifier of attachment's owner
        /// </summary>
        public virtual long OwnerId { get; set; }
        /// <summary>
        /// sets or gets identifier of attachment's owner
        /// </summary>
        public virtual User Owner { get; set; }
        #endregion
    }
در این حالت یکپارچه کردن ارسال فایل با ویرایشگر متن هم امکان پذیر خواهد بود و همچنین بازهم نیاز نیست فایلها چند باره ارسال شوند و به تفکیک هم میشود فایل‌های کاربران را در اختیارشان گذاشت.
نظرات مطالب
ASP.NET MVC #18
در سؤال قبل تنظیمات وب کانفیگ رو می‌خواستید برای چند لاگین. در وب کانفیگ، کار خاصی انجام نمیشه. اونجا فقط مشخص میشه که طول عمر کوکی لاگین چند روز باشه یا پس از لاگین، کاربر به چه صفحه‌ای هدایت شود. به همین جهت عنوان کردم که چطور می‌توان کاربر را به صفحات دیگری با کدنویسی هدایت کرد.
در این سؤال دوم عنوان کردید که کاربران وارد سیستم می‌شوند. حالا من چندتا زیر سیستم دارم. می‌خواهم برای هر زیر سیستم بر اساس «نقش‌های» کاربران (واژه علمی «کاربران خاصی» که عنوان کردید) بتوانند به زیر سیستم خودشون وارد شوند.
باید فیلتر AuthorizeAttribute را سفارشی کنید بر اساس Roleهای مشخص سیستم. اگر زیر سیستمی باید صرفا برای کاربران برای مثال Editor قابل دسترسی باشد، در این کلاس و فیلتر سفارشی مشتق شده از AuthorizeAttribute، اول باید چک کنید که کوکی سفارشی خاص حاصل از ورود موفقیت آمیز به صفحه لاگین دوم، تنظیم شده یا خیر (یا در ساده‌ترین حالت از سشن استفاده کنید). اگر خیر، بر اساس Role مشخص صفحه جاری، به یک صفحه لاگین ثانویه هدایت شود تا کاربر بتواند کوکی یا سشن لازم را پس از لاگین دوم تولید کند.
مطالب
C# 6 - String Interpolation
تا پیش از C# 6 یکی از روش‌های توصیه شده‌ی جهت اتصال رشته‌ها به هم، استفاده از متدهایی مانند string.Format و StringBuilder.AppendFormat بود:
using System;
 
namespace CS6NewFeatures
{
    class Person
    {
        public string FirstName { set; get; }
        public string LastName { set; get; }
        public int Age { set; get; }
    }
 
    class Program
    {
        static void Main(string[] args)
        {
            var person = new Person { FirstName = "User 1", LastName = "Last Name 1", Age = 50 };
            var message = string.Format("Hello!  My name is {0} {1} and I am {2} years old.",
                                          person.FirstName, person.LastName, person.Age);
            Console.Write(message);
        }
    }
}
مشکل این روش، کاهش خوانایی آن با بالا رفتن تعداد پارامترهای متد Format است و همچنین گاهی از اوقات فراموش کردن مقدار دهی بعضی از آن‌ها و یا حتی ذکر ایندکس‌هایی غیر معتبر که در زمان اجرا، برنامه را با یک خطا متوقف می‌کنند.
در C# 6 جهت رفع این مشکلات، راه حلی به نام String interpolation ارائه شده‌است و اگر افزونه‌ی ReSharper یا یکی از افزونه‌های Roslyn را نصب کرده باشید، به سادگی امکان تبدیل کدهای قدیمی را به فرمت جدید آن خواهید یافت:


در این حالت کد قدیمی فوق، به کد ذیل تبدیل خواهد شد:
static void Main(string[] args)
{
    var person = new Person { FirstName = "User 1", LastName = "Last Name 1", Age = 50 };
    var message = $"Hello!  My name is {person.FirstName} {person.LastName} and I am {person.Age} years old.";
    Console.Write(message);
}
در اینجا ابتدا یک $ در ابتدای رشته قرار گرفته و سپس هر متغیر به داخل {} انتقال یافته‌است. همچنین دیگر نیازی هم به ذکر string.Format نیست.
عملیاتی که در اینجا توسط کامپایلر صورت خواهد گرفت، تبدیل این کدهای جدید مبتنی بر String interpolation به همان string.Format قدیمی در پشت صحنه‌است. بنابراین این قابلیت جدید C# 6 را به کدهای قدیمی خود نیز می‌توانید اعمال کنید. فقط کافی است VS 2015 را نصب کرده باشید و دیگر شماره‌ی دات نت فریم ورک مورد استفاده مهم نیست.


امکان انجام محاسبات با String interpolation

زمانیکه $ در ابتدای رشته قرار گرفت، عبارات داخل {}‌ها توسط کامپایلر محاسبه و جایگزین می‌شوند. بنابراین می‌توان چنین محاسباتی را نیز انجام داد:
 var message2 = $"{Environment.NewLine}Test {DateTime.Now}, {3*2}";
Console.Write(message2);
بدیهی اگر $ ابتدای رشته فراموش شود، اتفاق خاصی رخ نخواهد داد.


تغییر فرمت عبارات نمایش داده شده توسط String interpolation

همانطور که با string.Format می‌توان نمایش سه رقم جدا کننده‌ی هزارها را فعال کرد و یا تاریخی را به نحوی خاص نمایش داد، در اینجا نیز همان قابلیت‌ها برقرار هستند و باید پس از ذکر یک : عنوان شوند:
 var message3 = $"{Environment.NewLine}{1000000:n0} {DateTime.Now:dd-MM-yyyy}";
Console.Write(message3);
حالت کلی و استاندارد آن در متد string.Format به صورت {index[,alignment][:formatString]} است.


سفارشی سازی String interpolation
 
اگر متغیر رشته‌‌ای معرفی شده‌ی توسط $ را با یک var مشخص کنیم، نوع آن به صورت پیش فرض، از نوع string خواهد بود. برای نمونه در مثال‌های فوق، message و message2 از نوع string تعریف می‌شوند. اما این رشته‌های ویژه را می‌توان از نوع IFormattable و یا FormattableString نیز تعریف کرد.
در حقیقت رشته‌های آغاز شده‌ی با $ از نوع IFormattable هستند و اگر نوع متغیر آن‌ها ذکر نشود، به صورت خودکار به نوع FormattableString که اینترفیس IFormattable را پیاده سازی می‌کند، تبدیل می‌شوند. بنابراین پیاده سازی این اینترفیس، امکان سفارشی سازی خروجی string interpolation را میسر می‌کند. برای نمونه می‌خواهیم در مثال message2، نحوه‌ی نمایش تاریخ را سفارشی سازی کنیم.
class MyDateFormatProvider : IFormatProvider
{
    readonly MyDateFormatter _formatter = new MyDateFormatter();
 
    public object GetFormat(Type formatType)
    {
        return formatType == typeof(ICustomFormatter) ? _formatter : null;
    }
 
    class MyDateFormatter : ICustomFormatter
    {
        public string Format(string format, object arg, IFormatProvider formatProvider)
        {
            if (arg is DateTime)
                return ((DateTime)arg).ToString("MM/dd/yyyy");
            return arg.ToString();
        }
    }
}
در اینجا ابتدا کار با پیاده سازی اینترفیس IFormatProvider شروع می‌شود. متد GetFormat آن همیشه به همین شکل خواهد بود و هر زمانیکه نوع ارسالی به آن ICustomFormatter بود، یعنی یکی از اجزای {} دار در حال آنالیز است و خروجی مدنظر آن همیشه از نوع ICustomFormatter است که نمونه‌ای از پیاده سازی آن‌را جهت سفارشی سازی DateTime ملاحظه می‌کنید.
پس از پیاده سازی این سفارشی کننده‌ی تاریخ، نحوه‌ی استفاده‌ی از آن به صورت ذیل است:
static string formatMyDate(FormattableString formattable)
{
      return formattable.ToString(new MyDateFormatProvider());
}
ابتدا یک متد static را تعریف کنید که ورودی آن از نوع FormattableString باشد؛ از این جهت که رشته‌های شروع شده‌ی با $ نیز از همین نوع هستند. سپس سفارشی سازی پردازش {}‌ها در قسمت ToString آن انجام می‌شود و در اینجا می‌توان یک IFormatProvider جدید را معرفی کرد.
در ادامه برای اعمال این سفارشی سازی، فقط کافی است متد formatMyDate را به رشته‌ی مدنظر اعمال کنیم:
 var message2 = formatMyDate($"{Environment.NewLine}Test {DateTime.Now}, {3*2}");
Console.Write(message2);

و اگر تنها می‌خواهید فرهنگ جاری را عوض کنید، از روش ساده‌ی زیر استفاده نمائید:
public static string faIr(IFormattable formattable)
{
    return formattable.ToString(null, new CultureInfo("fa-Ir"));
}
در اینجا با اعمال متد faIr به عبارت شروع شده‌ی با $، فرهنگ ایران به رشته‌ی جاری اعمال خواهد شد.
نمونه‌ی کاربردی‌تر آن اعمال InvariantCulture به String interpolation است:
 static string invariant(FormattableString formattable)
{
    return formattable.ToString(CultureInfo.InvariantCulture);
}


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


غیرفعال سازی String interpolation

اگر می‌خواهید در رشته‌ای که با $ شروع شده، بجای محاسبه‌ی عبارتی، دقیقا خود آن‌را نمایش دهید (و { را escape کنید)، از {{}} استفاده کنید:
 var message0 = $"Hello! My name is {person.FirstName} {{person.FirstName}}";
در این مثال اولین {} محاسبه خواهد شد و دومی خیر.


پردازش عبارات شرطی توسط String interpolation

همانطور که عنوان شد، امکان ذکر یک عبارت کامل هم در بین {} وجود دارد (محاسبات، ذکر یک عبارت LINQ، ذکر یک متد و امثال آن). اما در این میان اگر یک عبارت شرطی مدنظر بود، باید بین () قرار گیرد:
 Console.Write($"{(person.Age>50 ? "old": "young")}");
علت اینجا است که کامپایلر سی‌شارپ، : بین {} را به format specifier تفسیر می‌کند. نمونه‌ی آن‌را پیشتر با مثال «تغییر فرمت عبارات نمایش داده شده» ملاحظه کردید. ذکر : در اینجا به معنای شروع مشخص سازی فرمتی است که قرار است به این حاصل اعمال شود. برای تغییر این رفتار پیش فرض، کافی است عبارت مدنظر را بین () ذکر کنیم تا تمام آن به صورت یک عبارت سی‌شارپ تفسیر شود.
مطالب
مدیریت حالت در برنامه‌های Blazor توسط الگوی Observer - قسمت دوم
در قسمت قبل، روشی را بر اساس الگوی Observer، برای به اشتراک گذاری حالت و مدیریت سراسری آن، بررسی کردیم. در این روش می‌توان چندین مخزن حالت را نیز داشت؛ اما هر کدام مستقل از هم عمل می‌کنند. برای تکمیل آن فرض کنید قرار است عمل افزودن مقدار یک شمارشگر، در دو مخزن حالت متفاوت و مجزای از هم، در هر کدام سبب بروز تغییر حالتی خاص شود که در این مطلب روش مدیریت آن‌را بررسی خواهیم کرد.


نیاز به یک Dispatcher برای تعامل با بیش از یک مخزن حالت


در اینجا برای نمونه دو مخزن حالت تعریف شده‌اند؛ اما روش تعامل با این مخازن حالت، دیگر مانند قبل نیست. برای نمونه در اثر تعامل یک کاربر با View ای خاص، رخدادی صادر شده و اینبار مدیریت این رخداد توسط یک Action (که عموما یک پیام رشته‌ای است)، به Dispatcher مرکزی ارسال می‌شود (و نه مستقیما به مخزن حالت خاصی). اکنون این Dispatcher، اکشن رسیده را به مخازن کد مشترک به آن ارسال می‌کند تا عمل متناسب با آن اکشن درخواستی را انجام دهند. مابقی آن همانند قبل است که پس از تغییر حالت در هر کدام از مخازن حالت، کار به روز رسانی UI، در کامپوننت‌های مشترک صورت خواهد گرفت. بدیهی است در اینجا مخازن حالت، مجاز به صرفنظر کردن از یک اکشن خاص هستند و الزامی به پیاده سازی آن ندارند. هدف اصلی این است که اگر اکشنی قرار بود در تمام مخازن حالت پیاده سازی شود و حالت‌های آن‌ها را تغییر دهد، روشی را برای مدیریت آن داشته باشیم.
بنابراین اگر به این الگوی جدید دقت کنید، چیزی نیست بجز یک الگوی Observer دو سطحی:
الف) Dispatcher ای (Subject) که مشترک‌هایی را مانند مخازن حالت دارد (Observers).
ب) مخازن حالتی (Subjects) که مشترک‌هایی را مانند کامپوننت‌ها دارند (Observers).

اگر پیشتر با React کار کرده باشید، این الگو را تحت عناوینی مانند Flux و یا Redux می‌شناسید و در اینجا می‌خواهیم پیاده سازی #C آن‌را بررسی کنیم:


در الگوی Flux، در اثر تعامل یک کاربر با کامپوننتی، اکشنی به سمت یک Dispatcher ارسال می‌شود. سپس Dispatcher این اکشن را به مخزن حالتی جهت مدیریت آن ارسال می‌کند که در نهایت سبب تغییر حالت آن شده و به روز رسانی UI را در پی خواهد داشت.


پیاده سازی یک Dispatcher برای تعامل با بیش از یک مخزن حالت

پیش از هر کاری نیاز است قالب اکشن‌های ارسالی را که قرار است توسط مخازن حالت مورد پردازش قرار گیرند، مشخص کنیم:
namespace BlazorStateManagement.Stores
{
    public interface IAction
    {
        public string Name { get; }
    }
}
عموما هر اکشنی با نام و یا پیامی مشخص می‌شود. بر این اساس می‌توان اکشن افزودن و یا کاهش مقادیر شمارشگر را به صورت زیر تعریف کرد:
namespace BlazorStateManagement.Stores.CounterStore
{
    public class IncrementAction : IAction
    {
        public const string Increment = nameof(Increment);

        public string Name { get; } = Increment;
    }

    public class DecrementAction : IAction
    {
        public const string Decrement = nameof(Decrement);

        public string Name { get; } = Decrement;
    }
}
مزیت تعریف و استفاده از یک کلاس در اینجا این است که اگر نیاز بود به همراه اکشنی، اطلاعات اضافه‌تری نیز به سمت مخازن کد ارسال شوند، می‌توان آن‌ها را داخل هر کدام از کلاس‌ها، بسته به نیاز برنامه تعریف کرد و صرفا محدود به Name و یا یک مقدار رشته‌ای معرف آن، نخواهند بود.

پس از تعریف ساختار یک اکشن، اکنون نوبت به پیاده سازی راه حلی برای ارسال آن به تمام مخازن حالت برنامه است:
using System;

namespace BlazorStateManagement.Stores
{
    public interface IActionDispatcher
    {
        void Dispatch(IAction action);
        void Subscribe(Action<IAction> actionHandler);
        void Unsubscribe(Action<IAction> actionHandler);
    }

    public class ActionDispatcher : IActionDispatcher
    {
        private Action<IAction> _actionHandlers;

        public void Subscribe(Action<IAction> actionHandler) => _actionHandlers += actionHandler;

        public void Unsubscribe(Action<IAction> actionHandler) => _actionHandlers -= actionHandler;

        public void Dispatch(IAction action) => _actionHandlers?.Invoke(action);
    }
}
پیاده سازی ActionDispatcher ای را که ملاحظه می‌کنید، دقیقا مشابه CounterStore قسمت قبل است و در اینجا توسط متد Subscribe، مخازن حالت برنامه مشترک آن شده و یا توسط متد Unsubscribe، قطع اشتراک می‌کنند. همچنین متد Dispatch نیز شبیه به متد BroadcastStateChange قسمت قبل عمل می‌کند و سبب می‌شود تا اکشن ارسالی به آن، به تمام مشترکین این سرویس، ارسال شود.
این سرویس را نیز با طول عمر Scoped به سیستم تزریق وابستگی‌های برنامه معرفی می‌کنیم که سبب می‌شود تا پایان عمر برنامه (بسته شدن مرورگر یا ریفرش کامل صفحه‌ی جاری)، در حافظه باقی مانده و وهله سازی مجدد نشود. به همین جهت تزریق آن در مخازن حالت مختلف برنامه، دقیقا حالت یک Dispatcher اشتراکی را پیدا خواهد کرد.
namespace BlazorStateManagement.Client
{
    public class Program
    {
        public static async Task Main(string[] args)
        {
            var builder = WebAssemblyHostBuilder.CreateDefault(args);
            // ...
            builder.Services.AddScoped<IActionDispatcher, ActionDispatcher>();
            // ...
        }
    }
}


استفاده از IActionDispatcher در مخازن حالت برنامه

در ادامه می‌خواهیم مخازن حالت برنامه را تحت کنترل سرویس IActionDispatcher قرار دهیم تا کاربر بتواند اکشنی را به Dispatcher ارسال کند و سپس Dispatcher این درخواست را به تمام مخازن حالت موجود، جهت بروز واکنشی (در صورت نیاز)، اطلاعات رسانی نماید.
برای این منظور سرویس ICounterStore قسمت قبل ، به صورت زیر تغییر می‌کند که اینترفیس IDisposable را پیاده سازی کرده و همچنین دیگر به همراه متدهای عمومی افزایش و یا کاهش مقدار نیست:
using System;

namespace BlazorStateManagement.Stores.CounterStore
{
    public interface ICounterStore : IDisposable
    {
        CounterState State { get; }

        void AddStateChangeListener(Action listener);
        void BroadcastStateChange();
        void RemoveStateChangeListener(Action listener);
    }
}
بر این اساس، پیاده سازی CounterStore به صورت زیر تغییر خواهد کرد:
using System;

namespace BlazorStateManagement.Stores.CounterStore
{
    public class CounterStore : ICounterStore
    {
        private readonly CounterState _state = new();
        private bool _isDisposed;
        private Action _listeners;
        private readonly IActionDispatcher _actionDispatcher;

        public CounterStore(IActionDispatcher actionDispatcher)
        {
            _actionDispatcher = actionDispatcher ?? throw new ArgumentNullException(nameof(actionDispatcher));
            _actionDispatcher.Subscribe(HandleActions);
        }

        private void HandleActions(IAction action)
        {
            switch (action)
            {
                case IncrementAction:
                    IncrementCount();
                    break;
                case DecrementAction:
                    DecrementCount();
                    break;
            }
        }

        public CounterState State => _state;

        private void IncrementCount()
        {
            _state.Count++;
            BroadcastStateChange();
        }

        private void DecrementCount()
        {
            _state.Count--;
            BroadcastStateChange();
        }

        public void AddStateChangeListener(Action listener) => _listeners += listener;

        public void RemoveStateChangeListener(Action listener) => _listeners -= listener;

        public void BroadcastStateChange() => _listeners.Invoke();

        public void Dispose()
        {
            Dispose(disposing: true);
            GC.SuppressFinalize(this);
        }

        protected virtual void Dispose(bool disposing)
        {
            if (!_isDisposed)
            {
                try
                {
                    if (disposing)
                    {
                        _actionDispatcher.Unsubscribe(HandleActions);
                    }
                }
                finally
                {
                    _isDisposed = true;
                }
            }
        }
    }
}
توضیحات:
- با توجه به اینکه CounterStore یک سرویس ثبت شده‌ی در سیستم است، می‌تواند از مزیت تزریق سایر سرویس‌ها در سازنده‌ی خودش بهره‌مند شود؛ مانند تزریق سرویس جدید IActionDispatcher.
- پس از تزریق سرویس جدید IActionDispatcher، متدهای Subscribe آن‌را در سازنده‌ی کلاس و Unsubscribe آن‌را در حین Dispose سرویس، فراخوانی می‌کنیم. البته فراخوانی و یا پیاده سازی Unsubscribe و Dispose در اینجا غیرضروری است؛ چون طول عمر این کلاس با طول عمر برنامه یکی است.
- بر اساس این الگوی جدید، هر اکشنی که به سمت Dispatcher مرکزی ارسال می‌شود، در نهایت به متد HandleActions یکی از مخازن حالت تعریف شده، خواهد رسید:
        private void HandleActions(IAction action)
        {
            switch (action)
            {
                case IncrementAction:
                    IncrementCount();
                    break;
                case DecrementAction:
                    DecrementCount();
                    break;
            }
        }
در اینجا می‌توان با استفاده از patterns matching، بر اساس نوع اکشن مدنظر، عملیات خاصی را انجام داد. فقط در اینجا دیگر متدهای IncrementCount و DecrementCount، عمومی نیستند. به همین جهت باید به کامپوننت شمارشگر مراجعه کرد و تعریف قبلی:
@inject ICounterStore CounterStore

@code {

    private void IncrementCount()
    {
        CounterStore.IncrementCount();
    }
را به صورت زیر تغییر داد:
- ابتدا در انتهای فایل Client\_Imports.razor، فضای نام سرویس جدید IActionDispatcher را اضافه می‌کنیم:
@using BlazorStateManagement.Stores
- سپس از آن جهت ارسال IncrementAction به مخازن حالت برنامه استفاده خواهیم کرد:
// ...
@inject IActionDispatcher ActionDispatcher


@code {

    private void IncrementCount()
    {
        ActionDispatcher.Dispatch(new IncrementAction());
    }
با این تغییر جدید، هربار که بر روی دکمه‌ی افزایش مقدار شمارشگر، کلیک می‌شود، در آخر یک IncrementAction به تمام مخازن حالت موجود در برنامه ارسال خواهد شد و آن‌ها بر اساس نیازشان تصمیم خواهند گرفت که آیا به آن واکنش نشان دهند یا خیر.

کدهای کامل این مطلب را از اینجا می‌توانید دریافت کنید: BlazorStateManagement-Part-2.zip
مطالب
Asp.Net Identity #2
پیشتر در اینجا در مورد تاریخچه‌ی سیستم Identity مطالبی را عنوان کردیم. در این مقاله می‌خواهیم نحوه‌ی برپایی سیستم Identity را بحث کنیم.
ASP.NET Identity مانند ASP.NET Membership به اسکیمای SQL Server وابسته نیست؛ اما Relational Storage همچنان واحد ذخیره سازی پیش فرض و آسانی می‌باشد. بدین جهت که تقریبا بین همه‌ی توسعه دهندگان جا افتاده است. ما در این نوشتار از LocalDB جهت ذخیره سازی جداول استفاده می‌کنیم. ذکر این نکته ضروری است که سیستم Identity از Entity Framework Code First جهت ساخت اسکیما استفاده می‌کند.
پیش از هر چیز، ابتدا یک پروژه‌ی وب را ایجاد کنید؛ مانند شکل زیر:

در مرحله‌ی بعد سه پکیج زیر را باید نصب کنیم :

- Microsoft.AspNet.Identity.EntityFramework

-   Microsoft.AspNet.Identity.OWIN

-  Microsoft.Owin.Host.SystemWeb

بعد از اینکه پکیج‌های بالا را نصب کردیم، باید فایل Web.config را بروز کنیم. اولین مورد، تعریف یک Connection String می‌باشد:
<connectionStrings>
 <add name="IdentityDb" 
      providerName="System.Data.SqlClient"
      connectionString="Data Source=(localdb)\v11.0;Initial Catalog=IdentityDb;Integrated Security=True;Connect Timeout=15;Encrypt=False;TrustServerCertificate=False;MultipleActiveResultSets=True"/>
 </connectionStrings>
بعد از آن، تعریف یک کلید در قسمت AppSettings تحت عنوان Owin:AppStartup است:
<appSettings>
 <add key="webpages:Version" value="3.0.0.0" />
 <add key="webpages:Enabled" value="false" />
 <add key="ClientValidationEnabled" value="true" />
 <add key="UnobtrusiveJavaScriptEnabled" value="true" />
 <add key="owin:AppStartup" value="Users.IdentityConfig" />
 </appSettings>
Owin مدل شروع برنامه (Application Startup Model) خودش را تعریف می‌کند که از کلاس کلی برنامه (منظور Global.asax) جداست. AppSetting تعریف شده با نام owin:Startup شناخته می‌شود و مشخص کننده کلاسی است که Owin وهله سازی خواهد کرد. وقتی برنامه شروع به کار می‌کند، تنظیمات خودش را از این کلاس دریافت خواهد کرد (در این نوشتار کلاس IdentityConfig می‌باشد).

ساخت کلاس User
مرحله‌ی بعد ساخت کلاس User می‌باشد. این کلاس بیانگر یک کاربر می‌باشد. کلاس User باید از کلاس IdentityUser ارث بری کند که این کلاس در فضای نام Microsoft.AspNet.Identity.EntityFramework قرار دارد. کلاس IdentityUser فراهم کننده‌ی یک کاربر عمومی و ابتدایی است. اگر بخواهیم اطلاعات اضافی مربوط به کاربر را ذخیره کنیم باید آنها در کلاسی که از کلاس IdentityUser ارث بری می‌کند قرار دهیم. کلاس ما در اینجا AppUser نام دارد.
using System;
using Microsoft.AspNet.Identity.EntityFramework;
namespace Users.Models 
{
   public class AppUser : IdentityUser 
  {
      // پروپرتی‌های اضافی در اینجا
  }
}

ساخت کلاس Database Context برنامه
مرحله‌ی بعد ساخت کلاس DbContext برنامه می‌باشد که بر روی کلاس AppUser عمل میکند. کلاس Context برنامه که ما در اینجا آن را AppIdentityDbContext تعریف کرده‌ایم، از کلاس <IdentityDbContext<T ارث بری می‌کند که T همان کلاس User می‌باشد (در مثال ما AppUser) .
using System.Data.Entity;
using Microsoft.AspNet.Identity.EntityFramework;
using Users.Models;
namespace Users.Infrastructure {
 public class AppIdentityDbContext : IdentityDbContext<AppUser> 
{
   public AppIdentityDbContext() 
              : base("IdentityDb") { }

  static AppIdentityDbContext() 
 {
    Database.SetInitializer<AppIdentityDbContext>(new IdentityDbInit());
  }
 public static AppIdentityDbContext Create() {
 return new AppIdentityDbContext();
 }
 }
public class IdentityDbInit
 : DropCreateDatabaseIfModelChanges<AppIdentityDbContext> {
 protected override void Seed(AppIdentityDbContext context) {
 PerformInitialSetup(context);
 base.Seed(context);
 }
 public void PerformInitialSetup(AppIdentityDbContext context) {
 // initial configuration will go here
 }
 }
}

ساخت کلاس User Manager
یکی از مهمترین کلاسهای Identity کلاس User Manager (مدیر کاربر) می‌باشد که نمونه‌هایی از کلاس User را مدیریت می‌کند. کلاسی را که تعریف می‌کنیم، باید از کلاس <UserManager<T ارث بری کند که T همان کلاس User می‌باشد. کلاس UserManager خاص EF نمی‌باشد و ویژگی‌های عمومی بیشتری برای ساخت و انجام عملیات بر روی داده‌های کاربر را فراهم می‌نماید.

کلاس UserManager حاوی متدهای بالا است. اگر دقت کنید، می‌بینید که تمامی متدهای بالا به کلمه‌ی Async ختم می‌شوند. زیرا Asp.Net Identity تقریبا کل ویژگیهای برنامه نویسی Async را پیاده سازی کرده است و این بدین معنی است که عملیات میتوانند به صورت غیر همزمان اجرا شده و دیگر فعالیت‌ها را بلوکه نکنند.

using Microsoft.AspNet.Identity;
using Microsoft.AspNet.Identity.EntityFramework;
using Microsoft.AspNet.Identity.Owin;
using Microsoft.Owin;
using Users.Models;

namespace Users.Infrastructure 
{
 public class AppUserManager : UserManager<AppUser> 
{

 public AppUserManager(IUserStore<AppUser> store)
 : base(store) {
 }
 public static AppUserManager Create( IdentityFactoryOptions<AppUserManager> options, IOwinContext context) 
{
 AppIdentityDbContext db = context.Get<AppIdentityDbContext>();
 AppUserManager manager = new AppUserManager(new UserStore<AppUser>(db));
 return manager;
 }
 }
}

زمانی که Identity نیاز به وهله‌ای از کلاس AppUserManager داشته باشد، متد استاتیک Create را صدا خواهد زد. این عمل زمانی اتفاق می‌افتد که عملیاتی بر روی داده‌های کاربر انجام گیرد. برای ساخت وهله‌ای از کلاس AppUserManager نیاز به کلاس <UserStor<AppUser دارد. کلاس <UserStore<T یک پیاده سازی از رابط <IUserStore<T  توسط  EF میباشد که وظیفه‌ی آن فراهم کننده‌ی پیاده سازی Storage-Specific متدهای تعریف شده در کلاس User Manager (که در اینجا AppUserManager ) می‌باشد. برای ساخت <UserStore<T نیاز به وهله‌ای از کلاس AppIdentityDbContext می‌باشد که از طریق Owin به صورت زیر قابل دریافت است:

AppIdentityDbContext db = context.Get<AppIdentityDbContext>();

یک پیاده سازی از رابط IOwinContext، به عنوان پارامتر به متد Create پاس داده می‌شود. در این پیاده سازی، یک تابع جنریک به نام Get تعریف شده که اقدام به برگشت وهله ای از اشیای ثبت شده‌ی در کلاس شروع Owin می‌نماید.


ساخت کلاس شروع Owin

اگر خاطرتان باشد یک کلید در قسمت AppSettings فایل Web.config به صورت زیر تعریف کردیم:

<add key="owin:AppStartup" value="Users.IdentityConfig" />

قسمت Value کلید بالا از دو قسمت تشکیل شده است: Users بیانگر فضای نام برنامه‌ی شماست و IdentityConfig بیانگر کلاس شروع می‌باشد. (البته شما می‌توانید هر نام دلخواهی را برای کلاس شروع بگذارید. فقط دقت داشته باشید که نام کلاس شروع و مقدار، با کلیدی که تعریف می‌کنید یکی باشد)

Owin مستقل از ASP.NET اعلام شده است و قراردادهای خاص خودش را دارد. یکی از این قراردادها تعریف یک کلاس و وهله سازی آن به منظور بارگذاری و پیکربندی میان افزار و انجام دیگر کارهای پیکربندی که نیاز است، می‌باشد. به طور پیش فرض این کلاس Start نام دارد و در پوشه‌ی App_Start تعریف می‌شود. این کلاس حاوی یک متد به نام Configuration می‌باشد که بوسیله زیرساخت Owin فراخوانی می‌شود و یک پیاده سازی از رابط Owin.IAppBuilder به عنوان آرگومان به آن پاس داده می‌شود که کار پشتیبانی از Setup میان افزار مورد نیاز برنامه را برعهده دارد.

using Microsoft.AspNet.Identity;
using Microsoft.Owin;
using Microsoft.Owin.Security.Cookies;
using Owin;
using Users.Infrastructure;
namespace Users 
{
 public class IdentityConfig 
{
 public void Configuration(IAppBuilder app) 
{
 app.CreatePerOwinContext<AppIdentityDbContext>(AppIdentityDbContext.Create);
 app.CreatePerOwinContext<AppUserManager>(AppUserManager.Create);
 app.UseCookieAuthentication(new CookieAuthenticationOptions {
 AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
 LoginPath = new PathString("/Account/Login"),
 });
 }
 }
}

رابط IAppBuilder بوسیله تعدادی متد الحاقی که در کلاسهایی که در فضای نام Owin تعریف شده‌اند، تکمیل شده است. متد CreatePerOwinContext کار ساخت وهله‌ای از کلاس AppUserManager و کلاس AppIdentityDbContext را برای هر درخواست بر عهده دارد. این مورد تضمین می‌کند که هر درخواست، به یک داده‌ی تمیز از Asp.Net Identity دسترسی خواهد داشت و نگران اعمال همزمانی و یا کش ضعیف داده نخواهد بود. متد UseCookieAuthentication به Asp.Net Identity می‌گوید که چگونه از کوکی‌ها برای تصدیق هویت کاربران استفاده کند که Optionهای آن از طریق کلاس CookieAuthenticationOptions مشخص می‌شود. مهمترین قسمت در اینجا پروپرتی LoginPath می‌باشد و مشخص می‌کند که کلاینت‌های تصدیق هویت نشده، هنگام دسترسی به یک منبع محافظت شده، به کدام URL هدایت شوند که توسط یک رشته به متد PathString پاس داده می‌شود.

خوب دوستان برپایی سیستم Identity به پایان رسید. انشالله در قسمت بعدی به چگونگی استفاده‌ی از این سیستم خواهیم پرداخت.

مطالب
آزمایش Web APIs توسط افزونه Rest Client
اگر تجربه‌ی نوشتن Web API را داشته باشید، قطعا نیاز به ابزارها و یا کتابخانه‌هایی را برای تست API‌ها داشته‌اید، تا بتوانید از صحت عملکرد آنها مطمئن شوید. از جمله روش‌ها و یا ابزارهایی که میتوانید برای تست و بررسی API‌ها استفاده کنید، می‌توان به postman (دوره آشنایی با postman ) ، سواگر  و stress اشاره کرد که پیشتر در سایت جاری مقالات مفیدی درباره‌ی آن‌ها نوشته شده‌است که می‌توانید برای آشنایی به آنها مراجعه کنید.

در این مقاله قصد داریم یک افزونه را معرفی نماییم که بر روی Visual Studio Code نصب می‌شود و این امکان را به ما می‌دهد تا بتوانیم API‌ها را فراخوانی و تست کنیم که در ادامه به تفصیل به آن خواهیم پرداخت.
با توجه به اینکه این افزونه مختص VS Code می‌باشد، باید ابتدا vs code را نصب نمایید.
در قسمت افزونه‌ها در بخش جستجو، عبارت Rest client را وارد نمایید و منتظر بمانید تا نتایج، نمایش داده شوند و سپس آن را نصب نمایید.

در زمان انتشار این مقاله، بیش از یک میلیون بار دانلود شده‌است.

قبل از اینکه بتوانیم با نحوه‌ی کار این افزونه آشنا شویم، نیاز داریم API هایی باشند تا بتوانیم آن‌ها را فراخوانی کنیم. هرچند سایت‌هایی نیز هستند که چنین امکانی را فراهم کرده‌اند و میتوانیم API‌های آنها را فراخوانی کنیم. در این مقاله، چند API  ساده را با ASP.NET Core پیاده سازی میکنیم و سپس اقدام به فراخوانی هر کدام از آنها خواهیم کرد.
با توجه به اینکه این مقاله در مورد Web API و آموزش آن نیست، از توضیح کدهای زیر خودداری می‌شود.
 public class Person
    {
        public int Id { get; set; }
        public string Name { get; set; }
        public int Age { get; set; }
    }
 شبیه سازی یک Repository
 public class PeopleRepository
    {
        private List<Person> _people;
        public PeopleRepository()
        {
            _people = new List<Person> {
            new Person{ Id=1,Age=28,Name="Uthman"},
            new Person{ Id=2,Age=27,Name="Vahid"},
            new Person{ Id=3,Age=26,Name="Hadi"},
            new Person{ Id=4,Age=25,Name="Saman"},
            new Person{ Id=5,Age=20,Name="Sirwan"},
            };
        }

        public List<Person> GetAll()
        {
            return _people;
        }

    }

و چند API  برای کار با کلاس Person :
[Route("api/people")]
    public class PeopleController : Controller
    {
        PeopleRepository _repository;
        public PeopleController()
        {
            _repository = new PeopleRepository();
        }

        [HttpGet("")]
        public IActionResult Get()
        {
            return Ok(_repository.GetAll());
        }

        [HttpGet("{id:int}")]
        public IActionResult Get(int id)
        {
            return Ok(_repository.GetAll().FirstOrDefault(p => p.Id == id));
        }

        [HttpPost]
        public IActionResult Post([FromBody]Person person)
        {
            return Ok(person);
        }

        [HttpPut("{id:int}")]
        public IActionResult Put(int id,[FromBody] Person person)
        {
            if (id != person.Id)
                return BadRequest();
            return Ok(person);
        }

        [HttpDelete("{id:int}")]
        public IActionResult Delete(int id)
        {
            return Ok();
        }

        [HttpPost("avatar")]
        public IActionResult Post(IFormFile file)
        {
            return Ok();
        }
    }
تا به اینجا صرفا چند API را تهیه کرده‌ایم که در ادامه میخواهیم هر کدام از آنها را با افزونه‌ی Rest Client فراخوانی کنیم و با نحوه‌ی عملکرد آن‌ها آشنا شویم.

 مراحل انجام کار
 ابتدا نیاز هست یک فایل را ایجاد نماییم؛ با پسوند http و یا اینکه میتوانید یک فایل متنی را ایجاد نمایید و زبان آن را بر روی HTTP تنظیم نمایید.

اجرای درخواست GET برای نمایش لیست افراد
GET http://localhost:59416/api/people
با نوشتن یک خط فوق میتوانیم API مربوط به لیست افراد را فراخوانی کنیم.

برای فراخونی دستور فوق دو راه پیش رو است:
1- بعد از نوشتن آدرس هر API، یک دکمه بالای آدرس ظاهر می‌شود، تحت عنوان Send Request که با کلیک بر روی این دکمه، درخواست ارسال خواهد شد.
2- انتخاب آدرس فوق و فشردن کلید‌های ctrl+alt+r یا بجای فشردن این کلید‌ها میتوانید در Command  Palette،دستور Send Request را جستجو کنید و انتخاب نمایید:


بعد از اجرا به یکی از روش‌های فوق، در یک پنجره‌ی جدید در سمت راست، نتیجه‌ی درخواست ظاهر میشود که در بخش بالا توضیحاتی در مورد درخواست و در بخش پایین، خروجی آن قابل مشاهده می‌باشد


تا به اینجا توانستیم فقط با نوشتن آدرس API مورد نظر، آن را فراخوانی کنیم.

برای ارسال پارامترهایی در هدر درخواست فقط کافیست دقیقا در خط پایین آدرس، به صورت field-name:field-value، هر پارامتری را که میخواهید، به همراه درخواست ارسال کنید. برای نمونه برای API لیست افراد که در بالا تست کردیم، میتوانیم هدر را به صورت زیر تنظیم نماییم :

GET http://localhost:59416/api/people
Content-Type: application/json


اجرای درخواست GET برای دریافت یک شخص خاص

GET http://localhost:59416/api/people/1

خروجی آن به صورت زیر می‌باشد


درخواست POST برای درج کاربر جدید:

POST http://localhost:59416/api/people
content-type: application/json

{
    "id": 10,
    "name": "ali",
    "age":37
}

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


درخواست PUT برای آپدیت یک شخص :

PUT http://localhost:59416/api/people/3
content-type: application/json

{
    "id": 3,
    "name": "ali",
    "age":37
}

درخواست DELETE برای حذف شخص:

DELETE http://localhost:59416/api/people/3
content-type: application/application/json


ارسال توکن اعتبارسنجی :

در صورتی که یک API نیاز به اعتبار سنجی دارد و باید توکن را به همراه درخواستی ارسال نمایید، می‌توانید در هدر درخواست، همانند زیر، توکن را ارسال نمایید

GET http://localhost:59416/api/people
content-type: application/json
Authorization: Bearer token


آپلود فایل:

یکی از API هایی که در مثال ابتدای مقاله داشتیم، مربوط به آپلود فایل آواتار هست که از ورودی، یک IFormFile را به عنوان ورودی دریافت میکند. برای آپلود فایل به کمک افزونه Rest Client میتوانیم به صورت زیر فایل را ارسال نماییم 

POST http://localhost:59416/api/people/avatar
Content-Type: multipart/form-data; boundary=----MyBoundary

------MyBoundary
Content-Disposition: form-data; name="file";filename="Studio"
content-type: image/png

< C:\Users\rahimi\Downloads\Studio.png

------MyBoundary--

قبل از آدرس فایل، وجود  > ضروری می‌باشد. 


فعال سازی دکمه Send Request به ازای هر آدرس:

اگر در یک فایل، چند آدرس را همانند عکس زیر داشته باشید، فقط یک دکمه‌ی Send Request وجود خواهد داشت که کلیک بر روی آن منجر به فراخوانی اولین url می‌شود.

برای داشتن یک دکمه Send Request به ازای هر API، باید بین هر کدام از API ها، حداقل سه تا # قرار دهید.

### Get All People
GET http://localhost:59416/api/people
content-type: application/json

### Get Person
GET http://localhost:59416/api/people/1


### Create 
POST http://localhost:59416/api/people
content-type: application/json

{
    "id": 10,
    "name": "ali",
    "age":37
}

### Edit person
PUT http://localhost:59416/api/people/3
content-type: application/json

{
    "id": 3,
    "name": "ali",
    "age":37
}

### Delete person
DELETE http://localhost:59416/api/people/3
content-type: application/application/json


### Upload Avatar
POST http://localhost:59416/api/people/avatar 
Content-Type: multipart/form-data; boundary=----MyBoundary

------MyBoundary
Content-Disposition: form-data; name="file";filename="Studio"
content-type: image/png

< C:\Users\rahimi\Downloads\Studio.png

------MyBoundary--

افزونه‌ی Rest Client، فراتر از توضیحات این مقاله می‌باشد. در صورت علاقه و برای مطالعه بیشتر در مورد آن، می‌توانید به لینک صفحه افزونه مراجعه نمایید.

نظرات مطالب
EF Code First #9
من یک سری مدل به شکل زیر دارم:
    public class Context : DbContext
    {
        public Context():base("Server=.;Database=EfTest;Trusted_Connection=True;Integrated Security=true;")
        {
            
        }

        public DbSet<Student> Students { get; set; }
        public DbSet<Teacher> Teachers { get; set; }

    }


    public class Person
    {
        public int Id { get; set; }
        public DateTime InsertTime { get; set; }
        public string FirstName { get; set; }
        public string LastName { get; set; }

    }


    public class Student:Person
    {
        public string Student1 { get; set; }
    }

    public class Teacher:Person
    {
        public string Teacher1 { get; set; }

    }
بدون هیچ تنظیماتی، یعنی طبق تعاریف بالا الان باید حالت به شکل TPH باشد ولی در ساخت نهایی دو جدول دانش آموز و معلم تشکیل میشه. من در این مدتی که ef کار کردم روی این حالت بودم و راضی هستم ولی برای یکی از دوستان EF سعی داره جدولی به اسم People رو از روی Person تشکیل بده.
مطالب
سفارشی سازی ASP.NET Core Identity - قسمت ششم - فارسی سازی پیام‌ها
هرچند ASP.NET Core Identity تمام پیام‌های خطایی را که ارائه می‌دهد از یک فایل resx دریافت می‌کند، اما این فایل در نگارش 1.1 آن حداقل قابلیت چندزبانی شدن را ندارد و اگر فایل resx فارسی آن‌را تهیه کنیم، توسط این فریم ورک استفاده نخواهد شد. در ادامه ابتدا نگاهی خواهیم داشت به زیرساخت استفاده شده‌ی در این فریم ورک برای بومی سازی پیام‌های داخلی آن و سپس نحوه‌ی فارسی کردن آن‌را بررسی می‌کنیم.


ASP.NET Core Identity 1.1 چگونه پیام‌های خطای خود را تامین می‌کند؟

نگارش 1.1 این فریم ورک به همراه یک فایل Resources.resx است که تمام پیام‌های خطاهای ارائه شده‌ی توسط متدهای مختلف آن‌را به همراه دارد. این فایل توسط کلاس IdentityErrorDescriber به نحو ذیل استفاده می‌شود:
    public class IdentityErrorDescriber
    {
        public virtual IdentityError DefaultError()
        {
            return new IdentityError
            {
                Code = nameof(DefaultError),
                Description = Resources.DefaultError
            };
        }
برای مثال برای نمایش یک پیام خطای عمومی، به کلاس Resources معادل فایل resx یاد شده مراجعه کرده و خاصیت DefaultError آن‌را ارائه می‌دهد و به همین نحو برای سایر خطاها و اخطارها.
سپس کلاس IdentityErrorDescriber به سیستم تزریق وابستگی‌های آن اضافه شده و هرجائیکه نیاز به نمایش پیامی را داشته، از آن استفاده می‌کند.
بنابراین همانطور که ملاحظه می‌کنید کلاس Resources آن ثابت است و قابل تغییر نیست. به همین جهت اگر معادل فارسی این فایل را تهیه کنیم، توسط این فریم ورک به صورت خودکار استفاده نخواهد شد.


فارسی سازی IdentityErrorDescriber

بهترین راه فارسی سازی کلاس IdentityErrorDescriber، ارث بری از آن و بازنویسی متدهای virtual آن است که اینکار در کلاس CustomIdentityErrorDescriber انجام شده‌است:
    public class CustomIdentityErrorDescriber : IdentityErrorDescriber
    {
        public override IdentityError DefaultError()
        {
            return new IdentityError
            {
                Code = nameof(DefaultError),
                Description = "خطایی رخ داده‌است."
            };
        }
پس از بازنویسی کامل این کلاس، اکنون نوبت به جایگزینی آن با IdentityErrorDescriber پیش‌فرض است:
services.AddScoped<IdentityErrorDescriber, CustomIdentityErrorDescriber>();

services.AddIdentity<User, Role>(identityOptions =>
            {
            }).AddUserStore<ApplicationUserStore>()
               // the rest of the setting …
              .AddErrorDescriber<CustomIdentityErrorDescriber>()
               // the rest of the setting …
معرفی CustomIdentityErrorDescriber، در دو قسمت معرفی عمومی آن به سیستم تزریق وابستگی‌ها و همچنین متد AddErrorDescriber زنجیره‌ی AddIdentity کلاس IdentityServicesRegistry انجام شده‌است.
به این ترتیب این فریم ورک هرزمانیکه نیاز به وهله‌ای از نوع IdentityErrorDescriber را داشته باشد، از وهله‌ی فارسی سازی شده‌ی ما استفاده می‌کند.


مشکل! هنوز پس از جایگزینی سرویس IdentityServicesRegistry اصلی، تعدادی از خطاها فارسی نیستند!

اگر به کلاس PasswordValidator آن مراجعه کنید، در سازنده‌ی کلاس یک چنین تعریفی را می‌توان مشاهده کرد:
    public class PasswordValidator<TUser> : IPasswordValidator<TUser> where TUser : class
    {
        public PasswordValidator(IdentityErrorDescriber errors = null)
        {
            Describer = errors ?? new IdentityErrorDescriber();
        }
یعنی اگر ما این کلاس PasswordValidator را سفارشی سازی کردیم و فراموش کردیم که سازنده‌ی آن‌را هم بازنویسی کنیم، پارامتر errors آن نال خواهد بود (چون از مقدار پیش‌فرض پارامترها استفاده کرده‌اند). یعنی از new IdentityErrorDescriber اصلی (بدون مراجعه‌ی به سیستم تزریق وابستگی‌ها و استفاده‌ی از نسخه‌ی سفارشی سازی شده‌ی ما) استفاده می‌کند. بنابراین در هر دو کلاس سفارشی سازی شده‌ی اعتبارسنجی کاربران و کلمات عبور آن‌ها، ذکر سازنده‌ی پیش‌فرض این کلاس‌ها و ذکر پارامتر IdentityErrorDescriber آن، اجباری است:
    public class CustomPasswordValidator : PasswordValidator<User>
    {
        private readonly IUsedPasswordsService _usedPasswordsService;
        private readonly ISet<string> _passwordsBanList;

        public CustomPasswordValidator(
            IdentityErrorDescriber errors,// How to use CustomIdentityErrorDescriber
            IOptionsSnapshot<SiteSettings> configurationRoot,
            IUsedPasswordsService usedPasswordsService) : base(errors)



    public class CustomUserValidator : UserValidator<User>
    {
        private readonly ISet<string> _emailsBanList;

        public CustomUserValidator(
            IdentityErrorDescriber errors,// How to use CustomIdentityErrorDescriber
            IOptionsSnapshot<SiteSettings> configurationRoot
            ) : base(errors)
به این ترتیب، زمانیکه این کلاس‌ها توسط سیستم تزریق وابستگی‌ها وهله سازی می‌شوند، IdentityErrorDescriber آن دقیقا همان کلاس فارسی سازی شده‌ی ما خواهد بود و دیگر شرط ()errors ?? new IdentityErrorDescriber در کلاس پایه محقق نمی‌شود تا بازهم به همان تامین کننده‌ی پیش فرض خطاها مراجعه کند؛ چون  base(errors) آن با کلاس جدید ما مقدار دهی شده‌است.

یک نکته: اگر کلاس‌های زیر را سفارشی سازی کردید، تمامشان از حالت ()errors ?? new IdentityErrorDescriber در سازنده‌ی کلاس خود استفاده می‌کنند. بنابراین ذکر مجدد و بازنویسی سازنده‌ی آن‌ها را فراموش نکنید (در حد ذکر مجدد سازنده‌ی کلاس پایه کفایت می‌کند و مابقی آن توسط سیستم تزریق وابستگی‌ها مدیریت خواهد شد):
- PasswordValidator
- RoleManager
- RoleStore
- UserStore
- UserValidator
- RoleValidator


کدهای کامل این سری را در مخزن کد DNT Identity می‌توانید ملاحظه کنید.
نظرات مطالب
EF Code First #7
سلام وحید جان ممنون از این همه لطف

من یک پروژه را با CodeFirst شروع کردم اما یه جایی اشتباه کردم فکر کنم اشتباهم توی یکی از Mapping‌ها باشه. اگه لطف کنید ببینید مشکل چیه.بدون استفاده از Mapping مشکلی نیست و دیتا بیس با روابطی که میخوام ایجاد میشه اما وقتی از Mapping استفاده میکنم با این خطا مواجه میشم:
{"Sequence contains more than one matching element"} 
چندتا کلاس‌ها به شکل زیر هست:
public class Driver
    {
        public int Id { get; set; }
        public string FirstName { get; set; }
        public string LastName { get; set; }
        public string NationalCode { get; set; }
        public string CellPhone { get; set; }
        public string LicenseNumber { get; set; }
        public bool IsDriverAssistance { get; set; }

        [InverseProperty("Driver")]
        public virtual ICollection<Transference> Transferences { get; set; }
        [InverseProperty("DriverAssistance")]
        public virtual ICollection<Transference> TransferencesForAssistance { get; set; }
        [InverseProperty("Driver")]
        public virtual ICollection<Tanker> Tankers { get; set; }
        [InverseProperty("DriverAssistance")]
        public virtual ICollection<Tanker> TankersForAssistance { get; set; }

    }

 public class Transference
    {
        public string Id { get; set; }
        public DateTime Date { get; set; }
        public Int16 Lytrazh { get; set; }
        public bool IsEMS { get; set; }
        public DateTime LoadingDate { get; set; }
        public DateTime DeliveryDate { get; set; }
        [InverseProperty("Transferences")]
        public virtual Driver Driver { get; set; }
        [InverseProperty("TransferencesForAssistance")]
        public virtual Driver DriverAssistance { get; set; }
        public virtual TypeOfTanker TypesOfTanker { get; set; }
        public virtual Tanker Tanker { get; set; }
        public virtual Consumer Consumer { get; set; }

    }
فکر کنم مشکل از این کلاس زیر باشه:
 public class TransferenceConfig : EntityTypeConfiguration<Transference>
    {
        public TransferenceConfig()
        {
            // one-to-many
            this.HasRequired(x => x.Consumer)
                .WithMany(x => x.Transferences);

                // one-to-many
            this.HasRequired(x => x.TypesOfTanker)
                .WithMany(x => x.Transferences);

            // one-to-many
            this.HasRequired(x => x.Tanker)
                .WithMany(x => x.Transferences);

            // one-to-many
            this.HasRequired(x => x.Driver)
                .WithMany(x => x.Transferences);

            // one-to-many
            this.HasRequired(x => x.DriverAssistance)
                .WithMany(x => x.Transferences);

        }
    }
مطالب
Data Contracts and Circular References
تشریح مسئله : در DataContractSerializer  قابلیتی به عنوان سریالایز کردن object‌ها به صورت درختی وجود داردکه اصطلاحا به اون  Circular References گفته می‌شود در این پست قصد دارم روش پیاده سازی، به همراه مزایای استفاده از این روش رو توضیح بدم.
نکته : آشنایی با مفاهیم اولیه WCF برای درک بهتر مطالب الزامی است.
در ابتدا لازم است تا مدل برنامه را تعریف کنیم. ابتدا یک پروژه از نوع WCF Service Application ایجاد کنید و مدل زیر را بسازید.

#Employee
   [DataContract]
    public class Employee
    {
        [DataMember]
        public string Name { get; set; }

        [DataMember]
        public Employee Manager { get; set; }
    }
#Department
[DataContract]
    public class Department
    {
        [DataMember]
        public string DeptName { get; set; }

        [DataMember]
        public List<Employee> Staff { get; set; }
    }
در مدل Employee یک خاصیت از نوع خود کلاس Employee وجود دارد که برای پیاده سازی مدل به صورت درختی است. در مدل Department هم لیستی از کارمندان دپارتمان را ذخیره می‌کنیم و قصد داریم این مدل رو از سمت سرور به کلاینت انتقال دهیم و نوع سریالایز کردن WCF رو در این مورد مشاهده کنیم. ابتدا سرویس و Contract مربوطه را می‌نویسیم.

#Contract
  [ServiceContract]
    public interface IDepartmentService
    {
        [OperationContract]
        Department GetOneDepartment();
    }
#Service
public class DepartmentService : IDepartmentService
    {
        public Department GetOneDepartment()
        {
            List<Employee> listOfEmployees = new List<Employee>();

              var masoud = new Employee() { Name = "Masoud" };
              var saeed = new Employee() { Name = "Saeed", Manager = masoud };
              var peyman = new Employee() { Name = "Peyman", Manager = saeed };
              var mostafa = new Employee() { Name = "Mostafa", Manager = saeed };

              return new Department() { DeptName = "IT", Staff = new List<Employee>() { masoud, saeed, peyman, mostafa } };
        }
    }
همانطور که در سرویس بالا مشخص است لیستی از کارمندان ساخته شده که خود این لیست به صورت درختی است و بعضی از کارمندان به عنوان مدیر کارمند دیگر تعیین شد است. حال برای دریافت اطلاعات سمت کلاینت یک پروژه از نوع Console ایجاد کنید و از روش AddServiceReference سرویس مورد نظر را اضافه کنید و کد‌های زیر را در کلاس Program کپی کنید.
 class Program
    {
        static void Main( string[] args )
        {
            DepartmentServiceClient client = new DepartmentServiceClient();

            var result = client.GetOneDepartment();
            WriteDataToFile( result );

            Console.ReadKey();
        }

        private static void WriteDataToFile( Department data )
        {
            DataContractSerializer dcs = new DataContractSerializer( typeof( Department ) );
            var ms = new MemoryStream();
            dcs.WriteObject( ms, data );
            ms.Seek( 0, SeekOrigin.Begin );
            var sr = new StreamReader( ms );
            var xml = sr.ReadToEnd();
            string filePath = @"d:\\data.xml";
            if ( !File.Exists( filePath ) )
            {
                File.Create( filePath );
            }         
                using ( TextWriter writer = new StreamWriter( filePath ) )
                {
                    writer.Write( xml );
                }                      
        }
یک متد به نام WriteDataToFile نوشتم که اطلاعات Department رو به فرمت Xml در فایل ذخیره می‌کند. بعد از اجرای برنامه خروجی مورد نظر در فایل Xml به صورت زیر است.
<Department xmlns="http://schemas.datacontract.org/2004/07/Service" xmlns:i="http://www.w3.org/2001/XMLSchema-instance">
  <Name>IT</Name>
  <Staff>
    <Employee>
      <Manager i:nil="true"/>
      <Name>Masoud</Name>
    </Employee>
    <Employee>
      <Manager>
        <Manager i:nil="true"/>
        <Name>Masoud</Name>
      </Manager>
      <Name>Saeed</Name>
    </Employee>
    <Employee>
      <Manager>
        <Manager>
          <Manager i:nil="true"/>
          <Name>Masoud</Name>
        </Manager>
        <Name>Saeed</Name>
      </Manager>
      <Name>Peyman</Name>
    </Employee>
    <Employee>
      <Manager>
        <Manager>
          <Manager i:nil="true"/>
          <Name>Masoud</Name>
        </Manager>
        <Name>Saeed</Name>
      </Manager>
      <Name>Mostafa</Name>
    </Employee>
  </Staff>
</Department>
در فایل بالا مشاهده می‌کنید که تعداد تکرار Masoud به اندازه تعداد استفاده اون در Department است. در این قسمت قصد داریم که از Circular Referencing موجود در DataContractSerializer استفاده کنیم. برای این کار کافیست از خاصیت IsReference موجود در DataContract استفاده کنیم. پس مدل Employee به صورت زیر تغییر میباید:
   [DataContract( IsReference = true )]
    public class Employee
    {
        [DataMember]
        public string Name { get; set; }

        [DataMember]
        public Employee Manager { get; set; }
    }
پروژه رو دوباره Run کنید و فایل xml ساخته شده به صورت زیر تغییر می‌کند.
<Department xmlns="http://schemas.datacontract.org/2004/07/Service" xmlns:i="http://www.w3.org/2001/XMLSchema-instance">
  <Name>IT</Name>
  <Staff>
    <Employee z:Id="i1" xmlns:z="http://schemas.microsoft.com/2003/10/Serialization/">
      <Manager i:nil="true"/>
      <Name>Masoud</Name>
    </Employee>
    <Employee z:Id="i2" xmlns:z="http://schemas.microsoft.com/2003/10/Serialization/">
      <Manager z:Ref="i1"/>
      <Name>Saeed</Name>
    </Employee>
    <Employee z:Id="i3" xmlns:z="http://schemas.microsoft.com/2003/10/Serialization/">
      <Manager z:Ref="i2"/>
      <Name>Peyman</Name>
    </Employee>
    <Employee z:Id="i4" xmlns:z="http://schemas.microsoft.com/2003/10/Serialization/">
      <Manager z:Ref="i2"/>
      <Name>Mostafa</Name>
    </Employee>
  </Staff>
</Department>
کاملا واضح است که تعداد Masoud به عنوان Employee فقط یک بار است و از z:ref برای ارتباط بین Object‌ها استفاده می‌شود. در این روش فقط یک بار هر object سریالاز می‌شود و هر جا که نیاز به استفاده از object مربوطه باشد فقط یک ارجاع به آن خواهد شد.
مزایا :استفاده از این روش در هنگام عمل سریالایز داده‌های زیاد و زمانی که  تعداد Object‌های موجود در ObjectGraph  زیاد باشد باعث افزایش کارایی و سرعت انجام عملیات سریالایز می‌شود.