ASP.NET Core Middleware 🧩
ءMiddleware نرمافزاری است که در یک pipeline برنامه کنار هم قرار میگیرد تا درخواستها و پاسخها را مدیریت کند. هر کامپوننت:
تصمیم میگیرد که آیا درخواست را به کامپوننت بعدی در pipeline ارسال کند یا نه. 🔀
میتواند قبل و بعد از کامپوننت بعدی، پردازش انجام دهد. ⏱️
برای ساختن request pipeline از Request Delegateها استفاده میشود. این delegateها هر درخواست HTTP را مدیریت میکنند.
ءRequest delegateها با استفاده از متدهای extension زیر پیکربندی میشوند:
• Run
• Map
• Use
یک request delegate میتواند:
• بهصورت in-line و با یک متد anonymous تعریف شود (که به آن in-line middleware میگویند)،
• یا در قالب یک کلاس قابل استفاده مجدد تعریف شود.
این کلاسهای reusable و متدهای anonymous در واقع همان middleware یا middleware component هستند.
هر middleware در pipeline مسئول است یا کامپوننت بعدی را فراخوانی کند، یا pipeline را short-circuit کند.
وقتی یک middleware pipeline را short-circuit میکند، به آن terminal middleware گفته میشود،
چون جلوی ادامهی پردازش توسط middlewareهای بعدی را میگیرد.
ساخت middleware pipeline با WebApplication 🛠
ءrequest pipeline در ASP.NET Core شامل یک دنباله از request delegateها است که یکی پس از دیگری فراخوانی میشوند.
⚡️ ASP.NET Core Request Delegates و Pipeline
هر delegate میتواند قبل و بعد از delegate بعدی عملیات انجام دهد.
توصیه: exception-handling delegateها را اوایل pipeline قرار دهید تا بتوانند خطاهای رخ داده در مراحل بعدی را بگیرند.
سادهترین اپلیکیشن ASP.NET Core 🟢
در سادهترین حالت، یک single request delegate تعریف میشود که همه درخواستها را هندل میکند.
در این حالت، pipeline واقعی وجود ندارد.
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.Run(async context =>
{
await context.Response.WriteAsync("Hello world!");
});
app.Run();
زنجیره کردن چند delegate با Use 🔗
ءnext نمایانگر delegate بعدی در pipeline است.
میتوان pipeline را با عدم فراخوانی next کوتاه کرد (Short-circuit).
معمولاً میتوان کارها را قبل و بعد از next انجام داد:
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.Use(async (context, next) =>
{
// کاری که میتواند Response بنویسد
await next.Invoke();
// کارهای logging یا دیگر عملیات غیر از نوشتن Response
});
app.Run(async context =>
{
await context.Response.WriteAsync("Hello from 2nd delegate.");
});
app.Run();
Short-Circuiting Pipeline ⚡️
اگر یک delegate درخواست را به delegate بعدی ندهد، pipeline short-circuit میشود.
کاربرد: جلوگیری از کار غیرضروری
مثال: Static File Middleware میتواند terminal middleware باشد و پس از پردازش فایل، pipeline را کوتاه کند.
توجه: Middlewareهایی که قبل از terminal middleware آمدهاند، هنوز بعد از next.Invoke کد اجرا میکنند.
⚠️ هشدار مهم
بعد یا هنگام ارسال Response به کلاینت، next.Invoke را فراخوانی نکنید!
تغییر header یا status code بعد از شروع Response → Exception
نوشتن به body بعد از next ممکن است:
باعث violation پروتکل شود (مثلاً نوشتن بیشتر از Content-Length)
فرمت body خراب شود (مثلاً HTML footer در فایل CSS)
نکته: HasStarted میتواند کمک کند بررسی کنید که آیا headers یا body قبلاً ارسال شدهاند یا نه.
Run delegates🏁
ءRun delegateها پارامتر next دریافت نمیکنند.
اولین Run delegate همیشه terminal است و pipeline را خاتمه میدهد.
ءRun یک convention است. بعضی از middleware componentها ممکن است متدهایی مثل Run[Middleware] ارائه دهند که در انتهای pipeline اجرا میشوند:
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.Use(async (context, next) =>
{
// Do work that can write to the Response.
await next.Invoke();
// Do logging or other work that doesn't write to the Response.
});
app.Run(async context =>
{
await context.Response.WriteAsync("Hello from 2nd delegate.");
});
app.Run();
در مثال بالا، Run delegate عبارت "Hello from 2nd delegate." را در response مینویسد و سپس pipeline را خاتمه میدهد.
اگر بعد از Run delegate، یک Use یا Run دیگر اضافه شود، دیگر فراخوانی نخواهد شد. 🛑
ترتیب 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 اضافه کنید:
نقش هر analyzer:
روی کیفیت کد، آسیبپذیریهای امنیتی و code smell تمرکز دارد. متدهای پیچیده، کدهای تکراری و باگهای احتمالی را شناسایی میکند.
مشکلات performance، ضعفهای امنیتی و استفادهی نادرست از APIها را پیدا میکند. صدها قانون برای async/await، LINQ، string handling و موارد دیگر دارد.
پیشنهادهای refactoring و تحلیل کد ارائه میدهد و کمک میکند کد تمیزتر و idiomaticتری در #C بنویسید.
درست نوشتن تستها را enforce میکند و اشتباهات رایج مثل نبود assertion یا attribute اشتباه را شناسایی میکند.
تنظیم IncludeAssets باعث میشود این analyzerها فقط در زمان کامپایل اجرا شوند و وارد خروجی نهایی برنامه نشوند. 📦
با فعال بودن TreatWarningsAsErrors، اگر هر analyzer مشکلی پیدا کند، build شکست میخورد.
شاید سختگیرانه به نظر برسد، اما جلوی انباشته شدن technical debt را میگیرد.
شما همیشه باید تلاش کنید پروژهتان صفر هشدار داشته باشد. بسیاری از توسعهدهندهها هشدارها را نادیده میگیرند، اما هشدارها معمولاً نشانهی مشکلات واقعی هستند که بعداً تبدیل به باگ میشوند. 🐞
ءAnalyzerها مشکلات را شناسایی میکنند، اما شما همچنین باید مشخص کنید کدام قوانین برای تیم شما مهم هستند و میزان سختگیری آنها چقدر باشد.
فایل editorconfig. استانداردهای کدنویسی را تعریف میکند و سطح severity هر قانون analyzer را مشخص میکند. شما میتوانید قوانین را بهصورت error، warning، suggestion علامتگذاری کنید یا کاملاً غیرفعالشان کنید.
این فایل را در همان دایرکتوری فایل sln. قرار دهید. همهی توسعهدهندگان تیم شما، بدون توجه به اینکه از چه IDEای استفاده میکنند، بهصورت خودکار از یک مجموعه قوانین یکسان پیروی خواهند کرد. 🤝
یک فایل پایهی editorconfig. برای شروع:
<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
میخوایم یکسری مطالب درمورد یکی از معروفترین کتابهای حوزهی نرمافزار داشته باشیم 📘
مهمترین سرفصلهاش رو با هم برسی میکنیم، و هر نکتهای که به چشمم جالب و کاربردی بود رو باهاتون شیر میکنم😄
اگه دوست دارید خلاصهی مفیدِ یه کتاب قطور رو بدون دردِ کمر و سردرد ،مفهومی یاد بگیرید، این پستها احتمالاً به کارتون میان 😉
مهمترین سرفصلهاش رو با هم برسی میکنیم، و هر نکتهای که به چشمم جالب و کاربردی بود رو باهاتون شیر میکنم😄
اگه دوست دارید خلاصهی مفیدِ یه کتاب قطور رو بدون دردِ کمر و سردرد ،مفهومی یاد بگیرید، این پستها احتمالاً به کارتون میان 😉
"Domain Driven Design"
📌سرفصل : ایزوله کردن دامنه
📌سرفصل : ایزوله کردن دامنه
Telegraph
Isolating the Domain
بخشی از نرمافزار که بهطور مشخص مسائل مربوط به domain را حل میکند، معمولاً فقط بخش کوچکی از کل سیستم نرمافزاری را تشکیل میدهد، هرچند اهمیت آن بهمراتب بیشتر از اندازهٔ آن است. برای بهکارگیری بهترین تفکر خود، لازم است بتوانیم به عناصر model نگاه کنیم…
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هایی که همچنان در حال استفاده هستند را ردیابی کرده و حافظهای که دیگر مورد نیاز نیست را آزاد میکند. ♻️