اشتراکها
هرچند ASP.NET Core Identity تمام پیامهای خطایی را که ارائه میدهد از یک فایل resx دریافت میکند، اما این فایل در نگارش 1.1 آن حداقل قابلیت چندزبانی شدن را ندارد و اگر فایل resx فارسی آنرا تهیه کنیم، توسط این فریم ورک استفاده نخواهد شد. در ادامه ابتدا نگاهی خواهیم داشت به زیرساخت استفاده شدهی در این فریم ورک برای بومی سازی پیامهای داخلی آن و سپس نحوهی فارسی کردن آنرا بررسی میکنیم.
ASP.NET Core Identity 1.1 چگونه پیامهای خطای خود را تامین میکند؟
نگارش 1.1 این فریم ورک به همراه یک فایل Resources.resx است که تمام پیامهای خطاهای ارائه شدهی توسط متدهای مختلف آنرا به همراه دارد. این فایل توسط کلاس IdentityErrorDescriber به نحو ذیل استفاده میشود:
برای مثال برای نمایش یک پیام خطای عمومی، به کلاس Resources معادل فایل resx یاد شده مراجعه کرده و خاصیت DefaultError آنرا ارائه میدهد و به همین نحو برای سایر خطاها و اخطارها.
سپس کلاس IdentityErrorDescriber به سیستم تزریق وابستگیهای آن اضافه شده و هرجائیکه نیاز به نمایش پیامی را داشته، از آن استفاده میکند.
بنابراین همانطور که ملاحظه میکنید کلاس Resources آن ثابت است و قابل تغییر نیست. به همین جهت اگر معادل فارسی این فایل را تهیه کنیم، توسط این فریم ورک به صورت خودکار استفاده نخواهد شد.
فارسی سازی IdentityErrorDescriber
بهترین راه فارسی سازی کلاس IdentityErrorDescriber، ارث بری از آن و بازنویسی متدهای virtual آن است که اینکار در کلاس CustomIdentityErrorDescriber انجام شدهاست:
پس از بازنویسی کامل این کلاس، اکنون نوبت به جایگزینی آن با IdentityErrorDescriber پیشفرض است:
معرفی CustomIdentityErrorDescriber، در دو قسمت معرفی عمومی آن به سیستم تزریق وابستگیها و همچنین متد AddErrorDescriber زنجیرهی AddIdentity کلاس IdentityServicesRegistry انجام شدهاست.
به این ترتیب این فریم ورک هرزمانیکه نیاز به وهلهای از نوع IdentityErrorDescriber را داشته باشد، از وهلهی فارسی سازی شدهی ما استفاده میکند.
مشکل! هنوز پس از جایگزینی سرویس IdentityServicesRegistry اصلی، تعدادی از خطاها فارسی نیستند!
اگر به کلاس PasswordValidator آن مراجعه کنید، در سازندهی کلاس یک چنین تعریفی را میتوان مشاهده کرد:
یعنی اگر ما این کلاس PasswordValidator را سفارشی سازی کردیم و فراموش کردیم که سازندهی آنرا هم بازنویسی کنیم، پارامتر errors آن نال خواهد بود (چون از مقدار پیشفرض پارامترها استفاده کردهاند). یعنی از new IdentityErrorDescriber اصلی (بدون مراجعهی به سیستم تزریق وابستگیها و استفادهی از نسخهی سفارشی سازی شدهی ما) استفاده میکند. بنابراین در هر دو کلاس سفارشی سازی شدهی اعتبارسنجی کاربران و کلمات عبور آنها، ذکر سازندهی پیشفرض این کلاسها و ذکر پارامتر IdentityErrorDescriber آن، اجباری است:
به این ترتیب، زمانیکه این کلاسها توسط سیستم تزریق وابستگیها وهله سازی میشوند، IdentityErrorDescriber آن دقیقا همان کلاس فارسی سازی شدهی ما خواهد بود و دیگر شرط ()errors ?? new IdentityErrorDescriber در کلاس پایه محقق نمیشود تا بازهم به همان تامین کنندهی پیش فرض خطاها مراجعه کند؛ چون base(errors) آن با کلاس جدید ما مقدار دهی شدهاست.
یک نکته: اگر کلاسهای زیر را سفارشی سازی کردید، تمامشان از حالت ()errors ?? new IdentityErrorDescriber در سازندهی کلاس خود استفاده میکنند. بنابراین ذکر مجدد و بازنویسی سازندهی آنها را فراموش نکنید (در حد ذکر مجدد سازندهی کلاس پایه کفایت میکند و مابقی آن توسط سیستم تزریق وابستگیها مدیریت خواهد شد):
- PasswordValidator
- RoleManager
- RoleStore
- UserStore
- UserValidator
- RoleValidator
کدهای کامل این سری را در مخزن کد DNT Identity میتوانید ملاحظه کنید.
ASP.NET Core Identity 1.1 چگونه پیامهای خطای خود را تامین میکند؟
نگارش 1.1 این فریم ورک به همراه یک فایل Resources.resx است که تمام پیامهای خطاهای ارائه شدهی توسط متدهای مختلف آنرا به همراه دارد. این فایل توسط کلاس IdentityErrorDescriber به نحو ذیل استفاده میشود:
public class IdentityErrorDescriber { public virtual IdentityError DefaultError() { return new IdentityError { Code = nameof(DefaultError), Description = Resources.DefaultError }; }
سپس کلاس IdentityErrorDescriber به سیستم تزریق وابستگیهای آن اضافه شده و هرجائیکه نیاز به نمایش پیامی را داشته، از آن استفاده میکند.
بنابراین همانطور که ملاحظه میکنید کلاس Resources آن ثابت است و قابل تغییر نیست. به همین جهت اگر معادل فارسی این فایل را تهیه کنیم، توسط این فریم ورک به صورت خودکار استفاده نخواهد شد.
فارسی سازی IdentityErrorDescriber
بهترین راه فارسی سازی کلاس IdentityErrorDescriber، ارث بری از آن و بازنویسی متدهای virtual آن است که اینکار در کلاس CustomIdentityErrorDescriber انجام شدهاست:
public class CustomIdentityErrorDescriber : IdentityErrorDescriber { public override IdentityError DefaultError() { return new IdentityError { Code = nameof(DefaultError), Description = "خطایی رخ دادهاست." }; }
services.AddScoped<IdentityErrorDescriber, CustomIdentityErrorDescriber>(); services.AddIdentity<User, Role>(identityOptions => { }).AddUserStore<ApplicationUserStore>() // the rest of the setting … .AddErrorDescriber<CustomIdentityErrorDescriber>() // the rest of the setting …
به این ترتیب این فریم ورک هرزمانیکه نیاز به وهلهای از نوع IdentityErrorDescriber را داشته باشد، از وهلهی فارسی سازی شدهی ما استفاده میکند.
مشکل! هنوز پس از جایگزینی سرویس IdentityServicesRegistry اصلی، تعدادی از خطاها فارسی نیستند!
اگر به کلاس PasswordValidator آن مراجعه کنید، در سازندهی کلاس یک چنین تعریفی را میتوان مشاهده کرد:
public class PasswordValidator<TUser> : IPasswordValidator<TUser> where TUser : class { public PasswordValidator(IdentityErrorDescriber errors = null) { Describer = errors ?? new IdentityErrorDescriber(); }
public class CustomPasswordValidator : PasswordValidator<User> { private readonly IUsedPasswordsService _usedPasswordsService; private readonly ISet<string> _passwordsBanList; public CustomPasswordValidator( IdentityErrorDescriber errors,// How to use CustomIdentityErrorDescriber IOptionsSnapshot<SiteSettings> configurationRoot, IUsedPasswordsService usedPasswordsService) : base(errors) public class CustomUserValidator : UserValidator<User> { private readonly ISet<string> _emailsBanList; public CustomUserValidator( IdentityErrorDescriber errors,// How to use CustomIdentityErrorDescriber IOptionsSnapshot<SiteSettings> configurationRoot ) : base(errors)
یک نکته: اگر کلاسهای زیر را سفارشی سازی کردید، تمامشان از حالت ()errors ?? new IdentityErrorDescriber در سازندهی کلاس خود استفاده میکنند. بنابراین ذکر مجدد و بازنویسی سازندهی آنها را فراموش نکنید (در حد ذکر مجدد سازندهی کلاس پایه کفایت میکند و مابقی آن توسط سیستم تزریق وابستگیها مدیریت خواهد شد):
- PasswordValidator
- RoleManager
- RoleStore
- UserStore
- UserValidator
- RoleValidator
کدهای کامل این سری را در مخزن کد DNT Identity میتوانید ملاحظه کنید.
زمانیکه به صفحهی دریافت نگارشهای مختلف NET Core. مراجعه میکنیم، بستههای مختلفی از یک نگارش قابل مشاهده هستند و در بدو امر واضح نیست که کدامیک را باید دریافت کرد. در این مطلب تفاوتهای بین این بستهها را مرور خواهیم کرد.
کدام نگارشهای NET Core. بر روی سیستم شما نصب هستند؟
پیش از انجام هرکاری نیاز است بررسی کنیم کدامیک از بستههای ارائه شده، بر روی سیستم جاری نصب هستند. برای انجام اینکار دستور زیر را در خط فرمان صادر کنید:
اگر این دستور کار نکرد و خطایی را دریافت کردید، یعنی NET Core. اصلا بر روی سیستم شما نصب نیست. برنامه dotnet.exe جزئی از runtime نصب شدهاست و به صورت خودکار به path سیستم اضافه میشود. به همین جهت است که در صورت نصب آن، فرمان dotnet در هر مسیری قابل اجرا است.
Runtime تنها ویژگیهای اساسی جهت اجرای برنامههای از پیش کامپایل شدهی NET Core. را با اجرای فرمانی مانند dotnet mydll.dll و یا اجرای دستور dotnet --info برای دریافت اطلاعاتی از جزئیات این ویژگیها، به همراه دارد. اما برای کار با سورس کدها، build، publish و هر کار دیگری با آنها، حتما باید SDK نیز نصب شود.
خروجی فرمان فوق بر روی سیستم من چنین چیزی است:
اولین شماره نگارش نمایش داده شدهی در این لیست (2.1.301)، شماره نگارش SDK فعال است. سپس شماره نگارش 2.1.1 به معنای شماره نگارش Runtime فعال بر روی سیستم است که هاست dotnet.exe به شمار میرود. سپس لیست SDKها و Runtimeهای نصب شدهی بر روی سیستم را نمایش میدهد.
باید دقت داشت که بر روی یک سیستم میتوان چندین SDK و چندین Runtime مختلف را نصب کرد و هر پروژه از شماره نگارش خاصی استفاده کند. شماره نگارش runtime استفاده شدهی در پروژهها در فایل csproj، توسط مدخل زیر مشخص میشود:
در مورد SDK اینطور نیست و همواره از آخرین SDK نصب شده (همان شماره نگارش SDK فعال فوق) استفاده میشود، مگر اینکه فایل ویژهای به نام global.json را در ریشهی اصلی solution قرار دهید؛ با این محتوای فرضی:
در این حالت پروژهی جاری را وادار میکنید بجای استفادهی از آخرین SDK نصب شدهی بر روی سیستم، از نگارش SDK خاصی استفاده کند.
البته در اکثر موارد نیازی به انجام این کار نیست؛ چون SDK، با تمام نگارشهای قبلی سازگار است و همواره استفادهی از آخرین SDK نصب شده توصیه میشود. به همین جهت فایل global.json را پس از ایجاد یک solution جدید مشاهده نمیکنید؛ مگر اینکه خودتان به دلایل خاصی آنرا اضافه و مقید نمائید.
تفاوت بستههای مختلف قابل دریافت NET Core. در چیست؟
زمانیکه برای دریافت آخرین نگارش NET Core. به سایت آن مراجعه میکنیم، به ازای هر نگارش، یک چنین لیستی قابل مشاهده است:
و اکنون سؤالی که مطرح میشود این است: کدامیک را باید دریافت کرد؟
Visual Studio
اگر کاربر ویندوز هستید، با نصب آخرین نگارش Visual Studio، میتوانید به همراه آن، آخرین نگارش SDK ،runtime و اجزای هاست برنامههای ASP.NET Core بر روی IIS را نیز بر روی سیستم خود نصب کنید.
NET Core SDK.
هدف از ارائهی بستهی SDK، انجام فرآیندهای build، اجرا و مدیریت امور مرتبط با NET Core.، بدون استفاده از Visual Studio و بر روی تمام سیستم عاملهای پشتیبانی شدهاست. زمانیکه یک بستهی SDK را نصب میکنید، به همراه آن این موارد نیز نصب میشوند:
به همین جهت حجم آن از بستهی تکی runtime بیشتر است و با نصب آن دیگر نیازی به دریافت مجزای بستههای runtime نیست.
بنابراین دلیل نصب آن میتواند شامل یکی از موارد زیر باشد:
- بر روی سیستمی که در حال توسعهی برنامههای مبتنی بر NET Core. هستید. این تمام چیزی است که به آن نیاز دارید.
- بر روی سروری که نیاز است دستور dotnet را برای انجام فرآیندهای build/publish اجرا کند.
NET Core Runtime.
بستههای Runtimes، کوچکترین بستهی ممکن در این لیست هستند و هدف از آنها صرفا اجرای برنامههای کامپایل شدهی NET Core. در سکوهای کاری مختلف پشتیبانی شدهی توسط آن است.
باید دقت داشت که اگر برنامهی شما از «ASP.NET Core meta package» استفاده میکند، این بسته در runtime لحاظ نشدهاست و در یک چنین حالتی باید بستهی ASP.NET Core را به صورت جداگانه دریافت و نصب کنید. هرچند اگر از این متاپکیجها استفاده نکنید و بستههای مورد نیاز را به صورت مستقیم به برنامهی خود اضافه کنید، این بستهها جزئی از فایلهای publish نهایی بوده و در این حالت برنامه توسط بستهی runtime نیز قابل اجرا است.
در این حالت برنامهی dotnet بجز اجرای برنامهها و ارائهی اطلاعاتی در مورد خود آن، کارهای دیگری را مانند build و یا publish، نمیتواند انجام دهد و برنامه در این حالت باید کاملا از پیش کامپایل شده باشد.
بنابراین دلیل نصب آن میتواند شامل یکی از موارد زیر باشد:
- برای اجرای برنامههای از پیش کامپایل شدهای که به همراه تمام وابستگیهای مورد نیاز هم هستند.
- برای اجرای برنامههای وبی که از ASP.NET Meta packages استفاده نمیکنند
ASP.NET Core Installer
همانطور که در توضیحات بستهی runtime عنوان شد، این بسته، متاپکیجهای ASP.NET Core را به همراه ندارد. اگر به آنها نیاز دارید، باید آنها را به صورت جداگانه توسط ASP.NET Core installer نصب کنید که شامل این موارد است:
NET Core Windows Hosting Pack.
نصب این بسته برای هاست برنامههای ASP.NET Core در ویندوز و بر روی IIS ضروری است و شامل این اجزا میشود:
بنابراین این بسته شامل تمام موارد یاد شدهاست منهای قابلیتهای SDK برای build و publish برنامهها.
بنابراین به صورت خلاصه
برای سرورها این موارد را نصب کنید:
- در ویندوز: Windows Server Hosting Bundle
- برای Mac و لینوکس: .NET Core Runtime + ASP.NET Core Runtimes
برای سیستم توسعهی شخصی این موارد را نصب کنید:
- SDK
- اگر از ویندوز استفاده میکنید: Visual Studio هم به همراه SDK نصب میشود.
برای اجرای برنامههای از پیش کامپایل شده که به همراه تمام وابستگیهای مورد نیاز هم هستند:
- تنها Runtime را نصب کنید.
اگر این برنامهی از پیش کامپایل شده از ASP.NET Runtime Meta packages استفاده میکند:
- ASP.NET Runtimes را نیز نصب کنید.
کدام نگارشهای NET Core. بر روی سیستم شما نصب هستند؟
پیش از انجام هرکاری نیاز است بررسی کنیم کدامیک از بستههای ارائه شده، بر روی سیستم جاری نصب هستند. برای انجام اینکار دستور زیر را در خط فرمان صادر کنید:
dotnet --info
Runtime تنها ویژگیهای اساسی جهت اجرای برنامههای از پیش کامپایل شدهی NET Core. را با اجرای فرمانی مانند dotnet mydll.dll و یا اجرای دستور dotnet --info برای دریافت اطلاعاتی از جزئیات این ویژگیها، به همراه دارد. اما برای کار با سورس کدها، build، publish و هر کار دیگری با آنها، حتما باید SDK نیز نصب شود.
خروجی فرمان فوق بر روی سیستم من چنین چیزی است:
C:\Users\Vahid>dotnet --info .NET Core SDK (reflecting any global.json): Version: 2.1.301 Commit: 59524873d6 Runtime Environment: OS Name: Windows OS Version: 10.0.17134 OS Platform: Windows RID: win10-x64 Base Path: C:\Program Files\dotnet\sdk\2.1.301\ Host (useful for support): Version: 2.1.1 Commit: 6985b9f684 .NET Core SDKs installed: 2.1.300 [C:\Program Files\dotnet\sdk] 2.1.301 [C:\Program Files\dotnet\sdk] .NET Core runtimes installed: Microsoft.NETCore.App 2.1.0 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App] Microsoft.NETCore.App 2.1.1 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App] To install additional .NET Core runtimes or SDKs: https://aka.ms/dotnet-download
باید دقت داشت که بر روی یک سیستم میتوان چندین SDK و چندین Runtime مختلف را نصب کرد و هر پروژه از شماره نگارش خاصی استفاده کند. شماره نگارش runtime استفاده شدهی در پروژهها در فایل csproj، توسط مدخل زیر مشخص میشود:
<TargetFramework>netcoreapp2.1</TargetFramework>
{ "sdk": { "version": "2.1.300-rc.31211" } }
البته در اکثر موارد نیازی به انجام این کار نیست؛ چون SDK، با تمام نگارشهای قبلی سازگار است و همواره استفادهی از آخرین SDK نصب شده توصیه میشود. به همین جهت فایل global.json را پس از ایجاد یک solution جدید مشاهده نمیکنید؛ مگر اینکه خودتان به دلایل خاصی آنرا اضافه و مقید نمائید.
تفاوت بستههای مختلف قابل دریافت NET Core. در چیست؟
زمانیکه برای دریافت آخرین نگارش NET Core. به سایت آن مراجعه میکنیم، به ازای هر نگارش، یک چنین لیستی قابل مشاهده است:
• .NET Core Runtime • .NET Core SDK • .NET Core Hosting Bundle • Visual Studio • ASP.NET Core Installer
Visual Studio
اگر کاربر ویندوز هستید، با نصب آخرین نگارش Visual Studio، میتوانید به همراه آن، آخرین نگارش SDK ،runtime و اجزای هاست برنامههای ASP.NET Core بر روی IIS را نیز بر روی سیستم خود نصب کنید.
NET Core SDK.
هدف از ارائهی بستهی SDK، انجام فرآیندهای build، اجرا و مدیریت امور مرتبط با NET Core.، بدون استفاده از Visual Studio و بر روی تمام سیستم عاملهای پشتیبانی شدهاست. زمانیکه یک بستهی SDK را نصب میکنید، به همراه آن این موارد نیز نصب میشوند:
• .NET Core SDK • .NET Core Runtime • ASP.NET Core Runtime
بنابراین دلیل نصب آن میتواند شامل یکی از موارد زیر باشد:
- بر روی سیستمی که در حال توسعهی برنامههای مبتنی بر NET Core. هستید. این تمام چیزی است که به آن نیاز دارید.
- بر روی سروری که نیاز است دستور dotnet را برای انجام فرآیندهای build/publish اجرا کند.
NET Core Runtime.
بستههای Runtimes، کوچکترین بستهی ممکن در این لیست هستند و هدف از آنها صرفا اجرای برنامههای کامپایل شدهی NET Core. در سکوهای کاری مختلف پشتیبانی شدهی توسط آن است.
باید دقت داشت که اگر برنامهی شما از «ASP.NET Core meta package» استفاده میکند، این بسته در runtime لحاظ نشدهاست و در یک چنین حالتی باید بستهی ASP.NET Core را به صورت جداگانه دریافت و نصب کنید. هرچند اگر از این متاپکیجها استفاده نکنید و بستههای مورد نیاز را به صورت مستقیم به برنامهی خود اضافه کنید، این بستهها جزئی از فایلهای publish نهایی بوده و در این حالت برنامه توسط بستهی runtime نیز قابل اجرا است.
در این حالت برنامهی dotnet بجز اجرای برنامهها و ارائهی اطلاعاتی در مورد خود آن، کارهای دیگری را مانند build و یا publish، نمیتواند انجام دهد و برنامه در این حالت باید کاملا از پیش کامپایل شده باشد.
بنابراین دلیل نصب آن میتواند شامل یکی از موارد زیر باشد:
- برای اجرای برنامههای از پیش کامپایل شدهای که به همراه تمام وابستگیهای مورد نیاز هم هستند.
- برای اجرای برنامههای وبی که از ASP.NET Meta packages استفاده نمیکنند
ASP.NET Core Installer
همانطور که در توضیحات بستهی runtime عنوان شد، این بسته، متاپکیجهای ASP.NET Core را به همراه ندارد. اگر به آنها نیاز دارید، باید آنها را به صورت جداگانه توسط ASP.NET Core installer نصب کنید که شامل این موارد است:
- The ASP.NET Runtime Meta Packages - Microsoft.AspNetCore.App - Microsoft.AspNetCore.All
نصب این بسته برای هاست برنامههای ASP.NET Core در ویندوز و بر روی IIS ضروری است و شامل این اجزا میشود:
- 32 bit and 64 .NET Core Runtimes - ASP.NET Runtime Packages (Microsoft.AspNetCode.App/All) - IIS Hosting Components
بنابراین به صورت خلاصه
برای سرورها این موارد را نصب کنید:
- در ویندوز: Windows Server Hosting Bundle
- برای Mac و لینوکس: .NET Core Runtime + ASP.NET Core Runtimes
برای سیستم توسعهی شخصی این موارد را نصب کنید:
- SDK
- اگر از ویندوز استفاده میکنید: Visual Studio هم به همراه SDK نصب میشود.
برای اجرای برنامههای از پیش کامپایل شده که به همراه تمام وابستگیهای مورد نیاز هم هستند:
- تنها Runtime را نصب کنید.
اگر این برنامهی از پیش کامپایل شده از ASP.NET Runtime Meta packages استفاده میکند:
- ASP.NET Runtimes را نیز نصب کنید.
نظرات مطالب
ساخت ActionResult سفارشی
ارتقاء به ASP.NET Core 3.0
در ASP.NET Core نیز نکات کلی نوشتن ActionResultها برقرار است. فقط پیشتر اگر قطعه کد زیر در متد ExecuteResult نگارش 2.2 کار میکرد:
اکنون در نگارش 3x این استثناء را صادر میکند:
Response.Body.Write
System.InvalidOperationException: Synchronous operations are disallowed. Call WriteAsync or set AllowSynchronousIO to true instead.
- شبیه به این مفهوم در ASP.NET Core یک Tag Helper جدید هست به نام SecurityTrimmingTagHelper
- در پروژههای MVC 5.x هم یک نمونه security trimming دیگر با پیاده سازی HTML Helperها هست؛ مانند Secure Linkها و یا یک مثال دیگر
- در پروژههای MVC 5.x هم یک نمونه security trimming دیگر با پیاده سازی HTML Helperها هست؛ مانند Secure Linkها و یا یک مثال دیگر
سلام
میخواستم بدونم این سیستم افزونه پذیر رو میشه با Injection خود asp.net core پیاده سازی کرد یا حتما بایستی structure map باشه؟
اگر میشه، کدوم بهتره؟
gRPC + ASP.NET Core as a Migration Path for WCFs in .NET Core
Feature | WCF | ASP.Net Core + gRPC |
Platforms | Windows | Windows, Linux, MacOS |
Protocols | LRPC/Named Pipes/HTTP/TCP/MSMQ | Binary (GRPC) + HTTP2 (TCP/Named Pipes/LRPC) .AddProtocol(“ncacn_ip_tcp”, “8080”) .AddProtocol(“ncacn_np”, @”\pipe\MyService”) .AddProtocol(“ncalrpc”, “MyService”) By removing the ASP.NET Core stack and just using .NET Core |
Injected Aspects | Behaviors | ASP.NET Core DI Middleware/ gRPC interceptors |
Distributed Transactions | *Yes – [TransactionFlow], transactionscopes, and supported bindings | *No |
Transport Security | SSL/TLS | SSL/TLS |
Message Security | Certificates/credentials | Certificates/credentials https://docs.microsoft.com/en-us/aspnet/core/grpc/authn-and-authz?view=aspnetcore-3.0 |
Windows Authentication | Kerberos/NTLM | AAD Sync/ASFS + ASP.NET Core middleware |
Proxies/Contracts | Service Contracts/Data Contracts | Protocol Buffers |
Proxy-less Communication | WCF Channel Factory | † Protobuf-Net.GRPC |
از زمان Blazor 5x، امکان آپلود فایل به صورت استاندارد به Blazor اضافه شدهاست که نمونهی Blazor Server آنرا پیشتر در مطلب «Blazor 5x - قسمت 17 - کار با فرمها - بخش 5 - آپلود تصاویر» مطالعه کردید. در تکمیل آن، روش آپلود فایلها در برنامههای WASM را نیز بررسی خواهیم کرد. این برنامه از نوع hosted است؛ یعنی توسط دستور dotnet new blazorwasm --hosted ایجاد شدهاست و به صورت خودکار دارای سه بخش Client، Server و Shared است.
معرفی مدل ارسالی برنامه سمت کلاینت
فرض کنید مطابق شکل فوق، قرار است اطلاعات یک کاربر، به همراه تعدادی تصویر از او، به سمت Web API ارسال شوند. برای نمونه، مدل اشتراکی کاربر را به صورت زیر تعریف کردهایم:
ساختار کنترلر Web API دریافت کنندهی مدل برنامه
در این حالت امضای اکشن متد CreateUser واقع در کنترلر Files که قرار است این اطلاعات را دریافت کند، به صورت زیر است:
یعنی در سمت Web API، قرار است اطلاعات مدل User و همچنین لیستی از فایلهای آپلودی (احتمالی و اختیاری) را یکجا و در طی یک عملیات Post، دریافت کنیم. در اینجا نام پارامترهایی را هم که انتظار داریم، دقیقا userModel و inputFiles هستند. همچنین فایلهای آپلودی باید بتوانند ساختار IFormFile استاندارد ASP.NET Core را تشکیل داده و به صورت خودکار به پارامترهای تعریف شده، bind شوند. به علاوه content-type مورد انتظار هم FromForm است.
ایجاد سرویسی در سمت کلاینت، برای آپلود اطلاعات یک مدل به همراه فایلهای انتخابی کاربر
کدهای کامل سرویسی که میتواند انتظارات یاد شده را در سمت کلاینت برآورده کند، به صورت زیر است:
توضیحات:
- کامپوننت استاندارد InputFiles در Blazor Wasm، میتواند لیستی از IBrowserFileهای انتخابی توسط کاربر را در اختیار ما قرار دهد.
- fileParameterName، همان نام پارامتر "inputFiles" در اکشن متد سمت سرور مثال جاری است که به صورت متغیر قابل تنظیم شدهاست.
- model جنریک، برای نمونه وهلهای از شیء User است که به یک فرم Blazor متصل است.
- modelParameterName، همان نام پارامتر "userModel" در اکشن متد سمت سرور مثال جاری است که به صورت متغیر قابل تنظیم شدهاست.
- در ادامه یک MultipartFormDataContent را تشکیل دادهایم. توسط این ساختار میتوان فایلها و اطلاعات یک مدل را به صورت یکجا جمع آوری و به سمت سرور ارسال کرد. به این content ویژه، ابتدای لیستی از new StreamContentها را اضافه میکنیم. این streamها توسط متد OpenReadStream هر IBrowserFile دریافتی از کامپوننت InputFile، تشکیل میشوند. متد OpenReadStream به صورت پیشفرض فقط فایلهایی تا حجم 500 کیلوبایت را پردازش میکند و اگر فایلی حجیمتر را به آن معرفی کنیم، یک استثناء را صادر خواهد کرد. به همین جهت میتوان توسط پارامتر maxAllowedSize آن، این مقدار پیشفرض را تغییر داد.
- در اینجا مدل برنامه به صورت JSON به عنوان یک new StringContent اضافه شدهاست. مزیت کار کردن با JsonSerializer.Serialize استاندارد، ساده شدن برنامه و عدم درگیری با مباحث Reflection و خواندن پویای اطلاعات مدل جنریک است. اما در ادامه مشکلی را پدید خواهد آورد! این رشتهی ارسالی به سمت سرور، به صورت خودکار به یک مدل، Bind نخواهد شد و باید برای آن یک model-binder سفارشی را بنویسیم. یعنی این رشتهی new StringContent را در سمت سرور دقیقا به صورت یک رشته معمولی میتوان دریافت کرد و نه حالت دیگری و مهم نیست که اکنون به صورت JSON ارسال میشود؛ چون MultipartFormDataContent ویژهای را داریم، model-binder پیشفرض ASP.NET Core، انتظار یک شیء خاص را در این بین ندارد.
- تنظیم "form-data" را هم به عنوان Headers.ContentDisposition مشاهده میکنید. بدون وجود آن، ویژگی [FromForm] سمت Web API، از پردازش درخواست جلوگیری خواهد کرد.
- در آخر توسط متد PostAsync، این اطلاعات جمع آوری شده، به سمت سرور ارسال خواهند شد.
پس از تهیهی سرویس ویژهی فوق که میتواند اطلاعات فایلها و یک مدل را به صورت یکجا به سمت سرور ارسال کند، اکنون نوبت به ثبت و معرفی آن به سیستم تزریق وابستگیها در فایل Program.cs برنامهی کلاینت است:
تکمیل فرم ارسال اطلاعات مدل و فایلهای همراه آن در برنامهی Blazor WASM
در ادامه پس از تشکیل IFilesManagerService، نوبت به استفادهی از آن است. به همین جهت همان کامپوننت Index برنامه را به صورت زیر تغییر میدهیم:
در اینجا فیلدهای مورد استفادهی در فرم برنامه مشخص شدهاند:
- SelectedFiles همان لیست فایلهای انتخابی توسط کاربر است.
- UserModel شیءای است که به EditForm جاری متصل خواهد شد.
- توسط isProcessing ابتدا و انتهای آپلود به سرور را مشخص میکنیم.
- UploadErrorMessage، خطای احتمالی انتخاب فایلها مانند «فقط تصاویر را انتخاب کنید» را تعریف میکند.
بر این اساس، فرمی را که در تصویر ابتدای بحث مشاهده کردید، به صورت زیر تشکیل میدهیم:
توضیحات:
- UserModel که وهلهی از شیء اشتراکی User است، به EditForm متصل شدهاست.
- سپس توسط یک InputText و InputNumber، مقادیر خواص نام و سن کاربر را دریافت میکنیم.
- InputFile دارای ویژگی multiple هم امکان دریافت چندین فایل را توسط کاربر میسر میکند. پس از انتخاب فایلها، رویداد OnChange آن، توسط متد OnInputFileChange مدیریت خواهد شد:
- در اینجا امضای متد رویداد گردان OnChange را مشاهده میکنید. توسط متد GetMultipleFiles میتوان لیست فایلهای انتخابی توسط کاربر را دریافت کرد. نیاز است پارامتر maximumFileCount آنرا نیز تنظیم کنیم تا دقیقا مشخص شود چه تعداد فایلی مدنظر است؛ بیش از آن، یک استثناء را صادر میکند.
- در ادامه اگر فایلی انتخاب نشده باشد، یا فایل انتخابی، تصویری نباشد، با مقدار دهی UploadErrorMessage، خطایی را به کاربر نمایش میدهیم.
- در پایان این متد، لیست فایلهای دریافتی را به فیلد SelectedFiles انتساب میدهیم تا در ذیل InputFile، به صورت یک جدول نمایش داده شوند.
مرحلهی آخر تکمیل این فرم، تدارک متد رویدادگردان OnValidSubmit فرم برنامه است:
- در اینجا زمانیکه isProcessing به true تنظیم میشود، دکمهی ارسال اطلاعات، غیرفعال خواهد شد؛ تا از کلیک چندبارهی بر روی آن جلوگیری شود.
- سپس روش استفادهی از متد PostModelWithFilesAsync سرویس FilesManagerService را مشاهده میکنید که اطلاعات فایلها و مدل برنامه را به سمت اکشن متد api/Files/CreateUser ارسال میکند.
- در آخر با وهله سازی مجدد UserModel، به صورت خودکار فرم برنامه را پاک کرده و آمادهی دریافت اطلاعات بعدی میکنیم.
تکمیل کنترلر Web API دریافت کنندهی مدل برنامه
در ابتدای بحث، ساختار ابتدایی کنترلر Web API دریافت کنندهی اطلاعات FilesManagerService.PostModelWithFilesAsync فوق را معرفی کردیم. در ادامه کدهای کامل آنرا مشاهده میکنید:
نکات تکمیلی این کنترلر را در مطلب «بررسی روش آپلود فایلها در ASP.NET Core» میتوانید مطالعه کنید و از این لحاظ هیچ نکتهی جدیدی را به همراه ندارد؛ بجز پارامتر userModel آن:
همانطور که عنوان شد، userModel ارسالی به سمت سرور چون به همراه تعدادی فایل است، به صورت خودکار به شیء User نگاشت نخواهد شد. به همین جهت نیاز است model-binder سفارشی زیر را برای آن تهیه کرد:
در اینجا مقدار رشتهای پارامتر مزین شدهی توسط JsonModelBinder فوق، توسط متد استاندارد JsonSerializer.Deserialize تبدیل به یک شیء شده و به آن پارامتر انتساب داده میشود. اگر نخواهیم از این model-binder سفارشی استفاده کنیم، ابتدا باید پارامتر دریافتی را رشتهای تعریف کنیم و سپس خودمان کار فراخوانی متد JsonSerializer.Deserialize را انجام دهیم:
یک نکته تکمیلی: در Blazor 5x، از نمایش درصد پیشرفت آپلود، پشتیبانی نمیشود؛ از این جهت که HttpClient طراحی شده، در اصل به fetch API استاندارد مرورگر ترجمه میشود و این API استاندارد، هنوز از streaming پشتیبانی نمیکند . حتی ممکن است با کمی جستجو به راهحلهایی که سعی کردهاند بر اساس HttpClient و نوشتن بایت به بایت اطلاعات در آن، درصد پیشرفت آپلود را محاسبه کرده باشند، برسید. این راهحلها تنها کاری را که انجام میدهند، بافر کردن اطلاعات، جهت fetch API و سپس ارسال تمام آن است. به همین جهت درصدی که نمایش داده میشود، درصد بافر شدن اطلاعات در خود مرورگر است (پیش از ارسال آن به سرور) و سپس تحویل آن به fetch API جهت ارسال نهایی به سمت سرور.
کدهای کامل این مطلب را از اینجا میتوانید دریافت کنید: BlazorWasmUpload.zip
معرفی مدل ارسالی برنامه سمت کلاینت
فرض کنید مطابق شکل فوق، قرار است اطلاعات یک کاربر، به همراه تعدادی تصویر از او، به سمت Web API ارسال شوند. برای نمونه، مدل اشتراکی کاربر را به صورت زیر تعریف کردهایم:
using System.ComponentModel.DataAnnotations; namespace BlazorWasmUpload.Shared { public class User { [Required] public string Name { get; set; } [Required] [Range(18, 90)] public int Age { get; set; } } }
ساختار کنترلر Web API دریافت کنندهی مدل برنامه
در این حالت امضای اکشن متد CreateUser واقع در کنترلر Files که قرار است این اطلاعات را دریافت کند، به صورت زیر است:
namespace BlazorWasmUpload.Server.Controllers { [ApiController] [Route("api/[controller]/[action]")] public class FilesController : ControllerBase { [HttpPost] public async Task<IActionResult> CreateUser( [FromForm] User userModel, [FromForm] IList<IFormFile> inputFiles = null)
ایجاد سرویسی در سمت کلاینت، برای آپلود اطلاعات یک مدل به همراه فایلهای انتخابی کاربر
کدهای کامل سرویسی که میتواند انتظارات یاد شده را در سمت کلاینت برآورده کند، به صورت زیر است:
using System.Collections.Generic; using System.Linq; using System.Net.Http; using System.Net.Http.Headers; using System.Text; using System.Text.Json; using System.Threading.Tasks; using Microsoft.AspNetCore.Components.Forms; namespace BlazorWasmUpload.Client.Services { public interface IFilesManagerService { Task<HttpResponseMessage> PostModelWithFilesAsync<T>(string requestUri, IEnumerable<IBrowserFile> browserFiles, string fileParameterName, T model, string modelParameterName); } public class FilesManagerService : IFilesManagerService { private readonly HttpClient _httpClient; public FilesManagerService(HttpClient httpClient) { _httpClient = httpClient; } public async Task<HttpResponseMessage> PostModelWithFilesAsync<T>( string requestUri, IEnumerable<IBrowserFile> browserFiles, string fileParameterName, T model, string modelParameterName) { var requestContent = new MultipartFormDataContent(); requestContent.Headers.ContentDisposition = new ContentDispositionHeaderValue("form-data"); if (browserFiles?.Any() == true) { foreach (var file in browserFiles) { var stream = file.OpenReadStream(maxAllowedSize: 512000 * 1000); requestContent.Add(content: new StreamContent(stream, (int)file.Size), name: fileParameterName, fileName: file.Name); } } requestContent.Add( content: new StringContent(JsonSerializer.Serialize(model), Encoding.UTF8, "application/json"), name: modelParameterName); var result = await _httpClient.PostAsync(requestUri, requestContent); result.EnsureSuccessStatusCode(); return result; } } }
- کامپوننت استاندارد InputFiles در Blazor Wasm، میتواند لیستی از IBrowserFileهای انتخابی توسط کاربر را در اختیار ما قرار دهد.
- fileParameterName، همان نام پارامتر "inputFiles" در اکشن متد سمت سرور مثال جاری است که به صورت متغیر قابل تنظیم شدهاست.
- model جنریک، برای نمونه وهلهای از شیء User است که به یک فرم Blazor متصل است.
- modelParameterName، همان نام پارامتر "userModel" در اکشن متد سمت سرور مثال جاری است که به صورت متغیر قابل تنظیم شدهاست.
- در ادامه یک MultipartFormDataContent را تشکیل دادهایم. توسط این ساختار میتوان فایلها و اطلاعات یک مدل را به صورت یکجا جمع آوری و به سمت سرور ارسال کرد. به این content ویژه، ابتدای لیستی از new StreamContentها را اضافه میکنیم. این streamها توسط متد OpenReadStream هر IBrowserFile دریافتی از کامپوننت InputFile، تشکیل میشوند. متد OpenReadStream به صورت پیشفرض فقط فایلهایی تا حجم 500 کیلوبایت را پردازش میکند و اگر فایلی حجیمتر را به آن معرفی کنیم، یک استثناء را صادر خواهد کرد. به همین جهت میتوان توسط پارامتر maxAllowedSize آن، این مقدار پیشفرض را تغییر داد.
- در اینجا مدل برنامه به صورت JSON به عنوان یک new StringContent اضافه شدهاست. مزیت کار کردن با JsonSerializer.Serialize استاندارد، ساده شدن برنامه و عدم درگیری با مباحث Reflection و خواندن پویای اطلاعات مدل جنریک است. اما در ادامه مشکلی را پدید خواهد آورد! این رشتهی ارسالی به سمت سرور، به صورت خودکار به یک مدل، Bind نخواهد شد و باید برای آن یک model-binder سفارشی را بنویسیم. یعنی این رشتهی new StringContent را در سمت سرور دقیقا به صورت یک رشته معمولی میتوان دریافت کرد و نه حالت دیگری و مهم نیست که اکنون به صورت JSON ارسال میشود؛ چون MultipartFormDataContent ویژهای را داریم، model-binder پیشفرض ASP.NET Core، انتظار یک شیء خاص را در این بین ندارد.
- تنظیم "form-data" را هم به عنوان Headers.ContentDisposition مشاهده میکنید. بدون وجود آن، ویژگی [FromForm] سمت Web API، از پردازش درخواست جلوگیری خواهد کرد.
- در آخر توسط متد PostAsync، این اطلاعات جمع آوری شده، به سمت سرور ارسال خواهند شد.
پس از تهیهی سرویس ویژهی فوق که میتواند اطلاعات فایلها و یک مدل را به صورت یکجا به سمت سرور ارسال کند، اکنون نوبت به ثبت و معرفی آن به سیستم تزریق وابستگیها در فایل Program.cs برنامهی کلاینت است:
namespace BlazorWasmUpload.Client { public class Program { public static async Task Main(string[] args) { var builder = WebAssemblyHostBuilder.CreateDefault(args); // ... builder.Services.AddScoped<IFilesManagerService, FilesManagerService>(); // ... } } }
تکمیل فرم ارسال اطلاعات مدل و فایلهای همراه آن در برنامهی Blazor WASM
در ادامه پس از تشکیل IFilesManagerService، نوبت به استفادهی از آن است. به همین جهت همان کامپوننت Index برنامه را به صورت زیر تغییر میدهیم:
@code { IReadOnlyList<IBrowserFile> SelectedFiles; User UserModel = new User(); bool isProcessing; string UploadErrorMessage;
- SelectedFiles همان لیست فایلهای انتخابی توسط کاربر است.
- UserModel شیءای است که به EditForm جاری متصل خواهد شد.
- توسط isProcessing ابتدا و انتهای آپلود به سرور را مشخص میکنیم.
- UploadErrorMessage، خطای احتمالی انتخاب فایلها مانند «فقط تصاویر را انتخاب کنید» را تعریف میکند.
بر این اساس، فرمی را که در تصویر ابتدای بحث مشاهده کردید، به صورت زیر تشکیل میدهیم:
@page "/" @using System.IO @using BlazorWasmUpload.Shared @using BlazorWasmUpload.Client.Services @inject IFilesManagerService FilesManagerService <h3>Post a model with files</h3> <EditForm Model="UserModel" OnValidSubmit="CreateUserAsync"> <DataAnnotationsValidator /> <div> <label>Name</label> <InputText @bind-Value="UserModel.Name"></InputText> <ValidationMessage For="()=>UserModel.Name"></ValidationMessage> </div> <div> <label>Age</label> <InputNumber @bind-Value="UserModel.Age"></InputNumber> <ValidationMessage For="()=>UserModel.Age"></ValidationMessage> </div> <div> <label>Photos</label> <InputFile multiple disabled="@isProcessing" OnChange="OnInputFileChange" /> @if (!string.IsNullOrWhiteSpace(UploadErrorMessage)) { <div> @UploadErrorMessage </div> } @if (SelectedFiles?.Count > 0) { <table> <thead> <tr> <th>Name</th> <th>Size (bytes)</th> <th>Last Modified</th> <th>Type</th> </tr> </thead> <tbody> @foreach (var selectedFile in SelectedFiles) { <tr> <td>@selectedFile.Name</td> <td>@selectedFile.Size</td> <td>@selectedFile.LastModified</td> <td>@selectedFile.ContentType</td> </tr> } </tbody> </table> } </div> <div> <button disabled="@isProcessing">Create user</button> </div> </EditForm>
- UserModel که وهلهی از شیء اشتراکی User است، به EditForm متصل شدهاست.
- سپس توسط یک InputText و InputNumber، مقادیر خواص نام و سن کاربر را دریافت میکنیم.
- InputFile دارای ویژگی multiple هم امکان دریافت چندین فایل را توسط کاربر میسر میکند. پس از انتخاب فایلها، رویداد OnChange آن، توسط متد OnInputFileChange مدیریت خواهد شد:
private void OnInputFileChange(InputFileChangeEventArgs args) { var files = args.GetMultipleFiles(maximumFileCount: 15); if (args.FileCount == 0 || files.Count == 0) { UploadErrorMessage = "Please select a file."; return; } var allowedExtensions = new List<string> { ".jpg", ".png", ".jpeg" }; if(!files.Any(file => allowedExtensions.Contains(Path.GetExtension(file.Name), StringComparer.OrdinalIgnoreCase))) { UploadErrorMessage = "Please select .jpg/.jpeg/.png files only."; return; } SelectedFiles = files; UploadErrorMessage = string.Empty; }
- در ادامه اگر فایلی انتخاب نشده باشد، یا فایل انتخابی، تصویری نباشد، با مقدار دهی UploadErrorMessage، خطایی را به کاربر نمایش میدهیم.
- در پایان این متد، لیست فایلهای دریافتی را به فیلد SelectedFiles انتساب میدهیم تا در ذیل InputFile، به صورت یک جدول نمایش داده شوند.
مرحلهی آخر تکمیل این فرم، تدارک متد رویدادگردان OnValidSubmit فرم برنامه است:
private async Task CreateUserAsync() { try { isProcessing = true; await FilesManagerService.PostModelWithFilesAsync( requestUri: "api/Files/CreateUser", browserFiles: SelectedFiles, fileParameterName: "inputFiles", model: UserModel, modelParameterName: "userModel"); UserModel = new User(); } finally { isProcessing = false; SelectedFiles = null; } }
- سپس روش استفادهی از متد PostModelWithFilesAsync سرویس FilesManagerService را مشاهده میکنید که اطلاعات فایلها و مدل برنامه را به سمت اکشن متد api/Files/CreateUser ارسال میکند.
- در آخر با وهله سازی مجدد UserModel، به صورت خودکار فرم برنامه را پاک کرده و آمادهی دریافت اطلاعات بعدی میکنیم.
تکمیل کنترلر Web API دریافت کنندهی مدل برنامه
در ابتدای بحث، ساختار ابتدایی کنترلر Web API دریافت کنندهی اطلاعات FilesManagerService.PostModelWithFilesAsync فوق را معرفی کردیم. در ادامه کدهای کامل آنرا مشاهده میکنید:
using System.IO; using Microsoft.AspNetCore.Mvc; using BlazorWasmUpload.Shared; using Microsoft.AspNetCore.Hosting; using System.Threading.Tasks; using Microsoft.AspNetCore.Http; using System.Collections.Generic; using Microsoft.Extensions.Logging; using System.Text.Json; using BlazorWasmUpload.Server.Utils; using System.Linq; namespace BlazorWasmUpload.Server.Controllers { [ApiController] [Route("api/[controller]/[action]")] public class FilesController : ControllerBase { private const int MaxBufferSize = 0x10000; private readonly IWebHostEnvironment _webHostEnvironment; private readonly ILogger<FilesController> _logger; public FilesController( IWebHostEnvironment webHostEnvironment, ILogger<FilesController> logger) { _webHostEnvironment = webHostEnvironment; _logger = logger; } [HttpPost] public async Task<IActionResult> CreateUser( //[FromForm] string userModel, // <-- this is the actual form of the posted model [ModelBinder(BinderType = typeof(JsonModelBinder)), FromForm] User userModel, [FromForm] IList<IFormFile> inputFiles = null) { /*var user = JsonSerializer.Deserialize<User>(userModel); _logger.LogInformation($"userModel.Name: {user.Name}"); _logger.LogInformation($"userModel.Age: {user.Age}");*/ _logger.LogInformation($"userModel.Name: {userModel.Name}"); _logger.LogInformation($"userModel.Age: {userModel.Age}"); var uploadsRootFolder = Path.Combine(_webHostEnvironment.WebRootPath, "Files"); if (!Directory.Exists(uploadsRootFolder)) { Directory.CreateDirectory(uploadsRootFolder); } if (inputFiles?.Any() == true) { foreach (var file in inputFiles) { if (file == null || file.Length == 0) { continue; } var filePath = Path.Combine(uploadsRootFolder, file.FileName); using var fileStream = new FileStream(filePath, FileMode.Create, FileAccess.Write, FileShare.None, MaxBufferSize, useAsync: true); await file.CopyToAsync(fileStream); _logger.LogInformation($"Saved file: {filePath}"); } } return Ok(); } } }
[ModelBinder(BinderType = typeof(JsonModelBinder)), FromForm] User userModel,
using System; using System.Text.Json; using System.Threading.Tasks; using Microsoft.AspNetCore.Mvc.ModelBinding; namespace BlazorWasmUpload.Server.Utils { public class JsonModelBinder : IModelBinder { public Task BindModelAsync(ModelBindingContext bindingContext) { if (bindingContext == null) { throw new ArgumentNullException(nameof(bindingContext)); } var valueProviderResult = bindingContext.ValueProvider.GetValue(bindingContext.ModelName); if (valueProviderResult != ValueProviderResult.None) { bindingContext.ModelState.SetModelValue(bindingContext.ModelName, valueProviderResult); var valueAsString = valueProviderResult.FirstValue; var result = JsonSerializer.Deserialize(valueAsString, bindingContext.ModelType); if (result != null) { bindingContext.Result = ModelBindingResult.Success(result); return Task.CompletedTask; } } return Task.CompletedTask; } } }
[HttpPost] public async Task<IActionResult> CreateUser( [FromForm] string userModel, // <-- this is the actual form of the posted model [FromForm] IList<IFormFile> inputFiles = null) { var user = JsonSerializer.Deserialize<User>(userModel);
یک نکته تکمیلی: در Blazor 5x، از نمایش درصد پیشرفت آپلود، پشتیبانی نمیشود؛ از این جهت که HttpClient طراحی شده، در اصل به fetch API استاندارد مرورگر ترجمه میشود و این API استاندارد، هنوز از streaming پشتیبانی نمیکند . حتی ممکن است با کمی جستجو به راهحلهایی که سعی کردهاند بر اساس HttpClient و نوشتن بایت به بایت اطلاعات در آن، درصد پیشرفت آپلود را محاسبه کرده باشند، برسید. این راهحلها تنها کاری را که انجام میدهند، بافر کردن اطلاعات، جهت fetch API و سپس ارسال تمام آن است. به همین جهت درصدی که نمایش داده میشود، درصد بافر شدن اطلاعات در خود مرورگر است (پیش از ارسال آن به سرور) و سپس تحویل آن به fetch API جهت ارسال نهایی به سمت سرور.
کدهای کامل این مطلب را از اینجا میتوانید دریافت کنید: BlazorWasmUpload.zip
مسیرراهها
React 16x
پیش نیاز ها
- قسمت 1 : معرفی و شروع به کار
- قسمت 2 : بررسی پیشنیازهای جاوا اسکریپتی - بخش 1
- قسمت 3 : بررسی پیشنیازهای جاوا اسکریپتی - بخش 2
کامپوننت ها
- قسمت 4 : کامپوننتها - بخش 1 - کار با عبارات JSX
- قسمت 5 : کامپوننتها - بخش 2 - نمایش لیستها و مدیریت رویدادها و حالات
- قسمت 6 : کامپوننتها - بخش 3 - یک تمرین
ترکیب کامپوننت ها
- قسمت 7 : ترکیب کامپوننتها - بخش 1 - ارسال دادهها، مدیریت رخدادها
- قسمت 8 : ترکیب کامپوننتها - بخش 2 - مدیریت state
- قسمت 9 : ترکیب کامپوننتها - بخش 3 - Lifecycle Hooks
- قسمت 10 : ترکیب کامپوننتها - بخش 4 - یک تمرین
طراحی یک گرید
- قسمت 11 : طراحی یک گرید - بخش 1 - کامپوننت صفحه بندی
- قسمت 12 : طراحی یک گرید - بخش 2 - فیلتر کردن اطلاعات
- قسمت 13 : طراحی یک گرید - بخش 3 - مرتب سازی اطلاعات
- قسمت 14: طراحی یک گرید - بخش 4 - پویاسازی تعاریف ستونها
مسیریابی
- قسمت 15 :مسیریابی - بخش 1 - تعریف و تنظیم مسیریابیها
- قسمت 16 : مسیریابی - بخش 2 - پارامترهای مسیریابی
- قسمت 17 : مسیریابی - بخش 3 - یک تمرین
کار با فرم ها
- قسمت 18 : کار با فرمها - بخش 1 - دریافت ورودیها از کاربر
- قسمت 19 : کار با فرمها - بخش 2 - اعتبارسنجی ورودیهای کاربران
- قسمت 20 : کار با فرمها - بخش 3 - بهبود کیفیت کدهای فرم لاگین
- قسمت 21 : کار با فرمها - بخش 4 - چند تمرین
ارتباط با سرور
- قسمت 22 : ارتباط با سرور - بخش 1 - برپایی تنظیمات
- قسمت 23 : ارتباط با سرور - بخش 2 - شروع به کار با Axios
- قسمت 24 : ارتباط با سرور - بخش 3 - نکات تکمیلی کار با Axios
- قسمت 25 : ارتباط با سرور - بخش 4 - یک تمرین
احراز هویت و اعتبارسنجی کاربران
- قسمت 26 : احراز هویت و اعتبارسنجی کاربران - بخش 1 - ثبت نام و ورود به سیستم
- قسمت 27 : احراز هویت و اعتبارسنجی کاربران - بخش 2 - استخراج و نمایش اطلاعات JWT و خروج از سیستم
- قسمت 28 : احراز هویت و اعتبارسنجی کاربران - بخش 3 - فراخوانی منابع محافظت شده و مخفی کردن عناصر صفحه
- قسمت 29 : احراز هویت و اعتبارسنجی کاربران - بخش 4 - محافظت از مسیرها
React Hooks
- قسمت 30 : React Hooks - بخش 1 - معرفی useState و useEffect
- قسمت 31 : React Hooks - بخش 2 - مقایسه حالتهای مختلف مدیریت حالت با useState Hook
- قسمت 32 : React Hooks - بخش 3 - نکات ویژهی برقراری ارتباط با سرور
- قسمت 33 : React Hooks - بخش 4 - useContext Hook
توزیع برنامه
مدیریت پیشرفتهی حالت در React با Redux و Mobx
Redux
Redux
- قسمت اول : Redux چیست؟
- قسمت دوم : بررسی توابع Redux
- قسمت سوم : روش اتصال Redux به برنامههای React
- قسمت چهارم : انجام اعمال async با Redux
- قسمت پنجم : Redux Hooks
MobX
- قسمت ششم : MobX چیست؟
- قسمت هفتم : بررسی مفاهیم Mobx
- قسمت هشتم : تنظیمات پروژههای React برای کار با Mobx decorators
- قسمت نهم : مثالی از کتابخانهی mobx-react
- قسمت دهم : MobX Hooks و اعمال Async در Mobx
مطالب تکمیلی
- ساخت یک مثال Todo با MobX و React
- روش یکی کردن پروژههای React و ASP.NET Core
- آپلود فایلها توسط برنامههای React به یک سرور ASP.NET Core به همراه نمایش درصد پیشرفت
- روش کار با فایلهای ایستا در برنامههای React
- روش کار با فایلهای پویای ارائه شدهی توسط یک برنامهی ASP.NET Core در برنامههای React
- استفاده از قالب مخصوص Redux Toolkit جهت ایجاد پروژههای React/Redux
- تبدیل یک قالب HTML معمولی به قالب React
- React component lifecycle
- React reconciliation
اشتراکها