نظرات مطالب
اعتبارسنجی مبتنی بر JWT در ASP.NET Core 2.0 بدون استفاده از سیستم Identity
با تشکر . یعنی موقعی که توکن ایجاد میشود از claimها هم برای ایجاد آن استفاده میشود؟در اینجا
  private async Task<(string AccessToken, IEnumerable<Claim> Claims)> createAccessTokenAsync(User user)
        {
            var claims = new List<Claim>
            {
                // Unique Id for all Jwt tokes
                new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString(), ClaimValueTypes.String, _configuration.Value.Issuer),
                // Issuer
                new Claim(JwtRegisteredClaimNames.Iss, _configuration.Value.Issuer, ClaimValueTypes.String, _configuration.Value.Issuer),
                // Issued at
                new Claim(JwtRegisteredClaimNames.Iat, DateTimeOffset.UtcNow.ToUnixTimeSeconds().ToString(), ClaimValueTypes.Integer64, _configuration.Value.Issuer),
                new Claim(ClaimTypes.NameIdentifier, user.Id.ToString(), ClaimValueTypes.String, _configuration.Value.Issuer),
                new Claim(ClaimTypes.Name, user.Username, ClaimValueTypes.String, _configuration.Value.Issuer),
                new Claim("DisplayName", user.DisplayName, ClaimValueTypes.String, _configuration.Value.Issuer),
                // to invalidate the cookie
                new Claim(ClaimTypes.SerialNumber, user.SerialNumber, ClaimValueTypes.String, _configuration.Value.Issuer),
                // custom data
                new Claim(ClaimTypes.UserData, user.Id.ToString(), ClaimValueTypes.String, _configuration.Value.Issuer)
            };

           // add userclaims for permission
            var clmsuser= await _rolesService.FindUserClaimesAsync(user.Id);
            foreach (var cls in clmsuser)
            {
                claims.Add(new Claim(cls.ClaimType,cls.ClaimValue,ClaimValueTypes.String,_configuration.Value.Issuer));
            }
           
            // add roles
            var roles = await _rolesService.FindUserRolesAsync(user.Id);
            foreach (var role in roles)
            {
                claims.Add(new Claim(ClaimTypes.Role, role.Name, ClaimValueTypes.String, _configuration.Value.Issuer));
            }

            var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_configuration.Value.Key));
            var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);
            var now = DateTime.UtcNow;
            var token = new JwtSecurityToken(
                issuer: _configuration.Value.Issuer,
                audience: _configuration.Value.Audience,
                claims: claims,
                notBefore: now,
                expires: now.AddMinutes(_configuration.Value.AccessTokenExpirationMinutes),
                signingCredentials: creds);
            return (new JwtSecurityTokenHandler().WriteToken(token), claims);
        }

و هر تغییر در claimها در سمت کاربر برابر میشود با  برگشت توکن دستکاری شده از سمت سرور و مسدود شدن دسترسی؟
نظرات مطالب
چک لیست تهیه یک برنامه ASP.NET MVC
فرض کنید که در UI فقط به نام کاربر و آدرس ایمیل کاربر احتیاج است،کلاس کاربر در در Domain به شرح زیر است:
public class User
{
   public int UserId {get;set;}
   public string Name {get;set;}
   public string Family {get;set;}
   public string Web {get;set;}
   public string Email {get;set;}
   public DateTime RegisterDate {get;set;}
}
حالا در یک کنترلر فقط به نام کاربر و آدرس ایمیل کاربر احتیاج است،حالا میایم یک کلاس تعریف میکنیم که شامل فیلدهای مورد نیاز است:
public class UserInfoViewModel
{
   public string Name {get;set;}
   public string Family {get;set;}
   public string Email {get;set;}
}
و لایه سرویس یک متد منحصر بفرد مختص این کار می نویسیم ،مثلاً:
public UserInfoViewModel GetMemberByUserName(string username)
{
   var result = from u in _users
                   where u.UserName == username
                   select new UserInfoViewModel() {Name = u.Name,Family=u.Family,Email=u.Email};

   return result;
}
1-اگه این روش درسته،حالا:
  • - برای هر متد که نیازه یه سری فیلد مورد نیاز رو برگردونه،یه کلاس جداگانه باید تعریف کرد؟(در اینجا UserInfoViewModel
  • - این کلاس UserInfoViewModel باید جز ViewModel‌های لایه UI باشه و در لایه Models قرار بگیره؟
      در اینصورت (کلاس UserInfoViewModel باید جز ViewModel‌های لایه UI باشه )،لایه سرویس   وابسته به لایه UI نمیشه؟
      اگر جواب منفیه،
      کلاس UserInfoViewModel تو کدوم لایه باید قرار بگیره؟
      دیگه نباید پسوند ViewModel رو به این کلاس اضافه کرد،درسته؟
2-اگه این روش کلاً اشتباهه،راه حل شما دقیقاً چیه؟
نظرات مطالب
نحوه‌ی استخراج شماره سریال سخت افزار برای تولید یک قفل سخت افزاری
می توان از این کلاس هم که در واقع بسط داده شده‌ی روش مذکور است استفاده نمود:
    public class FingerPrint
    {
        private static string fingerPrint = string.Empty;
        public static string Value()
        {
            if (string.IsNullOrEmpty(fingerPrint))
            {
                fingerPrint = GetHash("CPU >> " + cpuId() + "\nBIOS >> " +
            biosId() + "\nBASE >> " + baseId()
                    +"\nDISK >> "+ diskId() + "\nVIDEO >> " + 
                    videoId() +"\nMAC >> "+ macId());
            }
            return fingerPrint;
        }
        private static string GetHash(string s)
        {
            MD5 sec = new MD5CryptoServiceProvider();
            ASCIIEncoding enc = new ASCIIEncoding();
            byte[] bt = enc.GetBytes(s);
            return GetHexString(sec.ComputeHash(bt));
        }
        private static string GetHexString(byte[] bt)
        {
            string s = string.Empty;
            for (int i = 0; i < bt.Length; i++)
            {
                byte b = bt[i];
                int n, n1, n2;
                n = (int)b;
                n1 = n & 15;
                n2 = (n >> 4) & 15;
                if (n2 > 9)
                    s += ((char)(n2 - 10 + (int)'A')).ToString();
                else
                    s += n2.ToString();
                if (n1 > 9)
                    s += ((char)(n1 - 10 + (int)'A')).ToString();
                else
                    s += n1.ToString();
                if ((i + 1) != bt.Length && (i + 1) % 2 == 0) s += "-";
            }
            return s;
        }


        #region Original Device ID Getting Code
        //Return a hardware identifier
        private static string identifier
        (string wmiClass, string wmiProperty, string wmiMustBeTrue)
        {
            string result = "";
            System.Management.ManagementClass mc =
        new System.Management.ManagementClass(wmiClass);
            System.Management.ManagementObjectCollection moc = mc.GetInstances();
            foreach (System.Management.ManagementObject mo in moc)
            {
                if (mo[wmiMustBeTrue].ToString() == "True")
                {
                    //Only get the first one
                    if (result == "")
                    {
                        try
                        {
                            result = mo[wmiProperty].ToString();
                            break;
                        }
                        catch
                        {
                        }
                    }
                }
            }
            return result;
        }
        //Return a hardware identifier
        private static string identifier(string wmiClass, string wmiProperty)
        {
            string result = "";
            System.Management.ManagementClass mc =
        new System.Management.ManagementClass(wmiClass);
            System.Management.ManagementObjectCollection moc = mc.GetInstances();
            foreach (System.Management.ManagementObject mo in moc)
            {
                //Only get the first one
                if (result == "")
                {
                    try
                    {
                        result = mo[wmiProperty].ToString();
                        break;
                    }
                    catch
                    {
                    }
                }
            }
            return result;
        }
        private static string cpuId()
        {
            //Uses first CPU identifier available in order of preference
            //Don't get all identifiers, as it is very time consuming
            string retVal = identifier("Win32_Processor", "UniqueId");
            if (retVal == "") //If no UniqueID, use ProcessorID
            {
                retVal = identifier("Win32_Processor", "ProcessorId");
                if (retVal == "") //If no ProcessorId, use Name
                {
                    retVal = identifier("Win32_Processor", "Name");
                    if (retVal == "") //If no Name, use Manufacturer
                    {
                        retVal = identifier("Win32_Processor", "Manufacturer");
                    }
                    //Add clock speed for extra security
                    retVal += identifier("Win32_Processor", "MaxClockSpeed");
                }
            }
            return retVal;
        }
        //BIOS Identifier
        private static string biosId()
        {
            return identifier("Win32_BIOS", "Manufacturer")
                + identifier("Win32_BIOS", "SMBIOSBIOSVersion")
            + identifier("Win32_BIOS", "IdentificationCode")
                + identifier("Win32_BIOS", "SerialNumber")
                + identifier("Win32_BIOS", "ReleaseDate")
                + identifier("Win32_BIOS", "Version");
        }
        //Main physical hard drive ID
        private static string diskId()
        {
            return identifier("Win32_DiskDrive", "Model")
            + identifier("Win32_DiskDrive", "Manufacturer")
            + identifier("Win32_DiskDrive", "Signature")
            + identifier("Win32_DiskDrive", "TotalHeads");
        }
        //Motherboard ID
        private static string baseId()
        {
            return identifier("Win32_BaseBoard", "Model")
            + identifier("Win32_BaseBoard", "Manufacturer")
            + identifier("Win32_BaseBoard", "Name")
            + identifier("Win32_BaseBoard", "SerialNumber");
        }
        //Primary video controller ID
        private static string videoId()
        {
            return identifier("Win32_VideoController", "DriverVersion")
            + identifier("Win32_VideoController", "Name");
        }
        //First enabled network card ID
        private static string macId()
        {
            return identifier("Win32_NetworkAdapterConfiguration",
                "MACAddress", "IPEnabled");
        }
        #endregion
    }
خروجی این کلاس Hexal است و به شکل زیر قابل استفاده می‌باشد:
/// Generates a 16 byte Unique Identification code of a computer 
/// Example: 4876-8DB5-EE85-69D3-FE52-8CF7-395D-2EA9

var computerId = FingerPrint.Value();

مطالب
توسعه برنامه های Cross Platform با Xamarin Forms & Bit Framework - قسمت پانزدهم
در این قسمت قصد داریم تا با زدن کدهای Platform Specific در Xamarin آشنا شویم. صد البته که در Xamarin Forms به کتابخانه‌های NET. ای دسترسی داریم و مواردی چون Entity Framework Core، Auto Mapper، Autofac و ... را می‌توانیم استفاده کنیم و در کنار اینها، مواردی چون Linq, Parallel Linq, Socket و ... نیز در دسترس ما هستند. در رابطه با مواردی چون کار با Clipboard, Geocoding, Gyroscope, Secure Store و ... نیز می‌توان از کتابخانه فوق العاده کاربردی Xamarin Essentials استفاده کرد که با یک کد CSharp می‌توانید روی Android/iOS/Windows جواب بگیرید.
اما فرض کنید که جستجو کرده اید و کد Cross Platform آماده‌ای برای استفاده نیافته‌اید؛ یا پیدا کرده‌اید، ولی صد در صد منطبق با نیازهای شما نیست. حال باید چه کنید؟ ابتدا باید کد مربوطه را بدانید که در Android/iOS/Windows (بسته به نیازتان) چگونه باید نوشت. در مورد Windows، خب تمامی امکانات سیستم عامل ویندوز را در زبان CSharp هم دارید. خبر خوب این است که این مهم نه تنها برای ویندوز که در مورد Android و iOS نیز برقرار است. به علاوه مستندات استفاده از آنها به زبان CSharp نیز موجود است. برای مثال نگاهی بیاندازید به روش Platform Specific استفاده از Bluetooth در Windows و AR Kit 2 در iOS و Job Scheduler در Android
صد البته که کتابخانه فوق العاده BluetoothLE وجود دارد و یک بار نوشتن کد، نه تنها روی Windows/Android/iOS که بر روی macOS و tvOS هم کار می‌کند!

با مثال گرفتن "ورژن برنامه" شروع می‌کنیم. هر چند با استفاده از Xamarin Essentials می شود با یک خط کد، ورژن برنامه را در هر پلتفرمی که باشیم گرفت؛ ولی فرض کنید که نمی‌شود. برای پیاده سازی این قابلیت ابتدا یک Interface را تعریف می‌کنیم و آن را در فولدر Contracts در پروژه XamApp قرار می‌دهیم:
public interface IAppVersionService
{
    string GetAppVersion();
}
سپس در پروژه XamApp.Android، در فولدر Implementations، کلاس زیر را می‌سازیم: (چون این کلاس در پروژه Android است، به 100% امکانات Android دسترسی داریم)
public class AndroidAppVersionService : IAppVersionService
{
    public Android.Content.Context Context { get; set; }

    public string GetAppVersion()
    {
        return Context.PackageManager.GetPackageInfo(Context.PackageName, 0).VersionName;
    }
}
این کد را از روی این جواب در StackOverFlow نوشته‌ام. همانطور که می‌بینید، دو کد، ساختاری شبیه به یکدیگر دارند. فقط تفاوت این است که Context.GetPackageManager در Java، در CSharp به Context.PackageManager تبدیل می‌شود؛ زیرا در Java چیزی به صورت Property و Get,Set وجود ندارد و Context.PackageManager در Java معادل می‌شود با دو متد Context.GetPackageManager و Context.SetPackageManager
تقریبا برای هر کاری در Android نیاز به Context دارید که می‌توانید آن را با Property Injection دریافت کنید.
سپس در فایل MainActivity.cs در کلاس XamAppPlatformInitializer، در متد RegisterTypes داریم:
containerBuilder.RegisterType<AndroidAppVersionService>()
    .As<IAppVersionService>()
    .PropertiesAutowired(PropertyWiringOptions.PreserveSetValues);
برای پیاده سازی همین امکان در iOS داریم:
public class iOSAppVersionService : IAppVersionService
{
    public string GetAppVersion()
    {
        var infoDictionary = NSBundle.MainBundle.InfoDictionary;
        return infoDictionary?["CFBundleShortVersionString"] as NSString;
    }
}
که از روی این جواب به دست آمده است. البته جواب مربوطه علاوه بر ورژن، نام برنامه را نیز به دست می‌آورد که نیاز ما نیست. اگر سایر جواب‌ها را نگاه کنید، می‌بینید که جواب‌های مربوط به Swift برای برنامه نویسان CSharp خوانایی دارند، ولی این در مورد کدهای Objective-C خیلی صادق نیست(!) برای حل این مشکل، کد Objective-C را در این سایت به Swift تبدیل کرده و سپس معادل CSharp آن را بنویسید.
و در نهایت برای UWP از روی این جواب داریم:
public string GetAppVersion()
{
    return $"{Package.Current.Id.Version.Major}.{Package.Current.Id.Version.Minor}"; 
}
که این دو نیز در AppDelegate.cs برای iOS و MainPage.xaml.cs برای UWP رجیستر می‌شوند.
برای استفاده نیز کافی است در هر View Model ای که قصد استفاده از این سرویس را دارید، یک Property از جنس IAppVersionService را تعریف کنید. در صورت Pull کردن آخرین تغییرات پروژه XamApp، می‌توانید نتیجه را در View و View Model با نام PlatformSpecificSamples ببینید.
خبر خوب این است که تمامی کدها به زبان CSharp نوشته می‌شوند و اگر مثلا وسط یک کد Platform Specific برای Android احتیاج به Auto Mapper پیدا کردید، می‌توانید از آن استفاده کنید. همچنین تمامی این کدها در Visual Studio دیباگ می‌شوند که خود نعمتی است.
حال اگر در ادامه کار، به یک کتابخانه 3rd Party که با Java نوشته شده نیاز پیدا کردیم چه؟ برای مثال این کتابخانه اطلاعاتی را در مورد Ringer گوشی، در اختیار ما قرار می‌دهد!
در Xamarin می‌توانید فایل‌های JAR و AAR و Header‌های Objective-C و Swift را در پروژه اضافه کنید و Wrapper به زبان CSharp تحویل بگیرید! علاوه بر مستندات مفصل خود Xamarin در این مورد که برای Android/iOS می توانید آنها را بخوانید. افراد زیادی بر همین اساس امکان استفاده از کتابخانه‌های 3rd Party زیادی را به Xamarin اضافه کرده‌اند. برخی از ابزارها نیز در این زمینه کاربردی هستند؛ برای مثال، برای ساخت C# Wrapper از روی C++,C از ابزار CppSharp می توانید استفاده کنید.
در نظر داشته باشید، اگر بخواهید کدی بزنید که فقط تفاوت رفتار در Android/iOS/Windows را دارد، یا بسته به گوشی، تبلت یا دسکتاپ بودن قرار است رفتارش تفاوت کند، مثلا یک پیام را فقط به دارندگان گوشی‌های اندرویدی نشان دهید، ولی با IUserDialogs که در هر سه پلتفرم کار می‌کند می‌خواهید این کد را بنویسید، احتیاجی به این کارها نیست و به سادگی تعریف یک Property با نام IDeviceService می‌توانید جواب لازم را بگیرید:
async Task ShowSomeAlertToAndroidPhoneUsersOnly()
{
    if (DeviceService.RuntimePlatform == RuntimePlatform.Android && DeviceService.Idiom == TargetIdiom.Phone)
    {
        await UserDialogs.AlertAsync("Some alert to android phone users only!", "Test");
    }
}

در برخی مواقع ما قصد سفارشی سازی کردن کنترل‌های UI را داریم. برای مثال زمانیکه از Entry در Xamarin Forms استفاده می‌کنیم، این به کنترل معادل Native خودش در هر پلتفرم تبدیل می‌شود، که همین باعث می‌شود بگوییم UI در Xamarin Forms به صورت Native است. حال در iOS که ما UITextField را به عنوان معادل Native کنترل Entry داریم، یک ویژگی داریم به نام ClearButtonMode که وقتی به مقدار WhileEditing تنظیم شود، در موقع تایپ کردن در UITextField، آن X پاک کردن متن باقی می‌ماند. این رفتار پیش فرض نیست و اگر ما قصد تغییر آن را داشته باشیم، یکی از متداول‌ترین راه‌ها، نوشتن Custom Renderer است. برای همین در iOS از EntryRenderer ارث بری می‌کنیم و سفارشی سازی مربوطه را انجام می‌دهیم و در نهایت EntryRenderer خودمان را رجیستر می‌کنیم.
public class XamAppEntryRenderer : EntryRenderer
{
    protected override void OnElementChanged(ElementChangedEventArgs<Entry> e)
    {
        base.OnElementChanged(e);

        if (e.NewElement != null) /* e.NewElement is a Xamarin Forms' Entry */
        {
            Control.ClearButtonMode = UITextFieldViewMode.WhileEditing; // Control is UITextField
        }
    }
}
برای Register کردن نیز داریم:
[assembly: ExportRenderer(typeof(Entry), typeof(XamAppEntryRenderer))]
در واقع این کد می‌گوید که از این به بعد، Entry‌ها در iOS، با کلاس جدید Render شوند. برای درک بهتر این مهم، فایل XamAppEntryRenderer.cs را در فولدر Renderer در پروژه XamApp.iOS مشاهده کنید.
نظرات مطالب
معرفی ASP.NET Identity
با تشکر از مطلب مفیدتون سوالی داشتم
 یک جا گفتید " مثلا الگوی دیتابیس‌ها برای ذخیره اطلاعات کاربران "  منظورتون از دیتابیس Table  هست؟
چرا از کد زیر task برای خروجی استفاده کردید
async Task<ActionResult> Register


مطالب
بررسی ویجت Kendo UI File Upload
Kendo UI به همراه یک ویجت وب مخصوص ارسال فایل‌ها به سرور نیز هست. این ویجت قابلیت ارسال چندین فایل با هم را به صورت Ajax ایی دارا است و همچنین کاربران می‌توانند فایل‌ها را با کشیدن و رها کردن بر روی آن، به لیست فایل‌های قابل ارسال اضافه کنند.
ارسال فایل Ajax ایی آن توسط HTML5 File API صورت می‌گیرد که در تمام مرورگرهای جدید پشتیبانی خوبی از آن وجود دارد. در مرورگرهای قدیمی‌تر، به صورت خودکار همان حالت متداول ارسال همزمان فایل‌ها را فعال می‌کند (یا همان post back معمولی).

فعال سازی مقدماتی kendoUpload

ابتدایی‌ترین حالت کار با kendoUpload، فعال سازی حالت post back معمولی است؛ به شرح زیر:
<form method="post" action="submit" enctype="multipart/form-data">
  <div>
    <input name="files" id="files" type="file" />    
    <input type="submit" value="Submit" class="k-button" />
  </div>
</form>
<script>
  $(document).ready(function() {
     $("#files").kendoUpload();
  });
</script>
در این حالت صرفا input با نوع file، با ظاهری سازگار با سایر کنترل‌های Kendo UI به نظر می‌رسد و عملیات ارسال فایل، همانند قبل به همراه یک post back است. این روش برای حالتی مفید است که بخواهید یک فایل را به همراه سایر عناصر فرم در طی یک مرحله به سمت سرور ارسال کنید.


فعال سازی حالت ارسال فایل Ajax ایی kendoUpload

برای فعال سازی ارسال Ajax ایی فایل‌ها در Kendo UI نیاز است خاصیت async آن‌را به نحو ذیل مقدار دهی کرد:
    <script type="text/javascript">
        $(function () {
            $("#files").kendoUpload({
                name: "files",
                async: { // async configuration
                    saveUrl: "@Url.Action("Save", "Home")", // the url to save a file is '/save'
                    removeUrl: "@Url.Action("Remove", "Home")", // the url to remove a file is '/remove'
                    autoUpload: false, // automatically upload files once selected
                    removeVerb: 'POST'
                },
                multiple: true,
                showFileList: true
            }); 
        });
    </script>
در اینجا دو آدرس ذخیره سازی فایل‌ها و همچنین حذف آن‌ها را مشاهده می‌کنید. امضای این دو اکشن متد در ASP.NET MVC به صورت ذیل هستند:
        [HttpPost]
        public ActionResult Save(IEnumerable<HttpPostedFileBase> files)
        {
            if (files != null)
            {
                // ...
                // Process the files and save them
                // ...
            }

            // Return an empty string to signify success
            return Content("");
        }

        [HttpPost]
        public ContentResult Remove(string[] fileNames)
        {
            if (fileNames != null)
            {
                foreach (var fullName in fileNames)
                {
                    // ...
                    // delete the files
                    // ...
                }
            }

            // Return an empty string to signify success
            return Content("");
        }
در هر دو حالت، لیستی از فایل‌ها توسط kendoUpload به سمت سرور ارسال می‌شوند. در حالت Save، محتوای این فایل‌ها جهت ذخیره سازی بر روی سرور در دسترس خواهد بود. در حالت Remove، صرفا نام این فایل‌ها برای حذف از سرور، توسط کاربر ارسال می‌شوند.
دو دکمه‌ی حذف با کارکردهای متفاوت در ویجت kendoUpload وجود دارند. در ابتدای کار، پیش از ارسال فایل‌ها به سرور:


کلیک بر روی دکمه‌ی حذف در این حالت، صرفا فایلی را از لیست سمت کاربر حذف می‌کند.

پس از ارسال فایل‌ها به سرور:


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


چند نکته‌ی تکمیلی
- تنظیم خاصیت autoUpload به true سبب می‌شود تا پس از انتخاب فایل‌ها توسط کاربر، بلافاصله و به صورت خودکار عملیات ارسال فایل‌ها به سرور آغاز شوند. اگر به false تنظیم شود، دکمه‌ی ارسال فایل‌ها در پایین لیست نمایش داده خواهد شد.
- شاید علاقمند باشید تا removeVerb را به DELETE تغییر دهید؛ بجای POST. به همین منظور می‌توان خاصیت removeVerb در اینجا مقدار دهی کرد.
- با تنظیم خاصیت multiple به true، کاربر قادر خواهد شد تا توسط صفحه‌ی دیالوگ انتخاب فایل‌ها، قابلیت انتخاب بیش از یک فایل را داشته باشد.
- showFileList نمایش لیست فایل‌ها را سبب می‌شود.


تعیین پسوند فایل‌‌های صفحه‌ی انتخاب فایل‌ها

هنگامیکه کاربر بر روی دکمه‌ی انتخاب فایل‌ها برای ارسال کلیک می‌کند، در صفحه‌ی دیالوگ باز شده می‌توان پسوندهای پیش فرض مجاز را نیز تعیین کرد.
برای این منظور تنها کافی است ویژگی accept را به input از نوع فایل اضافه کرد. چند مثال در این مورد:
<!-- Content Type with wildcard.  All Images -->
<input type="file" id="demoFile" title="Select file" accept="image/*" />
 
<!-- List of file extensions -->
<input type="file" id="demoFile" title="Select file" accept=".jpg,.png,.gif" />
 
<!-- Any combination of the above -->
<input type="file" id="demoFile" title="Select file" accept="audio/*,application/pdf,.png" />


نمایش متن کشیدن و رها کردن، بومی سازی برچسب‌ها و نمایش راست به چپ

همانطور که در تصاویر فوق ملاحظه می‌کنید، نمایش این ویجت راست به چپ و پیام‌های آن نیز ترجمه شده‌اند.
برای راست به چپ سازی آن مانند قبل تنها کافی است input مرتبط، در یک div با کلاس k-rtl محصور شود:
        <div class="k-rtl k-header">
            <input name="files" id="files" type="file"  />
        </div>
برای بومی سازی پیام‌های آن می‌توان مانند مثال ذیل، خاصیت localization را مقدار دهی کرد:
    <script type="text/javascript">
        $(function () {
            $("#files").kendoUpload({
                name: "files",
                async: {
                 //...
                },
                //...
                localization: {
                    select: 'انتخاب فایل‌ها برای ارسال',
                    remove: 'حذف فایل',
                    retry: 'سعی مجدد',
                    headerStatusUploading: 'در حال ارسال فایل‌ها',
                    headerStatusUploaded: 'پایان ارسال',
                    cancel: "لغو",
                    uploadSelectedFiles: "ارسال فایل‌ها",
                    dropFilesHere: "فایل‌ها را برای ارسال، کشیده و در اینجا رها کنید",
                    statusUploading: "در حال ارسال",
                    statusUploaded: "ارسال شد",
                    statusWarning: "اخطار",
                    statusFailed: "خطا در ارسال"
                }
            });
        });
    </script>
به علاوه متن dropFilesHere به صورت پیش فرض نامرئی است. برای نمایش آن نیاز است CSS موجود را بازنویسی کرد تا em مرتبط مرئی شود:
<style type="text/css">
div.k-dropzone {
    border: 1px solid #c5c5c5; /* For Default; Different for each theme */
}

div.k-dropzone em {
    visibility: visible;
}
</style>


تغییر قالب نمایش لیست فایل‌ها

لیست فایل‌ها در ویجت kendoUpload دارای یک قالب پیش فرض است که امکان بازنویسی کامل آن وجود دارد. ابتدا نیاز است یک kendo-template را بر این منظور تدارک دید:
    <script id="fileListTemplate" type="text/x-kendo-template">
        <li class='k-file'>
            <span class='k-progress'></span>
            <span class='k-icon'></span>
            <span class='k-filename' title='#=name#'>#=name# (#=size# bytes)</span>
            <strong class='k-upload-status'></strong>
        </li>
    </script>
و سپس برای استفاده از آن خواهیم داشت:
    <script type="text/javascript">
        $(function () {
            $("#files").kendoUpload({
                name: "files",
                async: {
                // ...
                },
                // ...
                template: kendo.template($('#fileListTemplate').html()),
                // ...
            });
        });
    </script>
در این قالب، مقدار size هر فایل نیز در کنار نام آن نمایش داده می‌شود.


رخدادهای ارسال فایل‌ها

افزونه‌ی kendoUpload در حالت ارسال Ajax ایی فایل‌ها، رخدادهایی مانند شروع به ارسال، موفقیت، پایان، درصد ارسال فایل‌ها و امثال آن‌را نیز به همراه دارد که لیست کامل آن‌ها را در ذیل مشاهده می‌کنید:
    <script type="text/javascript">
        $(function () {
            $("#files").kendoUpload({
                name: "files",
                async: { // async configuration
                //...
                },
                //...
                localization: {
                },
                cancel: function () {
                    console.log('Cancel Event.');
                },
                complete: function () {
                    console.log('Complete Event.');
                },
                error: function () {
                    console.log('Error uploading file.');
                },
                progress: function (e) {
                    console.log('Uploading file ' + e.percentComplete);
                },
                remove: function () {
                    console.log('File removed.');
                },
                select: function () {
                    console.log('File selected.');
                },
                success: function () {
                    console.log('Upload successful.');
                },
                upload: function (e) {
                    console.log('Upload started.');
                }
            }); 
        });
    </script>


ارسال متادیتای اضافی به همراه فایل‌های ارسالی

فرض کنید می‌خواهید به همراه فایل‌های ارسالی به سرور، پارامتر codeId را نیز ارسال کنید. برای این منظور باید خاصیت e.data رویداد upload را به نحو ذیل مقدار دهی کرد:
    <script type="text/javascript">
        $(function () {
            $("#files").kendoUpload({
                name: "files",
                async: {
                //...
                },
                //...
                localization: {
                },
                upload: function (e) {
                    console.log('Upload started.');
                    // Sending metadata to the save action
                    e.data = {
                        codeId: "1234567",
                        param2: 12
                        //, ...
                    };
                }
            });
        });
    </script>
سپس در سمت سرور، امضای متد Save بر اساس پارامترهای تعریف شده در سمت کاربر، به نحو ذیل تغییر می‌کند:
   [HttpPost]
  public ActionResult Save(IEnumerable<HttpPostedFileBase> files, string codeId)


فعال سازی ارسال batch

اگر در متد Save سمت سرور یک break point قرار دهید، مشاهده خواهید کرد که به ازای هر فایل موجود در لیست در سمت کاربر، یکبار متد Save فراخوانی می‌شود و عملا متد Save، لیستی از فایل‌ها را در طی یک فراخوانی دریافت نمی‌کند. برای فعال سازی این قابلیت تنها کافی است خاصیت batch را به true تنظیم کنیم:
    <script type="text/javascript">
        $(function () {
            $("#files").kendoUpload({
                name: "files",
                async: {
                    // ....
                    batch: true
                },
            });
        });
    </script>
به این ترتیب دیگر لیست فایل‌ها به صورت مجزا در سمت کاربر نمایش داده نمی‌شود و تمام آن‌ها با یک کاما از هم جدا خواهند شد. همچنین دیگر شاهد نمایش درصد پیشرفت تکی فایل‌ها نیز نخواهیم بود و اینبار درصد پیشرفت کل batch گزارش می‌شود.
در یک چنین حالتی باید دقت داشت که تنظیم maxRequestLength در web.config برنامه الزامی است؛ زیرا به صورت پیش فرض محدودیت 4 مگابایتی ارسال فایل‌ها توسط ASP.NET اعمال می‌شود:
<?xml version="1.0" encoding="utf-8"?>
<configuration>
  <system.web>
    <!-- The request length is in kilobytes, execution timeout is in seconds  -->
    <httpRuntime maxRequestLength="10240" executionTimeout="120" />
  </system.web>

  <system.webServer>
    <security>
      <requestFiltering>
        <!-- The content length is in bytes  -->
        <requestLimits maxAllowedContentLength="10485760"/>
      </requestFiltering>
    </security>
  </system.webServer>
</configuration>
مطالب دوره‌ها
ساخت یک Mini ORM با AutoMapper
Mini ORM‌ها برخلاف ORMهای کاملی مانند Entity framework یا NHibernate، کوئری‌های LINQ را تبدیل به SQL نمی‌کنند. در اینجا کار با SQL نویسی مستقیم شروع می‌شود و مهم‌ترین کار این کتابخانه‌ها، نگاشت نتیجه‌ی دریافتی از بانک اطلاعاتی به اشیاء دات نتی هستند. خوب ... AutoMapper هم دقیقا همین کار را انجام می‌دهد! بنابراین در ادامه قصد داریم یک Mini ORM را به کمک AutoMapper طراحی کنیم.


کلاس پایه AdoMapper

public abstract class AdoMapper<T> where T : class
{
    private readonly SqlConnection _connection;
 
    protected AdoMapper(string connectionString)
    {
        _connection = new SqlConnection(connectionString);
    }
 
    protected virtual IEnumerable<T> ExecuteCommand(SqlCommand command)
    {
        command.Connection = _connection;
        command.CommandType = CommandType.StoredProcedure;
        _connection.Open();
 
        try
        {
            var reader = command.ExecuteReader();
            try
            {
                return Mapper.Map<IDataReader, IEnumerable<T>>(reader);
            }
            finally
            {
                reader.Close();
            }
        }
        finally
        {
            _connection.Close();
        }
    }
 
    protected virtual T GetRecord(SqlCommand command)
    {
        command.Connection = _connection;
        _connection.Open();
        try
        {
            var reader = command.ExecuteReader();
            try
            {
                reader.Read();
                return Mapper.Map<IDataReader, T>(reader);
            }
            finally
            {
                reader.Close();
            }
        }
        finally
        {
            _connection.Close();
        }
    }
 
    protected virtual IEnumerable<T> GetRecords(SqlCommand command)
    {
        command.Connection = _connection;
        _connection.Open();
        try
        {
            var reader = command.ExecuteReader();
            try
            {
                return Mapper.Map<IDataReader, IEnumerable<T>>(reader);
            }
            finally
            {
                reader.Close();
            }
        }
        finally
        {
            _connection.Close();
        }
    }
}
در اینجا کلاس پایه Mini ORM طراحی شده را ملاحظه می‌کنید. برای نمونه قسمت GetRecords آن مانند مباحث استاندارد ADO.NET است. فقط کار خواندن و همچنین نگاشت رکوردهای دریافت شده از بانک اطلاعاتی به شیء‌ایی از نوع T توسط AutoMapper انجام خواهد شد.


نحوه‌ی استفاده از کلاس پایه AdoMapper

در کدهای ذیل نحوه‌ی ارث بری از کلاس پایه AdoMapper و سپس استفاده از متدهای آن‌را ملاحظه می‌کنید:
public class UsersService : AdoMapper<User>, IUsersService
{
    public UsersService(string connectionString)
        : base(connectionString)
    {
    }
 
    public IEnumerable<User> GetAll()
    {
        using (var command = new SqlCommand("SELECT * FROM Users"))
        {
            return GetRecords(command);
        }
    }
 
    public User GetById(int id)
    {
        using (var command = new SqlCommand("SELECT * FROM Users WHERE Id = @id"))
        {
            command.Parameters.Add(new SqlParameter("id", id));
            return GetRecord(command);
        }
    }
}
در این مثال نحوه‌ی تعریف کوئری‌های پارامتری نیز در متد GetById به نحو متداولی مشخص شده‌است. کار نگاشت حاصل این کوئری‌ها به اشیاء دات نتی را AutoMapper انجام خواهد داد. نحوه‌ی کار نیز، نگاشت فیلد f1 به خاصیت f1 است (هم نام‌ها به هم نگاشت می‌شوند).


تعریف پروفایل مخصوص AutoMapper

ORMهای تمام عیار، کار نگاشت فیلدهای بانک اطلاعاتی را به خواص اشیاء دات نتی، به صورت خودکار انجام می‌دهند. در اینجا همانند روش‌های متداول کار با AutoMapper نیاز است این نگاشت را به صورت دستی یکبار تعریف کرد:
public class UsersProfile : Profile
{
    protected override void Configure()
    {
        this.CreateMap<IDataRecord, User>();
    }
 
    public override string ProfileName
    {
        get { return this.GetType().Name; }
    }
}
و سپس در ابتدای برنامه آن‌را به AutoMapper معرفی نمود:
Mapper.Initialize(cfg => // In Application_Start()
{
    cfg.AddProfile<UsersProfile>();
});


سفارشی سازی نگاشت‌های AutoMapper

فرض کنید کلاس Advertisement زیر، معادل است با جدول Advertisements بانک اطلاعاتی؛ با این تفاوت که در کلاس تعریف شده، خاصیت TitleWithOtherName تطابقی با هیچکدام از فیلدهای بانک اطلاعاتی ندارد. بنابراین اطلاعاتی نیز به آن نگاشت نخواهد شد.
public class Advertisement
{
    public int Id { set; get; }
    public string Title { get; set; }
    public string Description { get; set; }
    public int UserId { get; set; }
 
    public string TitleWithOtherName { get; set; }
}
برای رفع این مشکل می‌توان حین تعریف پروفایل مخصوص Advertisement، آن‌را سفارشی سازی نیز نمود:
public class AdvertisementsProfile : Profile
{
    protected override void Configure()
    {
        this.CreateMap<IDataRecord, Advertisement>()
            .ForMember(dest => dest.TitleWithOtherName,
                       options => options.MapFrom(src =>
                            src.GetString(src.GetOrdinal("Title"))));
    }
 
    public override string ProfileName
    {
        get { return this.GetType().Name; }
    }
}
در اینجا پس از تعریف نگاشت مخصوص کار با IDataRecordها، عنوان شده‌است که هر زمانیکه به خاصیت TitleWithOtherName رسیدی، مقدارش را از فیلد Title دریافت و جایگزین کن.


کدهای کامل این مطلب را از اینجا می‌توانید دریافت کنید.
مطالب
مدیریت پیشرفته‌ی حالت در React با Redux و Mobx - قسمت چهارم - انجام اعمال async با Redux
در قسمت اول این سری، با مفاهیم توابع خالص و ناخالص آشنا شدیم و همچنین عنوان شد که هرگونه تعامل با یک API خارجی به عنوان یک اثر جانبی و یا side effect در نظر گرفته شده و توابع را ناخالص می‌کند. به علاوه Redux تنها امکان کار با توابع خالص قابل پیش بینی را دارد و همچنین dispatch تمام اکشن‌ها توسط آن، به صورت پیش‌فرض synchronous است و نه asynchronous. اما در برنامه‌های واقعی نیاز است بتوان با یک API خارجی کارکرد و اطلاعاتی را از آن دریافت نمود و یا به آن ارسال کرد. برای رفع این مشکل، یک کتابخانه‌ی کمکی به نام redux-thunk ایجاد شده‌است که جزئیات کار با آن‌را در این قسمت بررسی می‌کنیم.


معرفی کتابخانه‌ی Redux Thunk

thunk، تابعی است که خروجی تابعی دیگر است؛ مانند مثال زیر:
function definitelyNotAThunk() {
   return function aThunk() {
     console.log('Hello, I am a thunk.');
   }
}
هدف اصلی از انجام یک چنین کاری، فراهم آوردن روشی برای اجرای به تاخیر افتاده‌است. برای مثال زمانیکه برای اجرای آن می‌نویسیم ()definitelyNotAThunk، تابع درونی آن هنوز اجرا نشده‌است. اجرای کامل آن چنین شکلی را دارد: ()()definitelyNotAThunk. حالا فرض کنید میان‌افزاری پیش از اجرای reducer قرار گرفته‌است که به تمام اشیاء رسیده‌ی به آن (یا همان اکشن‌ها در اینجا) گوش فرا می‌دهد. اگر اکشنی بجای یک شیء، یک تابع را بازگرداند، این میان‌افزار، آن‌را اجرا می‌کند یا همان ()() که عنوان شد. این کل کاری است که میان‌افزار 14 سطری redux-thunk انجام می‌دهد. زمانیکه از این میان‌افزار استفاده می‌شود، تابع درونی، دو پارامتر dispatch و getState را دریافت می‌کند. پارامتر dispatch که در حقیقت یک متد است، امکان اجرای اعمال synchronous و ارسال آن‌ها را به سمت reducer متناظر، میسر می‌کند.
برای مثال فرض کنید که نیاز است یک فراخوانی Ajax ای صورت گیرد و پس از پایان آن، جهت به روز رسانی state، یک شیء اکشن، به سمت reducer متناظری dispatch شود. مشکل اینجا است که نمی‌توان به Redux، یک callback حاصل از دریافت نتیجه‌ی عملیات Ajax ای و یا یک Promise را ارسال کرد. تمام این‌ها یک اثر جانبی یا side effect هستند که با توابع خالص Redux ای سازگاری ندارند. برای مدیریت یک چنین مواردی، یک میان‌افزار را به نام redux-thunk ایجاد کرده‌اند که اجازه‌ی dispatch تابعی را می‌دهد (همان thunk در اینجا) که قرار است action اصلی را در زمانی دیگر dispatch کند. به این ترتیب Redux اطلاعاتی را در مورد یک عمل async نخواهد داشت؛ میان‌افزاری در این بین آن‌را دریافت می‌کند و زمانیکه آن‌را dispatch می‌کنیم، آنگاه اکشن متناظر با آن، به redux منتقل می‌شود. به این ترتیب امکان منتظر ماندن تا زمان رسیدن پاسخ از شبکه، میسر می‌شود.
فرض کنید یک action creator متداول به صورت زیر ایجاد شده‌است:
export const getAllItems = () => ({
   type: UPDATE_ALL_ITEMS,
   items,
});
اکنون این سؤال مطرح می‌شود که چگونه می‌توان متوجه شد، پاسخی از سمت API دریافت شده‌است؟
برای پاسخ به این سؤال، اینبار action creator فوق را بر اساس الگوی redux-thunk به صورت زیر بازنویسی می‌کنیم:
export const getAllItems = () => {
  return dispatch => {
    Api.getAll().then(items => {
      dispatch({
        type: UPDATE_ALL_ITEMS,
        items,
      });
    });
  };
};
اینبار action creator ای را داریم که بجای بازگشت یک شیء، یک تابع را بازگشت داده‌است که به آن thunk گفته می‌شود و پارامتر dispatch را دریافت می‌کند. در این حالت زمانیکه یک Promise بازگشت داده می‌شود (امکان منتظر نتیجه شدن را فراهم می‌کند)، کار dispatch اکشن اصلی مدنظر (برای مثال ارسال آرایه‌ای از اشیاء)، صورت می‌گیرد.


برپایی پیش‌نیازها

در اینجا برای افزودن کامپوننتی که اطلاعات خودش را از یک API خارجی تامین می‌کند، از همان برنامه‌ی به همراه کامپوننت شمارشگر که در قسمت قبل آن‌را تکمیل کردیم، استفاده می‌کنیم. فقط در آن کتابخانه‌ها‌ی Axios و همچنین redux thunk را نیز نصب می‌کنید. به همین جهت در ریشه‌ی پروژه‌ی React این قسمت، دستور زیر را در خط فرمان صادر کنید:
> npm install --save axios redux-thunk
برنامه‌ی backend مورد استفاده هم همان برنامه‌ای است که از قسمت 22 شروع به توسعه‌ی آن کردیم و کدهای کامل آن‌را از پیوست‌های انتهای بحث، می‌توانید دریافت کنید. این برنامه که در مسیر شروع شده‌ی با https://localhost:5001/api قرار می‌گیرد، جهت پشتیبانی از افعال مختلف HTTP مانند Get/Post/Delete/Update طراحی شده‌است. برای راه اندازی آن، به پوشه‌ی این برنامه، مراجعه کرده و فایل dotnet_run.bat آن‌را اجرا کنید، تا endpointهای REST Api آن قابل دسترسی شوند. برای مثال باید بتوان به مسیر https://localhost:5001/api/posts آن در مرورگر دسترسی یافت.

پس از نصب پیش‌نیازها و راه اندازی برنامه‌ی backend، در ابتدا فایل src\config.json را جهت درج مشخصات آدرس REST Api، ایجاد می‌کنیم:
{
   "apiUrl": "https://localhost:5001/api"
}
در ادامه می‌خواهیم در برنامه‌ی React خود، لیست مطالب برنامه‌ی backend را از سرور دریافت کرده و نمایش دهیم.


افزودن میان‌افزار redux-thunk به برنامه

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


چون نیاز به عملیات async وجود دارد، باید از میان‌افزار مخصوص thunk برای انجام آن استفاده کرد. برای این منظور به فایل src\index.js مراجعه کرده و میان‌افزار thunk را توسط تابع applyMiddleware، به متد createStore، معرفی می‌کنیم:
import { applyMiddleware, compose, createStore } from "redux";
import thunk from "redux-thunk";

//...

const store = createStore(
  reducer,
  compose(
    applyMiddleware(thunk),
    window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__()
  )
);

//...
در اینجا چون نیاز است چندین تابع را به متد createStore ارسال کرد، باید از متد compose برای اعمال دسته جمعی آن‌ها کمک گرفت.


دریافت اطلاعات از یک API خارجی به کمک redux-thunk

پس از آشنایی با روش کلی برقراری اتصالات سیستم react-redux در قسمت قبل، پیاده سازی دریافت اطلاعات را بر اساس همان الگو، پیاده سازی می‌کنیم:
1)  ایجاد نام نوع اکشن متناظر با دکمه‌ی افزودن مقدار
به فایل src\constants\ActionTypes.js، نوع جدید دریافت مطالب را اضافه می‌کنیم:
export const GetPostsSuccess = "GetPostsSuccess";
export const GetPostsStarted = "GetPostsStarted";
export const GetPostsFailure = "GetPostsFailure";
در حین دریافت اطلاعات از API، حداقل سه اکشن نمایش loading (و یا GetPostsStarted در اینجا)، نمایش نهایی اطلاعات دریافت شده‌ی از سرور (و یا GetPostsSuccess در اینجا) و یا نمایش خطاهای حاصل (با نوع GetPostsFailure در اینجا) باید مدنظر باشند. به همین جهت سه نوع مختلف در اینجا تعریف شده‌اند.

2) ایجاد متد Action Creator
در فایل src\actions\index.js، متد ایجاد کننده‌ی شیء اکشن ارسالی به reducer متناظری را تعریف می‌کنیم تا بتوان بر اساس نوع آن در reducer دریافت اطلاعات، منطق نهایی را پیاده سازی کرد:
import axios from "axios";

import { apiUrl } from "../config.json";
import * as types from "../constants/ActionTypes";

export const fetchPosts = () => {
  return (dispatch, getState) => {
    dispatch(getPostsStarted());
    axios.get(apiUrl + "/posts").then(response => {
      dispatch(getPostsSuccess(response.data)).catch(err => {
        dispatch(getPostsFailure(err));
      });
    });
  };
};

export const fetchPostsAsync = () => {
  return async (dispatch, getState) => {
    dispatch(getPostsStarted());
    try {
      const { data } = await axios.get(apiUrl + "/posts");
      console.log(data);
      dispatch(getPostsSuccess(data));
    } catch (error) {
      dispatch(getPostsFailure(error));
    }
  };
};

const getPostsSuccess = posts => ({
  type: types.GetPostsSuccess,
  payload: { posts }
});

const getPostsStarted = () => ({
  type: types.GetPostsStarted
});

const getPostsFailure = error => ({
  type: types.GetPostsFailure,
  payload: {
    error
  }
});
- در اینجا همان الگوی بازگشت یک تابع را بجای یک شیء، در توابع action creator، مشاهده می‌کنید.
- تابع fetchPosts، از همان روش قدیمی callback، برای مدیریت اطلاعات دریافتی از سرور استفاده می‌کند. زمانیکه اطلاعاتی دریافت شد، آن‌را با فراخوانی dispatch و با قالبی که تابع getPostsSuccess ارائه می‌دهد، به reducer متناظر، ارسال می‌کند.
- تابع fetchPostsAsync، نمونه‌ی به همراه async/await کار با کتابخانه‌ی axios است. هر دو روش callback و یا async/await در اینجا پشتیبانی می‌شوند.

به صورت پیش‌فرض action creators کتابخانه‌ی redux از اعمال async پشتیبانی نمی‌کنند. برای رفع این مشکل پس از ثبت میان‌افزار thunk، اینبار متدهای action creator، بجای بازگشت یک شیء، یک تابع را بازگشت می‌دهند که این تابع درونی در زمانی دیگر توسط میان‌افزار thunk و پیش از رسیدن به reducer، فراخوانی خواهد شد. این تابع درونی، دو پارامتر dispatch و  getState را دریافت می‌کند. هر دوی این‌ها نیز متد هستند. برای مثال اگر نیاز به دریافت وضعیت فعلی state در اینجا وجود داشت، می‌توان متد ()getState رسیده را فراخوانی کرد و حاصل آن‌را بررسی نمود. برای مثال شاید تصمیم گرفته شود که بر اساس وضعیت فعلی state، نیازی نیست تا اطلاعاتی از سرور دریافت شود و بهتر است همان اطلاعات کش شده‌ی موجود در state را بازگشت دهیم. البته در این مثال فقط از متد dispatch ارسالی، برای بازگشت نتیجه‌ی نهایی به reducer متناظر، استفاده شده‌است.

- در نهایت آرایه‌ی اشیاء مطلب دریافتی از سرور، به عنوان مقدار خاصیت posts شیء منتسب به خاصیت payload شیء ارسالی به reducer، در متد getPostsSuccess تعریف شده‌است. یعنی reducer متناظر، اطلاعات را از طریق خاصیت action.payload.posts شیء رسیده، دریافت می‌کند.
- همچنین دو اکشن شروع به دریافت اطلاعات (getPostsStarted) و بروز خطا (getPostsFailure) نیز در ابتدا و در قسمت catch عملیات async، به سمت reducer متناظر، dispatch خواهند شد.

3) ایجاد تابع reducer مخصوص دریافت اطلاعات از سرور
اکنون در فایل جدید src\reducers\posts.js، بر اساس نوع شیء رسیده و مقدار action.payload.posts آن، کار تامین آرایه‌ی posts موجود در state انجام می‌شود:
import * as types from "../constants/ActionTypes";

const initialState = { loading: false, posts: [], error: null };

export default function postsReducer(state = initialState, action) {
  switch (action.type) {
    case types.GetPostsStarted:
      return {
        loading: true,
        posts: [],
        error: null
      };
    case types.GetPostsSuccess:
      return {
        loading: false,
        posts: action.payload.posts,
        error: null
      };
    case types.GetPostsFailure:
      return {
        loading: false,
        posts: [],
        error: action.payload.error
      };
    default:
      return state;
  }
}
در این reducer با استفاده از یک switch، سه حالت ممکنی را که اکشن‌های رسیده‌ی به آن می‌توانند داشته باشند، مدیریت کرده‌ایم:
- در حالت آغاز کار و یا GetPostsStarted، با تنظیم خاصیت loading به true، سبب نمایش یک div «لطفا منتظر بمانید» خواهیم شد.
- در حالت دریافت نهایی اطلاعات از سرور، خاصیت loading به false تنظیم می‌شود تا div «لطفا منتظر بمانید» را مخفی کند. همچنین آرایه‌ی posts را نیز از payload رسیده استخراج کرده و به سمت کامپوننت‌ها ارسال می‌کند.
- در حالت بروز خطا و یا GetPostsFailure، خاصیت error شیء action.payload استخراج شده و جهت نمایش div متناظری، بازگشت داده می‌شود.

پس از تعریف این reducer باید آن‌را در فایل src\reducers\index.js به کمک combineReducers، با سایر reducer‌های موجود، ترکیب و یکی کرد تا در نهایت این rootReducer در فایل index.js اصلی برنامه، جهت ایجاد store اصلی redux، مورد استفاده قرار گیرد:
import { combineReducers } from "redux";

import counterReducer from "./counter";
import postsReducer from "./posts";

const rootReducer = combineReducers({
  counterReducer,
  postsReducer
});

export default rootReducer;


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

UI این قسمت از سه کامپوننت تشکیل شده‌است که کدهای کامل آن‌ها را در ادامه مشاهده می‌کنید:

الف) کامپوننت src\components\FetchPosts.jsx
import React from "react";

const FetchPosts = ({ fetchPostsAsync }) => {
  return (
    <section className="card mt-5">
      <div className="card-header text-center">
        <button className="btn btn-primary" onClick={fetchPostsAsync}>
          Fetch Posts
        </button>
      </div>
    </section>
  );
};

export default FetchPosts;
کار این کامپوننت، نمایش دکمه‌ی Fetch Posts است. با کلیک بر روی آن قرار است action creator ای به نام fetchPostsAsync اجرا شود که کدهای آن‌را پیشتر مرور کردیم.
همانطور که مشاهده می‌کنید، این کامپوننت هیچ اطلاعاتی از وجود کامپوننت دومی که قرار است لیست مطالب را نمایش دهد، ندارد. کارش تنها dispatch یک اکشن است.
بنابراین این کامپوننت از طریق props فقط یک اشاره‌گر به متد رویدادگردانی را دریافت می‌کند و اطلاعات دیگری را نیاز ندارد.

ب) کامپوننت src\components\Posts.jsx
import React from "react";

import Post from "./Post";

const Posts = ({ posts, loading, error }) => {
  return (
    <>
      <section className="card mt-5">
        <div className="card-header">
          <h2>Posts</h2>
        </div>
        <div className="card-body">
          {loading ? (
            <div className="alert alert-info">Loading ...</div>
          ) : (
            <div className="list-group list-group-flush">
              {posts.map(post => (
                <Post key={post.id} post={post} />
              ))}
            </div>
          )}
          {error && <div className="alert alert-warning">{error.message}</div>}
        </div>
      </section>
    </>
  );
};

export default Posts;
این کامپوننت، آرایه‌ای از اشیاء مطالب را دریافت کرده و با  ایجاد حلقه‌ای بر روی آن‌ها، توسط کامپوننت Post، هر کدام را در صفحه درج می‌کند. بنابراین این کامپوننت اکشنی را dispatch نمی‌کند. فقط از طریق props، یک آرایه‌ی posts، وضعیت جاری عملیات و خطاهای حاصل را باید دریافت کند.
در این کامپوننت اگر loading رسیده به true تنظیم شده باشد، یک div با عبارت loading نمایش داده می‌شود. در غیراینصورت، لیست مطالب را درج می‌کند. همچنین اگر خطایی نیز رخ داده باشد، آن‌را نیز درون یک div در صفحه نمایش می‌دهد.

ج) کامپوننت src\components\Post.jsx
import React from "react";

const Post = ({ post }) => {
  return (
    <article className="list-group-item">
      <header>
        <h2>{post.title}</h2>
      </header>
      <p>{post.body}</p>
    </article>
  );
};

export default Post;
این کامپوننت کار نمایش جزئیات هر رکورد مطلب را به عهده دارد؛ مانند نمایش عنوان و بدنه‌ی یک مطلب.


اتصال کامپوننت‌های FetchPosts و Posts به مخزن redux

مرحله‌ی آخر کار، تامین state کامپوننت‌های FetchPosts و Posts از طریق props است. به همین جهت باید دو دربرگیرنده را برای این دو کامپوننت ایجاد کنیم.
الف) ایجاد دربرگیرنده‌ی کامپوننت FetchPosts
برای این منظور فایل جدید src\containers\FetchPosts.js را با محتوای زیر ایجاد می‌کنیم:
import { connect } from "react-redux";

import { fetchPostsAsync } from "../actions";
import FetchPosts from "../components/FetchPosts";

const mapDispatchToProps = {
  fetchPostsAsync
};

export default connect(null, mapDispatchToProps)(FetchPosts);
- کار این تامین کننده، اتصال action creator ای به نام fetchPostsAsync به props کامپوننت FetchPosts است.
- چون اطلاعات state ای قرار نیست به این کامپوننت ارسال شود، تابع mapStateToProps را در اینجا مشاهده نمی‌کنید و با نال مقدار دهی شده‌است.

ب) ایجاد دربرگیرنده‌ی کامپوننت Posts
برای این منظور فایل جدید src\containers\Posts.js را با محتوای زیر ایجاد می‌کنیم:
import { connect } from "react-redux";

import Posts from "../components/Posts";

const mapStateToProps = state => {
  console.log("PostsContainer->mapStateToProps", state);
  return {
    ...state.postsReducer
  };
};

export default connect(mapStateToProps)(Posts);
- کار این تامین کننده، اتصال خاصیت posts بازگشت داده شده‌ی از طریق postsReducer، به props کامپوننت Posts است. البته چون کامپوننت Posts سه خاصیت { posts, loading, error } را از طریق props دریافت می‌کند، با استفاده از spread operator، هر سه خاصیت دریافتی از reducer را به صورت یک شیء جدید بازگشت داده‌ایم.
- کامپوننت Posts رویدادی را سبب نخواهد شد. به همین جهت تابع mapDispatchToProps را در اینجا تعریف و ذکر نکرده‌ایم.


استفاده از کامپوننت‌های دربرگیرنده جهت نمایش نهایی کامپوننت‌های تحت کنترل Redux

اکنون به فایل src\App.js مراجعه کرده و دو تامین کننده‌ی فوق را درج می‌کنیم:
import "./App.css";

import React from "react";

import CounterContainer from "./containers/Counter";
import FetchPostsContainer from "./containers/FetchPosts";
import PostsContainer from "./containers/Posts";

function App() {
  const prop1 = 123;
  return (
    <main className="container">
      <div className="row">
        <div className="col">
          <CounterContainer prop1={prop1} />
        </div>
        <div className="col">
          <FetchPostsContainer />
        </div>
        <div className="col">
          <PostsContainer />
        </div>
      </div>
    </main>
  );
}

export default App;
در اینجا FetchPostsContainer و PostsContainer سبب خواهند شد تا اتصالات مخزن اصلی redux، به کامپوننت‌هایی که توسط آن‌ها دربرگرفته شده‌اند، برقرار شود و کار تامین props آن‌ها صورت گیرد.

یک نکته: برای مثال در انتهای کامپوننت FetchPosts، سطر export default FetchPosts را داریم. اگر این سطر را حذف کنیم و بجای آن export default connect فوق را قرار دهیم، دیگر نیازی نخواهد بود تا FetchPostsContainer را از دربرگیرنده‌ها، import کرد و سپس بجای درج المان </FetchPosts> نوشت </FetchPostsContainer>. می‌توان همانند قبل از همان نام متداول </FetchPosts> استفاده کرد و import انجام شده نیز همانند سابق از همان فایل ماژول کامپوننت صورت می‌گیرد. یعنی می‌توان پوشه‌ی containers را حذف کرد و کدهای آن را دقیقا ذیل کلاس کامپوننت درج نمود.


کدهای کامل این قسمت را می‌توانید از اینجا دریافت کنید: state-management-redux-mobx-part04-backend.zip و state-management-redux-mobx-part04-frontend.zip
مطالب دوره‌ها
Lazy loading در تزریق وابستگی‌ها به کمک StructureMap
پیشنیاز این بحث، مطلب «استفاده از StructureMap به عنوان یک IoC Container» می‌باشد که پیشتر در این سری مطالعه کردید (در حد نحوه نصب StructureMap و آشنایی با تنظیمات اولیه آن)

ابتدا ساختار بحث جاری را به نحو زیر درنظر بگیرید:
namespace DI04.Services
{
    public interface IAccounting
    {
        void CreateInvoice(int orderId, int count);
    }
}

namespace DI04.Services
{
    public interface ISales
    {
        bool ShippingAllowed(int orderId);
    }
}

namespace DI04.Services
{
    public interface IOrderHandler
    {
        void Handle(int orderId, int count);
    }
}

using System;

namespace DI04.Services
{
    public class Accounting : IAccounting
    {
        public Accounting()
        {
            Console.WriteLine("Accounting ctor.");
        }

        public void CreateInvoice(int orderId, int count)
        {
            // ...
        }
    }
}

using System;

namespace DI04.Services
{
    public class Sales : ISales
    {
        public Sales()
        {
            Console.WriteLine("Sales ctor.");
        }

        public bool ShippingAllowed(int orderId)
        {
            // فقط جهت آزمایش سیستم
            return false;
        }
    }
}

using System;

namespace DI04.Services
{
    public class OrderHandler : IOrderHandler
    {
        private readonly IAccounting _accounting;
        private readonly ISales _sales;
        public OrderHandler(IAccounting accounting, ISales sales)
        {
            Console.WriteLine("OrderHandler ctor.");
            _accounting = accounting;
            _sales = sales;
        }

        public void Handle(int orderId, int count)
        {
            if (_sales.ShippingAllowed(orderId))
            {
                _accounting.CreateInvoice(orderId, count);
            }
        }
    }
}
در اینجا کار مدیریت سفارشات در کلاس OrderHandler انجام می‌شود. این کلاس دارای دو وابستگی تزریق شده در سازنده کلاس می‌باشد.
در متد Handle، اگر مجوز کار توسط متد ShippingAllowed صادر شد، آنگاه کار نهایی توسط متد CreateInvoice باید صورت گیرد. با توجه به اینکه تزریق وابستگی‌ها در سازنده کلاس صورت می‌گیرد، نیاز است پیش از وهله سازی کلاس OrderHandler، هر دو وابستگی آن وهله سازی و تزریق شوند. در حالیکه در مثال جاری هیچگاه به وهله‌ای از نوع IAccounting نیاز نخواهد شد؛ زیرا متد ShippingAllowed در این مثال، فقط false بر می‌گرداند.
و از این نمونه‌ها زیاد هستند. کلاس‌هایی با تعداد متدهای بالا و تعداد وابستگی‌های قابل توجه که ممکن است در طول عمر شیء وهله سازی شده این کلاس، تنها به یکی از این وابستگی‌ها نیاز شود و نه به تمام آن‌ها.
راه حلی برای این مساله در دات نت 4 با معرفی کلاس Lazy ارائه شده است؛ به این نحو که اگر برای مثال در اینجا accounting را Lazy تعریف کنیم، تنها زمانی وهله سازی خواهد شد که به آن نیاز باشد و نه پیش از آن.
 private readonly Lazy<IAccounting> _accounting;

سؤال: Lazy loading تزریق وابستگی‌ها را چگونه می‌توان توسط StructureMap فعال ساخت؟

ابتدا تعاریف کلاس  OrderHandlerرا به نحو زیر بازنویسی می‌کنیم:
using System;

namespace DI04.Services
{
    public class OrderHandlerLazy : IOrderHandler
    {
        private readonly Lazy<IAccounting> _accounting;
        private readonly Lazy<ISales> _sales;
        public OrderHandlerLazy(Lazy<IAccounting> accounting, Lazy<ISales> sales)
        {
            Console.WriteLine("OrderHandlerLazy ctor.");
            _accounting = accounting;
            _sales = sales;
        }

        public void Handle(int orderId, int count)
        {
            if (_sales.Value.ShippingAllowed(orderId))
            {
                _accounting.Value.CreateInvoice(orderId, count);
            }
        }
    }
}
در اینجا سازنده‌های کلاس، به صورت Lazy معرفی شده‌اند. دسترسی به فیلدهای sales و accounting نیز اندکی تغییر کرده‌اند و اینبار از طریق خاصیت Value آن‌ها باید انجام شود.
مرحله نهایی هم اندکی تغییر در نحوه معرفی تنظیمات اولیه StructureMap است:
using System;
using DI04.Services;
using StructureMap;

namespace DI04
{
    class Program
    {
        static void Main(string[] args)
        {
            // تنظیمات اولیه برنامه که فقط یکبار باید در طول عمر برنامه انجام شود
            ObjectFactory.Initialize(x =>
            {
                x.For<IOrderHandler>().Use<OrderHandlerLazy>();

                // Lazy loading
                x.For<Lazy<IAccounting>>().Use(c => new Lazy<IAccounting>(c.GetInstance<Accounting>));
                x.For<Lazy<ISales>>().Use(c => new Lazy<ISales>(c.GetInstance<Sales>));
            });

            var orderHandler = ObjectFactory.GetInstance<IOrderHandler>();
            orderHandler.Handle(orderId: 1, count: 10);
        }
    }
}
به این ترتیب زمانیکه برنامه به sales.Value می‌رسد آنگاه نیاز به وهله سازی شیء متناظر با آن‌را خواهد داشت که در اینجا از طریق متد GetInstance به آن ارسال خواهد گردید.

خروجی برنامه در این حالت:
OrderHandlerLazy ctor.
Sales ctor.
همانطور که مشاهده می‌کنید، هرچند کلاس OrderHandlerLazy دارای دو وابستگی تعریف شده در سازنده کلاس است، اما تنها وابستگی Sales آن زمانیکه به آن نیاز شده، وهله سازی گردیده است و خبری از وهله سازی کلاس Accounting نیست (چون مطابق تعاریف کلاس‌های برنامه هیچگاه به مسیر accounting.Value نخواهیم رسید؛ بنابراین نیازی هم به وهله سازی آن نخواهد بود).


دریافت مثال این قسمت
DI04.zip
مطالب
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  زیاد باشد باعث افزایش کارایی و سرعت انجام عملیات سریالایز می‌شود.