‫۳ سال و ۳ ماه قبل، شنبه ۲۵ اردیبهشت ۱۴۰۰، ساعت ۱۶:۴۱
روش دوم پیاده سازی این مثال: ارسال یک کامپوننت محصور کننده‌ی سراسری، به صورت پارامتر آبشاری، به تمام زیر کامپوننت‌ها

با استفاده از پارامترهای آبشاری می‌توان شیءای را در اختیار تمام کامپوننت‌های قرار گرفته شده‌ی در سلسله مراتب آن‌ها قرار داد. برای مثال اگر در فایل Client\Shared\MainLayout.razor، جائیکه سایر کامپوننت‌ها قرار است رندر شوند را توسط یک کامپوننت سطح بالا محصور کنیم:
<Alert>
  @Body
</Alert>
در این حالت هر کامپوننتی که بجای Body درج شود، می‌تواند به پارامترهای آبشاری Alert دسترسی پیدا کند؛ یعنی تمام کامپوننت‌های نمایشی برنامه. یا حتی می‌توان این Alert را در فایل Client\App.razor نیز در بالاترین سطح قرار داد و کل Router را توسط آن محصور کرد.
بنابراین طراحی ساده‌ی کامپوننت Alert ای (Client\Shared\Alert.razor) که تامین کننده‌ی یک پارامتر آبشاری سراسری است، به صورت زیر می‌تواند باشد:
<CascadingValue Value=this>
    @if(IsVisible)
    {
        <div class="alert @Css" role="alert">
            @Message
            <button type="button" class="close" data-dismiss="alert" aria-label="Close" @onclick="HideAlert">
                <span aria-hidden="true">&times;</span>
            </button>
        </div>
    }
    @ChildContent
</CascadingValue>

@code {
    [Parameter]
    public RenderFragment ChildContent { get; set; }

    private bool IsVisible;
    private string Message;
    private string Css = "alert-primary";

    public void ShowAlert(string message, AlertType alertType)
    {
        IsVisible = true;
        Message = message;

        Css = alertType switch
        {
            AlertType.Success => "alert-success",
            AlertType.Info => "alert-primary",
            AlertType.Danger => "alert-danger",
            AlertType.Warning => "alert-warning",
            _ => "alert-primary"
        };

        StateHasChanged();
    }

    public void HideAlert()
    {
        IsVisible = false;
    }
}
که البته در همان پوشه به همراه فایل Client\Shared\AlertType.cs نیز هست:
namespace BlazorWasmAlert.Client.Shared
{
    public enum AlertType
    {
        Success,
        Info,
        Danger,
        Warning
    }
}
در کامپوننت ویژه‌ی Alert، دو قابلیت استاندارد Blazor بکار گرفته شده‌اند:
الف) وجود یک CascadingValue که اینبار Value آن به خود کامپوننت اشاره می‌کند (Value=this). یعنی پارامتر آبشاری که در اختیار سایر کامپوننت‌های محصور شده‌ی توسط آن ارسال می‌شود، دقیقا وهله‌ای از کامپوننت Alert است که توسط آن می‌توان برای مثال، متد عمومی ShowAlert آن‌را فراخوانی کرد:
<CascadingValue Value=this>
ب) چون کامپوننت Alert قرار است کامپوننت‌هایی را که بجای body@ درج می‌شوند را نمایش دهد، اینکار را توسط یک RenderFragment انجام داده‌است.


پس از درج این کامپوننت در فایل layout، روش استفاده‌ی از آن برای مثال در کامپوننت Index به صورت زیر است:
@page "/"

<h1>Hello, world!</h1>

<button class="btn btn-primary" @onclick="ShowAlert">Show Alert!</button>

@code
{
    [CascadingParameter]
    public Alert Alert { get; set; }

    private void ShowAlert()
    {
        Alert.ShowAlert("This is a test!", AlertType.Info);
    }
}
در اینجا پارامتر آبشاری که دریافت می‌شود، دقیقا به کلاس و شیء Alert (وهله‌ای از کامپوننت Alert) اشاره می‌کند. به همین جهت می‌توان متد عمومی ShowAlert آن‌را در اینجا فراخوانی کرد.


پ.ن.
در طراحی Blazor، از طراحی React الهام گرفته شده‌است و CascadingValue آن دقیقا معادل Context API جدید React است.
‫۳ سال و ۳ ماه قبل، شنبه ۲۵ اردیبهشت ۱۴۰۰، ساعت ۱۳:۵۹
یک نکته‌ی تکمیلی
اگر در سمت کلاینت قرار است با متد HttpClient.GetByteArrayAsync کار شود، نیازی نیست در سمت سرور خروجی اکشن متد را توسط File.ReadAllBytesAsync تامین کرد. استفاده از متد return File همان کار را انجام می‌دهد و بسیار بهینه‌تر عمل می‌کند.
using Microsoft.AspNetCore.Mvc;

namespace BlazorWasmShowBinaryFiles.Server.Controllers
{
    [ApiController]
    [Route("api/[controller]")]
    public class ReportsController : ControllerBase
    {
        [HttpGet("[action]")]
        public IActionResult GetReportFile()
        {
            return File(virtualPath: "~/app_data/Report.mrt",
                        contentType: "application/octet-stream",
                        fileDownloadName: "Report.mrt");
        }
    }
}
‫۳ سال و ۳ ماه قبل، چهارشنبه ۲۲ اردیبهشت ۱۴۰۰، ساعت ۱۸:۰۳
خلاصه‌ی سریع خواص و متدهای NavigationManager 
NavigationManager.Uri
#> https://localhost:5001/counter/3?q=hi

NavigationManager.BaseUri
#> https://localhost:5001/

NavigationManager.NavigateTo("http://new location")
#> Navigates to new location

NavigationManager.LocationChanged
#> An event that fires when the navigation location has changed.

NavigationManager.ToAbsoluteUri("test")
#> https://localhost:5001/test

NavigationManager.ToBaseRelativePath(MyNavigationManager.Uri)
#> counter/3?q=hi
‫۳ سال و ۳ ماه قبل، دوشنبه ۲۰ اردیبهشت ۱۴۰۰، ساعت ۱۵:۵۵
یک نکته‌ی تکمیلی: امکان اعتبارسنجی دستی فرم‌ها در Blazor

در این مطلب با روش معرفی EditForm و خاصیت Model آن آشنا شدیم که کار اعتبارسنجی را به صورت خودکار مدیریت می‌کند. اگر خواستیم کنترل بیشتری را بر روی این فرآیند داشته باشیم، می‌توان عملیات اعتبارسنجی را دستی کرد:
@implements IDisposable

<EditForm EditContext="@_editContext" OnValidSubmit="submit">


    <button type="submit" disabled="@_isInvalidForm">Submit</button>
</EditForm>

@code
{
    private User _userModel = new User();
    private EditContext _editContext;
    private bool _isInvalidForm = true;


    protected override void OnInitialized()
    {
        _editContext = new EditContext(_userModel);
        _editContext.OnFieldChanged += HandleFieldChanged;
    }

    private void submit()
    {
        if(_editContext.Validate())
        {
           
        }
    }

    private void HandleFieldChanged(object sender, FieldChangedEventArgs e)
    {
        _isInvalidForm = !_editContext.Validate();
        StateHasChanged();
    }

    public void Dispose()
    {
        _editContext.OnFieldChanged -= HandleFieldChanged;
    }
}
اینبار در اینجا بجای استفاده از خاصیت Model، از خاصیت جدید EditContext استفاده می‌شود (تنها یکی از این دو را می‌توان ذکر کرد). روش مقدار دهی EditContext را در روال آغازین کامپوننت مشاهده می‌کنید که وهله‌ای از مدل را دریافت کرده و تحت بررسی قرار می‌دهد. EditContext یک پارامتر آبشاری است و به صورت خودکار در اختیار تمام کنترل‌ها و کامپوننت‌های محصور شده‌ی توسط EditForm قرار می‌گیرد.
نمونه‌ای از روش کار با آن‌را در متد submit مشاهده می‌کنید که باید به همراه فراخوانی متد Validate آن باشد و یا می‌توان به صورت زیر در مورد یک فیلد عمل کرد:
var isValid = !_editContext.GetValidationMessages(fieldIdentifier).Any();
و یا حتی می‌توان با استفاده از رخ‌داد OnFieldChanged، برای مثال بررسی کرد که آیا کل فرم معتبر هست یا خیر؟ و اگر خیر، برای مثال دکمه‌ی submit را غیرفعال کرد. در این حالت همواره بهتر است که پاکسازی رویداد OnFieldChanged را در پایان کار انجام داد، تا برنامه دچار نشتی حافظه نشود.
یک نکته‌ی تکمیلی: نمایش اطلاعاتی خاص تنها در حالت توسعه‌ی برنامه
با استفاده از سرویس توکار IWebHostEnvironment و متد ()IsDevelopment آن، می‌توان تشخیص داد که اکنون برنامه در حالت توسعه اجرا می‌شود یا توزیع. بر این اساس اگر کامپوننت DevOnly.razor را به کمک یک RenderFragment تشکیل دهیم، می‌توان if بررسی کردن این متد را تبدیل به یک کامپوننت با استفاده‌ی مجدد کرد:
@using Microsoft.Extensions.Hosting

@inject Microsoft.AspNetCore.Hosting.IWebHostEnvironment env

@if (isDevelopment)
{
    @ChildContent
}

@code {
    [Parameter] public RenderFragment ChildContent { get; set; }

    bool isDevelopment = false;

    protected override void OnInitialized()
    {
        isDevelopment = env.IsDevelopment();
    }
}
یک مثال:
<DevOnly>
    <p>Show some debugging info here!</p>
</DevOnly>
یک نکته‌ی تکمیلی: جایگزین کردن if/elseهای بررسی نال بودن با یک کامپوننت
در کدهای Razor، داشتن چنین قطعه کدی بسیار مرسوم هست:
@if(someValue is null) 
{
  <p>Loading..</p>
} 
else 
{
 // show actual content
}
@code {
  SomeClass someValue;
}
در ابتدای دریافت اطلاعات از سرویسی، مقداری که قرار است رندر شود، نال است و پس از پایان کار بارگذاری اطلاعات، قسمت else اجرا خواهد شد. می‌توان این if/else را توسط یک RenderFragment، تبدیل به کامپوننت Loading.razor با قابلیت استفاده‌ی مجدد کرد:
/* shows a loading gif/text if a value is null */
@if (Value == null)
{
    <div><img src="/img/SmallLoader.gif" alt="loading" /> Loading</div>
}
else
{
    @ChildContent
}
@code {

    [Parameter] public RenderFragment ChildContent { get; set; }

    [Parameter] public object Value { get; set; }
}
در این حالت اگر مقدار در حال بررسی نال بود، یک متن یا تصویر منتظر بمانید نمایش داده می‌شود و اگر خیر، استفاده کننده‌ی از این کامپوننت می‌تواند محتوای بدنه else را تامین کند؛ یک مثال از نحوه‌ی استفاده از کامپوننت فوق و جایگزین کردن if/else نوشته شده‌ی در ابتدای مطلب:
<Loading Value="someValue">
  <p>Content goes here</p>
</Loading>
‫۳ سال و ۳ ماه قبل، دوشنبه ۲۰ اردیبهشت ۱۴۰۰، ساعت ۰۵:۲۳
IHttpContextAccessor (با فرض ثبت سرویس آن) فقط در طول یک درخواست قابل استفاده و دسترسی است. ممکن است در ابتدای شروع برنامه که کار ساخت بانک اطلاعاتی یا اعمال Migration صورت می‌گیرد، این کدها را فراخوانی کنید. در آن لحظه HttpContext ای در دسترس نیست؛ چون هنوز کار به راه اندازی کنترلرها و رسیدن درخواستی نرسیده‌است (در حالت کلی در زمان Startup، اعمال صورت گرفته، خارج از HttpContext است). بنابراین در ابتدای برنامه، نال بودن آن‌را بررسی کنید. اگر نال بود، از Configuration برای شروع کار استفاده کنید. در مابقی حالات چون در طول درخواست‌ها استفاده می‌شود، مشکلی نخواهد داشت.
namespace TestBackend
{
  public class Startup
  {
    // ...

    public void ConfigureServices(IServiceCollection services)
    {
      // ...

      services.AddHttpContextAccessor();
      // + services.AddSession() && app.UseSession()
      services.AddDbContext<TestContext>((serviceProvider, options) =>
      {        
        options.UseSqlServer(GetConnectionString(serviceProvider));
      });

      // ...
    }

     // ...

    private string GetConnectionString(IServiceProvider serviceProvider)
    {
      var connectionStringTemplate = Configuration.GetConnectionString("ConnectionTemplate");

      try
      {
         var httpContextAccessor = serviceProvider.GetRequiredService<IHttpContextAccessor>(); // This needs services.AddHttpContextAccessor();
         var dbName = httpContextAccessor.HttpContext.Session.GetString("databasename"); // This needs services.AddSession(); && app.UseSession();
         return connectionStringTemplate.Replace("{db_Name}", dbName);
      }
      catch(Exception ex) 
      {
           var logger = serviceProvider.GetRequiredService<ILoggerFactory>().CreateLogger(nameof(Startup));
           logger.LogError("GetConnectionString error", ex, "Failed to get connection string.");
      }

      return connectionStringTemplate.Replace("{db_Name}", "---Default-DB-Name-Here---");
    }
  }
}
IdentityServer از زمان ارائه‌ی نگارش 5 آن دیگر رایگان نیست و پیشتر مایکروسافت از نگارش 4 آن در قالب‌های استاندارد پروژه‌های Blazor استفاده کرده بود. نگارش قبلی آن تنها در پروژه‌های NET 5x. پشتیبانی خواهد شد. نگارش 5 آن در پروژه‌های NET 6x. به همراه ذکر دقیق مجوز آن هنوز هم حضور خواهد داشت. از نگارش 7 دات نت، فکر دیگری خواهند کرد.