مسیرراه‌ها
React 16x
پیش نیاز ها
کامپوننت ها
ترکیب کامپوننت ها
طراحی یک گرید
مسیریابی 
کار با فرم ها
ارتباط با سرور
احراز هویت و اعتبارسنجی کاربران 
React Hooks  
توزیع برنامه

مدیریت پیشرفته‌ی حالت در React با Redux و Mobx   

       Redux
       MobX  

مطالب تکمیلی 
    نظرات مطالب
    رمزنگاری JWT و افزایش امنیت آن در ASP.NET Core
    یک نکته‌ی تکمیلی: نکته امنیتی در هنگام استفاده از توکن ها

    هنگامیکه کاربری اطلاعات خود را ویرایش میکند، معمولا یک ویوو مدل را از ورودی دریافت میکنیم و داده‌های آن کاربر را بر اساس آی‌دی که درون ویوو مدل ارسال شده‌است، ویرایش میکنیم. اما در این حالت کاربر میتواند با تغییر آیدی ارسالی در ویوو مدل، اطلاعات سایر کاربران را نیز تغییر دهد! برای جلوگیری از این کار میتوان به روش زیر عمل کرد. ابتدا در هنگام ساخت توکن، آیدی کاربر و یک امضا Signature (میتوان از یک GUID استفاده کرد) را در توکن نگهداری میکنیم و سپس توکن را به روش JWE رمزنگاری میکنیم تا اطلاعات توکن قابل مشاهده نباشد و در هنگام اعتبارسنجی توکن، امضای کاربر را با امضای درون توکن مقایسه میکنیم. اگر با هم تفاوت داشته باشند، به معنای آن است که توکن منقضی شده و قابل استفاده نیست. در هربار که کاربر درخواست توکنی را میدهد، باید امضای کاربر را تغییر داده و یک امضای جدید را برای او ثبت کنیم.
    سپس در هنگام اجرای اکشن مورد نظر، آیدی درون توکن و آیدی ارسالی جهت ویرایش اطلاعات را بررسی خواهیم کرد. اگر این دو با هم همخوانی نداشته باشند، اجازه‌ی اجرای اکشن مورد نظر را به او نخواهیم داد و سپس امضای کاربر را تغییر میدهیم تا توکن منقضی شود و یک استثناء را صادر میکنیم.
    برای پیاده سازی، ابتدا یک کلاس را برای بررسی مشخصات توکن و آیدی ارسالی میسازیم.
        public interface IJwtService
        {
            Task CheckId(int id, ClaimsPrincipal claimsPrincipal);
        }
        public class JwtService : IJwtService
        {
            private readonly IUserService _userService;
    
            public JwtService(IUserService userService)
            {
                _userService = userService;
            }
    
            public async Task CheckId(int id, ClaimsPrincipal claimsPrincipal)
            {
                var jwtId = Convert.ToInt32(claimsPrincipal.Identity.FindFirstValue(ClaimTypes.NameIdentifier));
                if (jwtId != id)
                {
                    var user = _userService.GetById(jwtId);
                    user.SecurityStamp = Guid.NewGuid();
                    await _userService.UpdateAsync(user);
                    throw new Exception("You are unauthorized to access this resource.");
                }
            }
        }
    و نحوه‌ی استفاده‌ی از آن در کنترلر و اکشن مورد نظر:
    private readonly IJwtService _jwtService;
    private readonly IUserService _userService;
    public UserController(IJwtService jwtService, IUserService userService)
            {
                _jwtService = jwtService;
                _userService = userService;
            }
    
            [HttpPut("Update")]
            public async Task<IActionResult> Update(UserEditViewModel editViewModel, CancellationToken cancellationToken)
            {
                _jwtService.CheckId(editViewModel.Id, HttpContext.User);
                await _userService.Update(editViewModel, cancellationToken);
            }
    مطالب
    تبدیل html به pdf با کیفیت بالا

    کتابخانه iTextSharp دارای کلاسی است به نام HTMLWorker که کار تبدیل عناصر HTML را به عناصر متناظر خودش، انجام می‌دهد. این کلاس در حال حاضر منسوخ شده درنظر گرفته می‌شود (اینطور توسط نویسندگان آن اعلام شده) و دیگر توسعه نخواهد یافت. بنابراین اگر از HTMLWorker استفاده می‌کنید با یک کلاس قدیمی که دارای HTML Parser ایی بسیار بدوی است طرف هستید و در کل برای تبدیل محتوای HTML ایی با ساختار بسیار ساده بد نیست؛ اما انتظار زیادی از آن نداشته باشید.
    جایگزین کلاس HTMLWorker در این کتابخانه در حال حاضر کتابخانه itextsharp.xmlworker است، که به صورت یک افزونه در کنار کتابخانه اصلی در حال توسعه می‌باشد. مشکل اصلی این کتابخانه، عدم پشتیبانی از UTF8 و راست به چپ است. بنابراین حداقل به درد کار ما نمی‌خورد.

    راه حل بسیار بهتری برای موضوع اصلی بحث ما وجود دارد و آن هم استفاده از موتور WebKit (همان موتوری که برای مثال در Apple Safari استفاده می‌شود) برای HTML parsing و سپس تبدیل نتیجه نهایی به PDF است. پروژه‌ای که این مقصود را میسر کرده، wkhtmltopdf نام دارد.
    توسط آن به کمک موتور WebKit، کار HTML Parsing انجام شده و سپس برای تبدیل عناصر نهایی به PDF از امکانات کتابخانه‌ای به نام QT استفاده می‌شود. کیفیت نهایی آن کپی مطابق اصل HTML قابل مشاهده در یک مرورگر است و با یونیکد و زبان فارسی هم مشکلی ندارد.

    برای استفاده از این کتابخانه‌ی native در دات نت، شخصی پروژه‌ای را ایجاد کرده است به نام WkHtmlToXSharp که محصور کننده‌ی wkhtmltopdf می‌باشد. در ادامه به نحوه استفاده از آن خواهیم پرداخت:

    الف) دریافت پروژه WkHtmlToXSharp
    پروژه WkHtmlToXSharp را از آدرس زیر می‌توانید دریافت کنید.

     این پروژه به همراه فایل‌های کامپایل شده نهایی wkhtmltopdf نیز می‌باشد و حجمی حدود 40 مگ دارد. به علاوه فعلا نسخه 32 بیتی آن در دسترس است. بنابراین باید دقت داشت که نباید تنظیمات پروژه دات نت خود را بر روی Any CPU قرار دهیم، زیرا در این حالت برنامه شما در یک سیستم 64 بیتی بلافاصله کرش خواهد کرد. تنظیمات target platform پروژه دات نتی ما حتما باید بر روی X86 تنظیم شود.

    ب) پس از دریافت این پروژه و افزودن ارجاعی به اسمبلی WkHtmlToXSharp.dll، استفاده از آن به نحو زیر می‌باشد:

    using System.IO;
    using WkHtmlToXSharp;
    using System;
    
    namespace Test2
    {
        public class WkHtmlToXSharpTest
        {
            public static void ConvertHtmlStringToPdfTest()
            {
                using (var wk = new MultiplexingConverter())
                {
                    wk.Begin += (s, e) => Console.WriteLine("Conversion begin, phase count: {0}", e.Value);
                    wk.Error += (s, e) => Console.WriteLine(e.Value);
                    wk.Warning += (s, e) => Console.WriteLine(e.Value);
                    wk.PhaseChanged += (s, e) => Console.WriteLine("PhaseChanged: {0} - {1}", e.Value, e.Value2);
                    wk.ProgressChanged += (s, e) => Console.WriteLine("ProgressChanged: {0} - {1}", e.Value, e.Value2);
                    wk.Finished += (s, e) => Console.WriteLine("Finished: {0}", e.Value ? "success" : "failed!");
    
                    wk.GlobalSettings.Margin.Top = "0cm";
                    wk.GlobalSettings.Margin.Bottom = "0cm";
                    wk.GlobalSettings.Margin.Left = "0cm";
                    wk.GlobalSettings.Margin.Right = "0cm";
    
                    wk.ObjectSettings.Web.EnablePlugins = false;
                    wk.ObjectSettings.Web.EnableJavascript = false;
                    wk.ObjectSettings.Load.Proxy = "none";
    
                    var htmlString = File.ReadAllText(@"c:\page.xhtml");
                    var tmp = wk.Convert(htmlString);
    
                    File.WriteAllBytes(@"tst.pdf", tmp);
                }
            }
        }
    }

    کار با وهله سازی از کلاس MultiplexingConverter شروع می‌شود. اگر علاقمند باشید که درصد پیشرفت کار به همراه خطاهای احتمالی پردازشی را ملاحظه کنید می‌توان از رخدادگردان‌هایی مانند ProgressChanged و Error استفاده نمائید که نمونه‌ای از آن در کد فوق بکارگرفته شده است.
    تبدیل HTML به PDF آنچنان تنظیمات خاصی ندارد زیرا فرض بر این است که قرار است از همان تنظیمات اصلی HTML مورد نظر استفاده گردد. اما اگر نیاز به تنظیمات بیشتری وجود داشت، برای مثال به کمک GlobalSettings آن می‌توان حاشیه‌های صفحات فایل نهایی تولیدی را تنظیم کرد.
    موتور WebKit با توجه به اینکه موتور یک مرورگر است، امکان پردازش جاوا اسکریپت را هم دارد. بنابراین اگر قصد استفاده از آن‌را نداشتید می‌توان خاصیت ObjectSettings.Web.EnableJavascript را به false مقدار دهی کرد.
    کار اصلی، در متد Convert انجام می‌شود. در اینجا می‌توان یک رشته را که حاوی فایل HTML مورد نظر است به آن ارسال کرد و نتیجه نهایی، آرایه‌ای از بایت‌ها، حاوی فایل باینری PDF تولیدی است.
    روش دیگر استفاده از این کتابخانه، مقدار دهی wk.ObjectSettings.Page می‌باشد. در اینجا می‌توان Url یک صفحه اینترنتی را مشخص ساخت. در این حالت دیگر نیازی نیست تا به متد Convert پارامتری را ارسال کرد. می‌توان از overload بدون پارامتر آن استفاده نمود.

    یک نکته:
    اگر می‌خواهید زبان فارسی را توسط این کتابخانه به درستی پردازش کنید، نیاز است حتما یک سطر زیر را به header فایل html خود اضافه نمائید:

    <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />

     
    پروژه‌ها
    فروشگاه اینترنتی (الکترونیک) با معماری سه لایه
    دانلود پروژه فروشگاه اینترنتی با معماری سه لایه

    این پروژه به کمک امکانات شیرین C#‎.NET, ASP.NET,jQuery در محیط Visual Studio 2010 طراحی و پیاده سازی شده است.
    ممکن است در این برنامه به اشکالاتی برخورد کنید چون یک پروژه کاری بوده که در طی یک کلاس درس تکمیل شده و هم اکنون در اختیار شما قرار می‌گیرد لطفا در صورت بروز مشکل از طریق بازخورد مطرح فرمایید و از ارسال پیام خصوصی خودداری نمایید.
    پروژه با معماری سه لایه طراحی و پیاده سازی شده است
     که شامل لایه DAL برای ارتباط با بانک
     BLL برای اعمال قوانین تجاری
    و لایه نمایش آن که ASP.NET استفاده شده است
     بانک اطلاعاتی برنامه با SQL SERVER 2008 می‌باشد که می‌توانید آن را تغییر داده و از بانک اطلاعاتی دلخواه خود استفاده کنید.
    فایل اسکریپت نیز در کنار پروژه برای نسخه‌های دیگر SQL قرار گرفته است.
     لازم به ذکر است که برنامه ProviderBase نیز می‌باشد.
    یعنی شما میتوانید بانک دلخواه خود را استفاده نموده و بدون کوچکترین تغییری در برنامه سایت از آن استفاده نمایید فقط کافی است که در فایل web.config مشخصات آن Provider را ثبت نموده و کدهای مربوط به Provider خود را بنویسید.
    برای استفاده ابتدا دیتابیس را در SQL خود Attach کرده و در فایل web.config در قسمت تنظیمات ConnectionString تنظیمات سرور و نام دیتابیس را وارد نمایید.

    این پروژه دارای امکانات
    • فروشگاه اینترنتی با قابلیت گروه بندی محصولات
    • ذخیره تصاویر در دیتابیس و بازیابی با یک Handler
    • سرویس اعلانات سایت
    • درج کلمات کلیدی هر صفحه به صورت اتوماتیک و با توجه به محتوای صفحه
    • استفاده از قابلیت Profile برای کاربران
    • کارت خرید و ذخیره آن در پروفایل کاربر
    • ثبت سوابق خرید کاربر
    • ثبت فیش‌های پرداختی کاربر
    • ثبت نام کاربران جدید در سایت
    • ثبت، نمایش و مدیریت سخنان قصار در نرم افزار
    • امکان Cache کردن اطلاعات دریافتی از بانک و مدیریت Cache 
    • پیاده سازی تمامی کویری‌های بان با Store Procedure در SQL Server
    • استفاده از سرویس Membership
    • خواندن تنظیمات برنامه از فایل Web.Config و معادل‌سازی آن با کلاس‌های مربوطه
    • و ...
    البته شاید کامل نباشد اما به عنوان یک منبع آموزشی در زمینه برنامه نویسی سه لایه و بعضی ایده‌های خاص ممکن است برای دوستان مبتدی و متوسطه در زمینه طراحی سایت مفید واقع شود.
    در صورت لزوم می‌توانید به لینک اصلی این پروژه در آدرس سایت ما مراجعه نمایید.
    نام کاربری و کلمه عبور مدیریت سایت:
    UserName: Admin
    Password: 1234567@
    ----------
    UserName: Saber_Fatholahi
    Password: 1234567@

    مطالب دوره‌ها
    تزریق وابستگی‌ها در حالتی‌که از یک اینترفیس چندین کلاس مشتق شده‌اند
    حین کار با ASP.NET Identity به اینترفیسی به نام IIdentityMessageService شبیه به اینترفیس ذیل می‌رسیم:
    namespace SameInterfaceDifferentClasses.Services.Contracts
    {
      public interface IMessageService
      {
       void Send(string message);
      }
    }
    فرض کنید از آن دو پیاده سازی در برنامه برای ارسال پیام‌ها توسط ایمیل و همچنین توسط SMS، وجود دارد:
    public class EmailService : IMessageService
    {
      public void Send(string message)
      {
       // ...
      }
    }
    
    public class SmsService : IMessageService
    {
      public void Send(string message)
      {
       //todo: ...
      }
    }
    اکنون کلاس مدیریت کاربران برنامه، در سازنده‌ی خود نیاز به دو وهله، از این سرویس‌های متفاوت، اما در اصل مشتق شده‌ی از یک اینترفیس دارد:
    public interface IUsersManagerService
    {
      void ValidateUserByEmail(int id);
    }
    
    public class UsersManagerService : IUsersManagerService
    {
      private readonly IMessageService _emailService;
      private readonly IMessageService _smsService;
     
      public UsersManagerService(IMessageService emailService, IMessageService smsService)
      {
       _emailService = emailService;
       _smsService = smsService;
      }
     
      public void ValidateUserByEmail(int id)
      {
       _emailService.Send("Validated.");
      }
    }
    در این حالت صرف تنظیمات ابتدایی انتساب یک اینترفیس، به یک کلاس مشخص کافی نیست:
    ioc.For<IMessageService>().Use<SmsService>();
    ioc.For<IMessageService>().Use<EmailService>();
    از این جهت که در سازنده‌ی کلاس UsersManagerService دقیقا مشخص نیست، پارامتر اول باید سرویس SMS باشد یا ایمیل؟
    برای حل این مشکل می‌توان به نحو ذیل عمل کرد:
    public static class SmObjectFactory
    {
      private static readonly Lazy<Container> _containerBuilder =
       new Lazy<Container>(defaultContainer, LazyThreadSafetyMode.ExecutionAndPublication);
     
      public static IContainer Container
      {
       get { return _containerBuilder.Value; }
      }
     
      private static Container defaultContainer()
      {
       return new Container(ioc =>
       {
        // map same interface to different concrete classes
        ioc.For<IMessageService>().Use<SmsService>();
        ioc.For<IMessageService>().Use<EmailService>();
     
        ioc.For<IUsersManagerService>().Use<UsersManagerService>()
         .Ctor<IMessageService>("smsService").Is<SmsService>()
         .Ctor<IMessageService>("emailService").Is<EmailService>();
       });
      }
    }
    در اینجا توسط متد Ctor که مخفف Constructor یا سازنده‌ی کلاس است، مشخص می‌کنیم که اگر به پارامتر smsService رسیدی، از کلاس SmsService استفاده کن و در مورد کلاس سرویس ایمیل نیز به همین ترتیب. اینبار اگر برنامه را اجرا کنیم:
     var usersManagerService = SmObjectFactory.Container.GetInstance<IUsersManagerService>();
    usersManagerService.ValidateUserByEmail(id: 1);


    همانطور که در تصویر مشخص است، هر کدام از پارامترها، توسط کلاس‌های متفاوتی مقدار دهی شده‌اند؛ هرچند از یک اینترفیس مشخص استفاده می‌کنند.

    کدهای کامل این مثال را از اینجا می‌توانید دریافت کنید:
    Dependency-Injection-Samples/DI09 
     
    مطالب دوره‌ها
    دسترسی سریع به مقادیر خواص توسط Reflection.Emit
    اگر پروژه‌های چندسال اخیر را مرور کرده باشید خصوصا در زمینه ORMها و یا Serializerها و کلا مواردی که با Reflection زیاد سروکار دارند، تعدادی از آن‌ها پیشوند fast را یدک می‌کشند و با ارائه نمودارهایی نشان می‌دهند که سرعت عملیات و کتابخانه‌های آن‌ها چندین برابر کتابخانه‌های معمولی است و ... سؤال مهم اینجا است که رمز و راز این‌ها چیست؟
    فرض کنید تعاریف کلاس User به صورت زیر است:
    public class User
    {
         public int Id { set; get; }
    }
    همانطور که در قسمت‌های قبل نیز عنوان شد، خاصیت Id در کدهای IL نهایی به صورت متدهای get_Id و set_Id ظاهر می‌شوند.
    حال اگر یک متد پویا ایجاد کنیم که بجای هر بار Reflection جهت دریافت مقدار Id، خود متد get_Id را مستقیما صدا بزند، چه خواهد شد؟
    پیاده سازی این نکته را در ادامه ملاحظه می‌کنید:
    using System;
    using System.Collections.Generic;
    using System.Diagnostics;
    using System.Reflection;
    using System.Reflection.Emit;
    
    namespace FastReflectionTests
    {
        /// <summary>
        /// کلاسی برای اندازه گیری زمان اجرای عملیات
        /// </summary>
        public class Benchmark : IDisposable
        {
            Stopwatch _watch;
            string _name;
    
            public static Benchmark Start(string name)
            {
                return new Benchmark(name);
            }
    
            private Benchmark(string name)
            {
                _name = name;
                _watch = new Stopwatch();
                _watch.Start();
            }
    
            public void Dispose()
            {
                _watch.Stop();
                Console.WriteLine("{0} Total seconds: {1}"
                                   , _name, _watch.Elapsed.TotalSeconds);
            }
        }
    
        public class User
        {
            public int Id { set; get; }
        }
    
        class Program
        {
            public static Func<object, object> GetFastGetterFunc(string propertyName, Type ownerType)
            {
                var propertyInfo = ownerType.GetProperty(propertyName, BindingFlags.Instance | BindingFlags.Public);
    
                if (propertyInfo == null)
                    return null;
                
                var getter = ownerType.GetMethod("get_" + propertyInfo.Name,
                                                 BindingFlags.Instance | BindingFlags.Public | BindingFlags.FlattenHierarchy);
                if (getter == null)
                    return null;
    
                var dynamicGetterMethod = new DynamicMethod(
                                                    name: "_",
                                                    returnType: typeof(object),
                                                    parameterTypes: new[] { typeof(object) },
                                                    owner: propertyInfo.DeclaringType,
                                                    skipVisibility: true);
                var il = dynamicGetterMethod.GetILGenerator();
    
                il.Emit(OpCodes.Ldarg_0); // Load input to stack
                il.Emit(OpCodes.Castclass, propertyInfo.DeclaringType); // Cast to source type
                // نکته مهم در اینجا فراخوانی نهایی متد گت بدون استفاده از ریفلکشن است
                il.Emit(OpCodes.Callvirt, getter); //calls its get method
    
                if (propertyInfo.PropertyType.IsValueType)
                    il.Emit(OpCodes.Box, propertyInfo.PropertyType);//box
    
                il.Emit(OpCodes.Ret);
    
                return (Func<object, object>)dynamicGetterMethod.CreateDelegate(typeof(Func<object, object>));
            }
    
    
            static void Main(string[] args)
            {
                //تهیه لیستی از داده‌ها جهت آزمایش
                var list = new List<User>();
                for (int i = 0; i < 1000000; i++)
                {
                    list.Add(new User { Id = i });
                }
    
                // دسترسی به اطلاعات لیست به صورت متداول از طریق ریفلکشن معمولی
                var idProperty = typeof(User).GetProperty("Id");
                using (Benchmark.Start("Normal reflection"))
                {
                    foreach (var item in list)
                    {
                        var id = idProperty.GetValue(item, null);
                    }
                }
    
                // دسترسی از طریق روش سریع دستیابی به اطلاعات خواص
                var fastIdProperty = GetFastGetterFunc("Id", typeof(User));
                using (Benchmark.Start("Fast Property"))
                {
                    foreach (var item in list)
                    {
                        var id = fastIdProperty(item);
                    }
                }
            }
        }
    }
    توضیحات:
    از کلاس Benchmark برای نمایش زمان انجام عملیات دریافت مقادیر Id از یک لیست، به دو روش Reflection متداول و روش صدا زدن مستقیم متد get_Id استفاده شده است.
    در متد GetFastGetterFunc، ابتدا به متد get_Id خاصیت Id دسترسی پیدا خواهیم کرد. سپس یک متد پویا ایجاد می‌کنیم تا این get_Id را مستقیما صدا بزند. حاصل کار را به صورت یک delegate بازگشت می‌دهیم. شاید عنوان کنید که در اینجا هم حداقل در ابتدای کار متد، یک Reflection اولیه وجود دارد. پاسخ این است که مهم نیست؛ چون در یک برنامه واقعی، تهیه delegates در زمان آغاز برنامه انجام شده و حاصل کش می‌شود. بنابراین در زمان استفاده نهایی، به هیچ عنوان با سربار Reflection مواجه نخواهیم بود.

    خروجی آزمایش فوق بر روی سیستم معمولی من به صورت زیر است:
     Normal reflection Total seconds: 2.0054177
    Fast Property Total seconds: 0.0552056
    بله. نتیجه روش GetFastGetterFunc واقعا سریع و باور نکردنی است!


    چند پروژه که از این روش استفاده می‌کنند
    Dapper
    AutoMapper
    fastJson

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


    ماخذ اصلی
    این کشف و استفاده خاص، از اینجا شروع و عمومیت یافته است و پایه تمام کتابخانه‌هایی است که پیشوند fast را به خود داده‌اند:
    2000% faster using dynamic method calls
    مطالب
    حذف اعراب از حروف و کلمات
    برای بهبود قسمت ثبت نام در یک سایت بهتر است بین «وحید» و «وَحید» تفاوتی قائل نشد. این مورد ممکن است خصوصا حین ارسال پیام‌های خصوصی در آینده جهت تشخیص افراد مشکل ساز شود. همچنین در تهیه slug برای نمایش در Urlها نیز باید اعراب را حذف کرد. منظور از slug، عنوان کوتاهی است که در انتهای یک آدرس ممکن است ذکر شود.
     http://www.site.com/post/12/slug

    سؤال: چگونه می‌توان اعراب را از متون فارسی یا عربی حذف کرد؟

    متد انجام اینکار را در ذیل مشاهده می‌کنید:
    using System.Globalization;
    using System.Text;
    
        static string RemoveDiacritics(string text)
        {
            var normalizedString = text.Normalize(NormalizationForm.FormD);
            var stringBuilder = new StringBuilder();
    
            foreach (var c in normalizedString)
            {
                var unicodeCategory = CharUnicodeInfo.GetUnicodeCategory(c);
                if (unicodeCategory != UnicodeCategory.NonSpacingMark)
                {
                    stringBuilder.Append(c);
                }
            }
    
            return stringBuilder.ToString().Normalize(NormalizationForm.FormC);
        }

    توضیحات

    متد Normalize با پارامتر NormalizationForm.FormD، سبب می‌شود تا کاراکترها به گلیف‌های اصلی تشکیل دهنده‌ی آن‌ها تجزیه شوند. به عبارتی، حروف از اعراب جدا خواهند شد. در ادامه این کاراکترها اسکن شده و صرفا مواردی که حروف پایه را تشکیل می‌دهند، جمع آوری و بازگشت داده می‌شوند. حالت NormalizationForm.FormC که در انتها بکار گرفته شده، برعکس است.
    در یونیکد یک حرف می‌تواند از یک یا چند code point تشکیل شود. در حالت FormC، هر حرف با اعراب آن یک code point را تشکیل می‌دهند. در حالت FormD، حرف با اعراب آن دو code point را تشکیل خواهند داد. به همین جهت در ابتدای کار، رشته تبدیل به حالت D شده تا بتوان اعراب آن‌را مجزای از حروف پایه حذف کرد.
    البته اعراب در اینجا به اعراب عربی ختم نمی‌شود. یک سری حروف اروپایی مانند  "ä" ،"ö" و "ü" را نیز شامل می‌شود.
    مطالب
    احراز هویت و اعتبارسنجی کاربران در برنامه‌های Angular - قسمت چهارم - به روز رسانی خودکار توکن‌ها
    در قسمت قبل، عملیات ورود به سیستم و خروج از آن‌را تکمیل کردیم. پس از ورود شخص به سیستم، هربار انقضای توکن دسترسی او، سبب خواهد شد تا وقفه‌ای در کار جاری کاربر، جهت لاگین مجدد صورت گیرد. برای این منظور، قسمتی از مطالب «اعتبارسنجی مبتنی بر JWT در ASP.NET Core 2.0 بدون استفاده از سیستم Identity»  و یا «پیاده سازی JSON Web Token با ASP.NET Web API 2.x» به تولید refresh_token در سمت سرور اختصاص دارد که از نتیجه‌ی آن در اینجا استفاده خواهیم کرد. عملیات به روز رسانی خودکار توکن دسترسی (access_token در اینجا) سبب خواهد شد تا کاربر پس از انقضای آن، نیازی به لاگین دستی مجدد نداشته باشد. این به روز رسانی در پشت صحنه و به صورت خودکار صورت می‌گیرد. refresh_token یک guid است که به سمت سرور ارسال می‌شود و برنامه‌ی سمت سرور، پس از تائید آن (بررسی صحت وجود آن در بانک اطلاعاتی و سپس واکشی اطلاعات کاربر متناظر با آن)، یک access_token جدید را صادر می‌کند.


    ایجاد یک تایمر برای مدیریت دریافت و به روز رسانی توکن دسترسی

    در مطلب «ایجاد تایمرها در برنامه‌های Angular» با روش کار با تایمرهای reactive آشنا شدیم. در اینجا قصد داریم از این امکانات جهت پیاده سازی به روز کننده‌ی خودکار access_token استفاده کنیم. در مطلب «احراز هویت و اعتبارسنجی کاربران در برنامه‌های Angular - قسمت دوم - سرویس اعتبارسنجی»، زمان انقضای توکن را به کمک کتابخانه‌ی jwt-decode، از آن استخراج کردیم:
      getAccessTokenExpirationDateUtc(): Date {
        const decoded = this.getDecodedAccessToken();
        if (decoded.exp === undefined) {
          return null;
        }
        const date = new Date(0); // The 0 sets the date to the epoch
        date.setUTCSeconds(decoded.exp);
        return date;
      }
    اکنون از این زمان در جهت تعریف یک تایمر خود متوقف شونده استفاده می‌کنیم:
      private refreshTokenSubscription: Subscription;
    
      scheduleRefreshToken() {
        if (!this.isLoggedIn()) {
          return;
        }
    
        this.unscheduleRefreshToken();
    
        const expiresAtUtc = this.getAccessTokenExpirationDateUtc().valueOf();
        const nowUtc = new Date().valueOf();
        const initialDelay = Math.max(1, expiresAtUtc - nowUtc);
        console.log("Initial scheduleRefreshToken Delay(ms)", initialDelay);
        const timerSource$ = Observable.timer(initialDelay);
        this.refreshTokenSubscription = timerSource$.subscribe(() => {
          this.refreshToken();      
        });
      }
    
      unscheduleRefreshToken() {
        if (this.refreshTokenSubscription) {
          this.refreshTokenSubscription.unsubscribe();
        }
      }
    کار متد scheduleRefreshToken، شروع تایمر درخواست توکن جدید است.
    - در ابتدا بررسی می‌کنیم که آیا کاربر لاگین کرده‌است یا خیر؟ آیا اصلا دارای توکنی هست یا خیر؟ اگر خیر، نیازی به شروع این تایمر نیست.
    -  سپس تایمر قبلی را در صورت وجود، خاتمه می‌دهیم.
    - در مرحله‌ی بعد، کار محاسبه‌ی میزان زمان تاخیر شروع تایمر Observable.timer را انجام می‌دهیم. پیشتر زمان انقضای توکن موجود را استخراج کرده‌ایم. اکنون این زمان را از زمان جاری سیستم برحسب UTC کسر می‌کنیم. مقدار حاصل، initialDelay این تایمر خواهد بود. یعنی این تایمر به مدت initialDelay صبر خواهد کرد و سپس تنها یکبار اجرا می‌شود. پس از اجرا، ابتدا متد refreshToken ذیل را فراخوانی می‌کند تا توکن جدیدی را دریافت کند.

    در متد unscheduleRefreshToken کار لغو تایمر جاری در صورت وجود انجام می‌شود.

    متد درخواست یک توکن جدید بر اساس refreshToken موجود نیز به صورت ذیل است:
      refreshToken() {
        const headers = new HttpHeaders({ "Content-Type": "application/json" });
        const model = { refreshToken: this.getRawAuthToken(AuthTokenType.RefreshToken) };
        return this.http
          .post(`${this.appConfig.apiEndpoint}/${this.appConfig.refreshTokenPath}`, model, { headers: headers })
          .finally(() => {
            this.scheduleRefreshToken();
          })
          .map(response => response || {})
          .catch((error: HttpErrorResponse) => Observable.throw(error))
          .subscribe(result => {
            console.log("RefreshToken Result", result);
            this.setLoginSession(result);
          });
      }
    در اینجا هرزمانیکه تایمر اجرا شود، این متد فراخوانی شده و مقدار refreshToken فعلی را به سمت سرور ارسال می‌کند. سپس سرور این مقدار را بررسی کرده و در صورت تعیین اعتبار، یک access_token و refresh_token جدید را به سمت کلاینت ارسال می‌کند که نتیجه‌ی آن به متد setLoginSession جهت ذخیره سازی ارسال خواهد شد.
    در آخر چون این تایمر، خود متوقف شونده‌است (متد Observable.timer بدون پارامتر دوم آن فراخوانی شده‌است)، یکبار دیگر کار زمانبندی دریافت توکن جدید بعدی را در متد finally انجام می‌دهیم (متد scheduleRefreshToken را مجددا فراخوانی می‌کنیم).


    تغییرات مورد نیاز در سرویس Auth جهت زمانبندی دریافت توکن دسترسی جدید

    تا اینجا متدهای مورد نیاز شروع زمانبندی دریافت توکن جدید، خاتمه‌ی زمانبندی و دریافت و به روز رسانی توکن جدید را پیاده سازی کردیم. محل قرارگیری و فراخوانی این متدها در سرویس Auth، به صورت ذیل هستند:
    الف) در سازنده‌ی کلاس:
      constructor(
        @Inject(APP_CONFIG) private appConfig: IAppConfig,
        private browserStorageService: BrowserStorageService,
        private http: HttpClient,
        private router: Router
      ) {
        this.updateStatusOnPageRefresh();
        this.scheduleRefreshToken();
      }
    این مورد برای مدیریت حالتی که کاربر صفحه را refresh کرده‌است و یا پس از مدتی مجددا از ابتدا برنامه را بارگذاری کرده‌است، مفید است.

    ب) پس از لاگین موفقیت آمیز
    در متد لاگین، پس از دریافت یک response موفقیت آمیز و تنظیم و ذخیره سازی توکن‌های دریافتی، کار زمانبندی دریافت توکن دسترسی بعدی بر اساس refresh_token فعلی انجام می‌شود:
    this.setLoginSession(response);
    this.scheduleRefreshToken();

    ج) پس از خروج از سیستم
    در متد logout، پس از حذف توکن‌های کاربر از کش مرورگر، کار لغو تایمر زمانبندی دریافت توکن بعدی نیز صورت خواهد گرفت:
    this.deleteAuthTokens();
    this.unscheduleRefreshToken();

    در این حالت اگر برنامه را اجرا کنید، یک چنین خروجی را که بیانگر دریافت خودکار توکن‌های جدید است، پس از مدتی در کنسول developer مرورگر مشاهده خواهید کرد:


    ابتدا متد scheduleRefreshToken اجرا شده و تاخیر آغازین تایمر محاسبه شده‌است. پس از مدتی متد refreshToken توسط تایمر فراخوانی شده‌است. در آخر مجددا متد scheduleRefreshToken جهت شروع یک زمانبندی جدید، اجرا شده‌است.

    اعداد initialDelay محاسبه شده‌ای را هم که ملاحظه می‌کنید، نزدیک به همان 2 دقیقه‌ی تنظیمات سمت سرور در فایل appsettings.json هستند:
      "BearerTokens": {
        "Key": "This is my shared key, not so secret, secret!",
        "Issuer": "http://localhost/",
        "Audience": "Any",
        "AccessTokenExpirationMinutes": 2,
        "RefreshTokenExpirationMinutes": 60
      }


    کدهای کامل این سری را از اینجا می‌توانید دریافت کنید.
    برای اجرای آن فرض بر این است که پیشتر Angular CLI را نصب کرده‌اید. سپس از طریق خط فرمان به ریشه‌ی پروژه‌ی ASPNETCore2JwtAuthentication.AngularClient وارد شده و دستور npm install را صادر کنید تا وابستگی‌های آن دریافت و نصب شوند. در آخر با اجرای دستور ng serve -o، برنامه ساخته شده و در مرورگر پیش فرض سیستم نمایش داده خواهد شد (و یا همان اجرای فایل ng-serve.bat). همچنین باید به پوشه‌ی ASPNETCore2JwtAuthentication.WebApp نیز مراجعه کرده و فایل dotnet_run.bat را اجرا کنید، تا توکن سرور برنامه نیز فعال شود.
    مطالب
    API Versioning
    فرض کنید امروز یک API را برای استفاده عموم ارائه میدهید. آیا با یک breaking change در منابع شما که باعث تغییر در داده‌های ورودی یا خروجی API شود، باید استفاده کنندگان این API در سیستمی که از آن استفاده کرده‌اند، تغییراتی را اعمال کنند یا خیر؟ جواب خیر می‌باشد؛ اصلی‌ترین استفاده از API Versioning دقیقا برای این منظور است که بدون نگرانی از توسعه‌های بعدی، از ورژن‌های قدیمی API بتوانیم استفاده کنیم. 
    در این مقاله با روش‌های مختلف ورژن بندی API آشنا خواهید شد.
    سه روش اصلی زیر را میتوان برای این منظور در نظر گرفت:
    1.  URI-based versioning 
    2.  Header-based versioning 
    3.  Media type-based versioning 

    پیاده سازی URI-based versioning
    حداقل به 3 طریق میتوان این مکانیسم را پیاده کرد:
    راه حل اول: اگر از Attribute Routing استفاده نمی‌کنید، با تغییر جزئی در تعاریف مسیریابی خود میتوانید به نتیجه مورد نظر برسید. برای ادامه کار دو ویوومدل و دو کنترلر را که هر کدام مربوط به ورژن خاصی از API ما هستند، پیاده سازی میکنیم:
    public class ItemViewModel
    {
        public int Id { get; set; }
        public string Name { get; set; }
        public string Country { get; set; }
    }
    public class ItemV2ViewModel : ItemViewModel
    {
        public double Price { get; set; }
    }
    ItemViewModel مربوط به ورژن 1 میباشد.
     public class ItemsController : ApiController   
     {
            [ResponseType(typeof(ItemViewModel))]
            public IHttpActionResult Get(int id)
            {
                var viewModel = new ItemViewModel { Id = id, Name = "PS4", Country = "Japan" };
                return Ok(viewModel);
            }
        }
     public class ItemsV2Controller : ApiController
        {
            [ResponseType(typeof(ItemV2ViewModel))]
            public IHttpActionResult Get(int id)
            {
                var viewModel = new ItemV2ViewModel { Id = id, Name = "Xbox One", Country = "USA", Price = 529.99 };
                return Ok(viewModel);
            }
        }
    ItemsController مربوط به ورژن 1 میباشد.
    اگر قرار باشد از مسیرهای متمرکز استفاده کنیم، کافی است که تغییرات زیر را اعمال کنیم:
    config.Routes.MapHttpRoute("ItemsV2", "api/v2/items/{id}", new
    {
        controller = "ItemsV2",
        id = RouteParameter.Optional
    });
    config.Routes.MapHttpRoute("Items", "api/items/{id}", new
    {
        controller = "Items",
        id = RouteParameter.Optional
    });
    config.Routes.MapHttpRoute(
        name: "DefaultApi",
        routeTemplate: "api/{controller}/{id}",
        defaults: new { id = RouteParameter.Optional }
    );
    در کد بالا به صراحت برای تک تک کنترلرها مسیریابی متناسب با ورژنی که آن را پشتیبانی میکند، معرفی کرده‌ایم.
    نکته: این تنظیمات خاص باید قبل از تنظیمات پیش فرض قرار گیرند.

    روش بالا به خوبی کار خواهد کرد ولی آنچنان مطلوب نمیباشد. به همین دلیل یک مسیریابی عمومی و کلی را در نظر میگیریم که شامل ورژن مورد نظر، در قالب یک پارامتر میباشد. در روش جایگزین باید به شکل زیر عمل کنید:
    config.Routes.MapHttpRoute(
    "defaultVersioned",
    "api/v{version}/{controller}/{id}",
    new { id = RouteParameter.Optional }, new { version = @"\d+" });
    
    config.Routes.MapHttpRoute(
    "DefaultApi",
    "api/{controller}/{id}",
    new { id = RouteParameter.Optional }
    );

    با این تنظیمات فعلا به مسیریابی ورژن بندی شده‌ای دست نیافته‌ایم. زیرا فعلا به هیچ طریق به Web API اشاره نکرده‌ایم که به چه صورت از این پارامتر version برای پیدا کردن کنترلر ورژن بندی شده استفاده کند و به همین دلیل این دو مسیریابی نوشته شده در عمل نتیجه یکسانی را خواهند داشت. برای رفع مشکل مطرح شده باید فرآیند پیش فرض انتخاب کنترلر را کمی شخصی سازی کنیم.

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

     به جای پیاده سازی مستقیم این اینترفیس، از پیاده سازی کننده پیش فرض موجود (DefaultHttpControllerSelector) اسفتاده کرده و HttpControllerSelector جدید ما از آن ارث بری خواهد کرد.

    public class VersionFinder
        {
            private static bool NeedsUriVersioning(HttpRequestMessage request, out string version)
            {
                var routeData = request.GetRouteData();
                if (routeData != null)
                {
                    object versionFromRoute;
                    if (routeData.Values.TryGetValue(nameof(version), out versionFromRoute))
                    {
                        version = versionFromRoute as string;
                        if (!string.IsNullOrWhiteSpace(version))
                        {
                            return true;
                        }
                    }
                }
                version = null;
                return false;
            }
            private static int VersionToInt(string versionString)
            {
                int version;
                if (string.IsNullOrEmpty(versionString) || !int.TryParse(versionString, out version))
                    return 0;
                return version;
            }
            public static int GetVersionFromRequest(HttpRequestMessage request)
            {
                string version;
    
                return NeedsUriVersioning(request, out version) ? VersionToInt(version) : 0;
            }
        }

    کلاس VersionFinder برای یافتن ورژن رسیده در درخواست جاری  مورد استفاده قرار خواهد گرفت. با استفاده از متد NeedsUriVersioning بررسی صورت می‌گیرد که آیا در این درخواست پارامتری به نام version وجود دارد یا خیر که درصورت موجود بودن، مقدار آن واکشی شده و درون پارامتر out قرار می‌گیرد. در متد GetVersionFromRequest بررسی میشود که اگر خروجی متد NeedsUriVersioning برابر با true باشد، با استفاده از متد VersionToInt مقدار به دست آمده را به int تبدیل کند.

     public class VersionAwareControllerSelector : DefaultHttpControllerSelector
        {
            public VersionAwareControllerSelector(HttpConfiguration configuration) : base(configuration) { }
            public override string GetControllerName(HttpRequestMessage request)
            {
                var controllerName = base.GetControllerName(request);
                var version = VersionFinder.GetVersionFromRequest(request);
                    
            return version > 0 ? GetVersionedControllerName(request, controllerName, version) : controllerName;
            }
            private string GetVersionedControllerName(HttpRequestMessage request, string baseControllerName,
            int version)
            {
                var versionControllerName = $"{baseControllerName}v{version}";
                HttpControllerDescriptor descriptor;
                if (GetControllerMapping().TryGetValue(versionControllerName, out descriptor))
                {
                    return versionControllerName;
                }
    
                throw new HttpResponseException(request.CreateErrorResponse(
                HttpStatusCode.NotFound,
                    $"No HTTP resource was found that matches the URI {request.RequestUri} and version number {version}"));
            }
        }

    متد  GetControllerName وظیفه بازگشت دادن نام کنترلر را برعهده دارد. ما نیز با لغو رفتار پیش فرض این متد و تهیه نام ورژن بندی شده کنترلر و معرفی این پیاده سازی از  IHttpControllerSelector به شکل زیر میتوانیم به Web API بگوییم که به چه صورت از پارامتر version موجود در درخواست استفاده کند. 

    config.Services.Replace(typeof(IHttpControllerSelector), new VersionAwareControllerSelector(config));

    حال با اجرای برنامه به نتیجه زیر خواهیم رسید: 

    راه حل دوم: برای زمانیکه Attribute Routing مورد استفاده شما است میتوان به راحتی با تعریف قالب‌های مناسب مسیریابی، API ورژن بندی شده را ارائه دهید.

    [RoutePrefix("api/v1/Items")]
        public class ItemsController : ApiController
        {
            [ResponseType(typeof(ItemViewModel))]
            [Route("{id:int}")]
            public IHttpActionResult Get(int id)
            {
                var viewModel = new ItemViewModel { Id = id, Name = "PS4", Country =        "Japan" };
                return Ok(viewModel);
            }
        }
    
    
     [RoutePrefix("api/V2/Items")]
        public class ItemsV2Controller : ApiController
        {
            [ResponseType(typeof(ItemV2ViewModel))]
            [Route("{id:int}")]
            public IHttpActionResult Get(int id)
            {
                var viewModel = new ItemV2ViewModel { Id = id, Name = "Xbox One", Country = "USA", Price = 529.99 };
                return Ok(viewModel);
            }
        }

    اگر توجه کرده باشید در مثال ما، نام‌های کنترلر‌ها متفاوت از هم میباشند. اگر بجای در نظر گرفتن نام‌های مختلف برای یک کنترلر در ورژن‌های مختلف، آن را با یک نام یکسان درون namespace‌های مختلف احاطه کنیم یا حتی آنها را درون Class Library‌های جدا نگهداری کنیم، به مشکل "یافت شدن چندین کنترلر که با درخواست جاری مطابقت دارند" برخواهیم خورد. برای حل این موضوع به راه حل سوم توجه کنید. 

    راه حل سوم: استفاده از یک NamespaceControllerSelector که پیاده سازی دیگری از اینترفیس IHttpControllerSelector میباشد. فرض بر این است که قالب پروژه به شکل زیر می‌باشد:

    کار با پیاده سازی اینترفیس IHttpRouteConstraint آغاز میشود:

    public class VersionConstraint : IHttpRouteConstraint
    {
        public VersionConstraint(string allowedVersion)
        {
            AllowedVersion = allowedVersion.ToLowerInvariant();
        }
        public string AllowedVersion { get; private set; }
    
        public bool Match(HttpRequestMessage request, IHttpRoute route, string parameterName,
        IDictionary<string, object> values, HttpRouteDirection routeDirection)
        {
            object value;
            if (values.TryGetValue(parameterName, out value) && value != null)
            {
                return AllowedVersion.Equals(value.ToString().ToLowerInvariant());
            }
            return false;
        }
    }

    کلاس بالا در واقع برای اعمال محدودیت خاصی که در ادامه توضیح داده میشود، پیاده سازی شده است.

    متد Match آن وظیفه چک کردن برابری مقدار کلید parameterName موجود در درخواست را با مقدار allowedVersion ای که API از آن پشتیبانی میکند، برعهده دارد. با استفاده از این Constraint مشخص کرده‌ایم که دقیقا چه زمانی باید Route نوشته شده انتخاب شود.

     به روش استفاده از این Constraint توجه کنید:

    namespace UriBasedVersioning.Namespace.Controllers.V1
    {
        using Models.V1;
    
    RoutePrefix("api/{version:VersionConstraint(v1)}/items")]
    public class ItemsController : ApiController
        {
            [ResponseType(typeof(ItemViewModel))]
            [Route("{id}")]
            public IHttpActionResult Get(int id)
            {
                var viewModel = new ItemViewModel { Id = id, Name = "PS4", Country = "Japan" };
                return Ok(viewModel);
            }
        }
    }
    namespace UriBasedVersioning.Namespace.Controllers.V2
    {
        using Models.V2;
    
    
    RoutePrefix("api/{version:VersionConstraint(v2)}/items")]
    public class ItemsController : ApiController
        {
            [ResponseType(typeof(ItemViewModel))]
            [Route("{id}")]
            public IHttpActionResult Get(int id)
            {
                var viewModel = new ItemViewModel { Id = id, Name = "Xbox One", Country = "USA", Price = 529.99 };
                return Ok(viewModel);
            }
        }
    }

    با توجه به کد بالا می‌توان دلیل استفاده از VersionConstraint را هم درک کرد؛ از آنجایی که ما دو Route شبیه به هم داریم، لذا باید مشخص کنیم که در چه شرایطی و کدام یک از این Route‌ها انتخاب شود. خوب، اگر برنامه را اجرا کرده و یکی از API‌های بالا را تست کنید، با خطا مواجه خواهید شد؛ زیرا فعلا این Constraint به سیستم Web API معرفی نشده است. تنظیمات زیر را انجام دهید:

    var constraintsResolver = new DefaultInlineConstraintResolver();
                constraintsResolver.ConstraintMap.Add(nameof(VersionConstraint), typeof
                (VersionConstraint));
    config.MapHttpAttributeRoutes(constraintsResolver);

    مرحله بعدی کار، پیاده سازی IHttpControllerSelector می‌باشد:

      public class NamespaceControllerSelector : IHttpControllerSelector
        {
            private readonly HttpConfiguration _configuration;
            private readonly Lazy<Dictionary<string, HttpControllerDescriptor>> _controllers;
            public NamespaceControllerSelector(HttpConfiguration config)
            {
                _configuration = config;
                _controllers = new Lazy<Dictionary<string,
                    HttpControllerDescriptor>>(InitializeControllerDictionary);
            }
            public HttpControllerDescriptor SelectController(HttpRequestMessage request)
            {
                var routeData = request.GetRouteData();
                if (routeData == null)
                {
                    throw new HttpResponseException(HttpStatusCode.NotFound);
                }
    
                var controllerName = GetControllerName(routeData);
                if (controllerName == null)
                {
                    throw new HttpResponseException(HttpStatusCode.NotFound);
                }
                var version = GetVersion(routeData);
                if (version == null)
                {
                    throw new HttpResponseException(HttpStatusCode.NotFound);
                }
                var controllerKey = string.Format(CultureInfo.InvariantCulture, "{0}.{1}",
                    version, controllerName);
                HttpControllerDescriptor controllerDescriptor;
                if (_controllers.Value.TryGetValue(controllerKey, out controllerDescriptor))
                {
                    return controllerDescriptor;
                }
                throw new HttpResponseException(HttpStatusCode.NotFound);
            }
            public IDictionary<string, HttpControllerDescriptor> GetControllerMapping()
            {
                return _controllers.Value;
            }
            private Dictionary<string, HttpControllerDescriptor> InitializeControllerDictionary()
            {
                var dictionary = new Dictionary<string,
                    HttpControllerDescriptor>(StringComparer.OrdinalIgnoreCase);
                var assembliesResolver = _configuration.Services.GetAssembliesResolver();
                var controllersResolver = _configuration.Services.GetHttpControllerTypeResolver();
                var controllerTypes = controllersResolver.GetControllerTypes(assembliesResolver);
                foreach (var controllerType in controllerTypes)
                {
                    var segments = controllerType.Namespace.Split(Type.Delimiter);
                    var controllerName =
                        controllerType.Name.Remove(controllerType.Name.Length -
                                                   DefaultHttpControllerSelector.ControllerSuffix.Length);
                    var controllerKey = string.Format(CultureInfo.InvariantCulture, "{0}.{1}",
                        segments[segments.Length - 1], controllerName);
    
                    if (!dictionary.Keys.Contains(controllerKey))
                    {
                        dictionary[controllerKey] = new HttpControllerDescriptor(_configuration,
                            controllerType.Name,
                            controllerType);
                    }
                }
                return dictionary;
            }
            private static T GetRouteVariable<T>(IHttpRouteData routeData, string name)
            {
                object result;
                if (routeData.Values.TryGetValue(name, out result))
                {
                    return (T)result;
                }
                return default(T);
            }
            private static string GetControllerName(IHttpRouteData routeData)
            {
                var subroute = routeData.GetSubRoutes().FirstOrDefault();
                var dataTokenValue = subroute?.Route.DataTokens.First().Value;
                var controllerName =
                    ((HttpActionDescriptor[])dataTokenValue)?.First().ControllerDescriptor.ControllerName.Replace("Controller", string.Empty);
                return controllerName;
            }
            private static string GetVersion(IHttpRouteData routeData)
            {
                var subRouteData = routeData.GetSubRoutes().FirstOrDefault();
                return subRouteData == null ? null : GetRouteVariable<string>(subRouteData, "version");
            }
        }

    سورس اصلی کلاس بالا از این آدرس قابل دریافت است. در تکه کد بالا بخشی که مربوط به چک کردن تکراری بودن آدرس میباشد، برای ساده سازی کار حذف شده است. ولی نکته‌ی مربوط به SubRoutes که برای واکشی مقادیر پارامترهای مرتبط با Attribute Routing می‌باشد، اضافه شده است. روال کار به این صورت است که ابتدا RouteData موجود در درخواست را واکشی کرده و با استفاده از متدهای GetControllerName و GetVersion، پارامتر‌های controller و version را جستجو میکنیم. بعد با استفاده از مقادیر به دست آمده، controllerKey را تشکیل داده و درون کنترلر‌های موجود در برنامه به دنبال کنترلر مورد نظر خواهیم گشت.

    کارکرد متد InitializeControllerDictionary :

    همانطور که میدانید به صورت پیش‌فرض Web API توجهی به فضای نام مرتبط با کنترلر‌ها ندارد. از طرفی برای پیاده سازی اینترفیس IHttpControllerSelector نیاز است متدی با امضای زیر را داشته باشیم:

    public IDictionary<string, HttpControllerDescriptor> GetControllerMapping()

    لذا در کلاس پیاده سازی شده، خصوصیتی به نام ‎‎_controllers را که از به صورت Lazy و از نوع بازگشتی متد بالا می‌باشد، تعریف کرده‌ایم. متد InitializeControllerDictionary کار آماده سازی داده‌های مورد نیاز خصوصیت ‎‎_controllers می‌باشد. به این صورت که تمام کنترلر‌های موجود در برنامه را واکشی کرده و این بار کلید‌های مربوط به دیکشنری را با استفاده از نام کنترلر و آخرین سگمنت فضای نام آن، تولید و درون دیکشنری مورد نظر ذخیره کرده‌ایم.

    حال تنظیمات زیر را اعمال کنید:

     public static class WebApiConfig
        {
            public static void Register(HttpConfiguration config)
            {
                var constraintsResolver = new DefaultInlineConstraintResolver();
                constraintsResolver.ConstraintMap.Add(nameof(VersionConstraint), typeof
                (VersionConstraint));
    
                config.MapHttpAttributeRoutes(constraintsResolver);
    
                config.Services.Replace(typeof(IHttpControllerSelector), new NamespaceControllerSelector(config));
    
            }
        }

    این بار برنامه را اجرا کرده و API‌های مورد نظر را تست کنید؛ بله بدون مشکل کار خواهد کرد.

    نکته تکمیلی: سورس مذکور در سایت کدپلکس برای حالتی که از Centralized Routes استفاده میکنید آماده شده است. روش مذکور در این مقاله هم  فقط قسمت Duplicate Routes آن را کم دارد که میتوانید اضافه کنید. پیاده سازی دیگری را از این راه حل هم میتوانید داشته باشید.

    پیاده سازی Header-based versioning  

    در این روش کافی است هدری را برای دریافت ورژن API درخواستی مشخص کرده باشید. برای مثال هدری به نام api-version، که استفاده کننده از این طریق، ورژن درخواستی خود را اعلام میکند. همانطور که قبلا اشاره کردیم، با استفاده از Constraint‌ها دست شما خیلی باز خواهد بود. برای مثال این بار به جای واکشی پارامتر version از RouteData، کد همان قسمت را برای واکشی آن از هدر درخواستی تغییر خواهیم داد:
    public class HeaderVersionConstraint : IHttpRouteConstraint
        {
            private const string VersionHeaderName = "api-version";
    
            public HeaderVersionConstraint(int allowedVersion)
            {
                AllowedVersion = allowedVersion;
            }
    
            public int AllowedVersion { get; }
    
            public bool Match(HttpRequestMessage request, IHttpRoute route, string parameterName,
                IDictionary<string, object> values,
                HttpRouteDirection routeDirection)
            {
                if (!request.Headers.Contains(VersionHeaderName)) return false;
    
                var version = request.Headers.GetValues(VersionHeaderName).FirstOrDefault();
    
                return VersionToInt(version) == AllowedVersion;
            }
            private static int VersionToInt(string versionString)
            {
                int version;
                if (string.IsNullOrEmpty(versionString) || !int.TryParse(versionString, out version))
                    return 0;
                return version;
            }
        }
    این بار در متد Match، به دنبال هدر خاصی به نام api-version می‌گردیم و مقدار آن را با AllowdVersion مقایسه میکنیم تا مشخص کنیم که آیا این Route نوشته شده برای ادامه کار واکشی کنترلر مورد نظر استفاده شود یا خیر. در ادامه یک RouteFactoryAttribute شخصی سازی شده را به منظور معرفی این محدودیت تعریف شده، در نظر میگیریم:
    public sealed class HeaderVersionedRouteAttribute : RouteFactoryAttribute
        {
            public HeaderVersionedRouteAttribute(string template) : base(template)
            {
                Order = -1;
            }
    
            public int Version { get; set; }
    
            public override IDictionary<string, object> Constraints => new HttpRouteValueDictionary
            {
                {"", new HeaderVersionConstraint(Version)}
            };
        }

    با استفاده از خصوصیت Constraints، توانستیم محدودیت پیاده سازی شده خود را به سیستم مسیریابی معرفی کنیم. توجه داشته باشید که این بار هیچ پارامتری در Uri تحت عنوان version را نداریم و از این جهت به صراحت کلیدی برای آن مشخص نکرده ایم (‎‎‎‎‏‏‎‎{"", new HeaderVersionConstraint(Version)} ‎‎ ).
    کنترلر‌های ما به شکل زیر خواهند بود:
        [RoutePrefix("api/items")]
        public class ItemsController : ApiController
        {
            [ResponseType(typeof(ItemViewModel))]
            [HeaderVersionedRoute("{id}", Version = 1)]
            public IHttpActionResult Get(int id)
            {
                var viewModel = new ItemViewModel { Id = id, Name = "PS4", Country = "Japan" };
                return Ok(viewModel);
            }
        }
      [RoutePrefix("api/items")]
        public class ItemsV2Controller : ApiController
        {
            [ResponseType(typeof(ItemV2ViewModel))]
            [HeaderVersionedRoute("{id}", Version = 2)]
            public IHttpActionResult Get(int id)
            {
                var viewModel = new ItemV2ViewModel { Id = id, Name = "Xbox One", Country = "USA", Price = 529.99 };
                return Ok(viewModel);
            }
        }

    این بار از VersionedRoute برای اعمال مسیر یابی Attribute-based استفاده کرده ایم و به صراحت ورژن هر کدام را با استفاده از خصوصیت Version آن مشخص کرده‌ایم. نتیجه:


    پیاده سازی Media type-based versioning
    قرار است ورژن API مورد نظر را که استفاده کننده درخواست میدهد، از دل درخواست‌هایی به فرم زیر واکشی کنیم:
    GET /api/Items HTTP 1.1
    Accept: application/vnd.mediatype.versioning-v1+json
    GET /api/Items HTTP 1.1
    Accept: application/vnd.mediatype.versioning-v2+json
    در این روش دست ما کمی بازتر است چرا که میتوانیم بر اساس فرمت درخواستی نیز تصمیماتی را برای ارائه ورژن خاصی از API  داشته باشیم. روال کار شبیه به روش قبلی با پیاده سازی یک Constraint و یک RouteFactoryAttribute انجام خواهد گرفت.
     public class MediaTypeVersionConstraint : IHttpRouteConstraint
        {
            private const string VersionMediaType = "vnd.mediatype.versioning";
    
            public MediaTypeVersionConstraint(int allowedVersion)
            {
                AllowedVersion = allowedVersion;
            }
    
            public int AllowedVersion { get; }
    
            public bool Match(HttpRequestMessage request, IHttpRoute route, string parameterName, IDictionary<string, object> values,
                HttpRouteDirection routeDirection)
            {
                if (!request.Headers.Accept.Any()) return false;
    
                var acceptHeaderVersion =
                    request.Headers.Accept.FirstOrDefault(x => x.MediaType.Contains(VersionMediaType));
    
                //Accept: application/vnd.mediatype.versioning-v1+json
                if (acceptHeaderVersion == null || !acceptHeaderVersion.MediaType.Contains("-v") ||
                    !acceptHeaderVersion.MediaType.Contains("+"))
                    return false;
    
                var version = acceptHeaderVersion.MediaType.Between("-v", "+");
                return VersionToInt(version)==AllowedVersion;
            }
    }
    در متد Match کلاس بالا به دنبال مدیا تایپ مشخصی هستیم که با هدر Accept برای ما ارسال میشود. برای آشنایی با فرمت ارسال Media Type‌ها میتوانید به اینجا مراجعه کنید. کاری که در اینجا انجام شده‌است، یافتن ورژن ارسالی توسط استفاده کننده می‌باشد که آن را با فرمت خاصی که نشان داده شد و مابین (v-) و (+) قرار داده، ارسال میشود. ادامه کار به مانند روش Header-based میباشد و از مطرح کردن آن در مقاله خودداری میکنیم.
    نتیجه به شکل زیر خواهد بود:


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


    نظرات مطالب
    طراحی گردش کاری با استفاده از State machines - قسمت دوم
    - آیا چنین انتقال هایی به شکل زیر کار اصولی می‌باشد؟ 

    به عنوان مثال اگر Trigger1 همان رویداد Save ما بوده که با توجه به Domain ای که در آن قرار داریم انجام آن در حالت‌های مشخص شده رویداد معتبری باشد، ماشین حالت بالا صحیح می‌باشد یا اینکه باید با یک رویداد دیگری به حالت State1 برگشته و دوباره انتقال به حالت State2 را انجام دهیم. البته متوجه هستم که به ازای تک تک رویدادها همیشه و نه لزوما باید دکمه متناظری را باتوجه به حالت فعلی شیء مورد، توسط کاربر قابل مشاهده باشد. با توجه به اینکه اگر کاربر نهایی به ازای تک تک رویدادهای موجود، لزوما دکمه‌های متناظری را در فرم مشاهده نکند و نیاز باشد یک دکمه Shortcut مانندی قرار بگیرد که احتمال دارد باعث چندین انتقال شود، Fire کردن چندین رویداد در پشت صحنه، کار اصولی می‌باشد؟

    - نحوه استفاده از ماشین حالت ایجاد شده در لایه Application/ServiceLayer به چه شکل خواهد بود؟ حدس و سعی بنده به شکل زیر می‌باشد:
     public InvoiceStateMachine(InvoiceModel model)
            {
                _stateMachine =
                    new StateMachine<InvoiceStatus, Trigger>(() => model.Status, state => model.Status = state);
    سپس تعریف فیلدی به شکل زیر به منظور داشتن یک رویداد Parameterised، در دل ماشین حالت ایجاد شده:
    public Func<InvoiceModel, Task> OnSaveAsync = null;
    private readonly StateMachine<InvoiceStatus, Trigger>.TriggerWithParameters<InvoiceModel> _saveTrigger;

    کانفیگ آن به شکل زیر:
     _stateMachine.Configure(InvoiceStatus.Pending)
                    .OnEntryFromAsync(_saveTrigger, async invoice => await OnSaveAsync(invoice))
    سپس در InvoiceService به شکل زیر عمل کرده:
     private void InitializeStateMachine(InvoiceModel model)
            {
                _stateMachine = new InvoiceStateMachine(model)
                {
                    OnSaveAsync = async invoiceModel =>
                    {
                        var result = model.IsNew() ? await CreateAsync(model) : await EditAsync(model);
                        if (!result.Succeeded)
                            throw new BusinessRuleValidationException(result.Message);
                    }
                };
            }

    و در نهایت متدی که از بیرون فراخوانی خواهد شد:
    [Transactional]
    public async Task<Result> SaveAsync(InvoiceModel model)
    {
        try
        {
            InitializeStateMachine(model);
            await _stateMachine.TryFireSaveAsync(model);
            return Success();
        }
        catch (BusinessRuleValidationException e)
        {
            return Failed(e.Message);
        }
    }

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