‫۳ سال و ۷ ماه قبل، جمعه ۲۶ دی ۱۳۹۹، ساعت ۱۸:۵۴
یک نکته‌ی تکمیلی: بهبود خروجی stack trace استثناءهای رخ داده
خروجی رشته‌ای stack trace استثناءهای رخ داده، سال‌ها است که به روز رسانی نشده و با افزوده شدن امکانات بیشتری به زبان #C، یک چنین خروجی‌های نامفهومی را تولید می‌کند:
System.InvalidOperationException: Collection was modified; enumeration operation may not execute.
.
.
.
at Program.<>c__DisplayClass8_0.<Method>b__0()
کتابخانه‌ی «Ben.Demystifier » با تغییر ساده‌ی ()exception.Demystify، یک شیء استثنای جدید را تولید می‌کند که خوانایی بیشتری را داشته و با مفاهیم جدید زبان #C سازگاری دارد. این کتابخانه اکنون در بسیاری از محصولات خود مایکروسافت هم مورد استفاده قرار می‌گیرد و تبدیل به یک استاندارد شده‌است. یک نمونه‌ی ساده‌ی استفاده‌ی از آن، لاگ کردن خطاها با استفاده از این شیء بهبود یافته‌است:
_logger.LogError(ex.Demystify(), "Invalid operation.");
‫۳ سال و ۷ ماه قبل، پنجشنبه ۲۵ دی ۱۳۹۹، ساعت ۱۷:۴۲
یک نکته‌ی تکمیلی: خروجی یونیکد در JSON نهایی
اگر مقادیر خواص شما دارای حروف یونیکد باشند، در خروجی نهایی ارائه شده، این حروف escape می‌شوند. یعنی به صورت برای مثال u003E\ نمایش داده خواهند شد. البته این مساله هیچ تاثیری را بر روی تبدیل این خروجی JSON، به شیء ابتدایی ندارد. فقط هدف آن‌ها بالا بردن امنیت قطعه string تولید شده جهت «درج خام» آن در یک صفحه‌است. برای نمونه اگر قصد ندارید این رشته‌ی نهایی JSON تولید شده را در بین تگ‌های <script> یک صفحه‌ی HTML به صورت مستقیما قرار دهید، می‌توانید این escape پیش‌فرض را غیرفعال کنید:
options = new JsonSerializerOptions
{
    Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping,
    WriteIndented = true
};
jsonString = JsonSerializer.Serialize(weatherForecast, options);
و یا حتی اگر می‌خواهید باز هم یکسری حروف خاص ASCII را به صورت escape شده داشته باشید، اما حروف یونیکد را به صورت معمولی مشاهده کنید، می‌توانید از روش زیر استفاده کنید:
var options = new JsonSerializerOptions
{
    Encoder = JavaScriptEncoder.Create(UnicodeRanges.All)
};
‫۳ سال و ۷ ماه قبل، چهارشنبه ۲۴ دی ۱۳۹۹، ساعت ۱۵:۲۷
اگر خاصیتی را به صورت زیر تعریف کرده باشید:
public string Summary { get; private set; }
در نگارش 5، باید حتما به همراه ویژگی [JsonInclude] هم باشد تا به درستی serialize و deserialize شود. همچنین ذکر private set هم ضروری است. نگارش 5، خواصی را که فقط به صورت get دار یا read-only تنظیم شده باشند، deserialize نمی‌کند.
‫۳ سال و ۷ ماه قبل، یکشنبه ۲۱ دی ۱۳۹۹، ساعت ۱۷:۰۲

فرض کنید نمی‌خواهید فایل‌های migration حاصل از ابزارهای EF-Core را وارد پروسه‌ی آنالیز افزونه‌ها کنید؛ چون این فایل‌ها به صورت خودکار تولید می‌شوند و اصلاح آن‌ها، یا کیفیت آن‌ها، مشکل ما نیست. برای این منظور، فقط کافی است داخل پوشه‌ی migrations، یک فایل اختصاصی editorconfig. را با محتوای زیر قرار داد:
[*.cs]
generated_code = true
به این ترتیب تمام فایل‌های cs قرارگرفته‌ی در این پوشه، به عنوان «کدهای به صورت خودکار تولید شده» علامتگذاری شده و دیگر مشکلات کیفیتی آن‌ها وارد پروسه‌ی آنالیز و build برنامه نمی‌شوند.
‫۳ سال و ۸ ماه قبل، چهارشنبه ۱۷ دی ۱۳۹۹، ساعت ۱۲:۰۴
یک نکته‌ی تکمیلی
security-code-scan را هم می‌توان به عنوان یک افزونه‌ی بررسی مسایل امنیتی، به مجموعه‌ی تنظیمات فوق اضافه کرد:
<PackageReference Include="SecurityCodeScan" Version="3.5.3">
  <PrivateAssets>all</PrivateAssets>
  <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
‫۳ سال و ۸ ماه قبل، چهارشنبه ۱۷ دی ۱۳۹۹، ساعت ۱۱:۵۸
یک نکته‌ی تکمیلی
با انتشار «NetAnalyzers 5.0.3» روش استفاده‌ی از آن، ذکر صریح PackageReference ای است که در متن عنوان شد؛ در غیراینصورت (استفاده از نگارش قدیمی به همراه SDK)، نیازی به این تنظیم را ندارد.
‫۳ سال و ۸ ماه قبل، شنبه ۱۳ دی ۱۳۹۹، ساعت ۱۸:۰۳
یک نکته‌ی تکمیلی: معادل این مطلب با EF Core 5x

کدهای Interceptor به این صورت اصلاح شده و تطابق خواهند یافت:
using System;
using System.Data;
using System.Data.Common;
using System.Globalization;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore.Diagnostics;
using Microsoft.Extensions.Logging;

namespace Utils
{
    public class EfExceptionsInterceptor : DbCommandInterceptor
    {
        private readonly ILogger<EfExceptionsInterceptor> _logger;

        public EfExceptionsInterceptor(ILogger<EfExceptionsInterceptor> logger)
        {
            _logger = logger ?? throw new ArgumentNullException(nameof(logger));
        }

        public override void CommandFailed(DbCommand command, CommandErrorEventData eventData)
        {
            logError(command, eventData);
        }

        public override Task CommandFailedAsync(DbCommand command, CommandErrorEventData eventData, CancellationToken cancellationToken = default)
        {
            logError(command, eventData);
            return Task.CompletedTask;
        }

        private void logError(DbCommand command, CommandErrorEventData eventData)
        {
            if (command == null || eventData == null)
            {
                return;
            }

            var ex = eventData.Exception;
            if (ex == null)
            {
                return;
            }

            var sqlData = logSqlAndParameters(command);
            var contextualMessage = $"{sqlData}{Environment.NewLine}OriginalException:{Environment.NewLine}{ex} {Environment.NewLine}";
            _logger.LogError(contextualMessage);
        }

        private static string logSqlAndParameters(DbCommand command)
        {
            // -- Name: [Value] (Type = {}, Direction = {}, IsNullable = {}, Size = {}, Precision = {} Scale = {})
            var builder = new StringBuilder();

            var commandText = command.CommandText ?? "<null>";
            builder.AppendFormat(CultureInfo.InvariantCulture, "{0}Command: {0}{1}", Environment.NewLine, commandText)
                .AppendLine();

            var parameters = command.Parameters.OfType<DbParameter>().ToList();

            if (parameters.Any())
            {
                builder.AppendFormat(CultureInfo.InvariantCulture, "{0}Parameters: ", Environment.NewLine)
                    .AppendLine();
            }

            foreach (var parameter in parameters)
            {
                builder.Append("-- ")
                    .Append(parameter.ParameterName)
                    .Append(": '")
                    .Append((parameter.Value == null || parameter.Value == DBNull.Value) ? "null" : parameter.Value)
                    .Append("' (Type = ")
                    .Append(parameter.DbType);

                if (parameter.Direction != ParameterDirection.Input)
                {
                    builder.Append(", Direction = ").Append(parameter.Direction);
                }

                if (!parameter.IsNullable)
                {
                    builder.Append(", IsNullable = false");
                }

                if (parameter.Size != 0)
                {
                    builder.Append(", Size = ").Append(parameter.Size);
                }

                if (((IDbDataParameter)parameter).Precision != 0)
                {
                    builder.Append(", Precision = ").Append(((IDbDataParameter)parameter).Precision);
                }

                if (((IDbDataParameter)parameter).Scale != 0)
                {
                    builder.Append(", Scale = ").Append(((IDbDataParameter)parameter).Scale);
                }

                builder.Append(')').Append(Environment.NewLine);
            }

            return builder.ToString();
        }
    }
}
سپس برای ثبت آن، چون یک سرویس را به صورت تزریق وابستگی‌ها دریافت کرده، یکبار باید آن‌را به سیستم تزریق وابستگی‌ها معرفی کرد:
services.AddScoped<EfExceptionsInterceptor>();
و بعد نحوه‌ی معرفی آن به AddDbContextPool به روش خاص زیر است که از سرویس‌پروایدر استفاده می‌کند:
services.AddDbContextPool<ApplicationDbContext>((serviceProvider, optionsBuilder) =>
                    optionsBuilder
                        .UseSqlServer(
                            connectionString,
                            sqlServerOptionsBuilder =>
                            {
                              // ...
                            })
.AddInterceptors(serviceProvider.GetRequiredService<EfExceptionsInterceptor>()));
این نکته‌ای است که جهت ثبت Interceptorهای دارای تزریق وابستگی، مورد استفاده قرار می‌گیرد.
‫۳ سال و ۸ ماه قبل، شنبه ۱۳ دی ۱۳۹۹، ساعت ۱۴:۰۸
یک نکته‌ی تکمیلی: روش رفع خطای CA1305

با «غنی سازی کامپایلر C# 9.0 با افزونه‌ها» نکات جالبی در کدها ظاهر می‌شوند که یکی از آن‌ها CA1305 است و به صورت خلاصه عنوان می‌کند که اگر متد در حال استفاده، دارای overload ای با پارامتر از نوع IFormatProvider هست، حتما از آن استفاده کنید؛ تا شاهد مشکلاتی مانند « تغییرات مهم مقایسه‌ی رشته‌ها در NET 5.0. » و « از متد DateTime.ToString بدون پارامتر استفاده نکنید! » نباشید. در مورد C# 6 - String Interpolation در متن جاری نکاتی در این مورد عنوان شده‌است. می‌توان جهت تکمیل آن، نکته‌ی ریز زیر را هم مدنظر داشت:
using static System.FormattableString;


var number = 11;
var text = Invariant($"{number}");
System.FormattableString استاندارد، به همراه متد استاتیک Invariant هم هست که همان کار ToString(CultureInfo.InvariantCulture) را انجام می‌دهد. جهت ساده سازی آن هم از ویژگی using static استفاده شده‌است.
یا می‌توان از ابزارهای خودکاری که با یکسری از مفاهیم سازگاری ندارند، برای تعیین مسیرها استفاده کرد و یا دستی آن‌ها را وارد کرد. نتیجه‌ی نهایی، بحث کامپایل شدن یا نشدن است.
‫۳ سال و ۸ ماه قبل، سه‌شنبه ۹ دی ۱۳۹۹، ساعت ۲۳:۴۱
نمایش رسمی null در جاوا اسکریپت به این صورت است:
{ "Value": null }
بنابراین اگر چنین اطلاعاتی به سمت سرور ارسال شود، به درستی پردازش خواهد شد؛ اگرنه باید از تبدیلگر زیر استفاده کنید:
    public class StringJsonConverter : JsonConverter<byte?>
    {
        public override byte? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
        {
            return reader.TokenType switch
            {
                JsonTokenType.Number => reader.GetByte(),
                JsonTokenType.Null => null,
                JsonTokenType.String =>
                    byte.TryParse(reader.GetString(), NumberStyles.Number, CultureInfo.InvariantCulture, out var parsed) ? parsed : (byte?)null,
                _ => throw new ArgumentOutOfRangeException(nameof(reader), reader.TokenType, "Cannot parse unexpected JSON token type.")
            };
        }

        public override void Write(Utf8JsonWriter writer, byte? value, JsonSerializerOptions options)
        {
            if (value.HasValue)
            {
                writer.WriteNumberValue(value.Value);
            }
            else
            {
                writer.WriteNullValue();
            }
        }
    }