- Fadeout unused variable names (#1324, PR: #3733)
- Updated debugger (PR: #3729)
- Fixed not supported exception when trying to decompile a BCL assembly on Mono. For now we do not try to resolve implementation assembly from a ref assembly (PR: omnisharp-roslyn/#1767)
- Added support for generic classes in test runner (#3722, PR: omnisharp-roslyn/#1768)
- Improved autocompletion performance (PR: omnisharp-roslyn/#1761)
- Move to Roslyn's .editorconfig support (omnisharp-roslyn/#1657, PR: omnisharp-roslyn/#1771)
- Fully update CompilationOptions when project files change (PR: omnisharp-roslyn/#1774)
عموما بستههای نیوگت تولید شده، قابلیت دیباگ ضعیفی را دارند. برای بالابردن بهبود تجربهی کاربری آنها میتوان توزیع فایلهای PDB و فعالسازی قابلیت Source Link را به آنها اضافه کرد.
فعالسازی توزیع فایلهای PDB به همراه بستههای NuGet
وجود فایلهای PDB، برای اجرای برنامهها ضرورتی ندارند؛ اما اگر ارائه شوند، به کمک آنها میتوان گزارشهای استثناءهای بسیار کاملتری را به همراه نام فایل و شماره سطرهای مرتبط موجود در Exception.StackTrace، مشاهده کرد.
پیشتر توسعه دهندگان بستههای NuGet، فایلهای PDB را خودشان با تعریف یک سری include در فایل مشخصات بسته، به فایل نهایی تولیدی اضافه میکردند. اما این روزها با ارائهی «NuGet.org Symbol Server»، دیگر افزودن فایلهای PDB به بستههای nupkg توصیه نمیشود. چون حجم نهایی و زمان بازیابی بستهها را بیش از اندازه افزایش میدهند؛ خصوصا اگر مصرف کنندهای قصد دیباگ آنها را نداشته باشد.
راه حل جدید توصیه شده، ارائهی فایلهای ویژهی snupkg. در کنار بستههای nupkg. معمولی است که حاوی فایلهای PDB متناظر با بستهی اصلی NuGet هستند.
برای فعالسازی آنها در پروژههای NET Core. بستههای نیوگت خود تنها کافی است دو تنظیم زیر را به فایل csproj اضافه کنید:
در این حالت پس از ساخت بستهی نیوگت توسط دستور «dotnet pack -c release»، دو فایل با پسوندهای snupkg و nupkg تولید میشوند که باید هر دو را به سایت NuGet ارسال کرد.
در سمت مصرف کننده، IDE شما باید برای کار با این Symbol Server تنظیم شده باشد.
فعالسازی تولید Source Link
وجود PDBها جهت دیباگ بستههای ارائه شده بسیار مفیدند؛ اما اگر بر روی کدهای نهایی بهینه سازی صورت گرفته باشد، ممکن است اطلاعات دیباگ آنها با کد اصلی تطابق پیدا نکنند. برای بهبود این وضعیت و ارتقاء آن به یک سطح بالاتر، مفهوم source link ارائه شدهاست که مستقل است از نوع زبان و همچنین سورس کنترل.
کار سورسلینک، افزودن متادیتای سورس کنترل انتخابی، به بستهی نهایی تولید شدهاست. به این صورت میتوان سورس کامل و متناظر با قطعه کد بسته و کتابخانهی ثالث در حال دیباگ را در IDE خود داشت و با آن به نحو متداولی کار کرد. یعنی سورس لینک، قابلیت «Step Into" the source code"» را مهیا میکند. متادیتای اضافه شده، دقیقا مشخص میکند که بستهی تولیدی نهایی، متناظر با کدام commit سورس کنترل است. به این ترتیب دقیقا میتوان به کدهای همان commit ای که بسته بر اساس آن کامپایل شدهاست، در IDE خود دسترسی یافت.
این قابلیت از Visual Studio 15.3 به بعد در اختیار کاربران آن است (گزینهی Enable Source Server Support، در قسمت Debug/General آن باید فعال شود). همچنین Rider و VSCode نیز از آن پشتیبانی میکنند. برای Rider باید در قسمت Tools/External symbols، گزینههای Use sources from symbol files when available و Allow downloading symbols from remote locations را فعال کنید.
همچنین برای فعالسازی آن در فایل csproj بستهی نیوگت خود میتوانید تنظیمات زیر را اضافه کنید:
البته در اینجا پروایدر مخصوص GitHub را مشاهده میکنید (اگر مخزن کد بستهی شما بر روی آن قرار دارد) و همچنین سایر پروایدرهای مخصوص سورس کنترلهای دیگر مانند Azure DevOps/VSTS نیز برای آن تهیه شدهاند.
روش فعالسازی Source Link در پروژهی VSCode
اگر از VSCode استفاده میکنید، نیاز است تنظیمات زیر را به قسمت configurations فایل launch.json خود اضافه کنید تا قابلیت «Step Into" the source code"» بستههای نیوگتی که از SourceLink پشتیبانی میکنند، با فشردن دکمهی F11 در حین دیباگ، فعال شود:
فعالسازی توزیع فایلهای PDB به همراه بستههای NuGet
وجود فایلهای PDB، برای اجرای برنامهها ضرورتی ندارند؛ اما اگر ارائه شوند، به کمک آنها میتوان گزارشهای استثناءهای بسیار کاملتری را به همراه نام فایل و شماره سطرهای مرتبط موجود در Exception.StackTrace، مشاهده کرد.
پیشتر توسعه دهندگان بستههای NuGet، فایلهای PDB را خودشان با تعریف یک سری include در فایل مشخصات بسته، به فایل نهایی تولیدی اضافه میکردند. اما این روزها با ارائهی «NuGet.org Symbol Server»، دیگر افزودن فایلهای PDB به بستههای nupkg توصیه نمیشود. چون حجم نهایی و زمان بازیابی بستهها را بیش از اندازه افزایش میدهند؛ خصوصا اگر مصرف کنندهای قصد دیباگ آنها را نداشته باشد.
راه حل جدید توصیه شده، ارائهی فایلهای ویژهی snupkg. در کنار بستههای nupkg. معمولی است که حاوی فایلهای PDB متناظر با بستهی اصلی NuGet هستند.
برای فعالسازی آنها در پروژههای NET Core. بستههای نیوگت خود تنها کافی است دو تنظیم زیر را به فایل csproj اضافه کنید:
<PropertyGroup> <IncludeSymbols>true</IncludeSymbols> <SymbolPackageFormat>snupkg</SymbolPackageFormat> </PropertyGroup>
در سمت مصرف کننده، IDE شما باید برای کار با این Symbol Server تنظیم شده باشد.
فعالسازی تولید Source Link
وجود PDBها جهت دیباگ بستههای ارائه شده بسیار مفیدند؛ اما اگر بر روی کدهای نهایی بهینه سازی صورت گرفته باشد، ممکن است اطلاعات دیباگ آنها با کد اصلی تطابق پیدا نکنند. برای بهبود این وضعیت و ارتقاء آن به یک سطح بالاتر، مفهوم source link ارائه شدهاست که مستقل است از نوع زبان و همچنین سورس کنترل.
کار سورسلینک، افزودن متادیتای سورس کنترل انتخابی، به بستهی نهایی تولید شدهاست. به این صورت میتوان سورس کامل و متناظر با قطعه کد بسته و کتابخانهی ثالث در حال دیباگ را در IDE خود داشت و با آن به نحو متداولی کار کرد. یعنی سورس لینک، قابلیت «Step Into" the source code"» را مهیا میکند. متادیتای اضافه شده، دقیقا مشخص میکند که بستهی تولیدی نهایی، متناظر با کدام commit سورس کنترل است. به این ترتیب دقیقا میتوان به کدهای همان commit ای که بسته بر اساس آن کامپایل شدهاست، در IDE خود دسترسی یافت.
این قابلیت از Visual Studio 15.3 به بعد در اختیار کاربران آن است (گزینهی Enable Source Server Support، در قسمت Debug/General آن باید فعال شود). همچنین Rider و VSCode نیز از آن پشتیبانی میکنند. برای Rider باید در قسمت Tools/External symbols، گزینههای Use sources from symbol files when available و Allow downloading symbols from remote locations را فعال کنید.
همچنین برای فعالسازی آن در فایل csproj بستهی نیوگت خود میتوانید تنظیمات زیر را اضافه کنید:
<PropertyGroup> <PublishRepositoryUrl>true</PublishRepositoryUrl> <EmbedUntrackedSources>true</EmbedUntrackedSources> </PropertyGroup> <ItemGroup> <PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.0.0" PrivateAssets="All" /> </ItemGroup>
روش فعالسازی Source Link در پروژهی VSCode
اگر از VSCode استفاده میکنید، نیاز است تنظیمات زیر را به قسمت configurations فایل launch.json خود اضافه کنید تا قابلیت «Step Into" the source code"» بستههای نیوگتی که از SourceLink پشتیبانی میکنند، با فشردن دکمهی F11 در حین دیباگ، فعال شود:
"justMyCode": false, "symbolOptions": { "searchMicrosoftSymbolServer": true }, "suppressJITOptimizations": true, "env": { "COMPlus_ZapDisable": "1" }
آشنایی با RLE
الگوریتم پردازش دوطرفهی یونیکد، جهت و سمت نمایش متن را بر اساس خواص جهتی هر حرف مشخص میکند. در این حالت اگر متن مورد نمایش، انگلیسی و یا فارسی خالص باشند به خوبی عمل میکند؛ اما اگر ترکیب این دو را در یک رشته داشته باشیم، نیاز است نحوهی جهت گیری و نمایش حروف را به Unicode bidirectional algorithm معرفی کنیم. این نوع مشکلات را فارسی زبانها در حین نمایش ترکیبی از متن فارسی و انگلیسی در Tooltips، برنامههای نمایش زیرنویسهای فیلمها، برنامههای گزارشگیری و امثال آن به وفور مشاهده میکنند.
راه حل استاندارد یونیکد آن، استفاده از حروف نامرئی یونیکد است که جهت نمایشی متن جاری را بازنویسی میکنند:
برای مثال حرف یونیکد نامرئی U202B به این معنا است: «از این لحظه به بعد تا اطلاع ثانوی، متن نمایش داده شده راست به چپ است؛ صرفنظر از خواص جهتی حروف مورد استفاده».
این تا اطلاع ثانوی یا POP نیز توسط حرف U202C مشخص شده و به پایان میرسد. به عبارتی یونیکد شبیه به یک پشته یا Stack عمل میکند.
مثال اول
عبارت «متن فارسی به همراه جملهی this is a test انگلیسی» را در نظر بگیرید. اکنون فرض کنید میخواهیم از آن جهت ارائه یک فایل readme مخصوص GitHub با فرمت mark down یا md استفاده کنیم:
همانطور که ملاحظه میکنید، جمله معکوس شدهاست. برای رفع این مشکل میتوان از کاراکتر نامرئی یونیکد 202b استفاده کرد. البته در mark down امکان تعریف سادهتر این کاراکتر به صورت ذیل نیز پیش بینی شدهاست:
مثال دوم
اغلب نمایشگرهای چپ به راست متون نیز در حالت پیش فرض، عبارت مثال اول را معکوس نمایش میدهند:
اگر از notepad استفاده کنید، به صورت توکار امکان افزودن RLE را به ابتدای جمله دارد:
مثال سوم
در زبانهای دات نتی نیز جهت نمایش صحیح متون ترکیبی، میتوان حرف RLE را به صورت ذیل به ابتدای یک جمله اضافه کرد:
این مورد خصوصا در ابزارهای گزارشگیری یا کار با API ویندوز میتواند مفید باشد.
تشخیص راست به چپ بودن متن
در محیط وب جهت نمایش صحیح یک متن نیز میتوان به مرورگرها کمک کرد. تعریف dir=rtl تفاوتی با قرار دادن RLE در ابتدای یک متن ندارد. در این حالت نیاز است بدانیم حروف RTL در چه بازهای از شماره حروف یونیکد قرار میگیرند:
که یک نمونهی ساده شدهی این بازهها، به صورت ذیل است:
و حالت پیشرفتهتر آنرا که سایت توئیتر برای ارائهی یک جعبه متنی به صورت خودکار راست به چپ شونده، مورد استفاده قرار میدهد، در اینجا میتوانید مطالعه کنید:
RTLText.module.js
نمایش صحیح عبارات ممیز دار در یک گزارش راست به چپ
استاندارد یونیکد یک سری کاراکتر را «کاراکتر ضعیف» معرفی کردهاست. برای مثال کاراکتر اسلش بکار رفته در یک تاریخ هم از این دست است. بنابراین اگر در یک گزارش تولیدی، شماره کد ممیز دار و یا یک تاریخ را معکوس مشاهده میکنید به این علت است که یک «نویسه ضعیف» مثل اسلش نمیتواند جهت را تغییر دهد؛ مگر اینکه از یک «نویسه قوی» برای دستکاری آن استفاده شود (مانند RLE و POP که در ابتدای بحث معرفی شدند).
یک مطلب تکمیلی در این مورد: «iTextSharp و نمایش صحیح تاریخ در متنی راست به چپ»
این اصول در تمام محیطهایی که از یونیکد پشتیبانی میکنند صادق است و تفاوتی نمیکند که ویندوز باشد یا Adobe reader و یا یک ابزار گزارشگیری که اصلا برای محیطهای راست به چپ طراحی نشدهاست.
کار با اعراب در متون راست به چپ
در یونیکد یک حرف میتواند از یک یا چند code point تشکیل شود. در حالت FormC، هر حرف، با اعراب آن یک code point را تشکیل میدهند. در حالت FormD، حرف با اعراب آن دو code point را تشکیل خواهند داد. به همین جهت نیاز است رشته را تبدیل به حالت D کرد تا بتوان اعراب آنرا مجزای از حروف پایه، حذف نمود.
البته اعراب در اینجا به اعراب عربی ختم نمیشود. یک سری حروف اروپایی مانند "ä" ،"ö" و "ü" را نیز شامل میشود.
یک مطلب تکمیلی در این مورد: «حذف اعراب از حروف و کلمات»
الگوریتم پردازش دوطرفهی یونیکد، جهت و سمت نمایش متن را بر اساس خواص جهتی هر حرف مشخص میکند. در این حالت اگر متن مورد نمایش، انگلیسی و یا فارسی خالص باشند به خوبی عمل میکند؛ اما اگر ترکیب این دو را در یک رشته داشته باشیم، نیاز است نحوهی جهت گیری و نمایش حروف را به Unicode bidirectional algorithm معرفی کنیم. این نوع مشکلات را فارسی زبانها در حین نمایش ترکیبی از متن فارسی و انگلیسی در Tooltips، برنامههای نمایش زیرنویسهای فیلمها، برنامههای گزارشگیری و امثال آن به وفور مشاهده میکنند.
راه حل استاندارد یونیکد آن، استفاده از حروف نامرئی یونیکد است که جهت نمایشی متن جاری را بازنویسی میکنند:
U+202A: LEFT-TO-RIGHT EMBEDDING (LRE) U+202B: RIGHT-TO-LEFT EMBEDDING (RLE) U+202D: LEFT-TO-RIGHT OVERRIDE (LRO) U+202E: RIGHT-TO-LEFT OVERRIDE (RLO) U+202C: POP DIRECTIONAL FORMATTING (PDF)
این تا اطلاع ثانوی یا POP نیز توسط حرف U202C مشخص شده و به پایان میرسد. به عبارتی یونیکد شبیه به یک پشته یا Stack عمل میکند.
مثال اول
عبارت «متن فارسی به همراه جملهی this is a test انگلیسی» را در نظر بگیرید. اکنون فرض کنید میخواهیم از آن جهت ارائه یک فایل readme مخصوص GitHub با فرمت mark down یا md استفاده کنیم:
همانطور که ملاحظه میکنید، جمله معکوس شدهاست. برای رفع این مشکل میتوان از کاراکتر نامرئی یونیکد 202b استفاده کرد. البته در mark down امکان تعریف سادهتر این کاراکتر به صورت ذیل نیز پیش بینی شدهاست:
‫
مثال دوم
اغلب نمایشگرهای چپ به راست متون نیز در حالت پیش فرض، عبارت مثال اول را معکوس نمایش میدهند:
اگر از notepad استفاده کنید، به صورت توکار امکان افزودن RLE را به ابتدای جمله دارد:
مثال سوم
در زبانهای دات نتی نیز جهت نمایش صحیح متون ترکیبی، میتوان حرف RLE را به صورت ذیل به ابتدای یک جمله اضافه کرد:
public const char RightToLeftEmbedding = (char)0x202B;
تشخیص راست به چپ بودن متن
در محیط وب جهت نمایش صحیح یک متن نیز میتوان به مرورگرها کمک کرد. تعریف dir=rtl تفاوتی با قرار دادن RLE در ابتدای یک متن ندارد. در این حالت نیاز است بدانیم حروف RTL در چه بازهای از شماره حروف یونیکد قرار میگیرند:
Right-to-left Unicode blocks for modern scripts are: Consecutive range of the main letters: U+0590 to U+05FF - Hebrew U+0600 to U+06FF - Arabic U+0700 to U+074F - Syriac U+0750 to U+077F - Arabic Supplement U+0780 to U+07BF - Thaana U+07C0 to U+07FF - N'Ko U+0800 to U+083F - Samaritan Arabic Extended: U+08A0 to U+08FF - Arabic Extended-A Consecutive presentation forms: U+FB1D to U+FB4F - Hebrew presentation forms U+FB50 to U+FDFF - Arabic presentation forms A More Arabic presentation forms: U+FE70 to U+FEFF - Arabic presentation forms B
private static readonly Regex _matchArabicHebrew = new Regex(@"[\u0600-\u06FF,\u0590-\u05FF]", RegexOptions.IgnoreCase | RegexOptions.Compiled); public static bool ContainsRtlFarsi(this string txt) { return !string.IsNullOrEmpty(txt) && _matchArabicHebrew.IsMatch(txt); }
RTLText.module.js
نمایش صحیح عبارات ممیز دار در یک گزارش راست به چپ
استاندارد یونیکد یک سری کاراکتر را «کاراکتر ضعیف» معرفی کردهاست. برای مثال کاراکتر اسلش بکار رفته در یک تاریخ هم از این دست است. بنابراین اگر در یک گزارش تولیدی، شماره کد ممیز دار و یا یک تاریخ را معکوس مشاهده میکنید به این علت است که یک «نویسه ضعیف» مثل اسلش نمیتواند جهت را تغییر دهد؛ مگر اینکه از یک «نویسه قوی» برای دستکاری آن استفاده شود (مانند RLE و POP که در ابتدای بحث معرفی شدند).
یک مطلب تکمیلی در این مورد: «iTextSharp و نمایش صحیح تاریخ در متنی راست به چپ»
این اصول در تمام محیطهایی که از یونیکد پشتیبانی میکنند صادق است و تفاوتی نمیکند که ویندوز باشد یا Adobe reader و یا یک ابزار گزارشگیری که اصلا برای محیطهای راست به چپ طراحی نشدهاست.
کار با اعراب در متون راست به چپ
در یونیکد یک حرف میتواند از یک یا چند code point تشکیل شود. در حالت FormC، هر حرف، با اعراب آن یک code point را تشکیل میدهند. در حالت FormD، حرف با اعراب آن دو code point را تشکیل خواهند داد. به همین جهت نیاز است رشته را تبدیل به حالت D کرد تا بتوان اعراب آنرا مجزای از حروف پایه، حذف نمود.
البته اعراب در اینجا به اعراب عربی ختم نمیشود. یک سری حروف اروپایی مانند "ä" ،"ö" و "ü" را نیز شامل میشود.
یک مطلب تکمیلی در این مورد: «حذف اعراب از حروف و کلمات»
اشتراکها
کتابخانه dragula
Drag and drop so simple it hurts Demo
Framework support includes vanilla JavaScript, Angular, and React.
- Official Angular bridge for
dragula
(demo) - Official Angular 2 bridge for
dragula
(demo) - Official React bridge for
dragula
(demo)
Features
- Super easy to set up
- No bloated dependencies
- Figures out sort order on its own
- A shadow where the item would be dropped offers visual feedback
- Touch events!
- Seamlessly handles clicks without any configuration
نظرات مطالب
Blazor 5x - قسمت 31 - احراز هویت و اعتبارسنجی کاربران Blazor WASM - بخش 1 - انجام تنظیمات اولیه
نگارش کامل و صحیح getBase64WithoutPadding به این صورت هست:
private static byte[] getBase64WithoutPadding(string base64) { // From: // https://github.com/dvsekhvalnov/jose-jwt/blob/master/jose-jwt/util/Base64Url.cs#L16 // https://github.com/auth0/jwt-decode/blob/master/lib/base64_url_decode.js#L15 // https://github.com/AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet/blob/0665af62cc58a28ebe184dd97f4d018f84e1d83d/src/Microsoft.IdentityModel.Tokens/Base64UrlEncoder.cs#L175 base64 = base64.Replace('-', '+'); // 62nd char of encoding base64 = base64.Replace('_', '/'); // 63rd char of encoding switch (base64.Length % 4) // Pad with trailing '='s { case 0: break; // No pad chars in this case case 2: base64 += "=="; break; // Two pad chars case 3: base64 += "="; break; // One pad char default: throw new ArgumentOutOfRangeException(nameof(base64), "Illegal base64url string!"); } return Convert.FromBase64String(base64); // Standard base64 decoder }
نظرات مطالب
TwitterBootstrapMVC
twitter Bootstrap کتابخانه جامعی از CSSها و JScriptها برای طراحی صفحات وب میباشد ک امروزه بسیار مقبول واقع شده است و ظاهری ساده و در عین حال کارامد را برای وب سایت شما با پشتیبانی از انواع مختلف از نمایشگرها به ارمغان میآورد .
در ادامهی تمرین قسمت قبل که مقدمات ثبت درخواست رزرو یک اتاق را فراهم کردیم، اکنون میخواهیم اگر کاربری بر روی دکمهی checkout now یک اتاق کلیک کرد، به درگاه مجازی پرباد منتقل شده، پرداخت را تکمیل کند، به برنامه هدایت شود و در آخر درخواست او در سیستم ثبت گردد. مزیت کار کردن با درگاه مجازی پرباد، امکان آزمایش محلی برنامه، بدون نیاز به یک درگاه بانکی واقعی است و زمانیکه قرار است با یک درگاه بانکی واقعی کار شود، فقط قسمت معرفی و تنظیمات ابتدایی مشخصات درگاه بانکی آن باید تغییر کند و نه هیچ قسمت دیگری از کدهای برنامه.
نصب پرباد و انجام تنظیمات اولیهی آن
بستههای نیوگت پرباد را در دو پروژهی زیر نصب خواهیم کرد:
الف) پروژهی Web API (و یا همان BlazorWasm.WebApi در مثال این سری):
که شامل بستههای ASP.NET Core آن و همچنین محل ذخیره سازی مبتنی بر EF-Core آن است.
ب) پروژهای که محل قرارگیری فایلهای Migration است (و یا همان BlazorServer.DataAccess) در این مثال:
که در اینجا فقط نیاز به بستهی EF-Core آن است تا بتوان Context مخصوص پرباد را در حین اعمال مهاجرتها شناسایی کرد.
پس از نصب این بستهها، به کلاس آغازین پروژهی Web API مراجعه کرده و تنظیمات سرویسها و همچنین میانافزار پرباد را انجام میدهیم:
چند نکته:
- در متد ConfigureGateways میتوان چندین درگاه را معرفی کرد که برای مثال در اینجا از درگاه مجازی و محلی آن استفاده شدهاست.
- در متد ConfigureStorage، تنظیمات EF-Core آنرا مشاهده میکنید. پرباد به همراه DbContext خاص خودش است. یعنی در این حالت برنامهی شما حداقل دو DbContext خواهد داشت؛ یکی ApplicationDbContext و دیگری ParbadDataContext.
- میخواهیم شمارهی تراکنشها را به صورت خودکار توسط پرباد مدیریت کنیم. به همین جهت میتوان عدد ابتدای آنرا توسط متد ConfigureAutoTrackingNumber مشخص کرد.
- در پایان هم تعاریف مسیریابی میانافزار آنرا مشاهده میکنید که میتواند برای حالت توسعه و ارائهی نهایی متفاوت باشد.
تکمیل خواص موجودیت RoomOrderDetail جهت کار با پرباد
موجودیت RoomOrderDetail را در قسمت قبل معرفی کردیم. پرباد به ازای هر تراکنش بانکی که صورت میگیرد، یا نیاز به یک TrackingNumber خودکار را دارد و یا دستی. یعنی یا میتوانیم شماره تراکنش خاص خودمان را تولید کنیم و در اختیار آن قرار دهیم و یا از آن درخواست کنیم تا این شماره را مدیریت کرده و به صورت خودکار تولید کند. در هر دو حالت نیاز است این شماره را به ردیفهای جدول جزئیات سفارشات اتاقهای هتل اضافه کرد که در این مثال ParbadTrackingNumber نام دارد:
همچنین در پایان عملیات هم فیلدهای IsPaymentSuccessful و وضعیت اتاق را تکمیل میکنیم.
ایجاد جداول متناظر با ParbadDataContext
همانطور که عنوان شد، اکنون برنامه به همراه دو DbContext است. بنابراین در این حالت در حین اجرای مهاجرتها، ذکر نام Context مدنظر اجباری است.
برای ایجاد مهاجرتهای متناظر با ParbadDataContext، از طریق خط فرمان به پوشهی BlazorServer.DataAccess وارد شده و دستورات زیر را اجرا میکنیم:
چون برنامه دو Context ای است، نیاز است دوبار دستور تولید مهاجرتها و دوبار دستور اعمال آنها را به بانک اطلاعاتی صادر کرد که روش آنرا در دستورات فوق مشاهده میکنید. پس از این دستورات، بانک اطلاعاتی برنامه شامل دو جدول جدید مخصوص پرباد خواهد بود:
روش یکپارچه سازی پرباد با یک برنامهی SPA
روش متداول کار با پرباد، بر اساس طراحی مخصوص ASP.NET Core آن است. ابتدا درخواستی را به آن ارسال میکنید. سپس پرباد شماره تراکنشی را تولید کرده و شروع تراکنش را در بانک اطلاعاتی ثبت میکند. در ادامه به صورت خودکار، کار ارسال اطلاعات به درگاه بانکی (برای مثال ارسال تمام فیلدهای یک فرم ویژهی آن بانک، بر اساس مستندات آن) و هدایت به درگاه بانکی را انجام میدهد. پس از پایان کار پرداخت، کار هدایت به اکشن متد دریافت تائیدیهی نهایی صورت میگیرد و همینجا کار به پایان میرسد. این روش هرچند برای برنامههای سمت سرور ASP.NET Core کار میکند، اما ... به همین نحو با برنامههای تک صفحهای وب مانند Blazor WASM قابل استفاده نیست. در اینجا روش تبادل اطلاعات با اکشن متدهای وب سرویسهای برنامه از طریق یک HttpClient است و در این حالت دیگر نمیتوان از مزایای Post و Redirect خودکار پرباد که در سمت سرور صورت میگیرد استفاده کرد. با استفاده از HttpClient، یک شیء را به سمت Web API ارسال میکنیم و در پاسخ، فقط یک شیء را دریافت میکنیم. در اینجا دیگر خبری از Redirect به درگاه اصلی بانکی و Post اطلاعات به آن نیست. بنابراین روش کار با پرباد در اینجا به صورت زیر خواهد بود:
الف) شماره Id سفارش و مبلغ نهایی آنرا از طریق یک درخواست Get معمولی به اکشن متدی در سمت سرور ارسال میکنیم. یعنی نیاز است ابتدا Url زیر را تشکیل داد که شماره سفارش و مبلغ آن، به صورت کوئری استرینگهایی به اکشن متد PayRoomOrder ارسال میشوند:
برنامهی کلاینت برای اینکه بتواند این هدایت را انجام دهد، نیاز به نکتهی خاصی را دارد که در ادامه توضیح داده خواهد شد.
ب) اکنون چون یک redirect سمت سرور صورت گرفته، به صورت معمولی در اکشن متد PayRoomOrder با پرباد پردازش صورت گرفته و به سمت درگاه هدایت میشویم. پس از پرداخت نهایی، باز هم به صورت خودکار به اکشن متد دیگری جهت تائید عملیات هدایت خواهیم شد.
ج) در پایان کار، اکشن متد سمت سرور، ما را به سمت کامپوننتی در برنامهی کلاینت Redirect خواهد کرد:
در اینجا شماره سفارش ابتدایی که مشخص است. همان شمارهای است که کار را با آن از سمت کلاینت آغاز کردیم. نکتهی مهم، TrackingNumber تراکنش است که بر اساس آن رکورد متناظری یافت شده و وضعیت نهایی آنرا به کاربر نمایش میدهیم.
بنابراین روش یکپارچه سازی پربابد با برنامههای SPA، بر اساس Redirectهای کامل است که سبب بارگذاری مجدد کل صفحه و آدرسها میشوند و در اینجا از HttpClient برای کار با پرباد استفاده نخواهیم کرد؛ چون تمام اعمال خودکار آنرا از دست خواهیم داد و مجبور به بازنویسی آنها خواهیم شد که در دراز مدت با تغییرات این کتابخانه، قابل نگهداری نخواهند بود. بنابراین بهتر است خود پرباد کار Redirectها و ارسال اطلاعات به درگاههای بانکی را مدیریت کند و نه ما از طریق کار با یک HttpClient.
آشنایی با گردش کار برنامه
در این مثال، مراحل زیر را طی خواهیم کرد:
1- شروع به انتخاب یک بازهی زمانی و تعداد شب اقامت
2- انتخاب یک اتاق از لیست اتاقها با کلیک بر روی دکمهی Book آن
3- کلیک بر روی دکمهی checkout، در صفحهی مشاهدهی جزئیات اتاق و شروع به پرداخت
4- هدایت به درگاه مجازی پرباد در سمت برنامهی Web API
5- پرداخت و هدایت خودکار به سمت برنامهی Web API، جهت تائید نهایی
6- هدایت نهایی به سمت برنامهی کلاینت، جهت نمایش اطلاعات پرداخت
ایجاد کنترلر پرداخت، توسط درگاه مجازی پرباد
پس از آشنایی با گردش کاری اطلاعات در اینجا، نیاز است بتوان لینک زیر را در برنامهی کلاینت تولید کرد و سپس کاربر را به سمت اکشن متد PayRoomOrder هدایت نمود:
این اکشن متد و کنترلر آن به صورت زیر تهیه میشود:
توضیحات:
در اینجا کدهای کامل ParbadPaymentController مشاهده میکنید.
- گردش کاری پرداخت، با فراخوانی اکشن متد PayRoomOrder شروع میشود که دو پارامتر شماره سفارش و مبلغ آنرا دریافت میکند.
نوع آن هم عمدا، HttpGet درنظر گرفته شدهاست تا دقیقا مشخص باشد که فقط با Redirect کامل به آن (هدایت کامل از سمت کلاینت به سمت سرور)، کار خواهد کرد و هدف دیگری را دنبال نمیکند.
- در اکشن متد PayRoomOrder، نیاز است لینک بازگشت از درگاه بانکی را مشخص کنیم. پس از اینکه کاربر پرداختی را انجام داد، مجددا به صورت خودکار، به سمت آدرسی در همین Web API و نه برنامهی سمت کلاینت هدایت میشود؛ چون هنوز کار پرباد به پایان نرسیده و باید عملیات انجام شده را تصدیق کند. به همین جهت ابتدا آدرس اکشن متدی که کار تائید نهایی را انجام میدهد، تولید کرده و به متد RequestAsync آن به همراه مبلغ نهایی و نوع درگاه، ارسال میکنیم.
- استفاده از UseAutoIncrementTrackingNumber سبب میشود تا پرباد خودش مدیریت TrackingNumber را انجام دهد که پس از پایان عملیات، توسط خاصیت result.TrackingNumber در دسترس خواهد بود.
- پس از پایان عملیات ابتدایی RequestAsync که سشن پرباد را ایجاد کرده و همچنین رکوردی را در بانک اطلاعاتی نیز ثبت میکند (در جداول درونی خود پرباد)، نیاز است رکورد سفارشی را که با آن کار را شروع کردیم یافته و TrackingNumber آنرا با مقدار واقعی دریافتی از پرباد، به روز رسانی کنیم. اینکار توسط متد UpdateRoomOrderTrackingNumberAsync انجام میشود:
بر اساس شماره سفارشی که داریم، رکورد متناظر با آنرا یافته و سپس trackingNumber تولیدی را در آن به روز رسانی میکنیم.
- اکنون با فراخوانی متد ()result.GatewayTransporter.TransportToGateway، دو کار مهم رخ میدهند:
الف) ارسال خودکار اطلاعات به سمت درگاه بانکی
ب) Redirect خودکار به سمت درگاه بانگی
به همین جهت است که علاقمند نبودیم تا این مراحل را توسط HttpClient برنامهی Blazor WASM مدیریت و بازنویسی کنیم.
- پس از هدایت به سمت درگاه بانکی و تکمیل پرداخت، اکنون مجددا به همان verifyUrl هدایت میشویم. یعنی اکنون به مرحلهی پردازش اکشن متد VerifyRoomOrderPayment در سمت Web API رسیدهایم.
در اینجا ابتدا invoice.TrackingNumber در حال پردازش را دریافت میکنیم. به کمک این عدد میتوان رکورد سفارش متناظر با آنرا یافت. به همین جهت است که آنرا به لیست فیلدهای جدول سفارشات اضافه کردیم. اینکار هم توسط متد GetOrderDetailByTrackingNumberAsync صورت میگیرد:
- در ادامه پرباد کار تصدیق اطلاعات دریافتی از درگاه بانکی را انجام میدهد. دراینجا اگر عملیات با موفقیت مواجه شود، سه فیلدی را که در ابتدای بحث در مورد ثبت اطلاعات تراکنش اضافه کردیم، به روز رسانی میکنیم:
- در اینجا بر اساس trackingNumber، سند متناظری را یافته و سپس بررسی میکنیم که آیا مبلغ سند، با مبلغ تائید شده، یکی هست یا خیر؟ اگر خیر، نیاز هست پرداخت را برگشت بزنیم که اینکار توسط متد کنسل پرباد قابل انجام است.
- در تمام این مراحل، کار Redirect به سمت کلاینت و کامپوننت payment-result آن، با فراخوانی متد return Redirect اکشن متدها صورت میگیرد که Url آن به صورت زیر تامین میشود:
در این متد Client_URL را از فایل appsettings.json برنامهی Web API دریافت میکنیم که به آدرس ریشهی برنامهی کلاینت اشاره میکند:
تکمیل قسمت سمت کلاینت عملیات پرداخت بانکی، توسط درگاه مجازی پرباد
تا اینجا کنترلری که کار پرداخت آنلاین را مدیریت میکند، پیاده سازی کردیم. قسمت آخر این بحث به تکمیل جزئیات این گردش کاری که شامل شروع عملیات سفارش و پرداخت از سمت کلاینت و نمایش پیام خطا یا موفقیت پرداخت به کاربر است، اختصاص دارد.
الف) تکمیل کامپوننت RoomDetails.razor جهت شروع به پرداخت آنلاین
کامپوننت RoomDetails.razor را در قسمت قبل آغاز کردیم و توسعهی آنرا تا جائی پیش بردیم که اعتبارسنجیهای آنرا به علت استفادهی از خواص تو در تو، به صورت دستی انجام دادیم. پس از مرحلهی اعتبارسنجی، اکنون میخواهیم کاربر را به سمت درگاه بانکی جهت پرداخت، هدایت کنیم:
متد HandleValidation را در انتهای قسمت قبل تکمیل کردیم. اکنون OrderDetails را بر اساس اطلاعات فرم و انتخابهای کاربر، تکمیل کرده و به متد SaveRoomOrderDetailsAsync ارسال میکنیم تا Id سفارش را تولید کنیم. این همان Id ای است که قرار است به سمت سرور و Web API ارسال کنیم تا بر اساس آن تراکنش و Tracking Number ای را بتوان به رکورد جاری انتساب داد. بنابراین نیاز به کنترلر سمت Web API ای را داریم که بتواند اینکار را انجام دهد:
- متد Create، بر اساس اطلاعات وارد شدهی توسط کاربر، آنها را تبدیل به یک رکورد سفارش جدید میکند و به سمت کلاینت بازگشت میدهد.
- متد GetOrderDetail، بر اساس trackingNumber دریافتی از پرباد، کار بازگشت رکورد متناظری را انجام میدهد. از آن در پایان کار، جهت نمایش وضعیت پرداخت، استفاده میکنیم.
این دو متد در سرویس سمت سرور RoomOrderDetailsService، به صورت زیر تامین شدهاند:
اکنون که Web API Endpoint مدنظر را ایجاد کردیم، نیاز است سرویس سمت کلاینتی را نیز جهت تعامل با آن تهیه کنیم:
- متد GetOrderDetailAsync بر اساس trackingNumber دریافتی پس از عملیات تصدیق پرداخت، کار بازگشت جزئیات رکورد متناظری را انجام میدهد.
- متد SaveRoomOrderDetailsAsync، یک رکورد سفارش جدید را ایجاد میکند. در اینجا با روش مشاهدهی خطای کامل بازگشتی از سمت سرور (در صورت وجود) هم آشنا شدهایم که در مواقع لزوم میتواند راهگشا باشد.
- در متد SaveRoomOrderDetailsAsync فعلا مقدار UserId اجباری را به عبارتی دلخواه، تنظیم کردهایم. این مورد را در قسمتهای بعدی با معرفی اعتبارسنجی و احراز هویت سمت کلاینت، تکمیل خواهیم کرد.
این سرویس جدید را هم باید به سیستم تزریق وابستگیهای برنامهی کلاینت معرفی کرد تا قابل استفاده شود:
بنابراین در متد HandleCheckout ای که در حال بررسی آن هستیم، ابتدا متد SaveRoomOrderDetailsAsync فوق فراخوانی میشود تا توسط Web API Endpoint متناظری، یک رکورد سفارش ابتدایی را ایجاد کرده و Id آنرا در اختیار ما قرار دهد.
سپس به قطعه کد مهم زیر میرسیم:
اینجا است که برای نمونه آدرس https://localhost:5001/api/ParbadPayment/PayRoomOrder?orderId=1&amount=1000 ساخته شده و توسط متد NavigateTo فراخوانی میشود. فراخوانی متداول متد NavigateTo در اینجا کارساز نیست؛ چون سبب reload آدرس درخواستی نمیشود. یعنی هدایتهای صورت گرفتهی توسط آن، در همان داخل مرورگر رخ میدهند و سبب ارسال درخواستی به سمت سرور نخواهند شد. میتوان این رفتار را با ذکر پارامتر دوم آن تغییر داد. در اینجا اگر پارامتر forceLoad را به true تنظیم کنیم، ابتدا سبب هدایت به آدرس درخواستی و سپس reload کامل صفحه میشود (دقیقا مثل اینکه شخصی، آدرسی را در نوار آدرس مرورگر وارد کند و سپس دکمهی enter را بفشارد). این reload است که برنامهی کلاینت را اکنون به سمت برنامهی Web API هدایت میکند.
نمایش وضعیت پرداخت، به کاربر در پایان گردش کاری آن
پس از این مراحل، مرحلهی آخر کار باقی ماندهاست؛ یعنی بازگشت از اکشن متد VerifyRoomOrderPayment سمت سرور، به کامپوننت PaymentResult سمت کلاینت، برای نمایش نتیجهی عملیات. به همین جهت کامپوننت جدید Pages\HotelRooms\PaymentResult.razor را ایجاد کرده و به صورت زیر تکمیل میکنیم:
این کامپوننت بر اساس مسیریابی که دارد:
سه پارامتر شماره سفارش، شماره تراکنش و پیامی را پس از پایان عملیات تصدیق پرداخت، از Web API، در طی یک redirect کامل دریافت میکند. در ادامه به کمک متد RoomOrderDetailService.GetOrderDetailAsync که آنرا پیشتر توسعه دادیم، اصل رکورد متناظر با این سفارش را بازیابی کرده و فیلدهای IsPaymentSuccessful و Status آنرا بررسی میکنیم (این فیلدها در زمان تصدیق پرداخت، در همان سمت سرور مقدار دهی میشوند). همچنین جهت محکمکاری، قسمتی از این اطلاعات را با Local Storage نیز انطباق دادهایم. اگر پرداخت، موفقیت آمیز باشد، شماره سفارش و همچنین شماره تراکنش را به کاربر نمایش میدهیم و یا پیام دریافتی از سرور را در صفحه درج میکنیم.
جلوگیری از ثبت سفارش اتاقی که رزرو شدهاست
پس از پایان عملیات سفارش یک اتاق، بهتر است امکان سفارش اتاقی را که دیگر در دسترس نیست، غیرفعال کنیم (تصویر فوق) که اینکار را میتوان توسط خاصیت IsBooked مدل UI کامپوننت نمایش لیست اتاقها انجام داد:
این خاصیت را در متدهای بازگشت لیست تمام اتاقها و یا بازگشت اطلاعات یک اتاق، به صورت زیر محاسبه و مقدار دهی میکنیم:
متد isRoomBooked، یک محاسبهی سمت سرور محسوب نمیشود؛ چون با استفاده از Includeهای نوشته شده، اطلاعات کامل اتاق و وابستگیهای آن (سرهای دیگر رابطهی تشکیل شده) را داریم و این محاسبات سبب رفت و برگشتی به سمت سرور نمیشوند.
اکنون که خاصیت IsBooked مقدار دهی شدهاست، در دو قسمت از آن استفاده خواهیم کرد:
الف) در کامپوننت نمایش لیست اتاقها
ب) در کامپوننت نمایش جزئیات یک اتاق
کدهای کامل این مطلب را از اینجا میتوانید دریافت کنید: Blazor-5x-Part-30.zip
نصب پرباد و انجام تنظیمات اولیهی آن
بستههای نیوگت پرباد را در دو پروژهی زیر نصب خواهیم کرد:
الف) پروژهی Web API (و یا همان BlazorWasm.WebApi در مثال این سری):
<Project Sdk="Microsoft.NET.Sdk.Web"> <ItemGroup> <PackageReference Include="Parbad.AspNetCore" Version="1.1.0" /> <PackageReference Include="Parbad.Storage.EntityFrameworkCore" Version="1.2.0" /> </ItemGroup> </Project>
ب) پروژهای که محل قرارگیری فایلهای Migration است (و یا همان BlazorServer.DataAccess) در این مثال:
<Project Sdk="Microsoft.NET.Sdk.Web"> <ItemGroup> <PackageReference Include="Parbad.Storage.EntityFrameworkCore" Version="1.2.0" /> </ItemGroup> </Project>
پس از نصب این بستهها، به کلاس آغازین پروژهی Web API مراجعه کرده و تنظیمات سرویسها و همچنین میانافزار پرباد را انجام میدهیم:
namespace BlazorWasm.WebApi { public class Startup { // ... public void ConfigureServices(IServiceCollection services) { // ... var connectionString = Configuration.GetConnectionString("DefaultConnection"); services.AddParbad() .ConfigureHttpContext(httpContextBuilder => httpContextBuilder.UseDefaultAspNetCore()) .ConfigureGateways(gatewayBuilder => { gatewayBuilder .AddParbadVirtual() .WithOptions(gatewayOptions => gatewayOptions.GatewayPath = "/MyVirtualGateway"); }) .ConfigureStorage(storageBuilder => { storageBuilder.UseEfCore(efCoreOptions => { var assemblyName = typeof(ApplicationDbContext).Assembly.GetName().Name; efCoreOptions.ConfigureDbContext = db => db.UseSqlServer( connectionString, sqlServerOptionsAction: sqlOptions => sqlOptions.MigrationsAssembly(assemblyName) ); }); }) .ConfigureAutoTrackingNumber(opt => opt.MinimumValue = 1) .ConfigureOptions(parbadOptions => { // parbadOptions.Messages.PaymentSucceed = "YOUR MESSAGE"; }); // ... } // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. public void Configure(IApplicationBuilder app, IWebHostEnvironment env) { // ... app.UseEndpoints(endpoints => { endpoints.MapControllers(); }); if (env.IsDevelopment()) { app.UseParbadVirtualGatewayWhenDeveloping(); } else { app.UseParbadVirtualGateway(); } } } }
- در متد ConfigureGateways میتوان چندین درگاه را معرفی کرد که برای مثال در اینجا از درگاه مجازی و محلی آن استفاده شدهاست.
- در متد ConfigureStorage، تنظیمات EF-Core آنرا مشاهده میکنید. پرباد به همراه DbContext خاص خودش است. یعنی در این حالت برنامهی شما حداقل دو DbContext خواهد داشت؛ یکی ApplicationDbContext و دیگری ParbadDataContext.
- میخواهیم شمارهی تراکنشها را به صورت خودکار توسط پرباد مدیریت کنیم. به همین جهت میتوان عدد ابتدای آنرا توسط متد ConfigureAutoTrackingNumber مشخص کرد.
- در پایان هم تعاریف مسیریابی میانافزار آنرا مشاهده میکنید که میتواند برای حالت توسعه و ارائهی نهایی متفاوت باشد.
تکمیل خواص موجودیت RoomOrderDetail جهت کار با پرباد
موجودیت RoomOrderDetail را در قسمت قبل معرفی کردیم. پرباد به ازای هر تراکنش بانکی که صورت میگیرد، یا نیاز به یک TrackingNumber خودکار را دارد و یا دستی. یعنی یا میتوانیم شماره تراکنش خاص خودمان را تولید کنیم و در اختیار آن قرار دهیم و یا از آن درخواست کنیم تا این شماره را مدیریت کرده و به صورت خودکار تولید کند. در هر دو حالت نیاز است این شماره را به ردیفهای جدول جزئیات سفارشات اتاقهای هتل اضافه کرد که در این مثال ParbadTrackingNumber نام دارد:
using System; using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; namespace BlazorServer.Entities { public class RoomOrderDetail { // ... [Required] public long ParbadTrackingNumber { get; set; } public bool IsPaymentSuccessful { get; set; } public string Status { get; set; } } }
ایجاد جداول متناظر با ParbadDataContext
همانطور که عنوان شد، اکنون برنامه به همراه دو DbContext است. بنابراین در این حالت در حین اجرای مهاجرتها، ذکر نام Context مدنظر اجباری است.
برای ایجاد مهاجرتهای متناظر با ParbadDataContext، از طریق خط فرمان به پوشهی BlazorServer.DataAccess وارد شده و دستورات زیر را اجرا میکنیم:
dotnet tool update --global dotnet-ef --version 5.0.4 dotnet build dotnet ef migrations --startup-project ../../BlazorWasm/BlazorWasm.WebApi/ add AddParbadFields --context ApplicationDbContext dotnet ef migrations --startup-project ../../BlazorWasm/BlazorWasm.WebApi/ add AddParbad --context Parbad.Storage.EntityFrameworkCore.Context.ParbadDataContext dotnet ef --startup-project ../../BlazorWasm/BlazorWasm.WebApi/ database update --context ApplicationDbContext dotnet ef --startup-project ../../BlazorWasm/BlazorWasm.WebApi/ database update --context Parbad.Storage.EntityFrameworkCore.Context.ParbadDataContext
روش یکپارچه سازی پرباد با یک برنامهی SPA
روش متداول کار با پرباد، بر اساس طراحی مخصوص ASP.NET Core آن است. ابتدا درخواستی را به آن ارسال میکنید. سپس پرباد شماره تراکنشی را تولید کرده و شروع تراکنش را در بانک اطلاعاتی ثبت میکند. در ادامه به صورت خودکار، کار ارسال اطلاعات به درگاه بانکی (برای مثال ارسال تمام فیلدهای یک فرم ویژهی آن بانک، بر اساس مستندات آن) و هدایت به درگاه بانکی را انجام میدهد. پس از پایان کار پرداخت، کار هدایت به اکشن متد دریافت تائیدیهی نهایی صورت میگیرد و همینجا کار به پایان میرسد. این روش هرچند برای برنامههای سمت سرور ASP.NET Core کار میکند، اما ... به همین نحو با برنامههای تک صفحهای وب مانند Blazor WASM قابل استفاده نیست. در اینجا روش تبادل اطلاعات با اکشن متدهای وب سرویسهای برنامه از طریق یک HttpClient است و در این حالت دیگر نمیتوان از مزایای Post و Redirect خودکار پرباد که در سمت سرور صورت میگیرد استفاده کرد. با استفاده از HttpClient، یک شیء را به سمت Web API ارسال میکنیم و در پاسخ، فقط یک شیء را دریافت میکنیم. در اینجا دیگر خبری از Redirect به درگاه اصلی بانکی و Post اطلاعات به آن نیست. بنابراین روش کار با پرباد در اینجا به صورت زیر خواهد بود:
الف) شماره Id سفارش و مبلغ نهایی آنرا از طریق یک درخواست Get معمولی به اکشن متدی در سمت سرور ارسال میکنیم. یعنی نیاز است ابتدا Url زیر را تشکیل داد که شماره سفارش و مبلغ آن، به صورت کوئری استرینگهایی به اکشن متد PayRoomOrder ارسال میشوند:
https://localhost:5001/api/ParbadPayment/PayRoomOrder?orderId=1&amount=1000
ب) اکنون چون یک redirect سمت سرور صورت گرفته، به صورت معمولی در اکشن متد PayRoomOrder با پرباد پردازش صورت گرفته و به سمت درگاه هدایت میشویم. پس از پرداخت نهایی، باز هم به صورت خودکار به اکشن متد دیگری جهت تائید عملیات هدایت خواهیم شد.
ج) در پایان کار، اکشن متد سمت سرور، ما را به سمت کامپوننتی در برنامهی کلاینت Redirect خواهد کرد:
https://localhost:5002/payment-result/OrderId/TrackingNumber/Message
بنابراین روش یکپارچه سازی پربابد با برنامههای SPA، بر اساس Redirectهای کامل است که سبب بارگذاری مجدد کل صفحه و آدرسها میشوند و در اینجا از HttpClient برای کار با پرباد استفاده نخواهیم کرد؛ چون تمام اعمال خودکار آنرا از دست خواهیم داد و مجبور به بازنویسی آنها خواهیم شد که در دراز مدت با تغییرات این کتابخانه، قابل نگهداری نخواهند بود. بنابراین بهتر است خود پرباد کار Redirectها و ارسال اطلاعات به درگاههای بانکی را مدیریت کند و نه ما از طریق کار با یک HttpClient.
آشنایی با گردش کار برنامه
در این مثال، مراحل زیر را طی خواهیم کرد:
1- شروع به انتخاب یک بازهی زمانی و تعداد شب اقامت
2- انتخاب یک اتاق از لیست اتاقها با کلیک بر روی دکمهی Book آن
3- کلیک بر روی دکمهی checkout، در صفحهی مشاهدهی جزئیات اتاق و شروع به پرداخت
4- هدایت به درگاه مجازی پرباد در سمت برنامهی Web API
5- پرداخت و هدایت خودکار به سمت برنامهی Web API، جهت تائید نهایی
6- هدایت نهایی به سمت برنامهی کلاینت، جهت نمایش اطلاعات پرداخت
ایجاد کنترلر پرداخت، توسط درگاه مجازی پرباد
پس از آشنایی با گردش کاری اطلاعات در اینجا، نیاز است بتوان لینک زیر را در برنامهی کلاینت تولید کرد و سپس کاربر را به سمت اکشن متد PayRoomOrder هدایت نمود:
https://localhost:5001/api/ParbadPayment/PayRoomOrder?orderId=1&amount=1000
namespace BlazorWasm.WebApi.Controllers { [Route("api/[controller]/[action]")] [ApiController] public class ParbadPaymentController : Controller { private readonly IConfiguration _configuration; private readonly IOnlinePayment _onlinePayment; private readonly IRoomOrderDetailsService _roomOrderService; public ParbadPaymentController( IConfiguration configuration, IOnlinePayment onlinePayment, IRoomOrderDetailsService roomOrderService) { _configuration = configuration; _onlinePayment = onlinePayment ?? throw new ArgumentNullException(nameof(onlinePayment)); _roomOrderService = roomOrderService ?? throw new ArgumentNullException(nameof(roomOrderService)); } [HttpGet] public async Task<IActionResult> PayRoomOrder(int orderId, long amount) { var verifyUrl = Url.Action( action: nameof(ParbadPaymentController.VerifyRoomOrderPayment), controller: nameof(ParbadPaymentController).Replace("Controller", string.Empty), values: null, protocol: Request.Scheme); var result = await _onlinePayment.RequestAsync(invoiceBuilder => invoiceBuilder.UseAutoIncrementTrackingNumber() .SetAmount(amount) .SetCallbackUrl(verifyUrl) .UseParbadVirtual() ); if (result.IsSucceed) { await _roomOrderService.UpdateRoomOrderTrackingNumberAsync(orderId, result.TrackingNumber); // It will redirect the client to the gateway. return result.GatewayTransporter.TransportToGateway(); } else { return Redirect(getClientReturnUrl(orderId, result.TrackingNumber, result.Message)); } } [HttpGet, HttpPost] public async Task<IActionResult> VerifyRoomOrderPayment() { var invoice = await _onlinePayment.FetchAsync(); var orderDetail = await _roomOrderService.GetOrderDetailByTrackingNumberAsync(invoice.TrackingNumber); if (invoice.Status == PaymentFetchResultStatus.AlreadyProcessed) { return Redirect(getClientReturnUrl(orderDetail.Id, invoice.TrackingNumber, "The payment is already processed.")); } var verifyResult = await _onlinePayment.VerifyAsync(invoice); if (verifyResult.Status == PaymentVerifyResultStatus.Succeed) { var result = await _roomOrderService.MarkPaymentSuccessfulAsync(verifyResult.TrackingNumber, verifyResult.Amount); if (result == null) { return Redirect(getClientReturnUrl(orderDetail.Id, verifyResult.TrackingNumber, "Can not mark payment as successful")); } return Redirect(getClientReturnUrl(orderDetail.Id, verifyResult.TrackingNumber, verifyResult.Message)); } return Redirect(getClientReturnUrl(orderDetail.Id, verifyResult.TrackingNumber, verifyResult.Message)); } private string getClientReturnUrl(int orderId, long trackingNumber, string errorMessage) { var clientBaseUrl = _configuration.GetValue<string>("Client_URL"); return new Uri(new Uri(clientBaseUrl), $"/payment-result/{orderId}/{trackingNumber}/{WebUtility.UrlEncode(errorMessage)}").ToString(); } } }
در اینجا کدهای کامل ParbadPaymentController مشاهده میکنید.
- گردش کاری پرداخت، با فراخوانی اکشن متد PayRoomOrder شروع میشود که دو پارامتر شماره سفارش و مبلغ آنرا دریافت میکند.
[HttpGet] public async Task<IActionResult> PayRoomOrder(int orderId, long amount)
- در اکشن متد PayRoomOrder، نیاز است لینک بازگشت از درگاه بانکی را مشخص کنیم. پس از اینکه کاربر پرداختی را انجام داد، مجددا به صورت خودکار، به سمت آدرسی در همین Web API و نه برنامهی سمت کلاینت هدایت میشود؛ چون هنوز کار پرباد به پایان نرسیده و باید عملیات انجام شده را تصدیق کند. به همین جهت ابتدا آدرس اکشن متدی که کار تائید نهایی را انجام میدهد، تولید کرده و به متد RequestAsync آن به همراه مبلغ نهایی و نوع درگاه، ارسال میکنیم.
- استفاده از UseAutoIncrementTrackingNumber سبب میشود تا پرباد خودش مدیریت TrackingNumber را انجام دهد که پس از پایان عملیات، توسط خاصیت result.TrackingNumber در دسترس خواهد بود.
- پس از پایان عملیات ابتدایی RequestAsync که سشن پرباد را ایجاد کرده و همچنین رکوردی را در بانک اطلاعاتی نیز ثبت میکند (در جداول درونی خود پرباد)، نیاز است رکورد سفارشی را که با آن کار را شروع کردیم یافته و TrackingNumber آنرا با مقدار واقعی دریافتی از پرباد، به روز رسانی کنیم. اینکار توسط متد UpdateRoomOrderTrackingNumberAsync انجام میشود:
namespace BlazorServer.Services { public class RoomOrderDetailsService : IRoomOrderDetailsService { // ... public async Task UpdateRoomOrderTrackingNumberAsync(int roomOrderId, long trackingNumber) { var order = await _dbContext.RoomOrderDetails.FindAsync(roomOrderId); if (order == null) { return; } order.ParbadTrackingNumber = trackingNumber; _dbContext.RoomOrderDetails.Update(order); await _dbContext.SaveChangesAsync(); } } }
- اکنون با فراخوانی متد ()result.GatewayTransporter.TransportToGateway، دو کار مهم رخ میدهند:
الف) ارسال خودکار اطلاعات به سمت درگاه بانکی
ب) Redirect خودکار به سمت درگاه بانگی
به همین جهت است که علاقمند نبودیم تا این مراحل را توسط HttpClient برنامهی Blazor WASM مدیریت و بازنویسی کنیم.
- پس از هدایت به سمت درگاه بانکی و تکمیل پرداخت، اکنون مجددا به همان verifyUrl هدایت میشویم. یعنی اکنون به مرحلهی پردازش اکشن متد VerifyRoomOrderPayment در سمت Web API رسیدهایم.
[HttpGet, HttpPost] public async Task<IActionResult> VerifyRoomOrderPayment()
namespace BlazorServer.Services { public class RoomOrderDetailsService : IRoomOrderDetailsService { // ... public async Task<RoomOrderDetailsDTO> GetOrderDetailByTrackingNumberAsync(long trackingNumber) { var roomOrderDetailsDTO = await _dbContext.RoomOrderDetails .Include(u => u.HotelRoom) .ThenInclude(x => x.HotelRoomImages) .ProjectTo<RoomOrderDetailsDTO>(_mapperConfiguration) .FirstOrDefaultAsync(u => u.ParbadTrackingNumber == trackingNumber); roomOrderDetailsDTO.HotelRoomDTO.TotalDays = roomOrderDetailsDTO.CheckOutDate.Subtract(roomOrderDetailsDTO.CheckInDate).Days; return roomOrderDetailsDTO; } } }
namespace BlazorServer.Services { public class RoomOrderDetailsService : IRoomOrderDetailsService { // ... public async Task<RoomOrderDetailsDTO> MarkPaymentSuccessfulAsync(long trackingNumber, long amount) { var order = await _dbContext.RoomOrderDetails.FirstOrDefaultAsync(x => x.ParbadTrackingNumber == trackingNumber); if (order?.IsPaymentSuccessful != false || order.TotalCost != amount) { return null; } order.IsPaymentSuccessful = true; order.Status = BookingStatus.Booked; var markPaymentSuccessful = _dbContext.RoomOrderDetails.Update(order); await _dbContext.SaveChangesAsync(); return _mapper.Map<RoomOrderDetailsDTO>(markPaymentSuccessful.Entity); } } }
- در تمام این مراحل، کار Redirect به سمت کلاینت و کامپوننت payment-result آن، با فراخوانی متد return Redirect اکشن متدها صورت میگیرد که Url آن به صورت زیر تامین میشود:
private string getClientReturnUrl(int orderId, long trackingNumber, string errorMessage) { var clientBaseUrl = _configuration.GetValue<string>("Client_URL"); return new Uri(new Uri(clientBaseUrl), $"/payment-result/{orderId}/{trackingNumber}/{WebUtility.UrlEncode(errorMessage)}").ToString(); }
{ "Client_URL": "https://localhost:5002/" }
تکمیل قسمت سمت کلاینت عملیات پرداخت بانکی، توسط درگاه مجازی پرباد
تا اینجا کنترلری که کار پرداخت آنلاین را مدیریت میکند، پیاده سازی کردیم. قسمت آخر این بحث به تکمیل جزئیات این گردش کاری که شامل شروع عملیات سفارش و پرداخت از سمت کلاینت و نمایش پیام خطا یا موفقیت پرداخت به کاربر است، اختصاص دارد.
الف) تکمیل کامپوننت RoomDetails.razor جهت شروع به پرداخت آنلاین
کامپوننت RoomDetails.razor را در قسمت قبل آغاز کردیم و توسعهی آنرا تا جائی پیش بردیم که اعتبارسنجیهای آنرا به علت استفادهی از خواص تو در تو، به صورت دستی انجام دادیم. پس از مرحلهی اعتبارسنجی، اکنون میخواهیم کاربر را به سمت درگاه بانکی جهت پرداخت، هدایت کنیم:
@page "/hotel-room-details/{Id:int}" @inject IJSRuntime JsRuntime @inject ILocalStorageService LocalStorage @inject IClientHotelRoomService HotelRoomService @inject IClientRoomOrderDetailsService RoomOrderDetailsService @inject NavigationManager NavigationManager @inject HttpClient HttpClient // ... @code { // ... private async Task HandleCheckout() { if (!await HandleValidation()) { return; } try { HotelBooking.OrderDetails.ParbadTrackingNumber = -1; HotelBooking.OrderDetails.RoomId = HotelBooking.OrderDetails.HotelRoomDTO.Id; HotelBooking.OrderDetails.TotalCost = HotelBooking.OrderDetails.HotelRoomDTO.TotalAmount; var roomOrderDetailsSaved = await RoomOrderDetailsService.SaveRoomOrderDetailsAsync(HotelBooking.OrderDetails); await LocalStorage.SetItemAsync(ConstantKeys.LocalRoomOrderDetails, roomOrderDetailsSaved); var paymentUri = new UriBuilderExt(new Uri(HttpClient.BaseAddress, $"/api/ParbadPayment/PayRoomOrder")) .AddParameter("orderId", roomOrderDetailsSaved.Id.ToString()) .AddParameter("amount", roomOrderDetailsSaved.TotalCost.ToString()) .Uri; NavigationManager.NavigateTo(paymentUri.ToString(), forceLoad: true); } catch (Exception e) { await JsRuntime.ToastrError(e.Message); } } // ... }
namespace BlazorWasm.WebApi.Controllers { [ApiController] [Route("api/[controller]/[action]")] public class RoomOrderController : Controller { private readonly IRoomOrderDetailsService _roomOrderService; public RoomOrderController(IRoomOrderDetailsService roomOrderService) { _roomOrderService = roomOrderService ?? throw new ArgumentNullException(nameof(roomOrderService)); } [HttpPost] public async Task<IActionResult> Create([FromBody] RoomOrderDetailsDTO details) { var result = await _roomOrderService.CreateAsync(details); return Ok(result); } [HttpGet] public async Task<IActionResult> GetOrderDetail(int trackingNumber) { var result = await _roomOrderService.GetOrderDetailByTrackingNumberAsync(trackingNumber); return Ok(result); } } }
- متد GetOrderDetail، بر اساس trackingNumber دریافتی از پرباد، کار بازگشت رکورد متناظری را انجام میدهد. از آن در پایان کار، جهت نمایش وضعیت پرداخت، استفاده میکنیم.
این دو متد در سرویس سمت سرور RoomOrderDetailsService، به صورت زیر تامین شدهاند:
namespace BlazorServer.Services { public class RoomOrderDetailsService : IRoomOrderDetailsService { // ... public async Task<RoomOrderDetailsDTO> CreateAsync(RoomOrderDetailsDTO details) { var roomOrder = _mapper.Map<RoomOrderDetail>(details); roomOrder.Status = BookingStatus.Pending; var result = await _dbContext.RoomOrderDetails.AddAsync(roomOrder); await _dbContext.SaveChangesAsync(); return _mapper.Map<RoomOrderDetailsDTO>(result.Entity); } public async Task<RoomOrderDetailsDTO> GetOrderDetailByTrackingNumberAsync(long trackingNumber) { var roomOrderDetailsDTO = await _dbContext.RoomOrderDetails .Include(u => u.HotelRoom) .ThenInclude(x => x.HotelRoomImages) .ProjectTo<RoomOrderDetailsDTO>(_mapperConfiguration) .FirstOrDefaultAsync(u => u.ParbadTrackingNumber == trackingNumber); roomOrderDetailsDTO.HotelRoomDTO.TotalDays = roomOrderDetailsDTO.CheckOutDate.Subtract(roomOrderDetailsDTO.CheckInDate).Days; return roomOrderDetailsDTO; } // ... } }
namespace BlazorWasm.Client.Services { public interface IClientRoomOrderDetailsService { Task<RoomOrderDetailsDTO> SaveRoomOrderDetailsAsync(RoomOrderDetailsDTO details); Task<RoomOrderDetailsDTO> GetOrderDetailAsync(long trackingNumber); } } namespace BlazorWasm.Client.Services { public class ClientRoomOrderDetailsService : IClientRoomOrderDetailsService { private readonly HttpClient _httpClient; public ClientRoomOrderDetailsService(HttpClient httpClient) { _httpClient = httpClient ?? throw new ArgumentNullException(nameof(httpClient)); } public Task<RoomOrderDetailsDTO> GetOrderDetailAsync(long trackingNumber) { // How to url-encode query-string parameters properly var uri = new UriBuilderExt(new Uri(_httpClient.BaseAddress, $"/api/roomorder/GetOrderDetail")) .AddParameter("trackingNumber", trackingNumber.ToString()) .Uri; return _httpClient.GetFromJsonAsync<RoomOrderDetailsDTO>(uri); } public async Task<RoomOrderDetailsDTO> SaveRoomOrderDetailsAsync(RoomOrderDetailsDTO details) { details.UserId = "unknown user!"; var response = await _httpClient.PostAsJsonAsync("api/roomorder/create", details); var responseContent = await response.Content.ReadAsStringAsync(); if (response.IsSuccessStatusCode) { return JsonSerializer.Deserialize<RoomOrderDetailsDTO>(responseContent); } else { //var errorModel = JsonSerializer.Deserialize<ErrorModel>(responseContent); throw new InvalidOperationException(responseContent); } } } }
- متد SaveRoomOrderDetailsAsync، یک رکورد سفارش جدید را ایجاد میکند. در اینجا با روش مشاهدهی خطای کامل بازگشتی از سمت سرور (در صورت وجود) هم آشنا شدهایم که در مواقع لزوم میتواند راهگشا باشد.
- در متد SaveRoomOrderDetailsAsync فعلا مقدار UserId اجباری را به عبارتی دلخواه، تنظیم کردهایم. این مورد را در قسمتهای بعدی با معرفی اعتبارسنجی و احراز هویت سمت کلاینت، تکمیل خواهیم کرد.
این سرویس جدید را هم باید به سیستم تزریق وابستگیهای برنامهی کلاینت معرفی کرد تا قابل استفاده شود:
namespace BlazorWasm.Client { public class Program { public static async Task Main(string[] args) { // ... builder.Services.AddScoped<IClientRoomOrderDetailsService, ClientRoomOrderDetailsService>();
سپس به قطعه کد مهم زیر میرسیم:
var paymentUri = new UriBuilderExt(new Uri(HttpClient.BaseAddress, $"/api/ParbadPayment/PayRoomOrder")) .AddParameter("orderId", roomOrderDetailsSaved.Id.ToString()) .AddParameter("amount", roomOrderDetailsSaved.TotalCost.ToString()) .Uri; NavigationManager.NavigateTo(paymentUri.ToString(), forceLoad: true);
نمایش وضعیت پرداخت، به کاربر در پایان گردش کاری آن
پس از این مراحل، مرحلهی آخر کار باقی ماندهاست؛ یعنی بازگشت از اکشن متد VerifyRoomOrderPayment سمت سرور، به کامپوننت PaymentResult سمت کلاینت، برای نمایش نتیجهی عملیات. به همین جهت کامپوننت جدید Pages\HotelRooms\PaymentResult.razor را ایجاد کرده و به صورت زیر تکمیل میکنیم:
@page "/payment-result/{OrderId:int}/{TrackingNumber:long}/{Message}" @inject ILocalStorageService LocalStorage @inject IClientRoomOrderDetailsService RoomOrderDetailService @inject IJSRuntime JsRuntime @inject NavigationManager NavigationManager @if (IsLoading) { <div style="position:fixed;top:50%;left:50%;margin-top:-50px;margin-left:-100px;"> <img src="images/ajax-loader.gif" /> </div> } else { <div class="container"> <div class="row mt-4 pt-4"> <div class="col-10 offset-1 text-center"> @if(IsPaymentSuccessful) { <h2 class="text-success">Booking Confirmed!</h2> <p>Your room has been booked successfully with order id @OrderId & tracking number @TrackingNumber .</p> } else { <h2 class="text-warning">Booking Failed!</h2> <p>@Message</p> } <a class="btn btn-primary" href="hotel-rooms">Back to rooms</a> </div> </div> </div> } @code { private bool IsLoading; private bool IsPaymentSuccessful; [Parameter] public int OrderId { set; get; } [Parameter] public long TrackingNumber { set; get; } [Parameter] public string Message { set; get; } protected override async Task OnInitializedAsync() { IsLoading = true; try { var finalOrderDetail = await RoomOrderDetailService.GetOrderDetailAsync(TrackingNumber); var localOrderDetail = await LocalStorage.GetItemAsync<RoomOrderDetailsDTO>(ConstantKeys.LocalRoomOrderDetails); if(finalOrderDetail is not null && finalOrderDetail.IsPaymentSuccessful && finalOrderDetail.Status == BookingStatus.Booked && localOrderDetail is not null && localOrderDetail.TotalCost == finalOrderDetail.TotalCost) { IsPaymentSuccessful = true; await LocalStorage.RemoveItemAsync(ConstantKeys.LocalRoomOrderDetails); await LocalStorage.RemoveItemAsync(ConstantKeys.LocalInitialBooking); } else { IsPaymentSuccessful = false; } } catch(Exception ex) { await JsRuntime.ToastrError(ex.Message); } finally { IsLoading = false; } } }
@page "/payment-result/{OrderId:int}/{TrackingNumber:long}/{Message}"
جلوگیری از ثبت سفارش اتاقی که رزرو شدهاست
پس از پایان عملیات سفارش یک اتاق، بهتر است امکان سفارش اتاقی را که دیگر در دسترس نیست، غیرفعال کنیم (تصویر فوق) که اینکار را میتوان توسط خاصیت IsBooked مدل UI کامپوننت نمایش لیست اتاقها انجام داد:
public class HotelRoomDTO { public bool IsBooked { get; set; } // ... }
namespace BlazorServer.Services { public class HotelRoomService : IHotelRoomService { // ... public async Task<List<HotelRoomDTO>> GetAllHotelRoomsAsync(DateTime? checkInDateStr, DateTime? checkOutDatestr) { var hotelRooms = await _dbContext.HotelRooms .Include(x => x.HotelRoomImages) .Include(x => x.RoomOrderDetails) .ProjectTo<HotelRoomDTO>(_mapperConfiguration) .ToListAsync(); foreach (var hotelRoom in hotelRooms) { hotelRoom.IsBooked = isRoomBooked(hotelRoom, checkInDateStr, checkOutDatestr); } return hotelRooms; } public async Task<HotelRoomDTO> GetHotelRoomAsync(int roomId, DateTime? checkInDate, DateTime? checkOutDate) { var hotelRoom = await _dbContext.HotelRooms .Include(x => x.HotelRoomImages) .Include(x => x.RoomOrderDetails) .ProjectTo<HotelRoomDTO>(_mapperConfiguration) .FirstOrDefaultAsync(x => x.Id == roomId); hotelRoom.IsBooked = isRoomBooked(hotelRoom, checkInDate, checkOutDate); return hotelRoom; } private bool isRoomBooked(HotelRoomDTO hotelRoom, DateTime? checkInDate, DateTime? checkOutDate) { if (checkInDate == null || checkOutDate == null) { return false; } return hotelRoom.RoomOrderDetails.Any(x => x.IsPaymentSuccessful && //check if checkin date that user wants does not fall in between any dates for room that is booked ((checkInDate < x.CheckOutDate && checkInDate.Value.Date >= x.CheckInDate) //check if checkout date that user wants does not fall in between any dates for room that is booked || (checkOutDate.Value.Date > x.CheckInDate.Date && checkInDate.Value.Date <= x.CheckInDate.Date)) ); } } }
اکنون که خاصیت IsBooked مقدار دهی شدهاست، در دو قسمت از آن استفاده خواهیم کرد:
الف) در کامپوننت نمایش لیست اتاقها
@if (room.IsBooked) { <button disabled class="btn btn-secondary btn-block">Sold Out</button> } else { <a href="@($"hotel-room-details/{room.Id}")" class="btn btn-success btn-block">Book</a> }
@if (HotelBooking.OrderDetails.HotelRoomDTO.IsBooked) { <button disabled class="btn btn-secondary btn-block">Sold Out</button> } else { <button type="submit" class="btn btn-success form-control">Checkout Now</button> }
کدهای کامل این مطلب را از اینجا میتوانید دریافت کنید: Blazor-5x-Part-30.zip
در حین کار با برنامههای وب، چشمپوشی از جاوا اسکریپت عملا ممکن نیست؛ هرچند با Blazor، امکان انجام کارهایی را یافتهایم که پیشتر با MVC و یا Razor pages میسر نبودند، اما هیچگاه به تنهایی نمیتواند جایگزین کامل جاوا اسکریپت، در تولید برنامههای وب باشد. بنابراین ضروری است که نحوهی یکپارچگی جاوا اسکریپت را با برنامههای مبتنی بر Blazor، بررسی کنیم.
ایجاد کامپوننت جدید BlazorJS
برای بررسی نحوهی تعامل جاوا اسکریپت و Blazor، در ابتدا کامپوننت جدید Pages\LearnBlazor\BlazorJS.razor را ایجاد کرده:
و همچنین مدخل منوی آنرا نیز بر اساس مسیریابی ابتدای فایل این کامپوننت، به فایل Shared\NavMenu.razor اضافه میکنیم:
روش فراخوانی کدهای جاوا اسکریپتی از طریق کدهای سیشارپ Blazor
فرض کنید میخواهیم در حین کلیک بر روی دکمهای مانند دکمهی حذف، ابتدا تائیدیهای را توسط تابع confirm جاوا اسکریپتی، از کاربر اخذ کنیم. روش انجام چنین کاری در برنامههای مبتنی بر Blazor به صورت زیر است:
توضیحات:
- در اینجا میخواهیم تابع استاندارد confirm جاوا اسکریپتی را از طریق کدهای سیشارپ، با کلیک بر روی دکمهی Test Confirm Button، فراخوانی کنیم. به همین جهت onclick@ این دکمه، به متد TestConfirmBox کدهای UI سیشارپ این کامپوننت، متصل شدهاست.
- برای دسترسی به توابع جاوا اسکریپتی، نیاز است سرویس توکار IJSRuntime را به کدهای کامپوننت تزریق کنیم که روش انجام آنرا توسط دایرکتیو inject@ مشاهده میکنید. برای دسترسی به این سرویس توکار، نیاز به تنظیمات ابتدایی خاصی نیست و اینکار پیشتر انجام شدهاست.
- سرویس JsRuntime تزریق شده، دو متد مهم InvokeVoidAsync و InvokeAsync را جهت فراخوانی توابع جاوا اسکریپتی به همراه دارد. اگر تابعی، خروجی غیر void داشته باشد، باید از متد InvokeAsync استفاده کرد. برای مثال خروجی تابع استاندارد confirm، از نوع boolean است. بنابراین نوع این خروجی را به صورت یک آرگومان جنریک متد InvokeAsync مشخص کردهایم.
- اولین پارامتر متد InvokeAsync، نام رشتهای تابع جاوا اسکریپتی است که قرار است صدا زده شود. پارامترهای اختیاری بعدی که به صورت params object?[]? args تعریف شدهاند، لیست نامحدود آرگومانهای ورودی این متد هستند.
- فیلد ConfirmMessage، پیامی را جهت اخذ تائید، تعریف میکند که به عنوان پارامتر متد confirm، توسط JsRuntime.InvokeAsync فراخوانی خواهد شد.
- فیلد ConfirmResult، نتیجهی فراخوانی متد confirm جاوا اسکریپتی را به همراه دارد.
- در اینجا روش عکس العمل نشان دادن به خروجی دریافتی از متد جاوااسکریپتی را نیز مشاهده میکنید. پس از پایان متد TestConfirmBox که یک متد رویدادگران است، همانطور که در مطلب بررسی «چرخهی حیات کامپوننتها» نیز بررسی کردیم، متد StateHasChanged، در پشت صحنه فراخوانی میشود که سبب رندر مجدد UI خواهد شد. بنابراین در حین رندر مجدد UI، بر اساس مقدار جدید ConfirmResult دریافت شدهی از کاربر، با تشکیل یک if/else@، میتوان به نتیجهی تائید یا عدم تائید کاربر، واکنش نشان داد. با این توضیحات در اولین بار نمایش کامپوننت جاری چون مقدار ConfirmResult مساوی false است، پیام زیر را مشاهده میکنیم:
اما در ادامه با کلیک بر روی دکمه و تائید پیام ظاهر شده، عبارت زیر ظاهر میشود:
روش افزودن یک کتابخانهی خارجی جاوا اسکریپتی به پروژههای Blazor
فرض کنید میخواهیم پیامهای برنامه را توسط کتابخانهی معروف جاوا اسکریپتی Toastr نمایش دهیم؛ با این دمو.
مرحلهی اول کار با این کتابخانه، دریافت فایلهای CSS و JS آن است. برای این منظور قصد داریم از برنامهی مدیریت بستههای LibMan استفاده کنیم:
بنابراین خط فرمان را در ریشهی پروژه گشوده و پنج دستور فوق را اجرا میکنیم. دستور اول، ابزار خط فرمان LibMan را نصب میکند. دستور دوم، یک فایل libman.json خالی را در این پوشه ایجاد میکند و سه دستور بعدی، جیکوئری، بوت استرپ و toastr را دریافت و در پوشهی wwwroot/lib قرار میدهند. Toastr برای اجرا، نیاز به jQuery نیز دارد.
البته تعاریف مداخل آنها به فایل libman.json نیز اضافه میشوند. مزیت آن، اجرای دستور libman restore برای بازیابی و نصب مجدد تمام بستههای ذکر شدهی در فایل libman.json است.
پس از دریافت بستههای سمت کلاینت آن، مداخل مرتبط را به فایل Pages\_Host.cshtml برنامهی Blazor Server اضافه خواهیم کرد (و یا در فایل wwwroot/index.html برنامههای Blazor WASM).
مدخل فایل css آنرا در قسمت head و فایل js آنرا پیش از بسته شدن تگ body تعریف میکنیم. در اینجا نیازی به ذکر پوشهی آغازین wwwroot نیست؛ چون base href تعریف شده، به این پوشه اشاره میکند.
یک نکته: میتوان فایل csproj برنامه را به صورت زیر تغییر داد تا کار اجرای دستور libman restore را قبل از build، به صورت خودکار انجام دهد:
روش فراخوانی یک کتابخانهی خارجی جاوا اسکریپتی در پروژههای Blazor
پس از افزودن فایلهای سمت کلاینت toastr و تعریف مداخل آن در فایل Pages\_Host.cshtml برنامهی Blazor Server جاری، اکنون میخواهیم از این کتابخانه استفاده کنیم. یک روش کار با این نوع کتابخانههای عمومی و سراسری به صورت زیر است:
- ابتدا فایل خالی جدید wwwroot\js\common.js را ایجاد میکنیم.
- سپس تابع عمومی و سراسری ShowToastr را بر اساس امکانات کتابخانهی toastr و مستندات آن، به صورت زیر ایجاد میکنیم:
چون تابع ShowToastr به شیء window انتساب داده شدهاست، در سراسر برنامهی جاری قابل دسترسی است.
سطر اول آن هم برای رفع عدم تداخل با بوت استرپ 4x اضافه شدهاست. بوت استرپ 4x به همراه کلاسهای CSS مشابهی است که نیاز است با تنظیم toastClass به مقداری دیگر، این تداخل را برطرف کرد.
- در ادامه مدخل تعریف فایل wwwroot\js\common.js را به انتهای تگ body فایل Pages\_Host.cshtml اضافه میکنیم:
در آخر برای آزمایش آن به کامپوننت Pages\LearnBlazor\BlazorJS.razor مراجعه کرده و تابع سراسری ShowToastr را دقیقا مانند روشی که در مورد تابع confirm بکار بردیم، توسط سرویس JsRuntime، فراخوانی میکنیم:
در اینجا دو دکمه، جهت فراخوانی متد ShowToastr با پارامترهای مختلفی تعریف شدهاند. چون تابع ShowToastr خروجی ندارد، به همین جهت اینبار از متد InvokeVoidAsync استفاده کردهایم. پارامتر اول آن، نام متد ShowToastr است. پارامترهای دوم و سوم آن با آرگومانهای (type, message) تعریف شدهی تابع ShowToastr تطابق دارند. به علاوه در این مثال، روش ارسال پارامترها را نیز در onlick@ توسط arrow functions مشاهده میکنید.
کاهش کدهای تکراری فراخوانی متدهای جاوا اسکریپتی با تعریف متدهای الحاقی
میتوان جهت کاهش تکرار کدهای استفاده از تابع ShowToastr، متدهای الحاقی زیر را برای سرویس IJSRuntime تهیه کرد:
و سپس فضای نام آنرا به فایل Imports.razor_ معرفی نمود تا در تمام کامپوننتهای برنامه قابل استفاده شوند.
به این ترتیب به فراخوانیهای ساده شدهی زیر خواهیم رسید:
فراخوانی یک متد عمومی واقع در کامپوننت فرزند از طریق کامپوننت والد
فرض کنید در کامپوننت فرزند Pages\LearnBlazor\LearnBlazorComponents\ChildComponent.razor که در قسمتهای قبل آنرا تکمیل کردیم، متد عمومی زیر تعریف شدهاست:
اکنون اگر بخواهیم این متد عمومی را از طریق کامپوننت والد یا دربرگیرندهی آن فراخوانی کنیم، نیاز است از مفهوم جدیدی به نام ref استفاده کرد. برای این منظور به کامپوننت Pages\LearnBlazor\ParentComponent.razor مراجعه کرده و تغییرات زیر را اعمال میکنیم:
با استفاده از ref@ که به فیلد ChildComp انتساب داده شدهاست، میتوان ارجاعی از کامپوننت فرزند را (وهلهای از کلاس مرتبط با آنرا) در کامپوننت جاری بدست آورد و سپس از آن جهت فراخوانی متدهای عمومی کامپوننت فرزند استفاده کرد.
کدهای کامل این مطلب را از اینجا میتوانید دریافت کنید: Blazor-5x-Part-11.zip
ایجاد کامپوننت جدید BlazorJS
برای بررسی نحوهی تعامل جاوا اسکریپت و Blazor، در ابتدا کامپوننت جدید Pages\LearnBlazor\BlazorJS.razor را ایجاد کرده:
@page "/BlazorJS" <h3>BlazorJS</h3> @code { }
<li class="nav-item px-3"> <NavLink class="nav-link" href="BlazorJS"> <span class="oi oi-list-rich" aria-hidden="true"></span> BlazorJS </NavLink> </li>
روش فراخوانی کدهای جاوا اسکریپتی از طریق کدهای سیشارپ Blazor
فرض کنید میخواهیم در حین کلیک بر روی دکمهای مانند دکمهی حذف، ابتدا تائیدیهای را توسط تابع confirm جاوا اسکریپتی، از کاربر اخذ کنیم. روش انجام چنین کاری در برنامههای مبتنی بر Blazor به صورت زیر است:
@page "/BlazorJS" @inject IJSRuntime JsRuntime <h3>BlazorJS</h3> <div class="row"> <button class="btn btn-secondary" @onclick="TestConfirmBox">Test Confirm Button</button> </div> <div class="row"> @if (ConfirmResult) { <p>Confirmation has been made!</p> } else { <p>Confirmation Pending!</p> } </div> @code { string ConfirmMessage = "Are you sure you want to click?"; bool ConfirmResult; async Task TestConfirmBox() { ConfirmResult = await JsRuntime.InvokeAsync<bool>("confirm", ConfirmMessage); } }
- در اینجا میخواهیم تابع استاندارد confirm جاوا اسکریپتی را از طریق کدهای سیشارپ، با کلیک بر روی دکمهی Test Confirm Button، فراخوانی کنیم. به همین جهت onclick@ این دکمه، به متد TestConfirmBox کدهای UI سیشارپ این کامپوننت، متصل شدهاست.
- برای دسترسی به توابع جاوا اسکریپتی، نیاز است سرویس توکار IJSRuntime را به کدهای کامپوننت تزریق کنیم که روش انجام آنرا توسط دایرکتیو inject@ مشاهده میکنید. برای دسترسی به این سرویس توکار، نیاز به تنظیمات ابتدایی خاصی نیست و اینکار پیشتر انجام شدهاست.
- سرویس JsRuntime تزریق شده، دو متد مهم InvokeVoidAsync و InvokeAsync را جهت فراخوانی توابع جاوا اسکریپتی به همراه دارد. اگر تابعی، خروجی غیر void داشته باشد، باید از متد InvokeAsync استفاده کرد. برای مثال خروجی تابع استاندارد confirm، از نوع boolean است. بنابراین نوع این خروجی را به صورت یک آرگومان جنریک متد InvokeAsync مشخص کردهایم.
- اولین پارامتر متد InvokeAsync، نام رشتهای تابع جاوا اسکریپتی است که قرار است صدا زده شود. پارامترهای اختیاری بعدی که به صورت params object?[]? args تعریف شدهاند، لیست نامحدود آرگومانهای ورودی این متد هستند.
- فیلد ConfirmMessage، پیامی را جهت اخذ تائید، تعریف میکند که به عنوان پارامتر متد confirm، توسط JsRuntime.InvokeAsync فراخوانی خواهد شد.
- فیلد ConfirmResult، نتیجهی فراخوانی متد confirm جاوا اسکریپتی را به همراه دارد.
- در اینجا روش عکس العمل نشان دادن به خروجی دریافتی از متد جاوااسکریپتی را نیز مشاهده میکنید. پس از پایان متد TestConfirmBox که یک متد رویدادگران است، همانطور که در مطلب بررسی «چرخهی حیات کامپوننتها» نیز بررسی کردیم، متد StateHasChanged، در پشت صحنه فراخوانی میشود که سبب رندر مجدد UI خواهد شد. بنابراین در حین رندر مجدد UI، بر اساس مقدار جدید ConfirmResult دریافت شدهی از کاربر، با تشکیل یک if/else@، میتوان به نتیجهی تائید یا عدم تائید کاربر، واکنش نشان داد. با این توضیحات در اولین بار نمایش کامپوننت جاری چون مقدار ConfirmResult مساوی false است، پیام زیر را مشاهده میکنیم:
اما در ادامه با کلیک بر روی دکمه و تائید پیام ظاهر شده، عبارت زیر ظاهر میشود:
روش افزودن یک کتابخانهی خارجی جاوا اسکریپتی به پروژههای Blazor
فرض کنید میخواهیم پیامهای برنامه را توسط کتابخانهی معروف جاوا اسکریپتی Toastr نمایش دهیم؛ با این دمو.
مرحلهی اول کار با این کتابخانه، دریافت فایلهای CSS و JS آن است. برای این منظور قصد داریم از برنامهی مدیریت بستههای LibMan استفاده کنیم:
dotnet tool install -g Microsoft.Web.LibraryManager.Cli libman init libman install bootstrap --provider unpkg --destination wwwroot/lib/bootstrap libman install jquery --provider unpkg --destination wwwroot/lib/jquery libman install toastr --provider unpkg --destination wwwroot/lib/toastr
البته تعاریف مداخل آنها به فایل libman.json نیز اضافه میشوند. مزیت آن، اجرای دستور libman restore برای بازیابی و نصب مجدد تمام بستههای ذکر شدهی در فایل libman.json است.
پس از دریافت بستههای سمت کلاینت آن، مداخل مرتبط را به فایل Pages\_Host.cshtml برنامهی Blazor Server اضافه خواهیم کرد (و یا در فایل wwwroot/index.html برنامههای Blazor WASM).
<head> <base href="~/" /> <link rel="stylesheet" href="lib/toastr/build/toastr.min.css" /> </head> <body> <script src="lib/jquery/dist/jquery.min.js"></script> <script src="lib/toastr/build/toastr.min.js"></script> <script src="_framework/blazor.server.js"></script> </body>
یک نکته: میتوان فایل csproj برنامه را به صورت زیر تغییر داد تا کار اجرای دستور libman restore را قبل از build، به صورت خودکار انجام دهد:
<Project Sdk="Microsoft.NET.Sdk.Web"> <PropertyGroup> <TargetFramework>net5.0</TargetFramework> </PropertyGroup> <Target Name="DebugEnsureLibManEnv" BeforeTargets="BeforeBuild" Condition=" '$(Configuration)' == 'Debug' "> <!-- Ensure libman is installed --> <Exec Command="libman --version" ContinueOnError="true"> <Output TaskParameter="ExitCode" PropertyName="ErrorCode" /> </Exec> <Error Condition="'$(ErrorCode)' != '0'" Text="libman is required to build and run this project. To continue, please run `dotnet tool install -g Microsoft.Web.LibraryManager.Cli`, and then restart your command prompt or IDE." /> <Message Importance="high" Text="Restoring dependencies using 'libman'. This may take several minutes..." /> <Exec WorkingDirectory="$(MSBuildProjectDirectory)" Command="libman restore" /> </Target> </Project>
روش فراخوانی یک کتابخانهی خارجی جاوا اسکریپتی در پروژههای Blazor
پس از افزودن فایلهای سمت کلاینت toastr و تعریف مداخل آن در فایل Pages\_Host.cshtml برنامهی Blazor Server جاری، اکنون میخواهیم از این کتابخانه استفاده کنیم. یک روش کار با این نوع کتابخانههای عمومی و سراسری به صورت زیر است:
- ابتدا فایل خالی جدید wwwroot\js\common.js را ایجاد میکنیم.
- سپس تابع عمومی و سراسری ShowToastr را بر اساس امکانات کتابخانهی toastr و مستندات آن، به صورت زیر ایجاد میکنیم:
window.ShowToastr = (type, message) => { // Toastr don't work with Bootstrap 4.2 toastr.options.toastClass = "toastr"; // https://github.com/CodeSeven/toastr/issues/599 if (type === "success") { toastr.success(message, "Operation Successful", { timeOut: 20000 }); } if (type === "error") { toastr.error(message, "Operation Failed", { timeOut: 20000 }); } };
سطر اول آن هم برای رفع عدم تداخل با بوت استرپ 4x اضافه شدهاست. بوت استرپ 4x به همراه کلاسهای CSS مشابهی است که نیاز است با تنظیم toastClass به مقداری دیگر، این تداخل را برطرف کرد.
- در ادامه مدخل تعریف فایل wwwroot\js\common.js را به انتهای تگ body فایل Pages\_Host.cshtml اضافه میکنیم:
<script src="lib/jquery/dist/jquery.min.js"></script> <script src="lib/toastr/build/toastr.min.js"></script> <script src="js/common.js"></script> <script src="_framework/blazor.server.js"></script> </body>
در آخر برای آزمایش آن به کامپوننت Pages\LearnBlazor\BlazorJS.razor مراجعه کرده و تابع سراسری ShowToastr را دقیقا مانند روشی که در مورد تابع confirm بکار بردیم، توسط سرویس JsRuntime، فراخوانی میکنیم:
@page "/BlazorJS" @inject IJSRuntime JsRuntime <div class="row"> <button class="btn btn-success" @onclick="@(()=>TestSuccess("Success Message"))">Test Toastr Success</button> <button class="btn btn-danger" @onclick="@(()=>TestFailure("Error Message"))">Test Toastr Failure</button> </div> @code { async Task TestSuccess(string message) { await JsRuntime.InvokeVoidAsync("ShowToastr", "success", message); } async Task TestFailure(string message) { await JsRuntime.InvokeVoidAsync("ShowToastr", "error", message); } }
کاهش کدهای تکراری فراخوانی متدهای جاوا اسکریپتی با تعریف متدهای الحاقی
میتوان جهت کاهش تکرار کدهای استفاده از تابع ShowToastr، متدهای الحاقی زیر را برای سرویس IJSRuntime تهیه کرد:
using System.Threading.Tasks; using Microsoft.JSInterop; namespace BlazorServerSample.Utils { public static class JSRuntimeExtensions { public static ValueTask ToastrSuccess(this IJSRuntime JSRuntime, string message) { return JSRuntime.InvokeVoidAsync("ShowToastr", "success", message); } public static ValueTask ToastrError(this IJSRuntime JSRuntime, string message) { return JSRuntime.InvokeVoidAsync("ShowToastr", "error", message); } } }
@using BlazorServerSample.Utils
async Task TestSuccess(string message) { //await JsRuntime.InvokeVoidAsync("ShowToastr", "success", message); await JsRuntime.ToastrSuccess(message); }
فراخوانی یک متد عمومی واقع در کامپوننت فرزند از طریق کامپوننت والد
فرض کنید در کامپوننت فرزند Pages\LearnBlazor\LearnBlazorComponents\ChildComponent.razor که در قسمتهای قبل آنرا تکمیل کردیم، متد عمومی زیر تعریف شدهاست:
@inject IJSRuntime JsRuntime @code { // ... public async Task TestSuccess(string message) { await JsRuntime.ToastrSuccess(message); } }
@page "/ParentComponent" <ChildComponent OnClickBtnMethod="ShowMessage" @ref="ChildComp" Title="This title is passed as a parameter from the Parent Component"> <ChildContent> A `Render Fragment` from the parent! </ChildContent> <DangerChildContent> A danger content from the parent! </DangerChildContent> </ChildComponent> <div class="row"> <button class="btn btn-success" @onclick="@(()=>ChildComp.TestSuccess("Done!"))">Show Alert</button> </div> @code { ChildComponent ChildComp; // ... }
کدهای کامل این مطلب را از اینجا میتوانید دریافت کنید: Blazor-5x-Part-11.zip
مسیرراهها
ASP.NET Core
ASP.NET Core 1.0
- قسمت اول: Net Core. چیست؟
- قسمت دوم: بررسی ساختار جدید Solution
- قسمت سوم: Middleware چیست؟
- قسمت چهارم: فعالسازی پردازش فایلهای استاتیک
- قسمت پنجم: فعالسازی صفحات مخصوص توسعه دهنده ها
- قسمت ششم: سرویسها و تزریق وابستگی ها
- قسمت هفتم: کار با فایلهای Config
- قسمت هشتم: فعالسازی ASP.NET MVC
- قسمت نهم: بررسی تغییرات مسیریابی
- قسمت دهم: بررسی تغییرات Viewها
- قسمت یازدهم: بررسی بهبودهای Razor
- قسمت دوازدهم: معرفی Tag Helpers
- قسمت سیزدهم: معرفی View Components
- قسمت چهاردهم: فعال سازی اعتبارسنجی ورودیهای کاربران
- قسمت پانزدهم: بررسی تغییرات Caching
- قسمت شانزدهم:کار با Sessions
- قسمت هفدهم: بررسی فریم ورک Logging
- قسمت هجدهم: کار با ASP.NET Web API
- قسمت نوزدهم: بومی سازی
- قسمت بیستم: بررسی تغییرات فیلترها
- قسمت بیست و یکم: بررسی تغییرات Bundling و Minification
- قسمت بیست و دوم: توزیع برنامه توسط IIS
- پیاده سازی Unobtrusive Ajax در ASP.NET Core 1.0
ASP.NET Core 2.0
- امکان استفادهی مستقیم از کتابخانههای Full .NET Framework در NET Core 2.0.
- Tag Helper Components در ASP.NET Core 2.0
- کار با Razor در ASP.NET Core 2.0
- فعالسازی Windows Authentication در برنامههای ASP.NET Core 2.0
ASP.NET Core 2.1
روش ارتقاء
ASP.NET Core Identity
کار با Areas در ASP.NET Core
کار با کوکیها در ASP.NET Core
بررسی روش آپلود فایلها در ASP.NET Core
ارسال ایمیل در ASP.NET Core
نوشتن Middleware سفارشی در ASP.NET Core
نوشتن TagHelperهای سفارشی برای ASP.NET Core
بررسی تغییرات Reflection در NET Core.
استفادهی گسترده از DateTimeOffset در NET Core.
بررسی روش دسترسی به HttpContext در ASP.NET Core
توزیع پروژههای ASP.NET Core 1.1 بدون ارائه فایلهای View آن
تغییرات رمزنگاری اطلاعات در NET Core.
ساخت بستههای نیوگت مخصوص NET Core.
تهیه قالب برای ارسال ایمیلها در ASP.NET Core توسط Razor Viewها
روش یافتن لیست تمام کنترلرها و اکشن متدهای یک برنامهی ASP.NET Core
بررسی روش آپلود فایلها از طریق یک برنامهی Angular به یک برنامهی ASP.NET Core
سفارشی سازی صفحهی اول برنامههای Angular CLI توسط ASP.NET Core
تغییرات Encoding در NET Core.
ASP.NET Core Identity
- سفارشی سازی
- امکان ساخت قالب برای پروژههای NET Core.
- تبدیل قالبهای سفارشی پروژههای NET Core. به بستههای NuGet
طراحی یک گرید با Angular و ASP.NET Core
- افزودن و اعتبارسنجی خودکار Anti-Forgery Tokens در برنامههای Angular مبتنی بر ASP.NET Core
- تولید هدرهای Content Security Policy توسط ASP.NET Core برای برنامههای Angular
- غیرمعتبر شدن کوکیهای برنامههای ASP.NET Core هاست شدهی در IIS پس از ریاستارت آن
- اعتبارسنجی مبتنی بر JWT در ASP.NET Core 2.0 بدون استفاده از سیستم Identity
- اعتبارسنجی مبتنی بر کوکیها در ASP.NET Core 2.0 بدون استفاده از سیستم Identity
- فعالسازی HSTS در ASP.NET Core
- مراحل تنظیم Let's Encrypt در IIS
- IdentityServer 4x
- قسمت اول - نیاز به تامین کنندهی هویت مرکزی
- قسمت دوم - ایجاد ساختار اولیهی مثال این سری
- قسمت سوم - بررسی مفاهیم OpenID Connect
- قسمت چهارم - نصب و راه اندازی IdentityServer
- قسمت پنجم - پیاده سازی ورود و خروج از سیستم
- قسمت ششم - کار با User Claims
- قسمت هفتم- امن سازی Web API
- قسمت هشتم- تعریف سطوح دسترسی پیچیده
- قسمت نهم- مدیریت طول عمر توکنها
- قسمت دهم- ذخیره سازی اطلاعات کاربران IDP در بانک اطلاعاتی
- قسمت یازدهم- استفاده از تامین کنندههای هویت خارجی
- قسمت دوازدهم- یکپارچه سازی با اکانت گوگل
- قسمت سیزدهم- فعالسازی اعتبارسنجی دو مرحلهای
- قسمت چهاردهم- آماده شدن برای انتشار برنامه
- قسمت اول - نیاز به تامین کنندهی هویت مرکزی
کار با Areas در ASP.NET Core
کار با کوکیها در ASP.NET Core
بررسی روش آپلود فایلها در ASP.NET Core
ارسال ایمیل در ASP.NET Core
نوشتن Middleware سفارشی در ASP.NET Core
نوشتن TagHelperهای سفارشی برای ASP.NET Core
بررسی تغییرات Reflection در NET Core.
استفادهی گسترده از DateTimeOffset در NET Core.
بررسی روش دسترسی به HttpContext در ASP.NET Core
توزیع پروژههای ASP.NET Core 1.1 بدون ارائه فایلهای View آن
تغییرات رمزنگاری اطلاعات در NET Core.
ساخت بستههای نیوگت مخصوص NET Core.
تهیه قالب برای ارسال ایمیلها در ASP.NET Core توسط Razor Viewها
روش یافتن لیست تمام کنترلرها و اکشن متدهای یک برنامهی ASP.NET Core
بررسی روش آپلود فایلها از طریق یک برنامهی Angular به یک برنامهی ASP.NET Core
سفارشی سازی صفحهی اول برنامههای Angular CLI توسط ASP.NET Core
تغییرات Encoding در NET Core.
تغییرات متدهای بازگشت فایلها به سمت کلاینت در ASP.NET Core
پیاده سازی برنامههای چند مستاجری در ASP.NET Core
مقدمهای بر تزریق وابستگیها درASP.NET Core
نمایش خطاهای اعتبارسنجی سمت سرور ASP.NET Core در برنامههای Angular
احراز هویت و اعتبارسنجی کاربران در برنامههای Angular - قسمت اول - معرفی و ایجاد ساختار برنامه
روش استفادهی صحیح از HttpClient در برنامههای دات نت
اجرای سرویسهای NodeJS در ASP.NET Core
بررسی خطاهای ممکن در حین راه اندازی اولیه برنامههای ASP.NET Core در IIS
تست کردن متدهای یک Controller به کمک PowerShell
کار با Visual Studio در ASP.NET Core
پیاده سازی برنامههای چند مستاجری در ASP.NET Core
مقدمهای بر تزریق وابستگیها درASP.NET Core
نمایش خطاهای اعتبارسنجی سمت سرور ASP.NET Core در برنامههای Angular
احراز هویت و اعتبارسنجی کاربران در برنامههای Angular - قسمت اول - معرفی و ایجاد ساختار برنامه
روش استفادهی صحیح از HttpClient در برنامههای دات نت
اجرای سرویسهای NodeJS در ASP.NET Core
بررسی خطاهای ممکن در حین راه اندازی اولیه برنامههای ASP.NET Core در IIS
تست کردن متدهای یک Controller به کمک PowerShell
کار با Visual Studio در ASP.NET Core