نظرات مطالب
React 16x - قسمت 26 - احراز هویت و اعتبارسنجی کاربران - بخش 1 - ثبت نام و ورود به سیستم
آیا زمان ثبت نام در سمت سرور نباید هم access_token و هم refresh_token رو تولید و به سمت کلاینت ارسال کرد؟
با بررسی کدهای مربوط به backend این سری و مبحث مربوط به اعتبارسنجی مبتنی بر JWT در ASP.NET Core 2.0 بدون استفاده از سیستم Identity  عملیات ثبت نام رو به صورت زیر پیاده سازی کردم.
//do register action and send sms message to client
//then call activateRegisterCode action from client to server
//In the following run operation authentication for user
 [AllowAnonymous]
        [HttpPost("[action]")]
        [IgnoreAntiforgeryToken]
        public async Task<IActionResult> ActivateRegisterCode([FromBody] ActiveCodeModel model)
        {
            if (model == null)
            { return BadRequest("درخواست نامعتبر"); }

            var user = await _userService.FindUserByKeyCodeAsync(model.Keycode);
            if (user == null)
            {
                return BadRequest("کد امنیتی معتبر نیست لطفا مجددا درخواست کد امنیتی کنید");
            }

            user.IsActive = true;
            var userUpdated = await _userService.UpdateAsync(user, CancellationToken.None);
            var jwt = await _tokenFactoryService.CreateJwtTokensAsync(userUpdated);

            this.Response.Headers.Add("x-auth-token", jwt.AccessToken);
            this.Response.Headers.Add("access-control-expose-headers", "x-auth-token");
            _antiforgery.RegeneratedAntiForgeryCookie(jwt.Claims);
            return Ok();


        }
در ادامه پیاده سازی action logout  برای حذف توکن‌ها از دیتابیس
 [AllowAnonymous]
        [HttpGet("[action]"), HttpPost("[action]")]
        public async Task<bool> Logout(string refreshtoken)
        {
            var claimsIdentity = this.User.Identity as ClaimsIdentity;
            var userIdValue = claimsIdentity.FindFirst(ClaimTypes.UserData)?.Value;

            if (!string.IsNullOrWhiteSpace(userIdValue) && Guid.TryParse(userIdValue, out Guid userId))
            {
                await _tokenStoreService.InvalidateUserTokensAsync(userId).ConfigureAwait(false);
            }
            await _tokenStoreService.DeleteExpiredTokensAsync().ConfigureAwait(false);
            await _uow.SaveChangesAsync().ConfigureAwait(false);
            _antiforgery.DeleteAntiForgeryCookie();
            return true;
        }
مشکلی که هست تو اکشن logout شی userIdValue خالیه و بنظر میرسه که کوکی مقدار نداره!
آیا باید زمان فراخوانی این اکشن refreshtoken رو هم بهش ارسال کنیم؟
تو اکشن لاگین هم کوکی‌ها مقدار نمیگیره یا اینکه اصلا با کوکی‌ها کار نداریم و بر اساس refreshtoken باید مقادیر claim‌های داخلش رو decode و بررسی کرد و سپس بر اساس اون در بانک اطلاعاتی عملیات خذف توکن‌های صادر شده در زمان ثبت نام و یا لاگین رو انجام داد.
مطالب
روش های مختلف پردازش یک رشته و تبدیل آن به نوع داده تاریخ
DateTime در طبقه بندی سی شارپ، جزء Strcut Type‌ها قرار می‌گیرد . عمدتا از DateTime برای مدیریت تاریخ، زمان و یا تاریخ-زمان استفاده می‌شود. خیلی از اوقات ما نیاز داریم تا رشته‌ای را به نوع تاریخ تبدیل کنیم تا بتوانیم عملیات مختلفی، همچون محاسبه‌ی اختلاف دو تاریخ، روز هفته، روز ماه و غیره را بدست آوریم. در دات نت متد‌های مختلفی وجود دارند که جداسازی تاریخ را از یک رشته برای ما فراهم می‌کنند:
  • Convert.ToDateTime()
  • DateTime.Parse()
  • DateTime.ParseExact()
  • DateTime.TryParse()
  • DateTime.TryParseExact()
در این مطلب این متد‌ها و تفاوت آنها را بررسی می‌کنیم.

تابع ()Convert.ToDateTime 
این تابع یک رشته‌ی با فرمت مشخص را به تاریخ و زمان تبدیل می‌کند. overload‌ها مختلف این تابع را در بخش زیر مشاهده می‌کنید:
• ToDateTime(string value)
Value  : رشته‌ای از تاریخ و زمان است.
 • ToDateTime(string value,IFormatProvider provide)
Value  : رشته‌ای از تاریخ و زمان است.
Provider : اطلاعات فرهنگ مورد نظر را فراهم می‌کند.
CultureInfo culture = new CultureInfo("en-US");    
DateTime tempDate = Convert.ToDateTime("1/1/2010 12:10:15 PM", culture);
در اینجا en-us اطلاعاتی را درباره‌ی فرهنگ کشور آمریکا، ارائه می‌دهد. لیست کامل فرهنگ‌های موجود در net. را می‌توانید در اینجا مشاهده کنید.
اگر رشته‌ی ما تهی (null) نباشد، بصورت درونی متد ()DateTime.Parse فراخوانی و نتیجه‌ی آن بازگردانده می‌شود. اما در صورتیکه رشته‌ی ارسالی ما تهی باشد، مقدار بازگردانده شده مقدار DateTime.MinValue که برابر با 0001/1/1 می‌باشد، بازگردانده می‌شود.
نمونه‌ای از خروجی این تابع با ورودی‌های مختلف :
string datestr = null;
Console.WriteLine(Convert.ToDateTime(datestr));//0001-01-01T00:00:00
datestr = "wrong string";
Console.WriteLine(Convert.ToDateTime(datestr));
//Unhandled Exception: System.FormatException: 
//The string was not recognized as a valid DateTime. 
//There is an unknown word starting at index 0.
datestr = "Tue Dec 30,2015";
Console.WriteLine(Convert.ToDateTime(datestr));
//Unhandled Exception: System.FormatException: 
//String was not recognized as a valid DateTime.

تابع () DateTime.Parse :
این تابع یک رشته‌ی با فرمت مشخص را به تاریخ و زمان تبدیل می‌کند. دو overload این تابع را در زیر می‌بینیم:
• DateTime.Parse(string value)
Value  : رشته‌ای از تاریخ و زمان می‌باشد.
• DateTime.Parse(String value, IFormatProvider provider)
Value : رشته‌ای از تاریخ و زمان می‌باشد.
Provider : اطلاعات فرهنگ مورد نظر را فراهم می‌کند.
  • DateTime.Parse(String value, IFormatProvider provider, DateTypeStyles styles)
Value : رشته‌ای از تاریخ و زمان می‌باشد.
Provider : اطلاعات فرهنگ مورد نظر را فراهم می‌کند 
Styles  : از طریق این پارامتر، تنظیمات قالب بندی رشته‌ی دریافتی برای سفارشی سازی کردن عملیات پردازش تعریف می‌شود. فرض کنید می‌خواهید کلیه‌ی فضاهای خالی (Space) را که قبل و بعد از رشته‌ی تاریخ هستند و نه در داخل رشته‌ی تاریخ، در زمان جداسازی و پردازش در نظر نگیرید. این پارامتر می‌تواند در اینجا به کمک ما بیاید (DateTimeStyles.AllowWhiteSpaces). مشاهده‌ی لیست کامل این خصوصیت از اینجا.
اگر مقدار رشته تهی باشد، استثنای Null نمایش داده خواهد شد (ArgumentNullException ) و اگر فرمت تاریخ ورودی صحیح نباشد، با استثنای فرمت غیرمعتبر روبرو خواهیم شد (FormatException).
string datestr = null;
Console.WriteLine(DateTime.Parse(datestr));
//// Exception: Argument null exception
datestr = "wrong string";            
Console.WriteLine(DateTime.Parse(datestr));
//// Exception: The string was not recognized as a valid DateTime. 
//// There is an unknown word starting at index 0.  
datestr = "Tue Dec 30, 2015";
//Unhandled Exception: System.FormatException: 
//String was not recognized as a valid DateTime. 
Console.WriteLine(DateTime.Parse(datestr));  

تابع ()DateTime.ParsExact
این تابع رشته‌ای شامل تاریخ و زمان را به‌همراه فرهنگ ارسالی، دریافت و آن را تبدیل به نوع DateTime می‌کند. فرمت رشته‌ی ارسالی باید با فرمت استاندارد رشته‌ی تاریخ یکسان باشد. overload‌های مختلف این تابع را در زیر مشاهده می‌کنید:
 • DateTime.ParseExact(string value, string format, IFormatProvider provider)
Value : رشته‌ای از تاریخ و زمان می‌باشد.
Provider : اطلاعات فرهنگ مورد نظر را فراهم می‌کند.

• DateTime.ParseExact(string value, string format, IFormatProvider provider, DateTimeStyles style)
Value : رشته‌ای از تاریخ و زمان می‌باشد.
Format : فرمت مورد نظر برای نمایش تاریخ بعد از تبدیل را مشخص می‌کند.
Provider : اطلاعات فرهنگ مورد نظر را فراهم می‌کند.
Styles  : از طریق این پارامتر تنظیمات قالب بندی رشته برای سفارشی کردن عملیات جداسازی و پردازش تعریف می‌شود.
• DateTime.ParseExact(string value, string[] formats, IFormatProvider provider, DateTimeStyles style)
 
Value : رشته‌ای از تاریخ و زمان می‌باشد.
Format : فرمت مورد نظر برای نمایش تاریخ بعد از تبدیل را مشخص می‌کند. تفاوت این حالت با حالت قبل این است که لیستی از فرمت‌ها را قبول می‌کند و حداقل باید یک فرمت با رشته‌ی ارسالی قابل انطباق باشد.
Provider : اطلاعات فرهنگ مورد نظر را فراهم می‌کند.
Styles : از طریق این پارامتر تنظیمات قالب بندی رشته برای سفارشی کردن عملیات جداسازی تعریف می‌شود. 
اگر مقدار رشته تهی باشد، استثنای Null نمایش داده خواهد شد (ArgumentNullException) و اگر فرمت تاریخ ورودی صحیح نباشد، با استثنای فرمت غیرمعتبر روبرو خواهیم شد(FormatException).
فرمت رشته‌ی تاریخ حتما باید با فرمت نوع تاریخ منطبق باشد. به همین منظور حالت‌های مختلفی را در آرایه می‌توان پیش بینی کرد. بطور مثال ممکن است رشته‌ی ما بصورت "2012-2-1" و یا "2012/2/1" باشد. بنابر این فرمت‌های "MM/dd/yyyy" و "MM-dd-yyyy" را از طریق آرایه ارسال می‌کنیم.
برای درک بهتر این موضوع، کد‌های زیر را مشاهده کنید: 
string datestr = null;
CultureInfo provider = CultureInfo.InvariantCulture;
Console.WriteLine(DateTime.ParseExact(datestr, "mm/dd/yyyy", provider));
//Unhandled Exception: System.ArgumentNullException: 
//String reference not set to an instance of a String.
datestr = "wrong date";
Console.WriteLine(DateTime.ParseExact(datestr, "mm/dd/yyyy", provider));
//Unhandled Exception: System.FormatException: 
//String was not recognized as a valid DateTime
datestr = "Tue Dec 30, 2015";
Console.WriteLine(DateTime.ParseExact(datestr, "mm/dd/yyyy", provider));
//Unhandled Exception: System.FormatException: 
//String was not recognized as a valid DateTime.
datestr = "10-22-2015";
Console.WriteLine(DateTime.ParseExact(datestr, "MM-dd-yyyy", provider));
//30/07/1394 12:00:00 ق.ظ
datestr = "10-22-2015";
Console.WriteLine(DateTime.ParseExact(datestr, new string[]
    {"MM-dd-yyyy", "MM/dd/yyyy", "MM.dd.yyyy"}, provider, DateTimeStyles.None));
//30/07/1394 12:00:00 ق.ظ

تابع ()DateTime.TryParse
این تابع رشته‌ای شامل تاریخ و زمان را به‌همراه فرهنگ مشخصی، دریافت و آن را تبدیل به نوع DateTime می‌کند و یک مقدار bool را برای اعلان این موضوع که عملیات تبدیل با موفقیت انجام شده است یا خیر، باز می‌گرداند. فرمت رشته‌ی ارسالی باید با فرمت رشته‌ی تاریخ یکسان باشد. overload‌های مختلف این تابع را در زیر مشاهده می‌کنید:
 
• DateTime.TryParse (String value, out DateTime result)
Value : رشته‌ای از تاریخ و زمان می‌باشد.
Result : مقدار تاریخ را بعد از جداسازی در خود ذخیره می‌کند.

• DateTime.TryParse(String value, IFormatProvider provider, DateTimeStyles styles, out DateTime result)
Value : رشته‌ای از تاریخ و زمان می‌باشد.
Provider : اطلاعات فرهنگ مورد نظر را فراهم می‌کند.
Styles  : از طریق این پارامتر می‌توانیم قالب پارامتر ارسالی را مشخص کنیم (مثلا نادیده گرفتن فضاهای خالی).
Result : مقدار تاریخ را بعد از جداسازی در خود ذخیره می‌کند.
این تابع همیشه سعی می‌کند رشته‌ی تاریخ را جداسازی کند. در صورت موفقیت در عملیات جداسازی رشته، تاریخ معتبر را بر می‌گرداند و در غیر اینصورت میزان کوچکترین تاریخ یا همان MinValue را که قبلا در همین مطلب اشاره شد، باز می‌گرداند. در صورتی هم که رشته‌، null یا با فرمت رشته‌ای غیر معتبر باشد، همان مقدار DateTime.MinValue بازگردانده می‌شود. همیشه با کنترل مقدار بازگشتی صحت انجام عملیات را می‌توان متوجه شد (true موفقیت در عملیات جداسازی و false عدم موفقیت در عملیات جداسازی).
نکته‌ی مهم عدم پرتاب استثتاء در صورت عدم موفقیت در عملیات جداسازی می‌باشد.
string datestr = null;
            DateTime temp;
            Console.WriteLine(DateTime.TryParse(datestr, out temp));
            Console.WriteLine(temp);
            //False
            //0001 - 01 - 01T00: 00:00

            datestr = "wrong date";
            Console.WriteLine(DateTime.TryParse(datestr, out temp));
            Console.WriteLine(temp);
            //False
            //0001 - 01 - 01T00: 00:00

            datestr = "Tue Dec 30, 2015";
            Console.WriteLine(DateTime.TryParse(datestr, out temp));
            Console.WriteLine(temp);
            //False
            //0001 - 01 - 01T00: 00:00

تابع ()DateTime.TryParseExact
این تابع رشته‌ای شامل تاریخ و زمان را به‌همراه فرهنگ مشخصی دریافت و آن را تبدیل به نوع DateTime می‌کند. رشته‌ی ارسالی باید منطبق با فرمت تاریخ باشد. overload‌های مختلف این تابع را در زیر مشاهده می‌کنید:

• DateTime.ParseExact(string value, string format, IFormatProvider provider, DateTimeStyles style)
Value : رشته‌ای از تاریخ و زمان می‌باشد.
Format : فرمت مورد نظر را برای نمایش تاریخ بعد از تبدیل، مشخص می‌کند.
Provider : اطلاعات فرهنگ مورد نظر را فراهم می‌کند.
Styles  : از طریق این پارامتر می‌توانیم قالب پارامتر ارسالی را مشخص کنیم (مثلا نادیده گرفتن فضاهای خالی).

• DateTime.ParseExact(string value, string[] formats, IFormatProvider provider, DateTimeStyles style)
Value : رشته‌ای از تاریخ و زمان می‌باشد.
Format : فرمت مورد نظر برای نمایش تاریخ بعد از تبدیل را مشخص می‌کند و می‌توان آرایه‌ای از فرمت‌ها را در اینجا ارسال کرد.
Provider : اطلاعات فرهنگ مورد نظر را فراهم می‌کند.
Styles  : از طریق این پارامتر می‌توانیم قالب پارامتر ارسالی را مشخص کنیم (مثلا نادیده گرفتن فضاهای خالی).
این تابع را در شرایط زیر، مقدار (MinValue( 1/1/0001 12:00:00 AM را باز می‌گرداند:
• رشته null  باشد.
• رشته خالی باشد "".
• فرمت رشته‌ی تاریخ غلط باشد.
• رشته با فرمت معرفی شده‌ی در provider، انطباق نداشته باشد.
• تنها در صورتی که مقدار انتخابی DateTimeStyle معتبر نباشد، استثنایی رخ می‌دهد.
همچنین همیشه پس از انجام عملیات، مقداری بولین را برای نمایش موفقیت یا عدم موفقیت جدا سازی، باز می‌گرداند.
  string datestr = null;
            DateTime temp;
            CultureInfo provider = CultureInfo.InvariantCulture;
            Console.WriteLine(DateTime.TryParseExact(datestr, "MM/dd/yyyy", provider, DateTimeStyles.None, out temp));
            Console.WriteLine(temp);
            //False
            //1/1/0001 12:00:00 AM

            datestr = "wrong date";
            Console.WriteLine(DateTime.TryParseExact(datestr, "MM/dd/yyyy", provider, DateTimeStyles.None, out temp));
            Console.WriteLine(temp);
            //False
            //1/1/0001 12:00:00 AM

            datestr = "Tue Dec 30, 2015";
            Console.WriteLine(DateTime.TryParseExact(datestr, "MM/dd/yyyy", provider, DateTimeStyles.None, out temp));
            Console.WriteLine(temp);
            //False
            //1/1/0001 12:00:00 AM

            datestr = "10‐22‐2015";
            Console.WriteLine(DateTime.TryParseExact(datestr, "MM/dd/yyyy", provider, DateTimeStyles.None, out temp));
            Console.WriteLine(temp);
            //False
            //1/1/0001 12:00:00 AM

            datestr = "10‐22‐2015";
            Console.WriteLine(DateTime.TryParseExact(
            datestr, "MM‐dd‐yyyy", provider, DateTimeStyles.None, out temp));
            Console.WriteLine(temp);
            //True
            //30/07/1394 12:00:00 ق.ظ

            datestr = "10‐12‐2015";
            Console.WriteLine(DateTime.TryParseExact(datestr, new string[] { "MM/dd/yyyy", "MM‐dd‐yyyy", "MM.dd.yyyy" }, provider, DateTimeStyles.None, out temp));
            Console.WriteLine(temp);
            //True
            //20/07/1394 12:00:00 ق.ظ


آنالیز متد‌های معرفی شده 
نوع DateTime متد‌های مختلفی را برای جداسازی رشته و تبدیل آن به تاریخ دارد. تفاوت‌های این متد‌ها را در بخش زیر بررسی می‌کنیم :

تفاوت Parse و ConvertToDateTime:
این دو متد شبیه به هم هستند، اما چند تفاوت کوچک با هم دارند:
• اگر مقدار رشته‌ی ارسالی null باشد، متد Parse، یک استثناء را ارسال می‌کند و متد ConvertToDateTime مقدار MinValue را باز می‌گرداند.
• در متد Parse می‌توان یک پارامتر اضافه‌تر نیز ارسال کرد که DateTimeStyle نامیده می‌شود. اما متد ConvertToDateTime این قابلیت را ندارد.
• تابع ConvertToDateTime به‌صورت داخلی از متد DateTime.Parse به‌همراه فرهنگ جاری استفاده می‌کند.

تفاوت تابع Parseو ParseExact:
این دو تابع شبیه به هم هستند، اما می‌توان یک پارامتر اضافی format را نیز به تابع ParseExact ارسال کرد. این پارامتر به ما کمک می‌کند تا رشته‌ای را که فرمت متفاوتی از حالت معمولی دارد، به تاریخ تبدیل کنیم. مثلا اگر رشته‌ی "11232015" بدین شکل باشد، فرمت باید به شکل "MMddyyyy" تعریف شود.

تفاوت تابع Parse و TryParse:
این دو متد شبیه به هم هستند، با این تفاوت که تابع TryParse در صورت عدم موفقیت جداسازی رشته، استثنائی را ارسال نمی‌کند و همیشه مقدار MinValue را در صورت عدم موفقیت در تبدیل، باز می‌گرداند.

تفاوت تابع TryParse و TryParseExact:
هر دو تابع شبیه به هم هستند؛ بجز در پارامتر format. تابع TryParseExact از یک پارامتر اضافه‌ی format استفاده می‌کند. ولی تابع TryParse اگر نتواند فرمت مورد نیاز را فراهم کند، مقدار minValue را باز می‌گرداند.
 
مطالب
شروع به کار با بوت استرپ 4
روش‌های مختلف دریافت و نصب بوت استرپ 4

اولین کاری را که باید جهت شروع به کار با بوت استرپ 4 انجام داد، نصب و افزودن آن به صفحه‌ی HTML جاری است. روش‌های زیادی برای انجام اینکار وجود دارند:
الف) استفاده از نگارش SASS آن
بوت استرپ 4 در اصل مبتنی بر SASS توسعه یافته‌است و فایل‌های آن با فرمت scss. ارائه می‌شوند. مزیت کار با این روش، امکان سفارشی سازی بوت استرپ 4 و یا مشارکت در پروژه‌ی آن است و بدیهی است پس از آن باید SASS را به CSS کامپایل و مورد استفاده قرار داد.

ب) استفاده از CDN و یا Content delivery network
- مزیت آن بالا رفتن سرعت سایت با کش شدن آن در شبکه و یا شبکه‌های توزیع محتوا است.
- اما این روش محدودیت و الزام کار آنلاین با فایل‌های بوت استرپ را نیز به همراه دارد.
برای کار با CDN‌های بوت استرپ، مطابق راهنمای آن، تنها کافی است مدخل فایل css آن‌را به head صفحه و مداخل فایل‌های js ذیل را پیش از بسته شدن تگ body قرار دهید:
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/css/bootstrap.min.css" integrity="sha384-MCw98/SFnGE8fJT3GXwEOngsV7Zt27NXFoaoApmYm81iuXoPkFOJwJ8ERdknLPMO" crossorigin="anonymous">

<script src="https://code.jquery.com/jquery-3.3.1.slim.min.js" integrity="sha384-q8i/X+965DzO0rT7abK41JStQIAqVgRVzpbzo5smXKp4YfRvH+8abtTE1Pi6jizo" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.3/umd/popper.min.js" integrity="sha384-ZMP7rVo3mIykV+2+9J3UJ46jBk0WLaUAdn689aCwoqbBJiSnjAK/l8WvCWPIPm49" crossorigin="anonymous"></script>
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/js/bootstrap.min.js" integrity="sha384-ChfqqxuZUCnJSK3+MXmPNIyE6ZbWh2IMqE241rYiqJxyMiZ6OW/JmZQ5stwEULTy" crossorigin="anonymous"></script>
در اینجا شاید نام دو فایل، برای شما تازگی داشته باشند:
- jquery-3.3.1.slim : در اینجا slim یک نگارش بسیار کوچک از jQuery می‌باشد که بوت استرپ 4 بر مبنای آن کار می‌کند. البته در یک پروژه‌ی واقعی احتمالا نیاز به نگارش کامل آن‌را خواهید داشت و یا اگر قصد حذف کردن جی‌کوئری را دارید، این نگارش، کم‌حجم‌ترین آن است.
- popper.min.js : برای نمونه Bootstrap dropdown برای کارکرد صحیح آن در نگارش 4، نیاز به این وابستگی جدید را دارد.

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

د) استفاده از ابزارهای مدیریت بسته‌ها
روشی را که ما در اینجا از آن استفاده خواهیم کرد، دریافت و نصب وابستگی‌های مورد نیاز جهت کار با بوت استرپ 4، توسط npm است. به همین جهت یک فایل جدید package.json را با محتوای ذیل ایجاد کنید:
{
  "name": "bootstrap.4",
  "version": "1.0.0",
  "description": "client side resources of the project",
  "scripts": {},
  "author": "VahidN",
  "license": "ISC",
  "dependencies": {
    "bootstrap": "^4.1.3",
    "components-font-awesome": "5.0.6",
    "jquery": "^3.3.1",
    "popper.js": "^1.14.4"
  }
}
سپس از طریق خط فرمان به این پوشه وارد شده و دستور npm install را جهت دریافت این وابستگی‌ها صادر کنید. یکی از مزیت‌های مهم این روش، آگاه شدن خودکار از به روز رسانی‌های وابستگی‌های فوق، توسط افزونه‌هایی مانند Version Lens است.
در اینجا font-awesome را نیز مشاهده می‌کنید؛ از این جهت که بوت استرپ 4 برخلاف نگارش 3 آن، به همراه گلیف آیکن‌های پیش‌فرض آن نیست.


ایجاد قالب ابتدایی شروع به کار با بوت استرپ 4

پس از دریافت وابستگی‌های مورد نیاز جهت شروع به کار با بوت استرپ 4 که هم اکنون باید در پوشه‌ی node_modules واقع در ریشه‌ی پوشه‌ی جاری موجود باشند، در ادامه حداقل قالبی را که برای کار با آن نیاز است، مرور می‌کنیم:
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
    <meta http-equiv="x-ua-compatible" content="ie=edge">
    <link rel="stylesheet" href="/node_modules/bootstrap/dist/css/bootstrap.min.css">
    <link rel="stylesheet" href="/node_modules/components-font-awesome/css/fa-solid.min.css">
    <link rel="stylesheet" href="/node_modules/components-font-awesome/css/fontawesome.min.css">
    <title>Bootstrap</title>
</head>
<body>
    <div class="container">

    </div>
    <script src="/node_modules/jquery/dist/jquery.min.js"></script>
    <script src="/node_modules/popper.js/dist/umd/popper.min.js"></script>
    <script src="/node_modules/bootstrap/dist/js/bootstrap.min.js"></script>
</body>
</html>
- در ابتدا سازگاری با edge و الزام به استفاده‌ی از آخرین نگارش IE نصب شده مشخص شده‌است.
- سپس viewport استاندارد، جهت تعیین اینکه این صفحه با ابزارهای موبایل نیز سازگار است، تعریف شده‌است.
- در قسمت head، مدخل فایل bootstrap.min.css تعریف شده‌است. همچنین مداخل مورد نیاز جهت کار با font-awesome را نیز مشاهده می‌کنید.
- پیش از بسته شدن تگ body، تعاریف jQuery، کتابخانه‌ی popper و سپس bootstrap.min.js قید شده‌اند. کتابخانه‌ی popper از مسیر umd آن دریافت شده‌است تا همه جا کار کند.


نکته‌ی مهم!
در نگارش نهایی برنامه‌ی شما، مسیرهای فایل‌های شروع شده‌ی با /node_modules/ نباید وجود داشته باشند. این فایل‌ها را بهتر است توسط ابزارهای bundling & minification یکی و سپس به صفحه اضافه کنید.


غنی سازی ویرایشگر VSCode برای کار ساده‌تر با بوت استرپ

VSCode یک ویرایشگر حرفه‌ای چندسکویی است که برای ویندوز، مک و لینوکس تهیه شده‌است. این ویرایشگر را می‌توان توسط افزونه‌های زیر برای کار ساده‌تر با بوت استرپ غنی کرد:
Bootstrap 4, Font awesome 4, Font Awesome 5 Free & Pro snippets: ساده سازی تشکیل تگ‌های بوت استرپ
Path Autocomplete: کار وارد کردن مسیر فایل‌ها و تصاویر را ساده می‌کند.
HTML CSS Support: کار آن غنی سازی intellisense این ویرایشگر جهت کار با ویژگی‌ها و همچنین کلاس‌های CSS است.
IntelliSense for CSS class names in HTML: انتخاب کلاس‌های CSS بوت استرپ را ساده‌تر می‌کند.
Live Server: کار آن راه اندازی یک وب سرور آزمایشی و سپس امکان مشاهده‌ی آنی تغییرات در برنامه و فایل HTML جاری، در مرورگر می‌باشد.


برای کار با آن، در حالیکه صفحه‌ی HTML جاری در VSCode باز است، بر روی دکمه‌ی Go Live اضافه شده‌ی در status bar آن کلیک کنید. پس از آن، یک وب سرور آزمایشی را بر روی پورت 5500 آغاز کرده و صفحه‌ی جاری را در آدرس http://127.0.0.1:5500/index.html در مرورگر پیش‌فرض سیستم نمایش می‌دهد. اکنون فایل HTML خود را در VSCode ویرایش کنید. ملاحظه خواهید کرد که بلافاصله این تغییرات در مرورگر قابل مشاهده هستند.


نگارش‌های راست به چپ بوت استرپ 4

قرار است بوت استرپ 4 نگارش رسمی راست به چپ نیز داشته باشد. به همین منظور می‌توانید در اینجا رای خود را اظهار کنید.
همچنین پروژه‌های زیر نیز چنین قابلیتی را ارائه می‌دهند:
  1. MahdiMajidzadeh/bootstrap-v4-rtl
  2. DediData/Bootstrap-RTL 
  3. GhalamborM/bootstrap4-rtl



کدهای کامل این قسمت را از اینجا می‌توانید دریافت کنید: Bootstrap4_01.zip
نظرات مطالب
نحوه صحیح تولید Url در ASP.NET MVC
فرم ادیت رو به صورت strongly typed از نوع یک partial view مستقل درست کنید.
سپس در کنترلر مرتبط، یک اکشن متد را مخصوص رندر کردن این partial view در نظر بگیرید. کار آن دریافت اطلاعات مرتبط با Model ارسالی به همین partial view است. سپس در آخر کار آن هم خواهیم داشت:

return PartialView("_MyPartialViewName", data);
حالا فراخوانی این اکشن متد توسط jQuery Ajax سبب پر شدن خودکار فیلدهای فرم strongly typed شما هم می‌شود. در حین درخواست، اطلاعات مدل از بانک اطلاعاتی دریافت شده و به Partial view ارسال می‌شود. چون strongly typed است، فیلدهای آن به صورت خودکار پر شده و نهایتا کل partial view به صورت یک رشته آماده شده در اختیار شما خواهد بود. بنابراین، قسمت عمده‌ای از کدهای سمت کاربر فوق کاهش پیدا می‌کنند. چون یک view کامل حاضر و آماده از سرور دریافت شده است که باید به صفحه توسط jQuery اضافه شود.

+
لطفا اینجا رو تبدیل به یک انجمن عمومی نکنید. عنوان بحث ساخت Url بود ... بعد تغییر جهت پیدا کرد به یک عنوان دیگر.
با تشکر از توجه شما.
 
مطالب
چند نکته کاربردی درباره Entity Framework
1) رفتار متصل و غیر متصل در EF  چیست؟
اولین نکته ای که به ذهنم می‌رسه اینه که برای استفاده از EF حتما باید درک صحیحی از رفتارها و قابلیت‌های اون داشته باشیم.  نحوه استفاده ازٍEF  رو به دو رفتار متصل و غیر متصل  تقسیم می‌کنیم.
حالت پیش فرضEF  بر مبنای رفتار متصل می‌باشد. در این حالت شما یک موجودیت رو از دیتابیس فرا می‌خونید EF  این موجودیت رو ردگیری می‌کنه اگه تغییری در اون مشاهده کنه بر روی اون برچسب "تغییر داده شد" می‌زنه و حتی اونقدر هوشمند هست که وقتی تغییرات رو ذخیره می‌کنید کوئری آپدیت رو فقط براساس فیلدهای تغییر یافته اجرا کنه. یا مثلا در صورتی که شما بخواهید به یک خاصیت رابطه ای دسترسی پیدا کنید اگر قبلا لود نشده باشه در همون لحظه از دیتابیس فراخوانی میشه،  البته این رفتارها هزینه بر خواهد بود و در تعداد زیاد موجودیت‌ها میتونه کارایی رو به شدت پایین بیاره.
رفتار متصل شاید در ویندوز اپلیکیشن کاربرد داشته باشه ولی در حالت وب اپلیکیشن کاربردی نداره چون با هر در خواستی به سرور همه چیز از نو ساخته میشه و پس از پاسخ به درخواست همه چی از بین میره.  پس DbContext  همیشه از بین می‌ره و ما برحسب نیاز، در درخواست‌های جدید به سرور ، دوباره  DbContext   رو می‌سازیم. پس از ساخته شدن DbContext  باید موجودیت مورد استفاده رو به اون معرفی کنیم و وضعیت اون موجودیت رو هم مشخص کنیم.( جدید ، تغییر یافته، حذف ، بدون تغییر ) در این حالت سیستم ردگیری تغییرات بی استفاده است و ما فقط در حال هدر دادن منابع سیستم هستیم.
در حالت متصل ما باید همیشه از یک DbContext  استفاده کنیم و همه موجودیت‌ها در آخر باید تحت نظر این DbContext باشند در یک برنامه واقعی کار خیلی سخت و پیچیده ای است. مثلا بعضی وقت‌ها مجبور هستیم از  موجودیت هایی که قبلا در حافظه برنامه بوده اند استفاده کنیم اگر این موجودیت در حافظه DbContext  جاری وجود نداشته باشه با معرفی کردن اون از طریق متد attach کار ادامه پیدا می‌کنه ولی اگر قبلا موجودیتی  در  سیستم ردگیری DbContext با همین شناسه وجود داشته باشد با خطای زیر مواجه می‌شویم.
An object with the same key already exists in the ObjectStateManager. The ObjectStateManager cannot track multiple objects with the same key
 این خطا مفهوم ساده و مشخصی داره ، دو شی با یک شناسه نمی‌توانند در یک DbContext وجود داشته باشند. معمولا در این حالت ما بااین اشیا تکراری کاری نداریم و فقط به شناسه اون شی برای نشان دادن روابط نیاز داریم و از دیگر خاصیت‌های اون جهت نمایش به کاربر استفاده می‌کنیم ولی متاسفانه DbContext   نمی‌دونه چی تو سر ما می‌گذره و فقط حرف خودشو می‌زنه! البته اگه خواستید با DbContext  بر سر این موضوع گفتگو کنید از کدهای زیر استفاده کنید:
T attachedEntity = set.Find(entity.Id);
var attachedEntry = dbContext.Entry(attachedEntity);
attachedEntry.CurrentValues.SetValues(entity);
خوب با توجه به صحبت‌های بالا اگر بخواهیم از رفتار غیر متصل استفاده کنیم باید تنظیمات زیر رو به متد  سازنده DbContext   اضافه کنیم. از اینجا به بعد همه چیز رو خودمون در اختیار می‌گیریم و ما مشخص می‌کنیم که کدوم موجودیت باید چه وضعیتی داشته باشه (افزودن ، بروز رسانی، حذف )و اینکه چه موقع روابط خودش را با دیگر موجودیتها فراخوانی کنه.
public DbContext()
        {
            this.Configuration.ProxyCreationEnabled = false;
            this.Configuration.LazyLoadingEnabled = false;
            this.Configuration.AutoDetectChangesEnabled = false;
         }
2) تعیین وضعیت یک موجودیت و راوبط آن در  EF چگونه است؟
با کد زیر می‌تونیم وضعیت یک موجدیت رو مشخص کنیم ، با اجرای هر یک از دستورات زیر موجودیت تحت نظر DbContext قرار می‌گیره یعنی عمل attach نیز صورت گرفته است :
dbContext.Entry(entity).State = EntityState.Unchanged ;
dbContext.Entry(entity).State = EntityState.Added ; //or  Dbset.Add(entity)
dbContext.Entry(entity).State = EntityState.Modified ;
dbContext.Entry(entity).State = EntityState.Deleted ; // or  Dbset.Remove(entity)

با اجرای این کد موجودیت  از سیستم ردگیری DbContext خارج می‌شه.
 dbContext.Entry(entity).State = EntityState.Detached;
در موجودیت‌های ساده با دستورات بالا نحوه ذخیره سازی را مشخص می‌کنیم در وضعیتی که با موجودیت‌های رابطه ای سروکار داریم باید به نکات زیر توجه کنیم.
در نظر بگیرید یک گروه از قبل وجود دارد و ما مشتری جدیدی می‌سازیم در این حالت انتظار داریم که فقط یک مشتری جدید ذخیره شده باشد:
// group id=19  Name="General"  
var customer = new Customer();
customer.Group = group;
customer.Name = "mohammadi";
dbContext.Entry(customer).State = EntityState.Added;
var customerstate = dbContext.Entry(customer).State;// customerstate=EntityState.Added
var groupstate = dbContext.Entry(group);// groupstate=EntityState.Added

 اگه از روش بالا استفاده کنید می‌بینید گروه General   جدیدی به همراه مشتری در  دیتابیس ساخته می‌شود.نکته مهمی که اینجا وجود داره اینه که DbContext  به id  موجودیت گروه توجهی نداره ، برای جلو گیری از این مشکل باید قبل از معرفی موجودیت‌های جدید رابطه هایی که از قبل وجود دارند را به صورت بدون تغییر attach  کنیم و بعد وضعیت جدید موجودیت رو اعمال کنیم.
// group id=19  Name="General"  
var customer = new Customer();
customer.Group = group;
 customer.Name = "mohammadi";
dbContext.Entry(group).State = EntityState.Unchanged;
dbContext.Entry(customer).State = EntityState.Added;
var customerstate = dbContext.Entry(customer).State;// customerstate=EntityState.Added
 var groupstate = dbContext.Entry(group);// groupstate=EntityState.Unchanged

در مجموع بهتره که موجودیت ریشه رو attach کنیم و بعد با توجه به نیاز تغییرات رو اعمال کنیم.
  // group id=19  Name="General"  
var customer = new Customer();
 customer.Group = group;
customer.Name = "mohammadi";
dbContext.Entry(customer).State = EntityState.Unchanged;
dbContext.Entry(customer).State = EntityState.Added;
var customerstate = dbContext.Entry(customer).State;// customerstate=EntityState.Added
var groupstate = dbContext.Entry(group);//// groupstate=EntityState.Unchanged

3) AsNoTracking   و Include  دو ابزار مهم در رفتار غیر متصل:
درصورتیکه ما تغییراتی روی داده‌ها نداشته باشیم و یا از روش‌های غیر متصل از موجودیت‌ها استفاده کنیم با استفاده از متد AsNoTracking() در زمان و حافظه سیستم صرف جویی می‌کنیم در این حالت موجودیت‌های فراخوانی شده از دیتابیس در سیستم ردگیری DbContext قرار نمی‌گیرند و  اگر  وضعیت آنها را بررسی کنیم در وضعیت Detached قرار دارند.
var customer  = dbContext.Customers.FirstOrDefault();
 var customerAsNoTracking  = dbContext.Customers.AsNoTracking().FirstOrDefault();
var customerstate = dbContext.Entry(customer).State;// customerstate=EntityState.Unchanged
var customerstateAsNoTracking  = dbContext.Entry(customerAsNoTracking).State;// customerstate=EntityState.Detached

نحوه بررسی کردن موجودیت‌های موجود در سیستم ردگیری DbContext :
var Entries = dbContext.ChangeTracker.Entries();    
var AddedEntries = dbContext.ChangeTracker.Entries().Where(entityEntry => entityEntry.State==EntityState.Added);
var ModifiedEntries = dbContext.ChangeTracker.Entries().Where(entityEntry => entityEntry.State==EntityState.Modified);
var UnchangedEntries = dbContext.ChangeTracker.Entries().Where(entityEntry => entityEntry.State==EntityState.Unchanged);
var DeletedEntries = dbContext.ChangeTracker.Entries().Where(entityEntry => entityEntry.State==EntityState.Deleted);
var DetachedEntries = dbContext.ChangeTracker.Entries().Where(entityEntry => entityEntry.State==EntityState.Detached);//* not working !
* در نظر داشته باشید وضعیت Detached وجود خارجی ندارد و به حالتی گفته می‌شود که DbContext در سیستم رد گیری خود اطلاعی از موجودیت مورد نظر نداشته باشد.
وقتی که سیستم فراخوانی خودکار رابطه‌ها خاموش باشد باید موقع فراخوانی موجودیت‌ها روابط مورد نیاز را هم با دستور Include  در سیستم فراخوانی کنیم.
 var CustomersWithGroup = dbContext.Customers.AsNoTracking().Include("Group").ToList();
 var CustomerFull = dbContext.Customers.AsNoTracking().Include("Group").Include("Bills").Include("Bills.BillDetails").ToList();
4) از متد AddOrUpdate در در فضای نام  System.Data.Entity.Migrations استفاده نکنیم، چرا؟
 در صورتی که از فیلد RowVersion و کنترل مسایل همزمانی استفاده کرده باشیم هر وقتی متد AddOrUpdate رو فراخوانی کنیم، تغییر اطلاعات توسط دیگر کاربران نادیده گرفته می‌شود.  با توجه به این که  متد AddOrUpdate  برای عملیات Migrations در نظر گرفته شده است، این رفتار کاملا طبیعی است. برای حل این مشکل می‌تونیم این متد رو با بررسی شناسه به سادگی پیاده سازی کنیم:
 public virtual void AddOrUpdate(T entity)
        {
            if (entity.Id == 0)
                Add(entity);
            else
                Update(entity);

        }

5) اگر بخواهیم موجودیت‌های رابطه ای در دیتا گرید ویو (ویندوز فرم) نشون بدیم باید چه کار کنیم؟

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

 public class DataGridViewChildRelationTextBoxCell : DataGridViewTextBoxCell
    {
        protected override object GetValue(int rowIndex)
        {
            try
            {
                var bs = (BindingSource)DataGridView.DataSource;
                var cl = (DataGridViewChildRelationTextBoxColumn)DataGridView.Columns[ColumnIndex];
                return getChildValue(bs.List[rowIndex], cl.DataPropertyName).ToString();
            }
            catch (Exception)
            {
                return "";
            }
        }

        private object getChildValue(object dataSource, string childMember)
        {
            int nextPoint = childMember.IndexOf('.');
            if (nextPoint == -1) return dataSource.GetType().GetProperty(childMember).GetValue(dataSource, null);
            string proName = childMember.Substring(0, nextPoint);
            object newDs = dataSource.GetType().GetProperty(proName).GetValue(dataSource, null);
            return getChildValue(newDs, childMember.Substring(nextPoint + 1));
        }
    }

    public class DataGridViewChildRelationTextBoxColumn : DataGridViewTextBoxColumn
    {
        public string DataMember { get; set; }

        public DataGridViewChildRelationTextBoxColumn()
        {
            CellTemplate = new DataGridViewChildRelationTextBoxCell();
        }
    }

نحوه استفاده را در ادامه می‌بینید. این روش توسط ویزارد گریدویو هم قابل استفاده است. موقع Add کردن Column نوع اون رو روی DataGridViewChildRelationTextBoxColumn   تنظیم کنید.

GroupNameColumn= new DataGridViewChildRelationTextBoxColumn(); //from your class
GroupNameColumn.HeaderText = "گروه مشتری";
GroupNameColumn.DataPropertyName = "Group.Name"; //EF  Property: Customer.Group.Name
GroupNameColumn.Visible = true;
GroupNameColumn.Width = 300;
DataGridView.Columns.Add(GroupNameColumn);
نظرات مطالب
ASP.NET MVC #17
کلا یک آنتی فورجری توکن برای کل صفحه کافی است. روشی که آقای رضایی کمی بالاتر مطرح کردند (قرار دادن آن در فایل layout) هم جالب است. به این صورت در همه‌جا و در تمام صفحات حضور خواهد داشت؛ آن‌هم فقط یکبار و نه بیشتر. نحوه‌ی خواندن و اضافه کردن آن نیز به صورت زیر خواهد بود:
function addToken(data) {
   data.__RequestVerificationToken = $("input[name=__RequestVerificationToken]").val();
   return data;
}
 
$.ajax({
   // .....
   data: addToken({ postId: postId }), // اضافه کردن توکن
   dataType: "html", // نوع داده مهم است
   // .....
});
سه نکته اینجا مهم است:
- data type حتما باید در این حالت html باشد.
- در قسمت data متد addToken کار افزودن خودکار آنتی فورجری‌توکن صفحه را انجام می‌دهد (محل آن مهم نیست که داخل فرم باشد یا خارج از آن).
- سطر contentType نباید ذکر شود.  
مطالب
آشنایی با الگوی M-V-VM‌ - قسمت پنجم

در این قسمت قصد داریم از امکانات جدید اعتبار سنجی تعریف شده در فضای نام استاندارد System.ComponentModel.DataAnnotations استفاده نمائیم. از سیلورلایت سه به بعد امکان استفاده از این فضای نام به سادگی در برنامه‌های سیلورلایت میسر است (همچنین در برنامه‌های ASP.Net MVC)؛ اما برای کار با آن در WPF نیاز به تعدادی متد کمکی می‌باشد...

فهرست مطالب:
فصل 5- تعیین اعتبار ورودی کاربر و الگوی MVVM
  • مقدمه
  • معرفی برنامه فصل
  • مدل برنامه‌ی فصل
  • ViewModel برنامه فصل
  • View برنامه فصل


دریافت قسمت پنجم
دریافت مثال قسمت پنجم


تعدادی از منابع و مآخذ مورد استفاده در این سری:

1. Model-View-ViewModel (MVVM) Explained
2. Model View ViewModel
3. DataModel-View-ViewModel pattern
4. 5 Minute Overview of MVVM in Silverlight
5. A Field Guide to WPF Presentation Patterns
6. An attempt at simple MVVM with WPF
7. WPF: If Heineken did MVVM Frameworks Part 1 of n
8. Modal dialogs with MVVM and Silverlight 4
9. How do I do… With the Model-View-ViewModel pattern
10. Intro to WPF MVVM
11. Introduction to MVVM pattern in WPF
12. Learning WPF M-V-VM
13. Binding Combo Boxes in WPF with MVVM
14. Model-View-ViewModel Pattern
15. Unit Testable WCF Web Services in MVVM and Silverlight 4
16. MVVM Part 1: Overview
17. Which came first, the View or the Model?
18. Stackoverflow's questions tagged with MVVM
19. WPF: MVVM (Model View View-Model) Simplified
20. WPF and MVVM tutorial 01, Introduction
21. WPF patterns : MVC, MVP or MVVM or…?
22. Silverlight, MVVM and Validation Part III
23. DotNetKicks.com - Stories recently tagged with 'MVVM'
24. DotNetShoutout - Stories tagged with MVVM
25. MVVM Light Toolkit
26. MVVM screen casts
27. What’s new in MVVM Light V3
28. Using RelayCommands in Silverlight 3 and WPF
29. WPF Apps With The Model-View-ViewModel Design Pattern
30. WPF MVVM and Showing Dialogs


مطالب
پَرباد - آموزش پیاده‌سازی پرداخت آنلاین در دات نت - آموزش پیشرفته
در قسمت قبل، با تنظیمات پَرباد آشنا شدیم. در این مقاله قصد داریم سایر امکانات قابل استفاده را آموزش دهیم.

آنچه شما در این مقاله یاد خواهید گرفت:

  • ایجاد صورت حساب پرداخت با استفاده از InvoiceBuilder
  • درگاه مجازی
  • استفاده از پروکسی
  • توکن پرداخت
  • تزریق وابستگی
  • Logging


ایجاد صورت حساب با استفاده از InvoiceBuilder

InvoiceBuilder به شما کمک می‌کند تا یک صورت حساب را جهت پرداخت آماده کنید.
مثال زیر را در نظر بگیرید:
var result = _onlinePayment.Request(Gateways.Mellat, 123, 25000, "http://www.mywebsite.com/foo/bar/");
همانطور که مشخص است، در این مثال یک صورت حساب با شماره رهگیری ۱۲۳ به مبلغ ۲۵۰۰۰ با یک آدرس بازگشتی به درگاه بانک ملت درخواست داده می‌شود.

اما همین دستور را با کمک InvoiceBuilder نیز می‌توان ایجاد کرد.
نمونه مثال بالا با استفاده از InvoiceBuilder
var result = _onlinePayment.Request(invoice =>
{
    invoice
          .SetTrackingNumber(123)
          .SetAmount(25000)
          .SetCallbackUrl("http://www.mywebsite.com/foo/bar/")
          .UseGateway(Gateways.Mellat);
});

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

در زیر نمونه‌هایی از کارایی آن را بررسی می‌کنیم.

  • تولید اتوماتیک کد رهگیری به صورت افزایشی
  • تولید اتوماتیک کد رهگیری به صورت تصادفی
  • ایجاد یک تولید کننده کد رهگیری توسط شما
  • صورت حساب سفارشی برای امکانات اختصاصی درگاه‌های بانکی

تولید اتوماتیک کد رهگیری به صورت افزایشی

در این روش، کد رهگیری (TrackingNumber) که مورد نیاز درگاه‌های بانکی است، به صورت اتوماتیک در هنگام ایجاد درخواست پرداخت، توسط پَرباد تولید می‌شود.
var result = _onlinePayment.Request(invoice =>
{
    invoice
          .UseAutoIncrementTrackingNumber()
          .SetAmount(25000)
          .SetCallbackUrl("http://www.mywebsite.com/foo/bar/")
          .UseGateway(Gateways.Mellat);
});
کد تولید شده، به صورت افزایشی است. در واقع در هر درخواست پرداخت جدید، یک کد رهگیری تولید می‌شود که یک واحد از کد تولید شده‌ی قبلی بیشتر است. 

شما همچنین می‌توانید مقدار اولیه این عدد را جهت شروع تولید، در پارامتر متد تعیین کنید. این مقدار همچنین در قسمت تنظیمات پَرباد نیز توسط متد ConfigureAutoTrackingNumber قابل تنظیم است.

تولید اتوماتیک کد رهگیری به صورت تصادفی

var result = _onlinePayment.Request(invoice =>
{
    invoice
          .UseAutoRandomTrackingNumber()
          .SetAmount(25000)
          .SetCallbackUrl("http://www.mywebsite.com/foo/bar/")
          .UseGateway(Gateways.Mellat);
});
در این روش کد رهگیری، به صورت تصادفی در محدوده Int64 توسط پَرباد تولید خواهد شد. کد‌های تولید شده در این روش تقریبا ٪۹۹.۹ غیر تکراری هستند. اما اگر به تمیز بودن کدهای تولید شده اهمیت می‌دهید، بهتر است از روش AutoIncrement که بالاتر توضیح داده شد، استفاده کنید.

ایجاد یک تولید کننده کد رهگیری توسط شما

اگر بنا به دلایلی قصد دارید خودتان نیز یک منبع تولید کد رهگیری را ایجاد کنید، می‌توانید به روش زیر عمل کنید.
public class MyTrackingNumberProvider : ITrackingNumberProvider
{
    public Task<long> ProvideAsync(CancellationToken cancellationToken = new CancellationToken())
    {
        // تولید و برگشت کد در اینجا
    }
}

نکته ۱: شما همچنین می‌توانید در منبع خود، از تزریق وابستگی‌ها نیز استفاده کنید. بدیهی است سرویسی را که تزریق می‌کنید، باید از قبل توسط سیستم تزریق وابستگی‌های اپلیکیشن شما، ثبت شده باشد.
نکته ۲؛ شما همچنین می‌توانید بدون ایجاد هیچ منبعی، به راحتی از متد SetTrackingNumber در InvoiceBuilder (که بالاتر توضیح داده شده) استفاده کنید.

سپس در هنگام ایجاد درخواست پرداخت به روش زیر از منبع خود استفاده کنید:
var result = _onlinePayment.Request(invoice =>
{
    invoice
          .UseTrackingNumberProvider<MyTrackingNumberProvider>()
          //  یا 
          .UseTrackingNumberProvider(new MyTrackingNumberProvider())
          //  یا 
          .UseTrackingNumberProvider(services => new MyTrackingNumberProvider())
});
همانطور که می‌بینید، متد‌های مختلفی جهت استفاده از منبع مورد نظر شما موجود است.

صورت حساب سفارشی برای امکانات اختصاصی درگاه‌های بانکی

درگاه‌های بانکی علاوه بر سرویس‌های پرداخت عادی، امکانات مختلفی دیگری را نیز ارائه می‌کنند. برای مثال بانک ملت دارای سرویسی به نام "باشگاه مشتریان بانک ملت" است که امکان واریز مبلغ را از حساب مشتری، به چندین حساب مختلف می‌دهد.
نکته مهم: همانطور که می‌دانید قبل از استفاده از این گونه سرویس‌ها، کلیه شماره حساب‌هایی که قصد واریز مبلغ به آنها را دارید، باید از قبل (در هنگام عقد قرارداد با بانک) به بانک مورد نظر داده شده باشد. در غیر اینصورت امکان استفاده از این گونه سرویس‌ها را ندارید.
نکته: در هنگام نوشتار این مقاله، این سرویس هنوز در پَرباد آماده نیست و در حال توسعه است.


درگاه مجازی

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

ابتدا در قسمت تنظیمات پَرباد،  آدرس مورد نظر برای درگاه مجازی را مانند کد زیر مشخص می‌کنیم:
services.AddParbad()
        .ConfigureGateways(gateways =>
        {
            gateways
               .AddParbadVirtual()
               .WithOptions(options => options.GatewayPath = "/MyVirtualGateway");
        });


در مثال بالا، درگاه مجازی توسط آدرس داده شده در دسترس خواهد بود. توجه داشته باشید که این آدرس حتما باید با یک ( / ) آغاز شده باشد.
سپس درگاه مجازی را در اپلیکیشن خود ثبت می‌کنید:
ASP.NET CORE (Startup.cs)
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
    app.UseMvc(routes =>
    {
        routes.MapRoute(
                    name: "default",
                    template: "{controller=Home}/{action=Index}/{id?}");
    });

    // ثبت درگاه مجازی
    app.UseParbadVirtualGateway();
}

ASP.NET WebForms, ASP.NET MVC (Startup.cs)
public void Configuration(IAppBuilder app)
{
    var parbad = ParbadBuilder.CreateDefaultBuilder()
        .ConfigureGateways(gateways =>
        {
            gateways
               .AddParbadVirtual()
               .WithOptions(options => options.GatewayPath = "/MyVirtualGateway");
        })
        .Build();

    app.UseParbadVirtualGateway(parbad.Services);
}


تنظیمات درگاه مجازی تا اینجا به پایان رسیده و فقط در هنگام ایجاد درخواست پرداخت، از میان درگاه‌ها، درگاه مجازی پَرباد را انتخاب کنید.
var result = _onlinePayment.Request(Gateways.ParbadVirtualGateway, 123, 25000, "http://www.mywebsite.com/foo/bar/");

و در نهایت به درگاه مجازی هدایت خواهید شد:

نمونه پروژه‌های کامل را در انتهای مقاله می‌توانید مشاهده کنید.

استفاده از پروکسی

با توجه به تغییرات اخیر در بانکداری کشور، احتمال آن وجود دارد که در آینده، بعضی از درگاه‌های بانکی فقط به IP‌های داخل کشور سرویس دهی کنند. اگر وب سایت شما بنا به دلایلی در سروری خارج از کشور میزبانی می‌شود، شما جهت استفاده از درگاه‌های بانکی، نیاز به یک سرور در داخل کشور دارید که نقش پروکسی را برای شما بازی کند. پَرباد این امکان را برای شما محیا کرده و کافیست اطلاعات پروکسی سرور خود را به شکل زیر برای درگاه بانکی مورد نظر ثبت کنید.
services.AddParbad()
        .ConfigureGateways(gateways =>
        {
            gateways
               .AddMellat()
               .WithOptions(options => 
               {
                    options.TerminalId = 123;
                    options.UserName = "abc";
                    options.UserPassword = "xyz;
               )
               .WithProxy(new Uri("Proxy Server URL"), "UserName", "Password");
        });
در مثال بالا، درگاه بانک ملت (صرفا جهت مثال) با یک پروکسی تنظیم شده است. برای سایر درگاه‌های بانکی، فرمت تنظیمات، کاملا مشابه مثال بالا است.

توکن پرداخت

پَرباد جهت شناسایی و تبادل اطلاعات پرداخت با خارج از سیستم خود و بانک‌ها، از یک توکن به ازاء هر پرداخت استفاده می‌کند. به عبارت دیگر، به ازاء هر درخواست پرداخت یک توکن تولید می‌شود. به این صورت، درخواست‌های پرداخت، غیر قابل دستکاری و غیر قابل حدس زدن توسط کاربران می‌شود.
نکته: پَرباد از یک تولید کننده پیش فرض توکن استفاده می‌کند و شما نیازی به انجام هیچگونه تنظیماتی ندارید. تولید کننده پیش فرض، از یک GUID در Query String استفاده می‌کند.
اگر قصد دارید روش مورد نظر خود را برای تولید توکن جهت شناسایی یک پرداخت پیاده‌سازی کنید، می‌توانید به روش زیر عمل کنید:
ابتدا تولید کننده توکن را تعریف کنید.
public class MyTokenProvider : IPaymentTokenProvider
{
    public Task<string> ProvideTokenAsync(Invoice invoice, CancellationToken cancellationToken = new CancellationToken())
    {
        // تولید و برگرداندن توکن در اینجا
    }

    public Task<string> RetrieveTokenAsync(CancellationToken cancellationToken = new CancellationToken())
    {
        // خواندن و برگرداندن توکن در اینجا
    }
}

نکته: شما همچنین می‌توانید از تزریق وابستگی‌ها نیز استفاده کنید. بدیهی است که در اینصورت باید سرویسی که تزریق می‌کنید، از قبل در سیستم تزریق وابستگی‌های اپلیکیشن شما ثبت شده باشد.
سپس تولید کننده توکن خود را در تنظیمات به پَرباد معرفی کنید:
services.AddParbad()
        .ConfigurePaymentToken(builder => builder.AddPaymentTokenProvider<MyTokenProvider>(ServiceLifetime.Transient));

ServiceLifetime، تایین کننده طول عمر سرویس شما است.
به این صورت پَرباد، شناسایی و ردیابی یک صورت حساب را با استفاده از تولید کننده توکن شما انجام خواهد داد.


تزریق وابستگی

همانطور که قبلا در مقاله آموزش تنظیمات نیز گفته شد، پَرباد به صورت توکار، از تزریق وابستگی استاندارد مایکروسافت استفاده می‌کند. بنابراین اگر اپلیکیشن شما نیز از تزریق وابستگی مشابهی استفاده می‌کند، نیازی به خواندن و یاد گرفتن این بخش ندارید و به راحتی می‌توانید از اینترفیس IOnlinePayment در هر کجا که نیاز داشتید جهت عملیات پرداخت استفاده کنید.
اما در صورتیکه در اپلیکیشن خود از تزریق وابستگی دیگری ( مانند Autofac ) استفاده می‌کنید، باید این دو سیستم را با یکدیگر هماهنگ کنید. خوشبختانه تمام کتابخانه‌های معروف تزریق وابستگی ( مانند Autofac )  از قبل این کار را برای شما محیا کرده‌اند و شما فقط نیاز به افزودن چند خط کد به اپلیکیشن فعلی خود را دارید.
جهت فهم بهتر و آموزش عملی، یک اپلیکیشن کامل ASP.NET MVC برای شما تهیه شده که از Autofac جهت تزریق وابستگی استفاده می‌کند. در این پروژه خواهید دید چگونه به راحتی پَرباد و Autofac را با یکدیگر هماهنگ کرده و هچنین اینترفیس IOnlinePayment را درون کنترلر تزریق می‌کنیم.
لینک پروژه‌ها در انتهای همین مقاله قابل مشاهده هستند.


Logging
لاگ کردن در پَرباد توسط سیستم استاندارد مایکروسافت انجام می‌شود. این بدان معنی است که شما امکان استفاده از کتابخانه‌های Logging بسیار زیادی را دارید. نحوه استفاده و تنظیم کتابخانه‌های معروف Logging در وب سایت آنها آورده شده و به راحتی می‌توانید آنها را با لاگ مایکروسافت تطابق دهید.

نمونه پروژه‌ها
مقاله‌های مرتبط
مطالب
از سرگیری مجدد، لغو درخواست و سعی مجدد دریافت فایل‌های حجیم توسط HttpClient
پس از آشنایی با «نکات دریافت فایل‌های حجیم توسط HttpClient»، در ادامه می‌توان سه قابلیت مهم از سرگیری مجدد، لغو درخواست و سعی مجدد دریافت فایل‌های حجیم را با HttpClient، همانند برنامه‌های download manager نیز پیاده سازی کرد.


از سرگیری مجدد درخواست ارسالی توسط HttpClient

یک نمونه از سرگیری مجدد درخواست را در مطلب «اضافه کردن قابلیت از سرگیری مجدد (resume) به HttpWebRequest» پیشتر در این سایت مطالعه کرده‌اید. اصول کلی آن نیز در اینجا صادق است. HTTP 1.1 از مفهوم range headers‌، برای دریافت پاسخ‌های جزئی پشتیبانی می‌کند. به این ترتیب در صورت پیاده سازی چنین قابلیتی در برنامه‌ی سمت سرور، می‌توان دریافت بازه‌ای از بایت‌ها را بجای دریافت فایل از ابتدا، از سرور درخواست کرد. به یک چنین قابلیتی Resume و یا از سرگیری مجدد گرفته می‌شود و درحین دریافت فایل‌های حجیم بسیار حائز اهمیت است.
var fileInfo = new FileInfo(outputFilePath);
long resumeOffset = 0;
if (fileInfo.Exists)
{
    resumeOffset = fileInfo.Length;
}
if (resumeOffset > 0)
{
    _client.DefaultRequestHeaders.Range = new RangeHeaderValue(resumeOffset, null);
}
در اینجا نحوه‌ی تنظیم یک RangeHeader را مشاهده می‌کنید. ابتدا نیاز است بررسی کنیم آیا فایل دریافتی از پیش موجود است؟ آیا قسمتی از این درخواست پیشتر دریافت شده و محتوای آن هم اکنون به صورت ذخیره شده وجود دارد؟ اگر بله، درخواست دریافت این فایل را بر اساس اندازه‌ی دریافتی فعلی آن، به سرور ارائه می‌کنیم.

یک نکته: تمام وب سرورها و یا برنامه‌های وب از یک چنین قابلیتی پشتیبانی نمی‌کنند.
روش تشخیص آن نیز به صورت زیر است:
var response = await client.GetAsync(url, HttpCompletionOption.ResponseHeadersRead);
if (response.Headers.AcceptRanges == null && resumeOffset > 0)
{
    // resume not supported, starting over
}
پس از خواندن هدر درخواست، اگر خاصیت AcceptRanges آن نال بود، یعنی قابلیت از سرگیری مجدد را ندارد. در این حالت باید فایل موجود فعلی را حذف و یا از نو (FileMode.CreateNew) بازنویسی کرد (بجای حالت FileMode.Append).


لغو درخواست ارسالی توسط HttpClient

پس از شروع غیرهمزمان client.GetAsync می‌توان متد CancelPendingRequests آن‌را فراخوانی کرد تا کلیه درخواست‌های مرتبط با این client لغو شوند. اما این متد صرفا برای حالت پیش‌فرض client.GetAsync که دریافت هدر + محتوا است کار می‌کند (یعنی حالت HttpCompletionOption.ResponseContentRead). اگر همانند نکات بررسی شده‌ی در مطلب «دریافت فایل‌های حجیم توسط HttpClient» صرفا درخواست خواندن هدر را بدهیم (HttpCompletionOption.ResponseHeadersRead)، چون کنترل ادامه‌ی بحث را خودمان بر عهده گرفته‌ایم، لغو آن نیز به عهده‌ی خودمان است و متد CancelPendingRequests بر روی آن تاثیر نخواهد داشت.
این نکته در مورد تنظیم خاصیت TimeOut نیز صادق است. این خاصیت فقط زمانیکه دریافت کل هدر + محتوا توسط متد GetAsync مدیریت شوند، تاثیر گذار است.
بنابراین درحالتیکه نیاز به کنترل بیشتر است، هرچند فراخوانی متد CancelPendingRequests ضرری ندارد، اما الزاما سبب قطع کل درخواست نمی‌شود و باید این لغو را به صورت ذیل پیاده سازی کرد:
ابتدا یک منبع توکن لغو عملیات را به صورت ذیل ایجاد می‌کنیم:
private readonly CancellationTokenSource _cts = new CancellationTokenSource();
سپس، متد لغو برنامه، تنها کافی است متد Cancel این cts را فراخوانی کند؛ تا عملیات دریافت فایل خاتمه یابد.
پس از این فراخوانی (()cts.Cancel)، نحوه‌ی واکنش به آن به صورت ذیل خواهد بود:
var result = await client.GetAsync(url, HttpCompletionOption.ResponseHeadersRead, _cts.Token);
using(var stream = await result.Content.ReadAsStreamAsync())
{
   byte[] buffer = new byte[80000];
   int bytesRead;
   while((bytesRead = await stream.ReadAsync(buffer, 0, buffer.Length)) > 0 &&
         !_cts.IsCancellationRequested)
   {
      outputStream.Write(buffer, 0, bytesRead);
   }
}
در اینجا از cts.Token به عنوان پارامتر سوم متد GetAsync استفاده شده‌است. همچنین قسمت ثبت اطلاعات دریافتی، در استریم خروجی نیز به صورت یک حلقه درآمده‌است تا بتوان خاصیت IsCancellationRequested این توکن لغو را بررسی کرد و نسبت به آن واکنش نشان داد.


سعی مجدد درخواست ارسالی توسط HttpClient

یک روش پیاده سازی سعی مجدد درخواست شکست خورده، توسط کتابخانه‌ی Polly است. روش دیگر آن نیز به صورت ذیل است:
public async Task DownloadFileAsync(string url, string outputFilePath, int maxRequestAutoRetries)
{
            var exceptions = new List<Exception>();

            do
            {
                --maxRequestAutoRetries;
                try
                {
                    await doDownloadFileAsync(url, outputFilePath);
                }
                catch (TaskCanceledException ex)
                {
                    exceptions.Add(ex);
                }
                catch (HttpRequestException ex)
                {
                    exceptions.Add(ex);
                }
                catch (Exception ex) when (isNetworkError(ex))
                {
                    exceptions.Add(ex);
                }

                // Wait a bit and try again later
               if (exceptions.Any())  await Task.Delay(2000, _cts.Token);
            } while (maxRequestAutoRetries > 0 &&
                     !_cts.IsCancellationRequested);

            var uniqueExceptions = exceptions.Distinct().ToList();
            if (uniqueExceptions.Any())
            {
                if (uniqueExceptions.Count() == 1)
                    throw uniqueExceptions.First();
                throw new AggregateException("Could not process the request.", uniqueExceptions);
            }
}

private static bool isNetworkError(Exception ex)
{
    if (ex is SocketException || ex is WebException)
        return true;
    if (ex.InnerException != null)
        return isNetworkError(ex.InnerException);
    return false;
}
در اینجا متد doDownloadFileAsync، پیاده سازی همان متدی است که در قسمت «لغو درخواست ارسالی توسط HttpClient» در مورد آن بحث شد. این قسمت دریافت فایل را در یک حلقه که حداقل یکبار اجرا می‌شود، قرار می‌دهیم. متد GetAsync استثناءهایی مانند TaskCanceledException (در حین TimeOut و یا فراخوانی متد CancelPendingRequests که البته همانطور که توضیح داده شد، بر روی روش کنترل Response تاثیری ندارند)، HttpRequestException پس از فراخوانی متد response.EnsureSuccessStatusCode (جهت اطمینان حاصل کردن از دریافت پاسخی بدون مشکل از طرف سرور) و یا SocketException و WebException را درصورت بروز مشکلی در شبکه، صادر می‌کند. نیازی به بررسی سایر استثناءها در اینجا نیست.
اگر یکی از این استثناءهای یاد شده رخ‌دادند، اندکی صبر کرده و مجددا درخواست را از ابتدا صادر می‌کنیم.
در پایان این سعی‌های مجدد، اگر استثنایی ثبت شده بود و همچنین عملیات نیز با موفقیت به پایان نرسیده بود، آن‌را به فراخوان صادر می‌کنیم.
نظرات مطالب
صفحه بندی و مرتب سازی خودکار اطلاعات به کمک jqGrid در ASP.NET MVC
با تشکر؛
یک سوال: آیا درون یک formatter می‌توان به سطر جاری دسترسی داشت؟ 
فرض کنید یک ستون برای عملیات داریم:
{
     resize: false, formatter: buttons
}
با کمک formatter فوق (button) قرار است یکسری دکمه‌ها نمایش داده شوند:
function buttons(cellvalue, options, rowObject) {
var addProjectUrl = $('#getAddProjectUrl').data('url') + "/" + options.rowId;

return "<a href=" + addProjectUrl + ">....
}
اکنون باید مقدار ستون قبل از ستون عملیات بررسی شود و نسبت به آن یک لینک دیگر به صورت فوق نمایش داده شود.