‫۹ ماه قبل، چهارشنبه ۸ آذر ۱۴۰۲، ساعت ۱۱:۱۵
یک نکته‌ی تکمیلی: backing fields تولید شده‌ی در حالت سازنده‌های اولیه، readonly نیستند.

اگر به کدهای #Low-Level C مطلب فوق دقت کنید، به یک چنین تعاریفی می‌رسیم:
private string <FirstName>k__BackingField;
یعنی فیلدهای خصوصی متناظر با پارامترهای سازنده‌ی تعریف شده‌ی توسط کامپایلر در C# 12.0 از نوع readonly نیستند. بنابراین باید دقت داشت که این فیلدها قابل تغییر هستند و برخلاف رویه‌ی متداول تعریف فیلدهای متناظر با پارامترهای تزریق وابستگی‌ها (مانند مثال زیر)، readonly تعریف نشده‌اند:
public class AbcController : Controller
{
   private readonly IMyService _myService;

   public AbcController(IMyService myService) {
     _myService = myService;
   }
}
یعنی در حالت استفاده‌ی از سازنده‌های اولیه‌ی C# 12 می‌توان این فیلدها را تغییر داد که باید به این مورد دقت کافی را داشت. برای نمونه اگر مانند مثال زیر این فیلد را نال کنیم و تغییر دهیم، ممکن است در سایر قسمت‌های استفاده کننده‌ی از آن مشکل درست شود:
public class MyController(IMyService _myService) : ControllerBase
{
   [HttpGet]
   public IActionResult RandomNumber()
   {
     var number = _myService.RandomNumber();

     _myService = null;

     var number2 = MyRandom();
     return Ok(number + number2);            
   }

   public int MyRandom()
   {
     return _myService.RandomNumber();
   }
}
در این مثال ابتدا از myService_ استفاده شده، سپس نال شده (و با خطایی هم مواجه نمی‌شویم چون readonly نیست) و مجددا از آن استفاده شده که به علت نال بودن، دیگر چنین چیزی میسر نیست.
‫۹ ماه قبل، سه‌شنبه ۷ آذر ۱۴۰۲، ساعت ۱۷:۰۶
یک نکته‌ی تکمیلی: تکامل lambda expressions در C# 12 با امکان تعریف مقدار پیش‌فرض پارامترها

در C# 12 می‌توان برای پارامترهای lambda expressions نیز مقدار پیش‌فرض تعریف کرد و از این لحاظ با مابقی قسمت‌ها و ویژگی‌های فعلی زبان، هماهنگی کاملی دارد:
var lambdaWithDefaultParam = (int val = 10) => val + 1;
Console.WriteLine(lambdaWithDefaultParam() == 11);
Console.WriteLine(lambdaWithDefaultParam(4) == 5);
در این مثال در حین فراخوانی lambda، زمانیکه پارامتری مشخص نشده‌است، از همان مقدار پیش‌فرض استفاده می‌کند.

همچنین در اینجا اگر به هر دلیلی نیاز به دسترسی مقدار پیش‌فرض را داشته باشید، روش کار به صورت زیر است:
Console.WriteLine(lambdaWithDefaultParam.Method.GetParameters()[0].DefaultValue);

یک نکته: دلیل اصلی اضافه کردن یک چنین قابلیتی، ساده سازی تعاریف Minimal API's است تا بتوان مقادیر پیش‌فرضی را برای پارامترهای درخواست رسیده، تعریف کرد:
app.MagGet("/items", (int? limit, int offset = 0) =>{
   // paginated query for items
});
‫۹ ماه قبل، سه‌شنبه ۷ آذر ۱۴۰۲، ساعت ۱۵:۰۴
یک نکته‌ی تکمیلی: تکامل اپراتور nameof در C# 12.0

همانطور که در این مطلب مشاهده کردید، اپراتور nameof، روشی بسیار مفید جهت دسترسی به نام متغیرها، نوع‌ها و یا اعضای یک کلاس است. در C# 12، این ویژگی اندکی بهبود یافته‌است و امکان دسترسی به اطلاعات اعضای یک کلاس را هم دارد:
public class NameofClass
{
    public string SomeProperty { get; set; }

    // Now legal with C# 12
    // would show "Length" on the console
    public const string NameOfSomePropertyLength = nameof(SomeProperty.Length); 
    
    public static int StaticField;
    public const string NameOfStaticFieldMinValue  = nameof(StaticField.MinValue);

    [Description($"String {nameof(SomeProperty.Length)}")]
    public int StringLength(string s)
    {
        return s.Length;
    }
}
در این مثال، اگر سعی کنیم مقدار NameOfSomePropertyLength را در کنسول نمایش دهیم، عبارت Length ظاهر خواهد شد. تا پیش از C# 12 برای دسترسی به یک چنین قابلیتی نیاز به نمونه سازی و تولید شیءای از کلاس NameofClass فوق وجود داشت تا بتوان اپراتور nameof را به خواص آن اعمال کرد. این محدودیت در C# 12 برطرف شده‌است.

همچنین همانطور که مشاهده می‌کنید، امکان دسترسی به اطلاعات فیلدهای استاتیک و یا بکارگیری این قابلیت در Attributes هم میسر شده‌است.
‫۹ ماه قبل، دوشنبه ۶ آذر ۱۴۰۲، ساعت ۱۶:۰۵
در یکی از پروژه هایی که جدیدا باهاش درگیر شده ام دقیقا این مطلب رعایت نشده است. با ASP.NET WebForm نوشته شده و در یک صفحه هم از سشن به کرات استفاده شده برای مقاصد لاگین و متغیرهای سراسری و همچنین از چند ترد برای دریافت موازی اطلاعات از چند وب سرویس.
اما برای دفاع، منبع خوبی پیدا نمی‌کنم برای ارایه به کارفرما. فرومی که لینک دادید هم داخلش یک لینک هست برای mvc اما صفحه ارور ۴۰۳ میده. آیا سند معتبری هم هست برای ارایه به افراد ثالث؟
‫۹ ماه قبل، یکشنبه ۵ آذر ۱۴۰۲، ساعت ۲۱:۴۶
نکته تکمیلی: Collection literalها اصطلاحاً target-typed هستند مثلاً در شرایطی که نیاز باشد از Ternary Operator درون [] استفاده شود نیازی به Cast کردن آن نیست و خود کامپایلر آن را تشخیص خواهد داد:

ارتقاء به دات نت 8
اگر این پروژه رو به دات نت 8 ارتقاء بدیم در کلاس TokenValidatorService دیگر نمیتوان با استفاده از accessToken.RawData توکن را خواند که آن را در پایگاه داده مورد بررسی قرار بدیم.
if (!(context.SecurityToken is JwtSecurityToken accessToken) ||
    string.IsNullOrWhiteSpace(accessToken.RawData) ||
    !await _tokenStoreService.IsValidTokenAsync(accessToken.RawData, userId))
{
    context.Fail("This token is not in our database.");
    return;
}
علت اینکار این است که در دات نت 8 کلاس JsonWebToken جایگزین JwtSecurityToken شده است ^ و همچنین برای گرفتن توکن باید از متد UnsafeToString استفاده کرد.
if (!(context.SecurityToken is JsonWebToken accessToken) ||
    string.IsNullOrWhiteSpace(accessToken.UnsafeToString()) ||
    !await tokenStoreService.IsValidTokenAsync(accessToken.UnsafeToString(), userId))
{
    context.Fail("This token is not in our database.");
    return;
}
علت این جایگزینی بهبود عملکرد تا 30 درصد و پردازش Async در کلاس JsonWebToken میباشد.
نحوه نمایش مقدار Display attribute پراپرتی‌ها در تگ‌های Label با استفاده از کامپوننت‌های جنریک

در ASP.NET Core اگه بخوایم Display یک پراپرتی رو نمایش بدیم به این صورت عمل میکنیم:
@model ProjectName.ViewModels.Identity.RegisterViewModel
<label asp-for="PhoneNumber"></label>
در حال حاضر چنین قابلیتی به صورت توکار در Blazor وجود ندارد، برای اینکه این قابلیت رو با استفاده از کامپوننت‌ها پیاده سازی کنیم میتوان از یک کامپوننت جنریک استفاده کرد (اطلاعات بیشتر در مورد کامپوننت‌های جنریک):
@using System.Reflection
@using System.Linq.Expressions
@using System.ComponentModel.DataAnnotations
@typeparam T
@if (ChildContent is null)
{
    <label>@Label</label>
}
else
{
    <label>
        @Label
        @ChildContent
    </label>
}
@code {

    [Parameter, EditorRequired]
    public Expression<Func<T>> DisplayNameFor { get; set; } = default!;

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

    private string Label => GetDisplayName();

    private string GetDisplayName()
    {
        var expression = (MemberExpression)DisplayNameFor.Body;
        var value = expression.Member.GetCustomAttribute(typeof(DisplayAttribute)) as DisplayAttribute;
        return value?.Name ?? expression.Member.Name;
    }
}

نحوه استفاده:
private LoginDto Login { get; } = new();
<CustomDisplayName DisplayNameFor="@(() => Login.UserName)" />
همچنین میتوان ChildContent رو که یک RenderFragment است مقداری دهی کرد که در کنار Display، مقدار ChildContent رو هم نمایش بده.
<CustomDisplayName DisplayNameFor="@(() => Login.UserName)">
    <span class="text-danger">(*)</span>
</CustomDisplayName>

نحوه استفاده در پروژه‌های چند زبانه
کامپوننت فوق برای استفاده در پروژه چند زبانه باید به صورت زیر تغییر پیدا کنه:
@using System.Reflection
@using System.Linq.Expressions;
@using System.ComponentModel.DataAnnotations;
@typeparam T
@if (ChildContent == null)
{
    <label>@Label</label>
}
else
{
    <label>
        @Label
        @ChildContent
    </label>
}
@code {

    [Parameter]
    public Expression<Func<T>> DisplayNameFor { get; set; } = default!;

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

    [Inject]
    public IStringLocalizerFactory LocalizerFactory { get; set; } = default!;

    private string Label => GetDisplayName();

    private string GetDisplayName()
    {
        var expression = (MemberExpression)DisplayNameFor.Body;
        var displayAttribute = expression.Member.GetCustomAttribute(typeof(DisplayAttribute)) as DisplayAttribute;
        if (displayAttribute is {ResourceType: not null })
        {
            // Try to dynamically create an instance of the specified resource type
            var resourceType = displayAttribute.ResourceType;
            var localizer = LocalizerFactory.Create(resourceType);

            return localizer[displayAttribute.Name ?? expression.Member.Name];
        }
        return displayAttribute?.Name ?? expression.Member.Name;
    }
}
برای اتریبیوت Display هم باید مقدار ResourceType مقدار دهی شود:
public class LoginDto
{
    [Display(Name = nameof(LoginDtoResource.UserName), ResourceType = typeof(LoginDtoResource))]
    [Required]
    [MaxLength(100)]
    public string UserName { get; set; } = default!;

    //...
}

LoginDtoResource یک فایل Resource است که باید با باز کردنش در ویژوال استودیو، Access modifier اونو به public تغییر بدید.
نحوه افزودن کلاس به Label ( ساده سازی تعاریف ویژگی‌های المان‌ها )
افزودن یک دیکشنری به کامپوننت:
[Parameter(CaptureUnmatchedValues = true)]
public Dictionary<string, object> InputAttributes { get; set; } = new();
استفاده از InputAttributes در تگ Label:
@if (ChildContent is null)
{
    <label @attributes="InputAttributes">
        @Label
    </label>
}
else
{
    <label @attributes="InputAttributes">
        @Label
        @ChildContent
    </label>
}
الان میتونیم هر تعداد اتریبیوتی رو به کامپوننت پاس بدیم:
<CustomDisplayName DisplayNameFor="@(() => Login.UserName)" class="form-label" id="test" for="UserName" />
یک نکته‌ی تکمیلی: روش استفاده از کتابخانه‌ها و کامپوننت‌های ثالث با Blazor 8x

همانطور که در این مطلب هم اشاره شد، حالت پیش‌فرض رندر در برنامه‌های Blazor 8x، فقط SSR است. بنابراین قسمت‌های تعاملی تمام کامپوننت‌ها (ثالث یا غیر ثالث) در این حالت کار نمی‌کنند؛ مگر اینکه:
- یکی از حالت‌های رندر تعاملی را در بالاترین سطح ممکن فعال کنید (اضافه کردن صریح rendermode@ در فایل App.razor به کامپوننت‌های HeadOutlet و Routes) تا تمام صفحات و کامپوننت‌های برنامه از آن ارث‌بری کنند.
- یا rendermode@ را در حین تعریف المان کامپوننت، صراحتا ذکر کنید (حالت تعریف رندر جزیره‌ای).
- یا rendermode@ را در حین تعریف صفحه‌ی جاری ذکر کنید تا تمام کامپوننت‌های واقع در آن صفحه، از آن ارث‌بری کنند.