Dependency Injection تمیز در ASP.NET Core برای تستپذیری بهتر ⚙️
یکی از مهمترین قدمها برای Clean Code در پروژههای ASP.NET Core این است که وابستگیها را مستقیم new نکنیم، بلکه از Dependency Injection استفاده کنیم. این کار هم کد را تمیزتر میکند، هم تستپذیری و امکان تغییر پیادهسازیها را بالا میبرد. 💡
فرض کنید در یک Web API کنترلری دارید که مستقیم وابسته به Service است:
این طراحی چند مشکل جدی دارد:
• کنترلر مستقیماً به
• تست Unit برای کنترلر سخت است، چون نمیتوان Mock تزریق کرد.
• در آینده اگر سرویس عوض شود (مثلاً کش اضافه شود)، باید خود کنترلر را تغییر بدهید.
راه تمیزتر این است که یک Interface تعریف کنید و آن را از DI Container تزریق کنید:
حالا کنترلر فقط Interface را میشناسد:
و در
مزیتها 🧩
• تستپذیری بالا: در تستها میتوان یک
• پیادهسازی قابلتعویض: بدون تغییر کنترلر میتوانید
• رعایت اصل D از SOLID (Dependency Inversion): وابستگی به Abstraction نه Concrete Class.
برای مطالعه بیشتر، مستندات رسمی مایکروسافت درباره DI در ASP.NET Core خیلی شفاف و بهروز است:
Microsoft Learn - Dependency injection in ASP.NET Core
قدمبهقدم اگر وابستگیهای مستقیم را به شکل Interface + DI تبدیل کنید، معماری اپلیکیشن تمیزتر، منعطفتر و آمادهٔ رشد میشود. 🚀
🔖 #CSharp #سی_شارپ #C# #_NET #ASP_NET_Core #Dependency_Injection #SOLID #Clean_Code
👤 Developix
💎 Channel: @DevelopixCSharp
یکی از مهمترین قدمها برای Clean Code در پروژههای ASP.NET Core این است که وابستگیها را مستقیم new نکنیم، بلکه از Dependency Injection استفاده کنیم. این کار هم کد را تمیزتر میکند، هم تستپذیری و امکان تغییر پیادهسازیها را بالا میبرد. 💡
فرض کنید در یک Web API کنترلری دارید که مستقیم وابسته به Service است:
public class OrdersController : ControllerBase
{
private readonly OrderService _orderService;
public OrdersController()
{
_orderService = new OrderService();
}
[HttpGet("api/orders/{id}")]
public async Task<IActionResult> Get(int id)
{
var order = await _orderService.GetByIdAsync(id);
return order is null ? NotFound() : Ok(order);
}
}
این طراحی چند مشکل جدی دارد:
• کنترلر مستقیماً به
OrderService Couple شده است.• تست Unit برای کنترلر سخت است، چون نمیتوان Mock تزریق کرد.
• در آینده اگر سرویس عوض شود (مثلاً کش اضافه شود)، باید خود کنترلر را تغییر بدهید.
راه تمیزتر این است که یک Interface تعریف کنید و آن را از DI Container تزریق کنید:
public interface IOrderService
{
Task<Order?> GetByIdAsync(int id);
}
public class OrderService : IOrderService
{
private readonly AppDbContext _db;
public OrderService(AppDbContext db)
{
_db = db;
}
public Task<Order?> GetByIdAsync(int id)
=> _db.Orders.FindAsync(id).AsTask();
}
حالا کنترلر فقط Interface را میشناسد:
[ApiController]
[Route("api/[controller]")]
public class OrdersController : ControllerBase
{
private readonly IOrderService _orderService;
public OrdersController(IOrderService orderService)
{
_orderService = orderService;
}
[HttpGet("{id}")]
public async Task<IActionResult> Get(int id)
{
var order = await _orderService.GetByIdAsync(id);
return order is null ? NotFound() : Ok(order);
}
}
و در
Program.cs (یا Startup قدیمیها):builder.Services.AddScoped<IOrderService, OrderService>();
مزیتها 🧩
• تستپذیری بالا: در تستها میتوان یک
FakeOrderService یا Mock تزریق کرد.• پیادهسازی قابلتعویض: بدون تغییر کنترلر میتوانید
CachingOrderService اضافه کنید.• رعایت اصل D از SOLID (Dependency Inversion): وابستگی به Abstraction نه Concrete Class.
برای مطالعه بیشتر، مستندات رسمی مایکروسافت درباره DI در ASP.NET Core خیلی شفاف و بهروز است:
Microsoft Learn - Dependency injection in ASP.NET Core
قدمبهقدم اگر وابستگیهای مستقیم را به شکل Interface + DI تبدیل کنید، معماری اپلیکیشن تمیزتر، منعطفتر و آمادهٔ رشد میشود. 🚀
🔖 #CSharp #سی_شارپ #C# #_NET #ASP_NET_Core #Dependency_Injection #SOLID #Clean_Code
👤 Developix
💎 Channel: @DevelopixCSharp
👍3❤1
در نمونهکد بالا متد GetUsersByRoleAsync در نسخهٔ اول بهخاطر فچکردن همه رکوردها در حافظه و بعد فیلتر کردن با
🔖 #CSharp #سی_شارپ
👤 Developix
💎 Channel: @DevelopixCSharp
Where روی List<T>، هم Memory مصرفی را بالا میبرد و هم روی دیتابیسهای بزرگ باعث افت شدید Performance میشود. در مقابل، در نسخهٔ دوم با نگه داشتن Query به صورت IQueryable<User> و استفاده از Where قبل از ToListAsync، فیلتر روی خود دیتابیس انجام میشود و فقط دادهٔ موردنیاز لود میشود. در سناریوهایی که جدولهای بزرگ دارید یا روی Cloud Database کار میکنید، این الگو باعث کاهش چشمگیر حجم دادهٔ منتقلشده و بهبود زمان پاسخ متدهای Repository خواهد شد. همچنین با برگرداندن IReadOnlyList<User> به جای List<User>، به Contract بهتری از نظر Encapsulation و جلوگیری از Mutation ناخواسته میرسید.🔖 #CSharp #سی_شارپ
👤 Developix
💎 Channel: @DevelopixCSharp
👍7
⚡ استفاده حرفهای از ConfigureAwait(false) در async/await
خیلی وقتها توی کتابخونهها و لایههای غیر UI، از async/await استفاده میشه ولی ناخواسته Performance و Scale خراب میشه؛ فقط به خاطر برنگشتنِ درست به Thread قدیمی.
نکتهٔ مهم اینه: در کدهای غیر UI (مثل لایهٔ Data Access، Service، Library) معمولاً نیازی نیست بعد از
✅ ایده:
هرجا در Library Code هستیم و بعد از await به Context قبلی نیاز نداریم، از
- Deadlockهای کلاسیک در ASP.NET قدیمی کمتر بشه
- Performance بهتر بشه چون نیاز به Capture/Restore Context نیست
مثال ساده در یک Service غیر UI:
📌 نکات مهم:
- توی UI (WPF, WinForms, MAUI) یا Razor Pages که بعد از await میخوای کنترلهای UI رو آپدیت کنی، از ConfigureAwait(false) استفاده نکن چون به Thread UI برنمیگردی.
- در ASP.NET Core بهخاطر نداشتن SynchronizationContext کلاسیک، مشکل Deadlock خیلی کمتره؛ ولی هنوز هم برای Libraryهای عمومی، استفاده از
- سعی کن این الگو رو توی کل لایههای Library یکدست نگه داری تا رفتار کد قابلپیشبینی بمونه.
برای توضیح رسمی مایکروسافت دربارهٔ async/await و Context:
مستندات async در C# (Microsoft Docs)
امتحان این روش توی Serviceها و Libraryها، مخصوصاً زیر لود بالا، خیلی زود توی لاگها و Performance خودش رو نشون میده 🚀
🔖 #CSharp #سی_شارپ #CSharp #async #ConfigureAwait #Performance #_NET
👤 Developix
💎 Channel: @DevelopixCSharp
خیلی وقتها توی کتابخونهها و لایههای غیر UI، از async/await استفاده میشه ولی ناخواسته Performance و Scale خراب میشه؛ فقط به خاطر برنگشتنِ درست به Thread قدیمی.
نکتهٔ مهم اینه: در کدهای غیر UI (مثل لایهٔ Data Access، Service، Library) معمولاً نیازی نیست بعد از
await برگردیم به همون SynchronizationContext قبلی. اینجاست که 🧠 ConfigureAwait(false) کمک میکنه.✅ ایده:
هرجا در Library Code هستیم و بعد از await به Context قبلی نیاز نداریم، از
ConfigureAwait(false) استفاده کنیم تا:- Deadlockهای کلاسیک در ASP.NET قدیمی کمتر بشه
- Performance بهتر بشه چون نیاز به Capture/Restore Context نیست
مثال ساده در یک Service غیر UI:
public async Task<UserDto> GetUserAsync(int id)
{
// فرض: این متد توی لایه Service یا Repository صدا زده میشه
using var client = new HttpClient();
var response = await client
.GetAsync($"https://api.example.com/users/{id}")
.ConfigureAwait(false);
response.EnsureSuccessStatusCode();
var json = await response.Content
.ReadAsStringAsync()
.ConfigureAwait(false);
return JsonSerializer.Deserialize<UserDto>(json)!;
}
📌 نکات مهم:
- توی UI (WPF, WinForms, MAUI) یا Razor Pages که بعد از await میخوای کنترلهای UI رو آپدیت کنی، از ConfigureAwait(false) استفاده نکن چون به Thread UI برنمیگردی.
- در ASP.NET Core بهخاطر نداشتن SynchronizationContext کلاسیک، مشکل Deadlock خیلی کمتره؛ ولی هنوز هم برای Libraryهای عمومی، استفاده از
ConfigureAwait(false) یک Practice خوب حساب میشه.- سعی کن این الگو رو توی کل لایههای Library یکدست نگه داری تا رفتار کد قابلپیشبینی بمونه.
برای توضیح رسمی مایکروسافت دربارهٔ async/await و Context:
مستندات async در C# (Microsoft Docs)
امتحان این روش توی Serviceها و Libraryها، مخصوصاً زیر لود بالا، خیلی زود توی لاگها و Performance خودش رو نشون میده 🚀
🔖 #CSharp #سی_شارپ #CSharp #async #ConfigureAwait #Performance #_NET
👤 Developix
💎 Channel: @DevelopixCSharp
🔥3
این کد C# را در نظر بگیرید:
در این پیادهسازی از static HttpClient استفاده شده، اما همچنان یک Memory Leak / Thread-Safety Anti Pattern جدی در نحوه تنظیم
چه چیزی باید در این کلاس اصلاح شود تا استفاده از HttpClient هم ایمنتر و هم بدون این Anti Pattern باشد؟
🔖 #CSharp #سی_شارپ
👤 Developix
💎 Channel: @DevelopixCSharp
public class UserService
{
private static HttpClient _client = new HttpClient();
public async Task<string> GetUserNameAsync(int id)
{
_client.BaseAddress = new Uri("https://api.example.com/");
_client.DefaultRequestHeaders.Clear();
_client.DefaultRequestHeaders.Add("X-Request-Id", Guid.NewGuid().ToString());
var response = await _client.GetAsync($"users/{id}");
response.EnsureSuccessStatusCode();
return await response.Content.ReadAsStringAsync();
}
}
در این پیادهسازی از static HttpClient استفاده شده، اما همچنان یک Memory Leak / Thread-Safety Anti Pattern جدی در نحوه تنظیم
BaseAddress و DefaultRequestHeaders وجود دارد که در سناریوهای چند-ترد یا چند-ریکوئست میتواند مشکلساز شود.چه چیزی باید در این کلاس اصلاح شود تا استفاده از HttpClient هم ایمنتر و هم بدون این Anti Pattern باشد؟
🔖 #CSharp #سی_شارپ
👤 Developix
💎 Channel: @DevelopixCSharp
👍3❤1
خیلی از Memory Leakهای ریز تو پروژههای #CSharp از یه چیز ساده شروع میشن: فراموشکردن Dispose روی resourceهایی مثل
تو داتنت جدید، using راحتتر و خواناتر شده و اگر درست استفاده بشه، هم کد تمیزتر میشه هم از نشت حافظه و handleها جلوگیری میکنه. 👇
۱️⃣ الگوی قدیمی using بلاکی
اینجا بهمحض خروج از بلاک، هر دو object بهصورت خودکار Dispose میشن.
۲️⃣ الگوی جدید using declaration (از C# 8 به بعد)
برای کاهش nesting و خوانایی بیشتر:
تو این حالت، scopeِ
۳️⃣ نکته مهم در متدهای طولانی
اگر متد خیلی طولانیه و resource فقط تو یه بخش کوچیک لازم میشه، بهتره همون الگوی بلاکی رو نگه داری تا resource زودتر آزاد بشه و تا آخر متد تو حافظه نمونه.
۴️⃣ async و IAsyncDisposable
برای کلاسهایی که
اینجا در انتهای scope، بهجای
جمعبندی: استفاده درست از using (بهخصوص
🔗 مرجع رسمی Microsoft Docs:
https://learn.microsoft.com/dotnet/csharp/language-reference/statements/using
🔖 #CSharp #سی_شارپ #C# #using #dispose #memory #performance #_NET
👤 Developix
💎 Channel: @DevelopixCSharp
HttpClient، SqlConnection، فایلها و… 😅تو داتنت جدید، using راحتتر و خواناتر شده و اگر درست استفاده بشه، هم کد تمیزتر میشه هم از نشت حافظه و handleها جلوگیری میکنه. 👇
۱️⃣ الگوی قدیمی using بلاکی
using (var stream = File.OpenRead("data.json"))
using (var reader = new StreamReader(stream))
{
var content = reader.ReadToEnd();
Console.WriteLine(content);
}
اینجا بهمحض خروج از بلاک، هر دو object بهصورت خودکار Dispose میشن.
۲️⃣ الگوی جدید using declaration (از C# 8 به بعد)
برای کاهش nesting و خوانایی بیشتر:
using var stream = File.OpenRead("data.json");
using var reader = new StreamReader(stream);
var content = reader.ReadToEnd();
Console.WriteLine(content);
// در انتهای متد، خودشون Dispose میشن
تو این حالت، scopeِ
using var کل متده؛ یعنی پایان متد = زمان Dispose. این الگو توی متدهای کوتاه و تمیز عالی جواب میده و از بلاکهای تو در تو کم میکنه. 💡۳️⃣ نکته مهم در متدهای طولانی
اگر متد خیلی طولانیه و resource فقط تو یه بخش کوچیک لازم میشه، بهتره همون الگوی بلاکی رو نگه داری تا resource زودتر آزاد بشه و تا آخر متد تو حافظه نمونه.
۴️⃣ async و IAsyncDisposable
برای کلاسهایی که
IAsyncDisposable رو پیادهسازی میکنن (مثلاً بعضی clientهای شبکه):await using var client = new SomeAsyncClient();
await client.DoWorkAsync();
اینجا در انتهای scope، بهجای
Dispose، متد DisposeAsync صدا زده میشه و resourceها بهشکل async آزاد میشن.جمعبندی: استفاده درست از using (بهخصوص
using var و await using) یه راه خیلی ساده و کاربردی برای جلوگیری از Memory Leak و تمیز نگهداشتن کده. چند جای پروژه رو که resource سنگین دارن چک و این الگو رو اعمال کن؛ تأثیرش تو performance و پایداری سرویس کاملاً قابل لمسه. 🚀🔗 مرجع رسمی Microsoft Docs:
https://learn.microsoft.com/dotnet/csharp/language-reference/statements/using
🔖 #CSharp #سی_شارپ #C# #using #dispose #memory #performance #_NET
👤 Developix
💎 Channel: @DevelopixCSharp
❤1
❓ در نمونه کد بالا در نسخهٔ اول برای محاسبهٔ میانگین یک کالکشن از اعداد، تمام دادهها دو بار پیمایش و چندین بار boxing/unboxing انجام میشود که هم روی کارایی و هم روی مصرف مموری در سناریوهای پردازش حجم بالای داده تأثیر منفی دارد. با استفاده از Generic و Delegate مناسب (مثل
🔖 #CSharp #سی_شارپ
👤 Developix
💎 Channel: @DevelopixCSharp
Func<T, double>) میتوان منطق را طوری پیادهسازی کرد که هم type-safe باشد و هم فقط یک بار روی دادهها پیمایش شود. کدام نسخه از این پیادهسازی در سناریوهای real-world (مثلاً پردازش لاگ، دادههای سنسور یا تراکنشها) مناسبتر است و چرا استفاده از object و is چکهای پیاپی میتواند به یک Anti Pattern در طراحی APIهای عمومی تبدیل شود؟🔖 #CSharp #سی_شارپ
👤 Developix
💎 Channel: @DevelopixCSharp
👎1
🕒 NodaTime؛ خداحافظ DateTime گیجکننده
کار با
اینجا NodaTime وارد میشه؛ یک کتابخانه قدرتمند برای مدیریت زمان و تاریخ که مدلش خیلی شفافتر از
چرا NodaTime؟
• تایپهای واضح مثل
• پشتیبانی دقیق از IANA Time Zone
• جلوگیری از باگهای زمانبندی، مخصوصاً اطراف تغییر ساعت (DST)
• API تمیز و قابل تست؛ مخصوصاً برای Domain-Driven Design
نصب با NuGet:
نمونه استفاده ساده:
برای هر سرویسی که زمان و زمانبندی توش مهمه، همراه کردنش با NodaTime جلوی کلی Memory Bug و رفتار عجیب رو میگیره. پیشنهاد میشه حتماً تو پروژه بعدی امتحان بشه و کمکم
مستندات رسمی:
Website & Docs
GitHub
🔖 #CSharp #سی_شارپ #CSharp #NodaTime #_NET #Time #DateTime #Backend
👤 Developix
💎 Channel: @DevelopixCSharp
کار با
DateTime و TimeZone در .NET مخصوصاً در پروژههای چندمنطقهای واقعاً دردسرسازه؛ از UTC و Local گرفته تا DST و Offsetهای مختلف. اینجا NodaTime وارد میشه؛ یک کتابخانه قدرتمند برای مدیریت زمان و تاریخ که مدلش خیلی شفافتر از
DateTime پیشفرضه و توی پروژههای مالی، رزرو، لاگینگ و سرویسهای بینالمللی عالی جواب میده. 🚀چرا NodaTime؟
• تایپهای واضح مثل
Instant، LocalDate، LocalDateTime، ZonedDateTime• پشتیبانی دقیق از IANA Time Zone
• جلوگیری از باگهای زمانبندی، مخصوصاً اطراف تغییر ساعت (DST)
• API تمیز و قابل تست؛ مخصوصاً برای Domain-Driven Design
نصب با NuGet:
dotnet add package NodaTime
نمونه استفاده ساده:
using NodaTime;
var now = SystemClock.Instance.GetCurrentInstant();
var tz = DateTimeZoneProviders.Tzdb["Europe/Berlin"];
var zoned = now.InZone(tz);
LocalDate invoiceDate = new(2026, 1, 26);
LocalDate dueDate = invoiceDate.PlusDays(14);
برای هر سرویسی که زمان و زمانبندی توش مهمه، همراه کردنش با NodaTime جلوی کلی Memory Bug و رفتار عجیب رو میگیره. پیشنهاد میشه حتماً تو پروژه بعدی امتحان بشه و کمکم
DateTime خام رو کنار بذاری. 💡مستندات رسمی:
Website & Docs
GitHub
🔖 #CSharp #سی_شارپ #CSharp #NodaTime #_NET #Time #DateTime #Backend
👤 Developix
💎 Channel: @DevelopixCSharp
👍4
در کد زیر تلاش شده یک Singleton ساده پیادهسازی شود، اما هم از نظر Thread-Safety و هم از نظر الگوی طراحی، چند مشکل جدی دارد و در سناریوهای Multi-Thread ممکن است چند نمونه از کلاس ساخته شود.
چه چیزی باید در این پیادهسازی اصلاح شود تا الگوی Singleton بهصورت ایمن و صحیح در سیشارپ پیادهسازی شود؟
🔖 #CSharp #سی_شارپ
👤 Developix
💎 Channel: @DevelopixCSharp
چه چیزی باید در این پیادهسازی اصلاح شود تا الگوی Singleton بهصورت ایمن و صحیح در سیشارپ پیادهسازی شود؟
🔖 #CSharp #سی_شارپ
👤 Developix
💎 Channel: @DevelopixCSharp
در نمونهکد بالا در نسخهی اشتباه، بهجای استفاده از LINQ و عملگرهای آماده برای حذف عناصر تکراری، بهصورت دستی روی لیست حلقه زده شده و برای هر آیتم یک
کدام رویکرد را برای سناریوهای Real-World با حجم داده بالا منطقیتر میدانید و چرا در Collections بزرگ، پرهیز از حلقههای تو در تو (Nested) تا این حد اهمیت دارد؟
🔖 #CSharp #سی_شارپ
👤 Developix
💎 Channel: @DevelopixCSharp
Any() جداگانه روی result صدا زده شده است؛ این کار در سناریوهایی که تعداد رکوردها زیاد است، باعث پیچیدگی زمانی O(n²)، افزایش فشار روی GC و کاهش کارایی میشود. در نسخهی اصلاحشده با استفاده از GroupBy و انتخاب آخرین آیتم هر گروه (براساس Timestamp)، هم کد خواناتر شده و هم فقط یک بار روی مجموعه اصلی پیمایش انجام میشود که در سناریوهای پردازش لاگ یا Events حجیم، مصرف CPU را بهطور محسوسی کاهش میدهد.کدام رویکرد را برای سناریوهای Real-World با حجم داده بالا منطقیتر میدانید و چرا در Collections بزرگ، پرهیز از حلقههای تو در تو (Nested) تا این حد اهمیت دارد؟
🔖 #CSharp #سی_شارپ
👤 Developix
💎 Channel: @DevelopixCSharp
❤1👍1
Forwarded from ابر ویراک
ویراک کلود | تجربهی ابری بدون محدودیت ☁️🚀
🎁 ۲۰٪ شارژ هدیه روی اولین واریزی
مزیتهایی که واقعاً فرق میسازن:
⚡️ پهنای باند 980 مگ
🌐با IP مازاد
⏱️ راهاندازی فوری
💳 پرداخت ساعتی
🛡 پشتیبانی ۲۴/۷
💬 هر سوالی داشتی، تیم ما کنارتـه
✅ مناسب استارتاپها، تیمهای فنی، تا سازمانهای بزرگ
همین الان شروع کن و با حداکثر منابع، سریعتر رشد کن 👇🏻
📞 تماس سریع: 02191555530
🔗 https://virakcloud.com
🎁 ۲۰٪ شارژ هدیه روی اولین واریزی
مزیتهایی که واقعاً فرق میسازن:
⚡️ پهنای باند 980 مگ
🌐با IP مازاد
⏱️ راهاندازی فوری
💳 پرداخت ساعتی
🛡 پشتیبانی ۲۴/۷
💬 هر سوالی داشتی، تیم ما کنارتـه
✅ مناسب استارتاپها، تیمهای فنی، تا سازمانهای بزرگ
همین الان شروع کن و با حداکثر منابع، سریعتر رشد کن 👇🏻
📞 تماس سریع: 02191555530
🔗 https://virakcloud.com