C# Geeks (.NET)
414 subscribers
155 photos
4 videos
159 links
Download Telegram
ترتیب middlewareها 🧱

در این دیاگرام کل pipeline پردازش درخواست در ASP.NET Core MVC و Razor Pages را نشان می‌دهد.
می‌توانید ببینید که در یک اپلیکیشن معمولی، middlewareهای موجود چه ترتیبی دارند و middlewareهای سفارشی کجا اضافه می‌شوند.
شما کنترل کامل دارید که middlewareهای موجود را جابه‌جا کنید یا middleware جدید تزریق کنید، متناسب با سناریوهای خودتان. 🔧

ءEndpoint middleware در دیاگرام بالا، filter pipeline مربوط به نوع اپلیکیشن (MVC یا Razor Pages) را اجرا می‌کند.

ءRouting middleware در دیاگرام بالا بعد از Static Files نشان داده شده است.
این همان ترتیبی است که قالب‌های پیش‌فرض پروژه با فراخوانی صریح app.UseRouting پیاده‌سازی می‌کنند.
اگر app.UseRouting را صدا نزنید، Routing middleware به‌صورت پیش‌فرض در ابتدای pipeline اجرا می‌شود.
برای اطلاعات بیشتر، بخش Routing را ببینید.

ترتیبی که middlewareها در فایل Program.cs اضافه می‌شوند، دقیقاً ترتیب اجرای آن‌ها روی request و ترتیب معکوس برای response را مشخص می‌کند.
این ترتیب برای امنیت، کارایی و عملکرد صحیح کاملاً حیاتی است. ⚠️

ترتیب پیشنهادی middlewareهای امنیتی

کد زیر در Program.cs middlewareهای مربوط به امنیت را در ترتیب توصیه‌شده اضافه می‌کند:
using Microsoft.AspNetCore.Identity;
using Microsoft.EntityFrameworkCore;
using WebMiddleware.Data;

var builder = WebApplication.CreateBuilder(args);

var connectionString = builder.Configuration.GetConnectionString("DefaultConnection")
?? throw new InvalidOperationException("Connection string 'DefaultConnection' not found.");
builder.Services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(connectionString));
builder.Services.AddDatabaseDeveloperPageExceptionFilter();

builder.Services.AddDefaultIdentity<IdentityUser>(options => options.SignIn.RequireConfirmedAccount = true)
.AddEntityFrameworkStores<ApplicationDbContext>();
builder.Services.AddRazorPages();
builder.Services.AddControllersWithViews();

var app = builder.Build();

if (app.Environment.IsDevelopment())
{
app.UseMigrationsEndPoint();
}
else
{
app.UseExceptionHandler("/Error");
app.UseHsts();
}

app.UseHttpsRedirection();
app.UseStaticFiles();
// app.UseCookiePolicy();

app.UseRouting();
// app.UseRateLimiter();
// app.UseRequestLocalization();
// app.UseCors();

app.UseAuthentication();
app.UseAuthorization();
// app.UseSession();
// app.UseResponseCompression();
// app.UseResponseCaching();

app.MapRazorPages();
app.MapDefaultControllerRoute();

app.Run();

در کد بالا: middlewareهایی که هنگام ساخت یک وب‌اپ با حساب کاربری فردی اضافه نمی‌شوند، کامنت شده‌اند.

همه‌ی middlewareها دقیقاً در همین ترتیب ظاهر نمی‌شوند، اما بسیاری از آن‌ها همین‌طور هستند. برای مثال:

قوانین مهم ترتیب middlewareها 📌

ءUseCors، UseAuthentication و UseAuthorization باید دقیقاً به همین ترتیب باشند.

ءUseCors در حال حاضر باید قبل از UseResponseCaching قرار بگیرد. این الزام در GitHub issue شماره dotnet/aspnetcore #23218 توضیح داده شده است.

ءUseRequestLocalization باید قبل از هر middlewareای باشد که ممکن است culture درخواست را بررسی کند، مثلاً ()app.UseStaticFiles

وقتی rate limiting به‌صورت endpoint-specific استفاده می‌شود (مثلاً با [EnableRateLimiting])، باید UseRateLimiter بعد از UseRouting صدا زده شود.

اگر فقط global limiterها استفاده شوند، می‌توان UseRateLimiter را قبل از UseRouting هم صدا زد.

ترتیب‌های جایگزین در برخی سناریوها

در بعضی سناریوها ترتیب middleware متفاوت است.
مثلاً ترتیب caching و compression وابسته به سناریو است و چندین ترتیب معتبر وجود دارد. برای مثال:
app.UseResponseCaching();
app.UseResponseCompression();

در این حالت، مصرف CPU ممکن است کاهش پیدا کند چون response فشرده‌شده cache می‌شود،
اما ممکن است چند نسخه‌ی مختلف از یک resource با الگوریتم‌های فشرده‌سازی متفاوت مثل Gzip یا Brotli در cache ذخیره شوند. 📦
⚡️ ترتیب Middleware در ASP.NET Core و نکات مهم

کش کردن و فشرده‌سازی فایل‌های استاتیک 📦💨

برای اینکه فایل‌های استاتیک Cacheable و Compressed شوند، ترتیب زیر پیشنهاد می‌شود:
app.UseResponseCaching();
app.UseResponseCompression();
app.UseStaticFiles();

ءProgram.cs: Middlewareهای رایج در سناریوهای مختلف

1️⃣ مدیریت Exception/Error

🔸️Development:
• UseDeveloperExceptionPage() → نمایش خطاهای runtime اپلیکیشن

• UseDatabaseErrorPage() → نمایش خطاهای database

🔹️Production:
• UseExceptionHandler() → گرفتن خطاهای ایجاد شده در middlewareهای بعدی

• UseHsts() → اضافه کردن هدر Strict-Transport-Security

• UseHttpsRedirection() → ریدایرکت HTTP به HTTPS

2️⃣ فایل‌های استاتیک و Policyها

• UseStaticFiles() → پاسخ به درخواست‌های فایل‌های استاتیک و کوتاه کردن pipeline

⚠️ توجه: هیچ بررسی authorization انجام نمی‌دهد. تمام فایل‌ها، از جمله wwwroot، عمومی هستند.

• UseCookiePolicy() → رعایت GDPR

3️⃣ Routing و Security

• UseRouting() → مسیردهی درخواست‌ها
• UseAuthentication() → بررسی هویت کاربر
• UseAuthorization() → اجازه دسترسی به منابع امن

⚠️توجه: Authentication short-circuit نمی‌کند؛ فقط هویت را بررسی می‌کند. مجوز دسترسی بعد از انتخاب Controller یا Razor Page اعمال می‌شود.

4️⃣ Session و Endpoint

• UseSession() → مدیریت وضعیت session
اگر از session استفاده می‌کنید، حتماً بعد از Cookie Policy و قبل از MVC Middleware قرار دهید.

• MapRazorPages() → اضافه کردن Endpointهای Razor Pages به pipeline

مثال کد کامل Program.cs
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
app.UseDatabaseErrorPage();
}
else
{
app.UseExceptionHandler("/Error");
app.UseHsts();
}

app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseCookiePolicy();
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.UseSession();
app.MapRazorPages();


نکات کلیدی 📝

• ءUseExceptionHandler → اولین middleware است، پس همه Exceptionهای بعدی را می‌گیرد.

• ءStatic File Middleware → زود فراخوانی می‌شود تا درخواست‌ها را هندل کرده و pipeline را کوتاه کند.

• ءSecurity → فایل‌های استاتیک بدون بررسی authorization هستند. برای ایمن‌سازی، به Secure Static Files در ASP.NET Core
مراجعه کنید.

Authentication vs Authorization

• Authentication → بررسی هویت
• Authorization → بعد از انتخاب Controller/Razor Page اعمال می‌شود

ترتیب فشرده‌سازی فایل‌ها

اگر Static Files قبل از ()UseResponseCompression بیاید → فایل‌های استاتیک فشرده نمی‌شوند، اما پاسخ Razor Pages فشرده می‌شود:
app.UseStaticFiles();
app.UseRouting();
app.UseResponseCompression();
app.MapRazorPages();

🔖هشتگ‌ها:
#ASPNETCore #Middleware #Pipeline #DotNe #RequestPipeline #CSharp
چگونه یک پروژه‌ی جدید NET. را در سال 2026 شروع کنیم 🚀

شروع یک پروژه‌ی جدید NET. هیجان‌انگیز است، اما می‌تواند گیج‌کننده هم باشد. تصمیم‌های زیادی باید گرفته شوند و انتخاب‌هایی که در چند روز اول انجام می‌دهید، روی پروژه‌ی شما برای ماه‌ها یا حتی سال‌ها تأثیر خواهند گذاشت.

بیایید شروع کنیم. 🔽

1️⃣ Directory.Build.props - Set Project-Wide Standards

فایل Directory.Build.props – تنظیم استانداردهای سراسری پروژه 🧱

هر solution در NET. باید با یک فایل Directory.Build.props شروع شود.
این فایل تنظیمات سراسری پروژه را تعریف می‌کند که روی تمام پروژه‌های داخل solution اعمال می‌شود.

بدون این فایل، مجبور می‌شوید همان تنظیمات را در چندین فایل csproj. تکرار کنید.
وقتی بخواهید یک تنظیم را تغییر دهید، باید همه‌ی فایل‌های پروژه را به‌صورت دستی آپدیت کنید.
این کار باعث ناسازگاری و اتلاف زمان می‌شود.

ءDirectory.Build.props این مشکل را با متمرکز کردن تنظیمات در یک مکان حل می‌کند.
این فایل را در همان دایرکتوری فایل sln. ایجاد می‌کنید و MSBuild به‌صورت خودکار آن را روی همه‌ی پروژه‌ها اعمال می‌کند.

این کانفیگی است که من برای هر پروژه‌ی جدید استفاده می‌کنم:
<Project>
<PropertyGroup>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<AnalysisLevel>latest</AnalysisLevel>
<AnalysisMode>All</AnalysisMode>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<CodeAnalysisTreatWarningsAsErrors>true</CodeAnalysisTreatWarningsAsErrors>
<EnforceCodeStyleInBuild>true</EnforceCodeStyleInBuild>
</PropertyGroup>
</Project>

در صورت نیاز، می‌توانید Directory.Build.props را در هر سطحی از ساختار دایرکتوری پروژه قرار دهید و تنظیمات را در آن سطح override کنید.

توضیح هر تنظیم:
🔸️Nullable:

قابلیت nullable reference types را فعال می‌کند و به جلوگیری از خطاهای null reference کمک می‌کند. کامپایلر زمانی که ممکن است به‌اشتباه از مقدار null استفاده کنید، هشدار می‌دهد.

🔹️ImplicitUsings:

ءnamespaceهای رایج را به‌صورت خودکار در هر فایل اضافه می‌کند. دیگر نیازی نیست بنویسید: using System; یا using System.Linq;

🔸️AnalysisLevel:

سطح آنالیز کد را روی آخرین نسخه تنظیم می‌کند. جدیدترین بررسی‌های کیفیت کد از سمت مایکروسافت فعال می‌شوند.

🔹️AnalysisMode:

تمام قوانین code analysis را فعال می‌کند و کامل‌ترین بازخورد ممکن درباره‌ی کیفیت کد را می‌دهد.

🔸️TreatWarningsAsErrors:

اگر هر هشداری وجود داشته باشد، کامپایل متوقف می‌شود. این کار شما را مجبور می‌کند مشکلات را همان لحظه حل کنید و اجازه ندهید انباشته شوند. 🚨

🔹️CodeAnalysisTreatWarningsAsErrors:

همین سخت‌گیری را روی هشدارهای code analysis هم اعمال می‌کند.

🔸️EnforceCodeStyleInBuild:

بررسی style کد را در زمان build اجرا می‌کند، نه فقط داخل IDE. یعنی در CI/CD هم تخلفات استایل شناسایی می‌شوند.

این تنظیمات یک پایه‌ی بسیار قوی برای کیفیت کد ایجاد می‌کنند.
مشکلات را زود شناسایی می‌کنند و یکپارچگی را در کل solution حفظ می‌کنند. 🧠

2️⃣ Add Static Code Analysis Packages

اضافه کردن پکیج‌های Static Code Analysis 🔍

کیفیت کد چیزی است که باید از روز اول به آن اهمیت بدهید.
پیروی از best practiceها بسیار راحت‌تر از این است که بعداً بخواهید آن‌ها را اصلاح کنید.
برای این کار از static code analysis استفاده می‌کنیم. static analyzerها کد شما را بدون اجرا بررسی می‌کنند.
آن‌ها اشتباهات رایج را پیدا می‌کنند، استانداردهای کدنویسی را enforce می‌کنند و باگ‌های احتمالی را قبل از رسیدن به production شناسایی می‌کنند.

این analyzerها هنگام کامپایل اجرا می‌شوند، پس هم در IDE و هم در CI/CD بازخورد فوری می‌گیرید. ⚡️
این پکیج‌ها را به فایل Directory.Build.props اضافه کنید:
<Project>
<PropertyGroup>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<AnalysisLevel>latest</AnalysisLevel>
<AnalysisMode>All</AnalysisMode>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<CodeAnalysisTreatWarningsAsErrors>true</CodeAnalysisTreatWarningsAsErrors>
<EnforceCodeStyleInBuild>true</EnforceCodeStyleInBuild>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Meziantou.Analyzer" Version="2.0.257">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="SonarAnalyzer.CSharp" Version="10.16.0.128591">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Roslynator.Analyzers" Version="4.14.1">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="xunit.analyzers" Version="1.26.0">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
</ItemGroup>
</Project>

نقش هر analyzer:
🔸️SonarAnalyzer.CSharp:

روی کیفیت کد، آسیب‌پذیری‌های امنیتی و code smell تمرکز دارد. متدهای پیچیده، کدهای تکراری و باگ‌های احتمالی را شناسایی می‌کند.

🔹️Meziantou.Analyzer:

مشکلات performance، ضعف‌های امنیتی و استفاده‌ی نادرست از APIها را پیدا می‌کند. صدها قانون برای async/await، LINQ، string handling و موارد دیگر دارد.

🔸️Roslynator.Analyzers:

پیشنهادهای refactoring و تحلیل کد ارائه می‌دهد و کمک می‌کند کد تمیزتر و idiomaticتری در #C بنویسید.

🔹️xunit.analyzers:

درست نوشتن تست‌ها را enforce می‌کند و اشتباهات رایج مثل نبود assertion یا attribute اشتباه را شناسایی می‌کند.

تنظیم IncludeAssets باعث می‌شود این analyzerها فقط در زمان کامپایل اجرا شوند و وارد خروجی نهایی برنامه نشوند. 📦

با فعال بودن TreatWarningsAsErrors، اگر هر analyzer مشکلی پیدا کند، build شکست می‌خورد.
شاید سخت‌گیرانه به نظر برسد، اما جلوی انباشته شدن technical debt را می‌گیرد.

شما همیشه باید تلاش کنید پروژه‌تان صفر هشدار داشته باشد. بسیاری از توسعه‌دهنده‌ها هشدارها را نادیده می‌گیرند، اما هشدارها معمولاً نشانه‌ی مشکلات واقعی هستند که بعداً تبدیل به باگ می‌شوند. 🐞

3️⃣ Enforce Coding Standards
اعمال استانداردهای کدنویسی 📏

ءAnalyzerها مشکلات را شناسایی می‌کنند، اما شما همچنین باید مشخص کنید کدام قوانین برای تیم شما مهم هستند و میزان سخت‌گیری آن‌ها چقدر باشد.

فایل editorconfig. استانداردهای کدنویسی را تعریف می‌کند و سطح severity هر قانون analyzer را مشخص می‌کند. شما می‌توانید قوانین را به‌صورت error، warning، suggestion علامت‌گذاری کنید یا کاملاً غیرفعالشان کنید.

این فایل را در همان دایرکتوری فایل sln. قرار دهید. همه‌ی توسعه‌دهندگان تیم شما، بدون توجه به اینکه از چه IDEای استفاده می‌کنند، به‌صورت خودکار از یک مجموعه قوانین یکسان پیروی خواهند کرد. 🤝

یک فایل پایه‌ی editorconfig. برای شروع:
[*]
charset = utf-8
indent_style = space
indent_size = 4
insert_final_newline = true
trim_trailing_whitespace = true

[*.cs]
# Nullable reference types
dotnet_diagnostic.CS8600.severity = error
dotnet_diagnostic.CS8601.severity = error
dotnet_diagnostic.CS8602.severity = error
dotnet_diagnostic.CS8603.severity = error
dotnet_diagnostic.CS8604.severity = error

# Code style rules
dotnet_style_qualification_for_field = false:warning
dotnet_style_qualification_for_property = false:warning
dotnet_style_qualification_for_method = false:warning
dotnet_style_qualification_for_event = false:warning

# Naming conventions
dotnet_naming_rule.interface_should_begin_with_i.severity = error
dotnet_naming_rule.interface_should_begin_with_i.symbols = interface
dotnet_naming_rule.interface_should_begin_with_i.style = begins_with_i

dotnet_naming_symbols.interface.applicable_kinds = interface
dotnet_naming_style.begins_with_i.required_prefix = I
dotnet_naming_style.begins_with_i.capitalization = pascal_case

# Async methods should end with Async
dotnet_naming_rule.async_methods_end_in_async.severity = error
dotnet_naming_rule.async_methods_end_in_async.symbols = any_async_methods
dotnet_naming_rule.async_methods_end_in_async.style = end_in_async

dotnet_naming_symbols.any_async_methods.applicable_kinds = method
dotnet_naming_symbols.any_async_methods.applicable_accessibilities = *
dotnet_naming_symbols.any_async_methods.required_modifiers = async

dotnet_naming_style.end_in_async.required_suffix = Async
dotnet_naming_style.end_in_async.capitalization = pascal_case

شما می‌توانید این فایل را مطابق سلیقه‌ی تیم خودتان شخصی‌سازی کنید. نکته‌ی کلیدی این است که از ابتدا روی استانداردها به توافق برسید و آن‌ها را به‌صورت خودکار enforce کنید. 🔒

با استفاده از editorconfig. ، کد reviewها سریع‌تر می‌شوند چون دیگر لازم نیست توسعه‌دهندگان درباره‌ی فرمت‌بندی یا naming convention بحث کنند. ابزارها این قوانین را به‌صورت خودکار اعمال می‌کنند. ⚙️

4️⃣ Centralize Package Management 📦

با بزرگ‌تر شدن solution، مدیریت نسخه‌ی پکیج‌های NuGet در چندین پروژه سخت می‌شود. پروژه‌های مختلف در نهایت از نسخه‌های متفاوت یک پکیج استفاده می‌کنند که می‌تواند باعث مشکلات سازگاری شود و آپدیت‌ها را سخت‌تر کند. Central Package Management (CPM) این مشکل را با مدیریت همه‌ی نسخه‌های پکیج در یک مکان حل می‌کند.

شما باید یک فایل Directory.Packages.props بسازید که تمام نسخه‌های پکیج برای solution را تعریف می‌کند. این فایل را در همان دایرکتوری .sln قرار دهید:
<Project>
<PropertyGroup>
<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
</PropertyGroup>

<ItemGroup>
<!-- Web -->
<PackageVersion Include="Microsoft.AspNetCore.OpenApi" Version="10.0.0" />

<!-- Database -->
<PackageVersion Include="Microsoft.EntityFrameworkCore" Version="10.0.0" />
<PackageVersion Include="Microsoft.EntityFrameworkCore.Design" Version="10.0.0" />
<PackageVersion Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="10.0.0" />

<!-- Testing -->
<PackageVersion Include="xunit" Version="2.9.3" />
<PackageVersion Include="xunit.runner.visualstudio" Version="3.1.5" />
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="18.0.1" />
<!-- Code Analysis -->
<PackageVersion Include="Meziantou.Analyzer" Version="2.0.257" />
<PackageVersion Include="SonarAnalyzer.CSharp" Version="10.16.0.128591" />
<PackageVersion Include="Roslynator.Analyzers" Version="4.14.1" />
<PackageVersion Include="xunit.analyzers" Version="1.26.0" />
</ItemGroup>
</Project>

برای فعال شدن CPM باید property زیر را در این فایل اضافه کنید:ManagePackageVersionsCentrally

وقتی CPM فعال باشد، فایل‌های پروژه پکیج‌ها را بدون مشخص کردن نسخه ریفرنس می‌کنند:
<ItemGroup>
<PackageReference Include="Microsoft.EntityFrameworkCore" />
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" />
</ItemGroup>

در صورت نیاز، می‌توانید نسخه‌ی یک پکیج را در یک پروژه‌ی خاص override کنید:
<ItemGroup>
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="10.0.1" />
</ItemGroup>
5️⃣ساده‌سازی توسعه‌ی لوکال با Aspire 🚀

راه‌اندازی محیط توسعه‌ی لوکال معمولاً آزاردهنده است. توسعه‌دهنده‌ها ساعت‌ها زمان صرف نصب دیتابیس‌ها، تنظیم connection stringها و هماهنگ کردن وابستگی‌ها با هم می‌کنند. 😵‍💫

حتی با وجود Docker، باز هم مقدار زیادی تنظیمات دستی وجود دارد؛ از جمله volumeها و کانفیگ هر کانتینر.

ءNET Aspire. این مشکل را با ارائه‌ی یک روش یکپارچه برای مدیریت وابستگی‌ها، پیکربندی و استقرار (deployment) حل می‌کند.

ءAspire یک application framework برای ساخت اپلیکیشن‌های cloud-native، قابل مشاهده (observable) و آماده‌ی production با NET. است. این فریم‌ورک به‌صورت پیش‌فرض موارد زیر را هندل می‌کند:
• Service discovery
• Configuration
• Health checks
• Telemetry 📊
از نسخه‌ی 13 به بعد، NET Aspire. به Aspire تغییر برند داد و حالا یک پلتفرم چندزبانه (multi-language) برای ساخت اپلیکیشن است.

ءAspire دو نوع پروژه‌ی جدید به solution شما اضافه می‌کند:

🔸️ءAppHost: ارکستریتوری که معماری اپلیکیشن و وابستگی‌ها را تعریف می‌کند.

🔹️ءServiceDefaults: تنظیمات مشترک و observability که روی همه‌ی سرویس‌ها اعمال می‌شود.

نحوه‌ی اضافه کردن Aspire به پروژه‌ی جدید

مرحله 1️⃣: نصب templateهای Aspire
dotnet new install Aspire.ProjectTemplates

مرحله 2️⃣: نصب Aspire CLI
dotnet tool install --global aspire.cli

مرحله 3️⃣: اضافه کردن Aspire به solution از طریق Visual Studio یا Rider، یا استفاده از CLI برای ساخت یک پروژه‌ی جدید Aspire.

مرحله 4️⃣: تعریف وابستگی‌ها در پروژه‌ی AppHost
var builder = DistributedApplication.CreateBuilder(args);

var cache = builder.AddRedis("cache");
var postgres = builder.AddPostgres("postgres");

builder
.AddProject<Projects.Products_Api>("products-api");
.WithReference(cache)
.WithReference(postgres);

builder.Build().Run();

مرحله 5️⃣: استفاده از connection stringها در پروژه‌ی API
var postgresConnection = configuration.GetConnectionString("postgres");
var redisConnection = configuration.GetConnectionString("cache");

ءAspire به‌صورت خودکار connection stringها را از طریق environment variableها تزریق می‌کند. نیازی نیست آن‌ها را دستی داخل appsettings.json تعریف کنید. ⚡️

با یکپارچگی Docker، وقتی پروژه‌ی AppHost را اجرا می‌کنید، Aspire تمام وابستگی‌ها را به‌صورت خودکار بالا می‌آورد. PostgreSQL، Redis و هر سرویس دیگری داخل کانتینر اجرا می‌شوند و Aspire چرخه‌ی عمر آن‌ها را مدیریت می‌کند. 🐳

داشبورد Aspire در مرورگر باز می‌شود و همه‌ی سرویس‌های در حال اجرا، وضعیت سلامت، لاگ‌ها و distributed traceها را نمایش می‌دهد. 📈

مزایای کلیدی استفاده از Aspire

🔸️Simplified local development:
اجرای تمام سرویس‌ها و دیتابیس‌ها با یک دستور واحد.

🔹️Built-in observability:
یکپارچگی آماده با OpenTelemetry برای لاگ، متریک و تریس.

🔸️Service discovery:
سرویس‌ها بدون URLهای هاردکد شده همدیگر را پیدا می‌کنند.

🔹️Configuration management:
پیکربندی متمرکز برای لوکال و کلاود.

🔸️Easy deployment:
استقرار کل سیستم روی Docker، Azure یا AWS با کمترین تنظیمات.

🔹️Consistent developer experience:
اعضای جدید تیم می‌توانند کل سیستم را در چند دقیقه اجرا کنند. 🎯
6️⃣ راه‌اندازی OpenTelemetry 🔍

درک این‌که داخل اپلیکیشن شما دقیقاً چه اتفاقی می‌افتد، حیاتی است. OpenTelemetry یک روش استاندارد برای جمع‌آوری و تحلیل داده‌های telemetry از اپلیکیشن‌ها فراهم می‌کند و به شما دید (visibility) دقیقی نسبت به رفتار سیستم می‌دهد. 👁

هر اپلیکیشن وابستگی‌های خارجی دارد؛ مثل دیتابیس‌ها، cacheها، APIها و سایر سرویس‌ها. مانیتور کردن این وابستگی‌ها برای درک عملکرد اپلیکیشن و نحوه‌ی تعامل آن با بقیه‌ی سیستم ضروری است. ⚙️

ءOpenTelemetry سه نوع داده از اپلیکیشن جمع‌آوری می‌کند:

• ءLogs توضیح می‌دهند چه اتفاقی افتاده است. 📝
• ءTraces نشان می‌دهند عملیات کجا انجام شده و چقدر زمان برده است. ⏱️
• ءMetrics نشان می‌دهند رویدادها با چه فرکانسی رخ می‌دهند. 📊

وقتی از Aspire استفاده می‌کنید، OpenTelemetry از قبل برای شما پیکربندی شده است. پروژه‌ی ServiceDefaults شامل تنظیمات observability است:
public static TBuilder ConfigureOpenTelemetry<TBuilder>(this TBuilder builder)
where TBuilder : IHostApplicationBuilder
{
builder.Logging.AddOpenTelemetry(logging =>
{
logging.IncludeFormattedMessage = true;
logging.IncludeScopes = true;
});

builder.Services.AddOpenTelemetry()
.WithMetrics(metrics =>
{
metrics.AddAspNetCoreInstrumentation()
.AddHttpClientInstrumentation()
.AddRuntimeInstrumentation();
})
.WithTracing(tracing =>
{
tracing.AddSource(builder.Environment.ApplicationName)
.AddAspNetCoreInstrumentation()
.AddHttpClientInstrumentation();
});

builder.AddOpenTelemetryExporters();
return builder;
}

ءAspire به‌صورت خودکار OpenTelemetry را طوری تنظیم می‌کند که logs، metrics و traces را به داشبورد Aspire ارسال کند.
اما Aspire داده‌های OpenTelemetry و داشبورد را ذخیره (persist) نمی‌کند. در محیط production، به یک راه‌حل قوی‌تر برای ذخیره و تحلیل داده‌های telemetry نیاز دارید. 🏗

می‌توانید exporterهای اضافی تنظیم کنید تا داده‌ها به ابزارهایی مثل Jaeger، Seq، Grafana یا سایر ابزارهای observability ارسال شوند. 📡

به‌صورت پیش‌فرض، OpenTelemetry فقط برای ASP.NET Core و HttpClient فعال است. برای وابستگی‌های اضافی مثل PostgreSQL یا Redis، باید پکیج‌های instrumentation را نصب کنید:
dotnet add package Npgsql.OpenTelemetry
dotnet add package OpenTelemetry.Instrumentation.StackExchangeRedis

سپس آن‌ها را در ServiceDefaults رجیستر کنید:
tracing.AddSource(builder.Environment.ApplicationName)
.AddAspNetCoreInstrumentation()
.AddHttpClientInstrumentation()
.AddNpgsql()
.AddRedisInstrumentation();

داشبورد Aspire، distributed traceها را در تمام سرویس‌ها نمایش می‌دهد. می‌توانید دقیقاً ببینید چه کوئری‌هایی روی دیتابیس اجرا شده‌اند، چقدر طول کشیده‌اند و bottleneckها کجا هستند. 🧠

شروع observability از همان روز اول به شما کمک می‌کند ویژگی‌های عملکردی سیستم را زودتر درک کنید. می‌توانید کوئری‌های کند را شناسایی کنید، API callها را بهینه کنید و مشکلات را قبل از رسیدن به production رفع کنید. 🚀
7️⃣ خودکارسازی Build و Test با GitHub Actions 🤖

توسعه‌ی مدرن نرم‌افزار نیازمند pipelineهای خودکار است. شما نمی‌توانید به استقرار دستی یا رد کردن تست‌های خودکار متکی باشید. 🚫

یک pipeline خوب CI/CD تضمین می‌کند که هر تغییر در کد به‌صورت یکسان build، test و deploy شود. این کار باگ‌ها را قبل از رسیدن به production شناسایی می‌کند و سرعت تحویل شما را افزایش می‌دهد. ⚡️

بدون pipeline مناسب CI/CD، زمان زیادی صرف کارهای دستی می‌کنید و ریسک خطای انسانی به‌شدت افزایش پیدا می‌کند.

برای توسعه‌دهندگان NET. که از GitHub استفاده می‌کنند، GitHub Actions انتخاب طبیعی است. این ابزار به‌صورت یکپارچه با ریپازیتوری شما کار می‌کند و هر چیزی که برای build و test اپلیکیشن‌های NET. نیاز دارید را فراهم می‌کند. 🧩

یک فایل به نام github/workflows/ci.yml. در ریپازیتوری خود بسازید:
name: BUILD_AND_TEST

on:
push:
branches: [ "main" ]
pull_request:
branches: [ "main" ]

jobs:
build:
runs-on: ubuntu-latest

strategy:
matrix:
dotnet-version: [ '10.0.x' ]

steps:
- uses: actions/checkout@v3

- name: Setup .NET
uses: actions/setup-dotnet@v3
with:
dotnet-version: ${{ matrix.dotnet-version }}

- name: Restore dependencies
run: dotnet restore

- name: Build
run: dotnet build --no-restore

- name: Test
run: dotnet test --no-build --no-restore --verbosity normal

- name: Build Docker image with Aspire
if: ${{ success() }}
run: |
dotnet tool install --global aspire.cli
aspire publish -o docker-compose-artifacts
docker compose -f docker-compose-artifacts/docker-compose.yaml build

این pipeline روی هر push به شاخه‌ی main و روی هر pull request اجرا می‌شود. وابستگی‌ها را restore می‌کند، solution را build می‌کند، تست‌ها را اجرا می‌کند و با استفاده از Aspire یک Docker image می‌سازد. 🐳

ءAspire CLI یک فایل Docker Compose تولید می‌کند که شامل تمام سرویس‌ها و وابستگی‌های آن‌هاست. سپس pipeline ایمیج‌های Docker را برای استقرار build می‌کند. 🏗

بهترین روش‌ها برای CI/CD:

• Keep pipelines fast:
هدف زیر ۱۰ دقیقه برای CI builds باشد. ⏱️
• Run tests on every pull request:
مشکلات را قبل از merge شدن به main شناسایی کنید. 🔍
• Store secrets securely:
از GitHub Secrets برای داده‌های حساس مثل API key و connection string استفاده کنید. هرگز secret را در ریپو commit نکنید. 🔐
• Fail fast:
اگر تست‌ها fail شدند، pipeline را فوراً متوقف کنید. برای کد خراب Docker image نسازید.
• Use caching:
پکیج‌های NuGet و artifactهای build را cache کنید تا اجراهای بعدی سریع‌تر شوند. ⚡️

8️⃣ قالب پروژه برای شروع 🚀

راه‌اندازی یک پروژه‌ی جدید NET. با مراحل بالا سخت نیست. اما چیزهای بسیار بیشتری وجود دارد که باید در پروژه تنظیم شوند، مثل:

• Code Structure: Clean Architecture / Vertical Slices / N-Layered
• Authentication و Authorization
• ASP .NET Core Identity
• JWT Claims و Refresh Token
• Logging
• Result Pattern
• Architecture Tests
• Unit Tests و Integration Tests
• ساختار endpointهای WebApi
• یکپارچه‌سازی با Jaeger و Seq

جمع‌بندی 🧠

شروع یک پروژه‌ی جدید NET. با فونداسیون درست، در بلندمدت زمان ذخیره می‌کند و از ایجاد technical debt جلوگیری می‌کند.

این روش‌ها خیلی سریع در طول عمر پروژه نتیجه می‌دهند. بهترین زمان برای تعریف استانداردهای کیفیت، ابتدای پروژه است. پروژه‌ی بعدی NET. خود را با این هفت قدم شروع کنید تا نرم‌افزار بهتری را سریع‌تر بسازید. 🚀

امیدوارم این مقاله برایتان مفید بوده باشد👋

🔖هشتگ‌ها:
#DotNet #SoftwareEngineering
#OpenTelemetry #Aspire #DeveloperExperience
ءSwagger در NET 10. حذف شده؟ چطور دوباره اضافه‌اش کنیم 🔍

شما یک پروژه‌ی NET 10. می‌سازید، برنامه را اجرا می‌کنید، تلاش می‌کنید صفحه‌ی Swagger را باز کنید، و با خطای 404 Not Found مواجه می‌شوید.
پس چطور باید آن را برگردانیم؟

چرا Swagger حذف شده است؟ 🤔

مایکروسافت پشتیبانی از Swagger را از NET 9. حذف کرده است. طبق این بحث در GitHub:
"The project is no longer actively maintained by its community owner. Issues have not been addressed or resolved..."

یعنی:
این پروژه دیگر به‌صورت فعال توسط مالک جامعه‌ی آن نگهداری نمی‌شود. مشکلات بررسی یا حل نشده‌اند.

مایکروسافت به سمت استفاده از OpenAPI حرکت کرده است، یعنی حالا مستندات OpenAPI می‌توانند بدون نیاز به Swagger تولید شوند. 📄

چطور Swagger را به NET 10. اضافه کنیم 🛠

ابتدا باید پکیج زیر را به پروژه‌ی Web API خود اضافه کنید:
Swashbuckle.AspNetCore.SwaggerUI

سپس داخل فایل Program.cs باید پارامتر options را به متد UseSwaggerUI اضافه کنید و endpoint را روی /openapi/v1.json تنظیم کنید.
// Program.cs
if (app.Environment.IsDevelopment())
{
app.MapOpenApi();

// Add this
app.UseSwaggerUI(options =>
{
options.SwaggerEndpoint("/openapi/v1.json", "API v1");
});
}


اجرای خودکار Swagger هنگام اجرا 🚀

اگر می‌خواهید برنامه هنگام اجرا به‌صورت خودکار با Swagger باز شود، به مسیر زیر بروید:
Properties > launchSettings.json

پروفایل https را پیدا کنید، مقدار launchBrowser را برابر true قرار دهید و launchUrl را روی swagger تنظیم کنید.
{
"$schema": "https://json.schemastore.org/launchsettings.json",
"profiles": {
"http": {
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": false,
"applicationUrl": "http://localhost:5155",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
},
"https": {
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": true,
"launchUrl": "swagger",
"applicationUrl": "https://localhost:7284;http://localhost:5155",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
}
}
}


جایگزین‌های Swagger 🔄

ءSwagger دوباره اضافه شده، اما آیا جایگزین‌های بهتری وجود دارند؟
Scalar

ءScalar یک جایگزین برای Swagger است. این ابزار به شما اجازه می‌دهد endpointها را با یک رابط کاربری متفاوت تست کنید.

برای اضافه کردن Scalar، پکیج Scalar.AspNetCore را در پروژه‌ی Web API خود نصب کنید.

سپس، در فایل Program.cs، فضای نام Scalar.AspNetCore را import کنید و کد زیر را اضافه کنید:
// Program.cs
using Scalar.AspNetCore; // <-- Import this namespace

...

if (app.Environment.IsDevelopment())
{
app.MapOpenApi();
app.MapScalarApiReference(); // <-- Add this line
}

برای اجرای برنامه همراه با Scalar، فایل launchSettings.json را باز کنید.

مقدار launchBrowser را روی true بگذارید و launchUrl را برابر scalar/v1 تنظیم کنید.
{
"$schema": "https://json.schemastore.org/launchsettings.json",
"profiles": {
"http": {
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": false,
"applicationUrl": "http://localhost:5155",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
},
"https": {
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": true,
"launchUrl": "scalar/v1",
"applicationUrl": "https://localhost:7284;http://localhost:5155",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
}
}
}

همچنین می‌توانید تنظیمات Scalar را شخصی‌سازی کنید. در این مثال، عنوان روی "My API" تنظیم شده، تم برابر با ScalarTheme.Mars است و نوار کناری مخفی شده است.
// Program.cs
if (app.Environment.IsDevelopment())
{
app.MapOpenApi();
app.MapScalarApiReference(options =>
{
options.WithTitle("My API");
options.WithTheme(ScalarTheme.Mars);
options.HideSidebar();
});
}
Redoc 📘

ءRedoc مستندات API را تولید می‌کند، اما اجازه‌ی تست endpointها را نمی‌دهد.

برای اضافه کردن Redoc، پکیج Redoc.AspNetCore را در پروژه‌ی Web API خود نصب کنید.

سپس کد زیر را به Program.cs اضافه کنید:
// Program.cs
if (app.Environment.IsDevelopment())
{
app.MapOpenApi();
app.UseReDoc(options =>
{
options.RoutePrefix = "docs";
options.SpecUrl = "/openapi/v1.json";
});
}

برای اجرای برنامه همراه با Redoc، فایل launchSettings.json را باز کنید.

مقدار launchBrowser را روی true بگذارید و launchUrl را برابر با docs تنظیم کنید (یا هر مقداری که در options.RoutePrefix گذاشته‌اید).
{
"$schema": "https://json.schemastore.org/launchsettings.json",
"profiles": {
"http": {
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": false,
"applicationUrl": "http://localhost:5155",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
},
"https": {
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": true,
"launchUrl": "docs",
"applicationUrl": "https://localhost:7284;http://localhost:5155",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
}
}
}


جایگزین‌ها به‌جای استفاده از UI 🧩

ممکن است نخواهید یک رابط کاربری (UI) شامل تمام endpointهای خود را در دسترس قرار دهید.

فایل http. 📄

وقتی یک Web API می‌سازید، یک فایل http. به‌صورت پیش‌فرض ایجاد می‌شود. اگر از Visual Studio استفاده می‌کنید، این فایل به شما اجازه می‌دهد endpoint پیش‌فرض WeatherForecast را تست کنید. وقتی برنامه در حال اجراست، می‌توانید درخواست‌ها را ارسال کنید یا آن‌ها را مستقیماً دیباگ کنید.

در Visual Studio، اگر بخواهید endpointهای بیشتری از برنامه‌ی خود را به فایل http. اضافه کنید، به مسیر زیر بروید:
View > Other Windows > Endpoints Explorer

این بخش لیستی از تمام endpointهای موجود در Web API شما را نمایش می‌دهد.

با راست‌کلیک روی یک endpoint و انتخاب گزینه‌ی Generate request،
ءVisual Studio آن درخواست را به فایل http. اضافه می‌کند.

Postman 📬

گزینه‌ی دیگر استفاده از Postman است. شما می‌توانید endpointها را به‌صورت دستی اضافه کنید یا یک سند OpenAPI را ایمپورت کنید.

برای ایمپورت کردن یک سند OpenAPI، برنامه‌ی خود را اجرا کنید و به مسیر زیر بروید:
/openapi/v1.json

آدرس URL را کپی کنید.

در Postman، به بخش Import بروید و URL را Paste کنید.
ءPostman سپس تمام endpointها را ایمپورت می‌کند تا بتوانید آن‌ها را تست کنید. 🚀

🔖هشتگ‌ها:
#DotNet #OpenAPI #Postman #HttpFile #WebAPI #ASPNetCore #DeveloperTools
معرفی کتاب‌ها 📚

ء"Head First Design Pattern" نوشته‌ی Eric Freeman و Elisabeth Robson یک کتاب کلاسیک است که در سال 2004 منتشر شده و درباره‌ی design patternهای نرم‌افزاری است.

بعد از انتشار، این کتاب به‌خاطر سادگی و سبک ارائه‌ی سرگرم‌کننده‌اش بسیار محبوب شد. این کتاب با استفاده از تصاویر قوی و روایت داستانی، مفاهیم پیچیده‌ای مثل design pattern را ساده و قابل‌فهم می‌کند. همچنین برخلاف بسیاری از کتاب‌های دیگر، فقط 13 تا 15 design pattern پرکاربرد را پوشش می‌دهد. این کتاب بسیار beginner friendly است برای یادگیری design patternها. واقعاً دوست‌داشتنی است. 😍

نمای کلی در سطح بالا 🧠
🔹️Head First Design Pattern

هدف این کتاب معرفی design patternها در توسعه‌ی نرم‌افزار به شکلی جذاب و قابل‌دسترس است. این کتاب از رویکردی منحصربه‌فرد استفاده می‌کند که شامل تصاویر بصری، طنز و مثال‌های عملی برای انتقال مؤثر مفاهیم پیچیده است.

مرور کلی کتاب:
• Introduction to Design Patterns:
معرفی design patternها و توضیح اینکه چرا در توسعه‌ی نرم‌افزار ضروری هستند و چگونه به ایجاد کدی flexible، reusable و maintainable کمک می‌کنند.

• Understanding Design Principles:
قبل از ورود به patternها، اصولی مثل encapsulation، inheritance و polymorphism توضیح داده می‌شود.

• Categorization:
ابتدا creational patternها، سپس structural و در نهایت behavioral patternها بررسی می‌شوند.

• Real-World Examples and Case Studies:
مثال‌های واقعی برای کاربرد عملی patternها ارائه می‌شود.

• Anti-Patterns and Pitfalls:
به anti-patternها و اشتباهات رایج اشاره می‌شود.

• Design Patterns in Context:
بررسی جایگاه patternها در معماری نرم‌افزار.

• Review and Reinforcement:
تمرین‌ها، کوییزها و پازل‌ها برای تثبیت یادگیری. 🧩
نوشتن Middleware سفارشی در ASP.NET Core 🧩

ءMiddleware نرم‌افزاری است که در قالب یک pipeline داخل برنامه کنار هم قرار می‌گیرد تا درخواست‌ها (requests) و پاسخ‌ها (responses) را مدیریت کند.
ءASP.NET Core مجموعه‌ی غنی‌ای از middlewareهای داخلی (built-in) را ارائه می‌دهد، اما در بعضی سناریوها ممکن است بخواهید یک middleware سفارشی (custom middleware) بنویسید.

کلاس Middleware

ءMiddleware معمولاً داخل یک کلاس کپسوله می‌شود و از طریق یک extension method در دسترس قرار می‌گیرد. مثال زیر یک middleware درون‌خطی (inline) را نشان می‌دهد که culture مربوط به درخواست جاری را از روی query string تنظیم می‌کند:
using System.Globalization;

var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();

app.UseHttpsRedirection();

app.Use(async (context, next) =>
{
var cultureQuery = context.Request.Query["culture"];
if (!string.IsNullOrWhiteSpace(cultureQuery))
{
var culture = new CultureInfo(cultureQuery);

CultureInfo.CurrentCulture = culture;
CultureInfo.CurrentUICulture = culture;
}

// Call the next delegate/middleware in the pipeline.
await next(context);
});

app.Run(async (context) =>
{
await context.Response.WriteAsync(
$"CurrentCulture.DisplayName: {CultureInfo.CurrentCulture.DisplayName}");
});

app.Run();

ءMiddleware درون‌خطی بالا برای نمایش نحوه‌ی ساخت یک middleware با استفاده از متد Microsoft.AspNetCore.Builder.UseExtensions.Use استفاده شده است. این متد، یک delegate تعریف‌شده به‌صورت درون‌خطی را به pipeline درخواست‌های برنامه اضافه می‌کند.

متدهای Use

دو overload برای متد Use وجود دارد:

یکی که HttpContext و <Func<Task می‌گیرد. در این حالت <Func<Task بدون پارامتر فراخوانی می‌شود.

دیگری که HttpContext و RequestDelegate می‌گیرد. در این حالت RequestDelegate با ارسال HttpContext فراخوانی می‌شود.

استفاده از overload دوم ترجیح داده می‌شود چون باعث صرفه‌جویی در دو allocation داخلی در هر درخواست می‌شود. ⚡️

تست Middleware

می‌توانید middleware را با ارسال culture تست کنید. مثلاً:
https://localhost:5001/?culture=es-es


انتقال Middleware به یک کلاس

کد زیر همان middleware قبلی را داخل یک کلاس قرار می‌دهد:
using System.Globalization;

namespace Middleware.Example;

public class RequestCultureMiddleware
{
private readonly RequestDelegate _next;

public RequestCultureMiddleware(RequestDelegate next)
{
_next = next;
}

public async Task InvokeAsync(HttpContext context)
{
var cultureQuery = context.Request.Query["culture"];
if (!string.IsNullOrWhiteSpace(cultureQuery))
{
var culture = new CultureInfo(cultureQuery);

CultureInfo.CurrentCulture = culture;
CultureInfo.CurrentUICulture = culture;
}

// Call the next delegate/middleware in the pipeline.
await _next(context);
}
}


الزامات کلاس Middleware

کلاس middleware باید شامل موارد زیر باشد:

• یک constructor عمومی با پارامتر از نوع RequestDelegate
• یک متد عمومی به نام Invoke یا InvokeAsync که:
• مقدار بازگشتی آن Task باشد
• اولین پارامتر آن از نوع HttpContext باشد

پارامترهای اضافی در constructor و متد Invoke/InvokeAsync از طریق Dependency Injection (DI) مقداردهی می‌شوند. 💉

Extension Method برای Middleware

معمولاً یک extension method ساخته می‌شود تا middleware از طریق IApplicationBuilder قابل استفاده باشد:
using System.Globalization;

namespace Middleware.Example;

public class RequestCultureMiddleware
{
private readonly RequestDelegate _next;

public RequestCultureMiddleware(RequestDelegate next)
{
_next = next;
}

public async Task InvokeAsync(HttpContext context)
{
var cultureQuery = context.Request.Query["culture"];
if (!string.IsNullOrWhiteSpace(cultureQuery))
{
var culture = new CultureInfo(cultureQuery);

CultureInfo.CurrentCulture = culture;
CultureInfo.CurrentUICulture = culture;
}

await _next(context);
}
}

public static class RequestCultureMiddlewareExtensions
{
public static IApplicationBuilder UseRequestCulture(
this IApplicationBuilder builder)
{
return builder.UseMiddleware<RequestCultureMiddleware>();
}
}

استفاده از Middleware در Program.cs
using Middleware.Example;
using System.Globalization;

var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();

app.UseHttpsRedirection();

app.UseRequestCulture();

app.Run(async (context) =>
{
await context.Response.WriteAsync(
$"CurrentCulture.DisplayName: {CultureInfo.CurrentCulture.DisplayName}");
});

app.Run();


وابستگی‌های Middleware

ءMiddleware باید از اصل Explicit Dependencies Principle پیروی کند؛ یعنی وابستگی‌های خود را در constructor مشخص کند.
🔹️ءMiddleware فقط یک بار در طول عمر برنامه ساخته می‌شود. 🕒

🔹️ءMiddleware می‌تواند وابستگی‌های خود را از طریق DI در constructor دریافت کند. همچنین متد UseMiddleware می‌تواند پارامترهای اضافی هم بگیرد.

وابستگی‌های سطح درخواست (Per-request)

از آنجا که middleware در زمان startup ساخته می‌شود، طول عمر آن برابر طول عمر برنامه است. بنابراین سرویس‌های با Scoped lifetime که در constructor تزریق شوند، با بقیه سرویس‌های هر درخواست به اشتراک گذاشته نمی‌شوند.

برای اشتراک سرویس‌های scoped بین middleware و سایر بخش‌ها، باید آن‌ها را در متد InvokeAsync تزریق کنیم:
namespace Middleware.Example;

public class MyCustomMiddleware
{
private readonly RequestDelegate _next;

public MyCustomMiddleware(RequestDelegate next)
{
_next = next;
}

// IMessageWriter is injected into InvokeAsync
public async Task InvokeAsync(HttpContext httpContext, IMessageWriter svc)
{
svc.Write(DateTime.Now.Ticks.ToString());
await _next(httpContext);
}
}

public static class MyCustomMiddlewareExtensions
{
public static IApplicationBuilder UseMyCustomMiddleware(
this IApplicationBuilder builder)
{
return builder.UseMiddleware<MyCustomMiddleware>();
}
}

تست Middleware سفارشی
using Middleware.Example;
var builder = WebApplication.CreateBuilder(args);

builder.Services.AddScoped<IMessageWriter, LoggingMessageWriter>();

var app = builder.Build();

app.UseHttpsRedirection();

app.UseMyCustomMiddleware();

app.MapGet("/", () => "Hello World!");

app.Run();

تعریف Interface و Implementation
namespace Middleware.Example;

public interface IMessageWriter
{
void Write(string message);
}

public class LoggingMessageWriter : IMessageWriter
{
private readonly ILogger<LoggingMessageWriter> _logger;

public LoggingMessageWriter(ILogger<LoggingMessageWriter> logger) =>
_logger = logger;

public void Write(string message) =>
_logger.LogInformation(message);
}


جمع‌بندی ذهنی 🧠

ءMiddleware قلب pipeline در ASP.NET Core است.
🔹️می‌تواند درون‌خطی یا در قالب کلاس پیاده‌سازی شود
🔸️باید RequestDelegate و متد Invoke/InvokeAsync داشته باشد
🔹️برای وابستگی‌های scoped، تزریق باید در InvokeAsync انجام شود
🔸️بهترین ابزار برای پیاده‌سازی cross-cutting concernها مثل:

• Logging
• Authentication
• Localization
• Exception Handling

🔖هشتگ‌ها:
#ASPNETCore #Middleware #DotNet #Backend
میخوایم یکسری مطالب درمورد یکی از معروف‌ترین کتاب‌های حوزه‌ی نرم‌افزار داشته باشیم 📘

مهم‌ترین سرفصل‌هاش رو با هم برسی می‌کنیم، و هر نکته‌ای که به چشمم جالب و کاربردی بود رو باهاتون شیر می‌کنم😄

اگه دوست دارید خلاصه‌ی مفیدِ یه کتاب قطور رو بدون دردِ کمر و سردرد ،مفهومی یاد بگیرید، این پست‌ها احتمالاً به کارتون میان 😉
Heap و Stack Allocation در C# 🧠💾(بخش اول)

درک نحوه‌ی تخصیص (allocation) و مدیریت حافظه در #C برای بهینه‌سازی عملکرد (performance) برنامه و اطمینان از استفاده‌ی کارآمد از منابع بسیار حیاتی است.

در این راهنمای جامع، مفاهیم heap و stack allocation در #C را بررسی می‌کنیم؛ اینکه چگونه کار می‌کنند، چه زمانی استفاده می‌شوند، و چگونه بر عملکرد کد شما تأثیر می‌گذارند.

مقدمه‌ای بر Memory Allocation

مدیریت حافظه در #C اغلب توسط runtime مربوط به NET. انجام می‌شود؛ به این معنا که توسعه‌دهندگان در بیشتر مواقع نیازی به تخصیص و آزادسازی دستی حافظه ندارند.

با این حال، درک اینکه داده‌های شما چگونه و در کجا تخصیص داده می‌شوند — در stack یا در heap — می‌تواند به شما کمک کند کدی کارآمدتر و با performance بهتر بنویسید. ⚙️

در سی‌شارپ ، stack و heap دو ناحیه‌ی حافظه هستند که برای ذخیره‌سازی داده‌ها استفاده می‌شوند:

• ءstack برای static memory allocation استفاده می‌شود

• ءheap برای dynamic memory allocation استفاده می‌شود

ءStack Allocation چیست؟

ءstack ناحیه‌ای از حافظه است که value typeها و pointerهای reference type را ذخیره می‌کند.
این ساختار از مدل Last-In, First-Out (LIFO) پیروی می‌کند؛ به این معنی که آخرین داده‌ی تخصیص داده‌شده، اولین داده‌ای است که آزاد می‌شود.

به دلیل این ساختار، stack allocation بسیار سریع و کارآمد است. 🚀

ویژگی‌های Stack Allocation:

🔹️Automatic Memory Management:
داده‌ها به‌صورت خودکار هنگام خروج از scope آزاد می‌شوند

🔹️LIFO Structure:
استفاده از مدل LIFO باعث استفاده‌ی بهینه از حافظه می‌شود

🔹️Limited Size:
اندازه‌ی stack محدود است و برای objectهای بزرگ مناسب نیست

ءHeap Allocation چیست؟

ءheap برای dynamic memory allocation استفاده می‌شود.
اینجاست که reference typeها (مانند objectها، stringها و ...) تخصیص داده می‌شوند.

ءheap توسط Garbage Collector (GC) مدیریت می‌شود که به‌صورت دوره‌ای حافظه‌ای که دیگر استفاده نمی‌شود را آزاد می‌کند. ♻️

ویژگی‌های Heap Allocation:

🔹️Managed by Garbage Collector:
ء GC فرآیند آزادسازی حافظه را به‌صورت خودکار مدیریت می‌کند

🔹️Dynamic Allocation:
ءobjectها می‌توانند حتی پس از اتمام متدی که آن‌ها را ایجاد کرده، باقی بمانند

🔹️Larger Memory Pool:
ءheap می‌تواند نسبت به stack objectهای بسیار بزرگ‌تری را ذخیره کند

تفاوت بین Stack و Heap

تفاوت‌های کلیدی بین stack و heap allocation در #C حول محور نحوه و زمان تخصیص و آزادسازی حافظه می‌چرخند.

حافظه‌ی stack کارآمد است و برای داده‌های کوتاه‌عمر (short-lived) که scope آن‌ها محدود به function یا method است مناسب می‌باشد.

در مقابل، heap برای داده‌هایی استفاده می‌شود که نیاز دارند فراتر از scope یک method باقی بمانند؛ مانند instanceهای reference type.

ءstack مدیریت حافظه‌ی خودکار و بسیار سریعی را فراهم می‌کند، اما از نظر اندازه محدود است و نمی‌تواند ساختارهای داده‌ای بزرگ و پیچیده را مدیریت کند.

ءheap در حالی که انعطاف‌پذیری بیشتر و تخصیص حافظه‌ی بزرگ‌تری ارائه می‌دهد، نیازمند garbage collection است که مقداری overhead در performance ایجاد می‌کند. 📉

نحوه‌ی عملکرد Stack در #C: مثال

برای درک بهتر stack allocation، به مثال زیر توجه کنید:
public void StackExample()
{
int a = 5;
int b = 10;
int result = a + b;
}

در مثال بالا:
اعداد صحیح a، b و result روی stack تخصیص داده می‌شوند زیرا از نوع value type هستند

زمانی که متد StackExample به پایان می‌رسد، تمام حافظه‌ی استفاده‌شده توسط این متغیرها به‌صورت خودکار آزاد می‌شود
Local Variables و Stack Allocation

متغیرهای محلی (Local variables)، شامل value typeها و reference به objectها، روی stack ذخیره می‌شوند.
با این حال، referenceهای مربوط به objectها به heap اشاره می‌کنند؛ جایی که داده‌ی واقعی object در آن قرار دارد.

زمانی که یک value type بخشی از یک reference type باشد (برای مثال، یک فیلد داخل یک class)، آن value type به‌عنوان بخشی از object مربوط به reference type روی heap ذخیره می‌شود.

نحوه‌ی عملکرد Heap در #C: مثال
کد زیر را در نظر بگیرید:
public class Person
{
public string Name;
public int Age;
}

public void HeapExample()
{
Person person = new Person();
person.Name = "Alice";
person.Age = 30;
}

در این مثال: object از نوع Person روی heap ایجاد می‌شود زیرا یک reference type است.reference با نام person روی stack ذخیره می‌شود که به object روی heap اشاره می‌کند.
فیلدهای Name و Age نیز به‌عنوان بخشی از object مربوط به Person روی heap ذخیره می‌شوند

Value Typeها در مقابل Reference Typeها

درک value typeها و reference typeها به شفاف‌سازی نحوه‌ی استفاده از حافظه کمک می‌کند.

🔹️Value Types:
به‌صورت مستقیم در حافظه‌ی stack ذخیره می‌شوند. مانند int، float، bool و structهای سفارشی

🔹️Reference Types:
روی heap ذخیره می‌شوند، اما reference مربوط به آن‌ها روی stack قرار می‌گیرد. مانند classها، arrayها و stringها

مثال: Value Type در مقابل Reference Type
// Value type, allocated on the stack
int x = 10;

// Reference type, reference on stack, object on heap
Person p1 = new Person();

در مثال بالا، x روی stack ذخیره می‌شود زیرا یک value type است، در حالی که p1 روی heap ذخیره می‌شود زیرا یک reference type است.

Memory Management در .NET

ءruntime مربوط به NET. شامل یک Garbage Collector (GC) است که به مدیریت تخصیص و آزادسازی حافظه برای heap کمک می‌کند.

ءGC به‌صورت خودکار objectهایی که همچنان در حال استفاده هستند را ردیابی کرده و حافظه‌ای که دیگر مورد نیاز نیست را آزاد می‌کند. ♻️
Heap و Stack Allocation در C# 🧠💾(بخش دوم)
Generational Garbage Collection
ءGC در NET. برای بهینه‌سازی پرفورمنس، objectها را به سه نسل تقسیم می‌کند:

🔹️Generation 0:
ءobjectهای کوتاه‌عمر که معمولاً در methodها تخصیص داده می‌شوند

🔸️Generation 1:
ءobjectهای با طول عمر متوسط که از collection مربوط به Gen 0 عبور کرده‌اند

🔹️Generation 2:
ءobjectهای بلندعمر که معمولاً برای داده‌های سطح application استفاده می‌شوند

مثال: نحوه‌ی عملکرد GC
public void GarbageCollectionExample()
{
// Allocated on the heap (Generation 0)
Person p1 = new Person();
}

مثال Generation 0 (GarbageCollectionExample):

ءinstance مربوط به Person با نام p1 روی heap تخصیص داده شده و در Generation 0 آغاز می‌شود که برای objectهای کوتاه‌عمر در نظر گرفته شده است.

اگر پس از اتمام method هیچ reference دیگری به p1 وجود نداشته باشد، garbage collector ممکن است آن را در طی collection مربوط به Generation 0 آزاد کند.

public void Generation1Example()
{
List<int> numbers = new List<int>();
for (int i = 0; i < 1000; i++)
{
numbers.Add(i);
}
// 'numbers' will likely survive Gen 0 collection and move to Gen 1.
}


مثال Generation 1 (Generation1Example):

ء<List<int با نام numbers روی heap تخصیص داده می‌شود. از آنجا که شامل چندین مقدار بوده و برای مدت نسبتاً طولانی‌تری باقی می‌ماند (برای مثال، تا پایان method)، ممکن است از garbage collection مربوط به Generation 0 عبور کرده و به Generation 1 ارتقاء یابد.

ءGeneration 1 برای objectهایی است که به اندازه‌ی objectهای Generation 0 کوتاه‌عمر نیستند اما همچنان نیازی به باقی‌ماندن در طول کل عمر application ندارند.

public class CacheManager
{
private static Dictionary<string, string> _cache = new Dictionary<string, string>();

public static void AddItem(string key, string value)
{
_cache[key] = value;
}
}

public void Generation2Example()
{
CacheManager.AddItem("user1", "data1");
CacheManager.AddItem("user2", "data2");
// '_cache' is a static reference and is long-lived, likely residing in Generation 2.
}


مثال Generation 2 (Generation2Example):

کلاس CacheManager از یک Dictionary به‌صورت static استفاده می‌کند، که به این معناست داده‌های آن در طول چرخه‌ی عمر application باقی می‌مانند.
از آنجا که این dictionary (_cache) بلندعمر است، احتمالاً به Generation 2 ارتقاء می‌یابد که objectهایی را نگه‌داری می‌کند که انتظار می‌رود برای مدت طولانی باقی بمانند؛ مانند cacheها، داده‌های static یا objectهای singleton.

مشکلات رایج و Best Practiceها

🔹️Stack Overflow:
زمانی رخ می‌دهد که تعداد زیادی فراخوانی method تودرتو یا recursion بیش از حد وجود داشته باشد که منجر به اتمام حافظه‌ی stack می‌شود

🔸️Memory Leaks:
با اینکه NET. دارای GC است، memory leak همچنان می‌تواند رخ دهد اگر referenceها به‌صورت ناخواسته زنده نگه داشته شوند و مانع از جمع‌آوری آن‌ها توسط GC شوند
Best Practices

🔹️Minimize Long-Lived Objects:
از نگه‌داشتن referenceها بیش از حد لازم خودداری کنید

🔸️Use Structs Wisely:
تنها زمانی از struct استفاده کنید که type کوچک و کوتاه‌عمر باشد تا از تخصیص غیرضروری روی heap جلوگیری شود

🔹️Avoid Recursive Calls:
برای recursionهای عمیق، از روش‌های iterative استفاده کنید تا از stack overflow جلوگیری شود