‫۲ سال و ۷ ماه قبل، شنبه ۹ بهمن ۱۴۰۰، ساعت ۱۷:۵۳
خواص IOptionsSnapshot<JwtBearerOptions> jwtBearerOptions تزریقی نال هستند. به صورت مستقیمی این مقادیر را از تنظیمات برنامه تامین کنید. در نهایت کدهای قبلی به صورت زیر تغییر می‌کنند:
public class TokenValidatorService : ITokenValidatorService
{
    private readonly BearerTokensOptionsDto _configuration;

    public TokenValidatorService(IOptionsSnapshot<SiteSettingsDto> configuration)
    {
        if (configuration == null)
        {
            throw new ArgumentNullException(nameof(configuration));
        }

        _configuration = configuration.Value?.BearerTokens ?? throw new ArgumentNullException(nameof(configuration));
    }

    public async Task<bool> IsValidJwtAsync(string token)
    {
        var tokenHandler = new JwtSecurityTokenHandler();
        try
        {
            var claimsPrincipal = tokenHandler.ValidateToken(token, new TokenValidationParameters
            {
                ValidIssuer = _configuration.Issuer, // site that makes the token
                ValidateIssuer = true,
                ValidAudience = _configuration.Audience, // site that consumes the token
                ValidateAudience = true,
                IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_configuration.Key)),
                ValidateIssuerSigningKey = true, // verify signature to avoid tampering
                ValidateLifetime = true, // validate the expiration
                ClockSkew = TimeSpan.Zero // tolerance for the expiration date
            }, out var securityToken);

            //var (success, _, _) = await IsValidClaimsPrincipalAsync(claimsPrincipal, securityToken);
            //return success;
            return true;
        }
        catch
        {
            return false;
        }
    }
}
‫۲ سال و ۷ ماه قبل، شنبه ۹ بهمن ۱۴۰۰، ساعت ۱۶:۵۵
یک نکته‌ی تکمیلی: عدم نیاز به بازنویسی متد «SaveChanges» از EF-Core 5x به بعد

در این مطلب جهت اعمال یکسری خواص Audit، متد SaveChanges بازنویسی شد. از EF-Core 5x به بعد می‌توان با استفاده از interceptors اینکار را به صورت جداگانه‌ای و خارج از DbContext برنامه انجام داد که قالب کلی آن چنین شکلی را پیدا می‌کند:
public class AuditableEntitiesInterceptor : SaveChangesInterceptor
{
    private readonly IHttpContextAccessor _httpContextAccessor;
    private readonly ILogger<AuditableEntitiesInterceptor> _logger;

    public AuditableEntitiesInterceptor(
        IHttpContextAccessor httpContextAccessor,
        ILogger<AuditableEntitiesInterceptor> logger)
    {
        _httpContextAccessor = httpContextAccessor ?? throw new ArgumentNullException(nameof(httpContextAccessor));
        _logger = logger ?? throw new ArgumentNullException(nameof(logger));
    }

    public override InterceptionResult<int> SavingChanges(
        DbContextEventData eventData,
        InterceptionResult<int> result)
    {
        if (eventData == null)
        {
            throw new ArgumentNullException(nameof(eventData));
        }

        BeforeSaveTriggers(eventData.Context);
        return result;
    }

    public override ValueTask<InterceptionResult<int>> SavingChangesAsync(
        DbContextEventData eventData,
        InterceptionResult<int> result,
        CancellationToken cancellationToken = default)
    {
        if (eventData == null)
        {
            throw new ArgumentNullException(nameof(eventData));
        }

        BeforeSaveTriggers(eventData.Context);
        return ValueTask.FromResult(result);
    }

    private void BeforeSaveTriggers(DbContext? context)
    {
        // ValidateEntities(context);
        // ApplyAudits(context?.ChangeTracker);
    }
}
و روش ثبت آن به صورت زیر است:
چون این interceptor از تزریق وابستگی‌ها استفاده می‌کند، ابتدا نیاز است آن‌را به صورت یک سرویس ثبت کرد:
services.AddSingleton<AuditableEntitiesInterceptor>();
سپس آن‌را از سیستم تزریق وابستگی‌ها دریافت و به Context معرفی نمود:
services.AddDbContextPool<ApplicationDbContext>((serviceProvider, optionsBuilder) =>
                    optionsBuilder
                        .UseSqlServer(connectionString)
                        .AddInterceptors(serviceProvider.GetRequiredService<AuditableEntitiesInterceptor>()));
‫۲ سال و ۷ ماه قبل، دوشنبه ۴ بهمن ۱۴۰۰، ساعت ۱۵:۴۱
یک نکته‌ی تکمیلی: اضافه شدن قابلیت مشابهی جهت «بالا بردن سرعت بارگذاری اولیه EF-Core 6x با تعداد مدل‌های زیاد»

اگر تعداد مدل‌های زیادی را دارید و بارگذاری اولیه‌ی برنامه‌ی مبتنی بر EF-Core 6x شما کند است، می‌توانید با استفاده از قابلیت جدید Compiled models آن، این سرعت را حداقل 8 برابر افزایش دهید. روش انجام آن نیز ساده‌است:
public class ExampleContext : DbContext
{
    public DbSet<Person> People { get; set; }
protected override void OnConfiguring(DbContextOptionsBuilder options)
    { 
        options.UseModel(CompiledModelsExample.ExampleContextModel.Instance);
        options.UseSqlServer("Server=(localdb)\\mssqllocaldb;Database=Test;Trusted_Connection=True;"); 
    }
}
به همراه اجرای یکی از دو دستور زیر:
CLI:
dotnet ef dbcontext optimize -c ExampleContext -o CompliledModels -n CompiledModelsExample
Package Manager Console:
Optimize-DbContext -Context ExampleContext -OutputDir CompiledModels -Namespace CompiledModelsExample

بنابراین به Context و متد OnConfiguring آن چنین سطری باید اضافه شود:
options.UseModel(CompiledModelsExample.ExampleContextModel.Instance);
که CompiledModelsExample آن همان نام فضای نام دلخواهی است که به دستور dotnet ef ارسال کرده‌ایم. مابقی آن نام Context به همراه Model است. البته اگر این سطر را هم اضافه نکنید، دستور dotnet ef فوق، آن‌را گوشزد خواهد کرد.

محدودیت‌های این روش در نگارش 6x:
- از فیلترهای سراسری (global query filters) پشتیبانی نمی‌کند.
- از تشکیل Lazy loading proxies پشتیبانی نمی‌کند. 
- از تشکیل Change tracking proxies پشتیبانی نمی‌کند. 
همچنین هربار که تغییری را در مدل‌ها ایجاد کردید، دستورات فوق را باید به صورت دستی مجددا اجرا کنید.
‫۲ سال و ۷ ماه قبل، شنبه ۲ بهمن ۱۴۰۰، ساعت ۱۸:۲۲
یک نکته: ساده شدن تعریف IEntityTypeConfiguration در EF-Core 6x
در EF-Core 6x دیگر نیازی به سطر builder.ApplyConfiguration در متد OnModelCreating نیست و می‌توان تعریف IEntityTypeConfiguration متناظری را در بالای همان موجودیت ذکر کرد:
[EntityTypeConfiguration(typeof(CustomerConfiguration))]
public class Customer
{
}
‫۲ سال و ۷ ماه قبل، شنبه ۲ بهمن ۱۴۰۰، ساعت ۱۷:۰۲
یک نکته‌ی تکمیلی: ساده شدن روش کار با الگوریتم‌های هش از دات نت 5 به بعد

همانطور که در این مطلب مشاهده کردید، برای محاسبه‌ی هش SHA256، روش توصیه شده به صورت زیر درآمده‌است:
using System.Security.Cryptography;
byte[] data = default; // Some data
using (SHA256 hash = SHA256.Create())
{
    byte[] digest = hash.ComputeHash(data);
}
این مورد در دات نت 5 به صورت زیر خلاصه شده‌است:
using System.Security.Cryptography;
byte[] data = default; // Some data
byte[] digest = SHA256.HashData(data);
مزیت این روش نه فقط ساده شدن آن و یا حذف نیاز به using است، بلکه کاهش تعداد تخصیص‌های حافظه‌ی آن نیز هست. یک چنین API ای در دات نت 6 برای HMAC نیز تهیه شده‌است. همچنین برای نمونه الگوریتم هش کردن PBKDF2 در دات نت 6 به صورت زیر ساده شده‌است:
using System.Security.Cryptography;
byte[] salt = RandomNumberGenerator.GetBytes(32);
byte[] prk = Rfc2898DeriveBytes.Pbkdf2(
    userPassword,
    salt,
    iterations: 200_000,
    HashAlgorithmName.SHA256,
    outputLength: 32);
‫۲ سال و ۷ ماه قبل، چهارشنبه ۲۹ دی ۱۴۰۰، ساعت ۱۶:۴۳
یک نکته‌ی تکمیلی: ویژگی Unicode در EF-Core 6x
در EF-Core 6x، اگر می‌خواهید نوع ستون رشته‌ای را غیریونیکد، مانند varchar‌ها تعیین کنید، می‌توان از ویژگی جدید Unicode برای انجام اینکار استفاده کرد:
public class Book
{
    public int Id { get; set; }

    public string Title { get; set; }

    [Unicode(false)]
    [MaxLength(22)]
    public string Isbn { get; set; }
}
در این حالت Title به nvarchar(max) ترجمه شده (چون حداکثر طولی برای آن مشخص نشده، طول آن max در نظر گرفته می‌شود) و Isbn به varchar(22)؛ چون اینبار حداکثر طول آن 22 است و همچنین یونیکد هم تعریف نشده‌است.
مزیت اینکار، ترجمه‌ی غیروابسته‌ی به بانک اطلاعاتی، توسط EF-Core است. یعنی بسته به بانک‌های اطلاعاتی مختلف، این ترجمه متفاوت خواهد بود (و نیازی به hard-code کردن نام خاصی در اینجا نیست) و همچنین اگر بانک اطلاعاتی از رشته‌های غیریونیکد پشتیبانی نکند، از ویژگی Unicode صرفنظر خواهد شد.
‫۲ سال و ۷ ماه قبل، دوشنبه ۲۷ دی ۱۴۰۰، ساعت ۱۴:۰۰
یک نکته‌ی تکمیلی: ساده شدن تنظیمات سراسری نوع‌ها در EF Core 6x

از EF-Core 6x به بعد، بجای حلقه‌ای که در انتهای بحث مشاهده می‌کنید و هدف آن یافتن تمام نوع‌های رشته‌ای در تمام مدل‌های در معرض دید EF-Core است و سپس اعمال تنظیمات خاصی به آن‌ها، می‌توان از روش ساده شده‌ی زیر استفاده کرد:
public class ClientSideDbContext : DbContext
{
    // ...

    protected override void ConfigureConventions(ModelConfigurationBuilder configurationBuilder)
    {
        configurationBuilder
            .Properties<string>()
            .HaveMaxLength(500)
            .UseCollation("nocase");

        configurationBuilder
            .Properties<decimal>()
            .HavePrecision(12, 2);
    }
}
در اینجا به تمام خواص رشته‌ای در معرض دید EF-Core، حداکثر طول 500 و collation ای از نوع nocase اعمال می‌شود؛ همچنین دقت تمام نوع‌های decimal نیز به صورت سراسری مشخص شده‌است.
‫۲ سال و ۷ ماه قبل، دوشنبه ۲۰ دی ۱۴۰۰، ساعت ۱۵:۱۵
- این مطلب برای شروع کار مناسب نیست و یک مطلب تکمیلی است. برای آشنایی با معماری برنامه‌های Blazor از اینجا شروع کنید؛ از این جهت که Blazor Server خودش یک پروژه‌ی وب کامل هست و نیازی به پروژه‌ی وب مجزا ندارد. نحوه‌ی دسترسی به اطلاعات آن با Blazor WASM متفاوت است و خیلی موارد دیگر (که نیازی به تکرار آن‌ها در اینجا نیست). نمونه‌اش منوی fetch data در تصویر برنامه‌ی دسکتاپ هست که اطلاعات خودش را از وب سرور اجرا شده‌ی به همراه برنامه دریافت می‌کند (این اتصال هم از نوع Web socket مخصوص SignalR هست که در سری Blazor سایت در مورد آن بحث شده). همچنین این را هم مدنظر داشته باشید که زمانیکه بحث برنامه‌ی «دسکتاپ» هست، یعنی برنامه‌های «کوچک» و «تک کاربره» که توضیحات این مطلب برای پوشش آن‌ها کافی است.
- اگر هم Web API راه دور دارید، فقط کافی است سرویس HttpClient را ثبت کنید (در همان سازنده‌ی فرم برنامه‌ی وین‌فرمز مثال فوق):
serviceCollection.AddScoped<HttpClient>();
بعد هم در صفحات razor کتابخانه‌ی مثال می‌توان به نحو متداولی با این سرویس کار کرد (و نکات کار با این سرویس در سری Blazor سایت بررسی شده‌اند):
@inject HttpClient Http