Developer Advocate
رفتن به کانال در Telegram
1 035
مشترکین
اطلاعاتی وجود ندارد24 ساعت
اطلاعاتی وجود ندارد7 روز
-130 روز
آرشیو پست ها
1 035
📌 Dependency Injection Scopes در .NET: عمیقتر از سطح Singleton, Scoped, Transient
درک دقیق تفاوت Scoped، Singleton و Transient در DI، برای طراحی معماری مدرن ضروری است:
🔹 Singleton
یک instance برای کل عمر اپلیکیشن ایجاد میشود؛ همهی درخواستها، همان آبجکت را میبینند.
مناسب برای state غیرقابل تغییر، سرویسهای cache یا connection poolها.
دقت کنید: اگر Singleton وابسته به یک Scoped باشد، Exception میخورید (capturing scoped dependencies).
services.AddSingleton<IMyService, MyService>();
🔹 Scoped
برای هر scope یک instance ساخته میشود؛ مثلا در ASP.NET Core، هر HTTP request یک اسکوپ جدید است و سرویس Scoped تا پایان درخواست حفظ میشود.
ایدهآل برای سرویسهایی مثل Unit of Work یا Contextهایی که باید در طول یک request پابرجا بمانند و بعد dispose شوند.
services.AddScoped<IMyService, MyService>();
🔹 Transient
هر بار درخواست شود، یک instance جدید میسازد؛ اگر در طول یک request چند بار resolve کنید، هر بار شیء مجزا دریافت میکنید.
مناسب برای stateless serviceها یا زمانی که نیاز به instance ایزوله دارید (البته مراقب performance باشید).
services.AddTransient<IMyService, MyService>();
👨💻 Best Practice:
هرگز یک وابستگی Scoped را به یک Singleton inject نکنید (known as “capturing a scoped service in the root”). هر scope باید چرخه زندگی خودش را مستقل از Singletonها مدیریت کند.
✳ توجه:
- Singleton ➡ عمر کل اپلیکیشن
- Scoped ➡ عمر هر HTTP request (یا custom scope)
- Transient ➡ عمر هر resolve
یک ابزار مفهومی برای درک بهتر:
📊 Singleton: Shared globally
📊 Scoped: Shared per-request
📊 Transient: Always new
این تفاوتها پایهی تصمیمگیری صحیح در معماری Dependency Injection هستند؛ بهدرستی انتخاب کنید تا آیندهی سیستمتان پایدار و مقیاسپذیر باشد.
@DeveloperAdvocate 🥑1 035
🌐 الگوهای معماری: Clean Architecture در داتنت مدرن
اگر هنوز معماری Clean رو صرفاً یک buzzword میدونید، وقتشه جدیتر بهش نگاه کنید. Clean Architecture با جدا کردن وظایف (Separation of Concerns) و معکوس کردن وابستگیها (Dependency Inversion)، به ما این امکان رو میده که اپلیکیشنهای داتنت رو به شدت منعطف و تستپذیر بسازیم.
🔸 ساختار لایهها
- Domain: منطق اصلی کسبوکار (Entities, Value Objects, Aggregates)
- Application: قراردادها و یوزکیسهای اپلیکیشن (Services, Ports)
- Infrastructure: جزئیات اجرا و ارتباط با ابزارها (EF Core, Redis, APIs)
- Presentation: هر نوع UI مثل WebAPI یا Blazor
همهی وابستگیها باید به سمت لایهی Domain باشه—نه بالعکس!
🔸 نمونهی وابستگی معکوس (Dependency Inversion) در .NET
فرض کنید یک سرویس پرداخت داریم که باید بدون دانش از جزئیات پیادهسازی، کار کنه:
// لایه Domain
public interface IPaymentGateway
{
Task PayAsync(Payment payment);
}
public class PaymentService
{
private readonly IPaymentGateway _gateway;
public PaymentService(IPaymentGateway gateway) => _gateway = gateway;
public async Task Checkout(Payment payment) => await _gateway.PayAsync(payment);
}
// لایه Infrastructure
public class StripePaymentGateway : IPaymentGateway
{
public async Task PayAsync(Payment payment)
{
// پیادهسازی مخصوص Stripe
}
}
// Startup یا DI Container
services.AddScoped<IPaymentGateway, StripePaymentGateway>();
services.AddScoped<PaymentService>();
🔸 نکات کلیدی در .NET
- Entity Framework Core باید فقط در لایه Infrastructure استفاده بشه. Domain نباید حتی اسم EF رو بدونه.
- Dependency Injection در .NET Core ابزار اصلی برای تزریق وابستگیها و تسهیل تستنویسی.
- Project References: پروژهی Domain هیچوقت نباید به لایههای دیگر ارجاع بده (one-way dependency).
🔸 توصیه عملی
اگر یک پروژهی بزرگ شروع میکنید، معماری Clean نه تنها تستپذیری و انعطاف رو افزایش میده، بلکه onboarding توسعهدهندههای جدید رو هم راحتتر میکنه. حتی برای REST APIهای کوچک، حداقل Domain و Application رو جدا کنید تا به دام spaghetti code نیفتید.
✴️ سوال: چطور Inject کردن Serviceهای خارجی (مثلاً HttpClient یا DbContext) رو بدون نشکستن قواعد Clean Architecture در پروژه حل میکنید؟ تجربیاتتون رو به اشتراک بگذارید!
@DeveloperAdvocate 🥑1 035
🎯 API Gateway و نقش آن در معماریهای مدرن
در معماری میکروسرویس، API Gateway به عنوان تک نقطه ورود (Single Entry Point) عمل میکند و مجموعهای از وظایف مهم را بر عهده میگیرد؛ از جمله Aggregation، Routing، Authentication، Rate Limiting و حتی سادهسازی ورژندهی سرویسها. ابزار Ocelot به عنوان راهکاری سبک و Cloud-Native در اکوسیستم .NET، این نقش را به سادگی و اثربخشی ایفا میکند.
🛠 پیکربندی ساده Ocelot – مثالی عملی
فرض کنید دو API با مسیرهای مختلف داریم:
-
UserApi در پورت 5001
- OrderApi در پورت 5002
تنظیم فایل ocelot.json به شکل زیر خواهد بود:
{
"Routes": [
{
"DownstreamPathTemplate": "/api/users/{everything}",
"DownstreamScheme": "http",
"DownstreamHostAndPorts": [{ "Host": "localhost", "Port": 5001 }],
"UpstreamPathTemplate": "/users/{everything}",
"UpstreamHttpMethod": [ "GET", "POST", "PUT", "DELETE" ]
},
{
"DownstreamPathTemplate": "/api/orders/{everything}",
"DownstreamScheme": "http",
"DownstreamHostAndPorts": [{ "Host": "localhost", "Port": 5002 }],
"UpstreamPathTemplate": "/orders/{everything}",
"UpstreamHttpMethod": [ "GET", "POST" ]
}
],
"GlobalConfiguration": {
"BaseUrl": "https://localhost:7000"
}
}
در برنامه .NET خود فقط کافیست Ocelot را اضافه و اجرا کنید:
builder.Services.AddOcelot();
var app = builder.Build();
await app.UseOcelot();
app.Run();
🚦 نکات کلیدی معماری
- یکدستسازی Auth: با افزودن DelegatingHandler میتوان توکن JWT را بررسی کرد، حتی اگر سرویسهای پاییندستی مستقل باشند.
- Aggregation: میتوانید پاسخ چند سرویس را تجمیع کنید و یک Response متحد به کلاینت ارائه دهید (Pattern مهم در BFF).
- Resilience: پشتیبانی Built-in از Rate Limiting، Circuit Breaker و QoS.
🔗 Ocelot با حداقل تنظیمات بسیار قابل توسعه است و نقطه شروعی برای الگوهای پیشرفتهتر مانند API Composition و BFF رخ میدهد. استفاده مؤثر از Gateway هزینه تغییر و توسعه Frontend و Backend را به شدت کاهش میدهد.
#API #Microservices #Ocelot #Net6 #GatewayPattern
@DeveloperAdvocate 🥑1 035
🔹 GitOps Workflow با ArgoCD برای استقرار اپلیکیشن .NET روی Kubernetes
یکی از پترنهای مدرن DevOps برای افزایش قابلیت اتکای دیپلویمنت و کنترل زیرساخت، بهرهگیری از GitOps با ابزارهایی مثل ArgoCD است. در این پست، یک سناریوی عملی برای استقرار یک اپلیکیشن ASP.NET Core روی Kubernetes را قدمبهقدم مرور میکنیم:
۱. ریپازیتوری Git منبع حقیقت
ساختار پیشنهادی:
/src # سورس اپ /helm # تمپلیتهای Helm (مثال: chart مناسب اپ) /manifests # مانفیستهای Kubernetes /.github # ورکفلوهای CI۲. تنظیم CI برای ساخت و Push ایمیج: در Pipeline (مثلاً GitHub Actions/Gitlab CI)، هر Push به شاخه main: - Build & Test کد .NET - ساخت Docker image:
dotnet publish -c Release -o out
docker build -t registry/app:$(git rev-parse --short HEAD) .
docker push registry/app:$(git rev-parse --short HEAD)
- بروز کردن مقدار image در values.yaml یا manifest (automated commit)
۳. استفاده از Pull-based Deploy با ArgoCD
- ArgoCD روی کلاستر مستقر است و مانفیستها را مستقیماً از ریپازیتوری Git میخواند.
- تعریف Application:
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: dotnet-app
namespace: argocd
spec:
project: default
source:
repoURL: 'https://github.com/org/repo'
targetRevision: main
path: helm/dotnet-app
helm:
valueFiles:
- values.yaml
destination:
server: 'https://kubernetes.default.svc'
namespace: production
syncPolicy:
automated:
prune: true
selfHeal: true
syncOptions:
- CreateNamespace=true
- هر Push جدید به Git (آپدیت image tag) → ArgoCD تغییر را تشخیص میدهد و استقرار به صورت خودکار انجام میشود.
۴. Best Practices
- استفاده از immutable tag (SHA-1) بهجای latest
- تجمیع configهای environment در فایلهای values جداگانه
- فعالسازی RBAC و کدریویو برای PRها جهت کاهش ریسک misconfiguration
- مانیتور کردن sync status مستقیماً از UI یا CLI آروگو
🔗 این رویکرد GitOps با ArgoCD کارایی، قابلیت ردیابی و ترمیم اتوماتیک استقرارهای .NET روی Kubernetes را به سطح enterprise ارتقا میدهد—حداکثر کیفیت با حداقل مداخله دستی.
@DeveloperAdvocate 🥑1 035
🎯 جستجوی Efficient بین بردارها در Vector Database با C# (نمونه کاربردی در RAG)
در اپلیکیشنهای Retrieval-Augmented Generation (RAG)، جستجو و بازیابی اسناد مشابه بر پایهی مشابهت برداری بسیار حیاتی است. اگر از پایگاههای داده برداری مدرن (مثل Weaviate، Pinecone یا Qdrant) استفاده میکنید، معمولاً API مشابه HTTP/REST دارند که میتوانید با آنها از طریق C# ارتباط بگیرید.
در مثال زیر، فرض میکنیم یک بردار پرسش
(queryVector) داریم و میخواهیم ۵ مورد مشابهتر را از Qdrant استخراج کنیم (با رویکرد POST به /search):
using System.Net.Http;
using System.Net.Http.Json;
// بردار پرسش ۷۶۸-بُعدی (میتواند خروجی embedding یک مدل زبانی باشد)
float[] queryVector = GetEmbeddings("متن پرسش شما ...");
var payload = new
{
vector = queryVector,
top = 5,
with_payload = true
};
using var httpClient = new HttpClient();
httpClient.BaseAddress = new Uri("http://localhost:6333"); // آدرس Qdrant
var response = await httpClient.PostAsJsonAsync("/collections/your_collection/points/search", payload);
response.EnsureSuccessStatusCode();
var searchResults = await response.Content.ReadFromJsonAsync<QdrantSearchResult>();
// استخراج نزدیکترین اسناد
foreach (var point in searchResults.result)
{
Console.WriteLine($"ID: {point.id}, Score: {point.score}");
// point.payload -- محتوای ذخیرهشده!
}
// --- مدل برای Parse خروجی ---
public class QdrantSearchResult
{
public List<ResultPoint> result { get; set; }
}
public class ResultPoint
{
public string id { get; set; }
public float score { get; set; }
public Dictionary<string, object> payload { get; set; }
}
🔹 نکات معماری:
- بهترین عملکرد را زمانی میگیرید که تمامی Embedها پیشپردازش و همواره نرمال شده باشند.
- اگر حجم داده زیاد شود، قابلیت فیلترسازی (filter) را هم به Query خود اضافه کنید تا جستجو سریع و دقیق بماند.
- این الگو را میتوانید برای Pinecone یا Weaviate با کمی تغییر در پیلود هم اعمال کنید.
💡 کار با بردارها در .NET و DBهای برداری، راه را برای نسل جدید برنامههای هوشمند باز میکند!
@DeveloperAdvocate 🥑1 035
🔹 Git Interactive Rebase: هنر بازنویسی تاریخچهی گیت برای حرفهایها
گاهی نیاز داریم قبل از مرج درخواست (PR)، کامیتهایمان را مثل یک داستان دقیق بازنویسی کنیم: تجمیع، تقسیم، یا تغییر پیام کامیتها. اینجاست که
git rebase -i قدرتنمایی میکند.
مثال: فرض کنید ۵ کامیت آخر روی شاخهی feature/awesome-feature را میخواهید بازآرایی و تمیز کنید:
git checkout feature/awesome-feature
git rebase -i HEAD~5
در پنجره باز شده، با تغییر pick به squash یا fixup میتوانید چند کامیت را با هم ادغام کنید. یا برای تغییر پیام commit کافیست reword بگذارید.
🔸 Scenario واقعی:
گاهی یک باگ بحرانی در master رفع شده و باید همان fix را به release/2.1 انتقال دهید. Copy-paste فایلها راهحل نیست! git cherry-pick را بهکار بگیرید:
git checkout release/2.1
git cherry-pick <bugfix-commit-sha>
با این کار تنها یک کامیت خاص (یا حتی بازهای از کامیتها) را بیرون از جریان عمودی تغییرات، وارد هر شاخه دلخواه میکنید. مدیریت hotfixها در پروژههایی با چندین release branch همینقدر تمیز و کنترل شده خواهد شد.
🔹 نکته معماری: همیشه قبل از interactive rebase یا cherry-pick، وضعیت کاری و شاخه را stash یا commit کنید تا در صورت بروز conflict، امکان بازیابی راحت داشته باشید. از hooks یا aliases برای خودکارسازی چکهای post-rebase بهره بگیرید.
🔸 در تیمهای پیشرفته، rebase فقط مخصوص tidy کردن قبل از merge یا squash نیست؛ بلکه کل جریان Git را به یک artifact تمیز و قابل دیباگ، بدون commitهای واسط و ناقص تبدیل میکند — و این راز تمایز پروژههای enterprise با بنیاد بازاری است! 🚀
@DeveloperAdvocate 🥑1 035
💡 Tips عمیق دربارهی Tupleها در #CSharp — فراتر از مورد مصرفهای معمول!
در سیشارپ، Tupleها فقط ساختارهایی سریع برای بازگرداندن چند مقادیر نیستند؛ ظرفیت واقعی آنها در انتقال دادههای پیچیده، بهینهسازی پرفورمنس و مدلسازی روابط کلیدی کشف میشود.
⭐️ ۱. Deconstruction پیشرفته
Tuples در اکشن! deconstruct نه فقط روی متدهای معمول، بلکه در ترکیب با LINQ و Pattern Matching بهشکلی بسیار تمیز و خوانا:
// فرض: لیست جفتهای رشته و عدد
var items = new List<(string Symbol, int Value)>
{
("A", 100),
("B", 200),
("C", 300)
};
foreach (var (symbol, value) in items)
{
Console.WriteLine($"{symbol}: {value}");
}
🔎 همراهی با Pattern Matching و سوئیچ Expressions:
(string, int) input = ("VIP", 42);
var result = input switch
{
("VIP", var x) when x > 40 => "Special Access",
("User", int y) => $"Regular user: {y}",
_ => "Unknown"
};
⭐️ ۲. استفاده از Tuples به عنوان کلید Dictionary — استادانه و متمایز!
وقتی ابعاد کلیدیسازی شما بیش از یک مقدار دارد و نمیخواهید دنبال ساخت کلاسهای پیچیده باشید، Tupleها با equality و hashing مناسب (از C# 7.0 به بالا) راهگشا هستند:
// کلید از دو مقدار (int, string)
var cache = new Dictionary<(int, string), string>();
cache[(42, "Alice")] = "Admin";
cache[(77, "Bob")] = "User";
if (cache.TryGetValue((42, "Alice"), out var role))
Console.WriteLine(role); // خروجی: Admin
// مزیت: equality مقادیر tuple بهصورت value-based انجام میشود:
Console.WriteLine(cache.ContainsKey((42, "Alice"))); // True
✅ پیشنهاد معماری:
برای keyکردن سریعتر و type-safe در dictionaryهای caching، بخشی از signature متدها، یا حتی بهعنوان composite key ساده در persistenceهای درونبرنامهای، tupleها بسیار موثر هستند و بهسادگی جایگزین struct پیچیده میشوند.
—
🚀 اگر شما هم الگوی جالبی با tupleها دارید، در کامنتها به اشتراک بگذارید!
#AdvancedCSharp #Tuple #DeepDotNet
@DeveloperAdvocate 🥑1 035
🎯 مقایسه پیشرفته بین Redux Toolkit و Zustand در مدیریت State در React
وقتی صحبت از مدیریت state در پروژههای مدرن React میشود، انتخاب ابزار مناسب میتواند معماری، کد، و حتی performance شما را متحول کند. بیایید دو کتابخانه مطرح را از جنبههای مختلف فنی مقایسه کنیم:
🔹 Redux Toolkit
ابزار رسمی و بهینه شدهی Redux با تمرکز بر نوشتن کمتر boilerplate، سازگار با اکوسیستم غنی middlewareها و devtools.
ویژگیها:
- ساختار اکوسیستم محور (با اکشنها و ریدوسرها)
- نوعدهی عالی با TypeScript
- ابزارهای DevTools و Middleware قدرتمند
- ساختار شدیداً قابل تست و پیشبینیپذیر
// counterSlice.js
import { createSlice } from '@reduxjs/toolkit'
const counterSlice = createSlice({
name: 'counter',
initialState: { value: 0 },
reducers: {
increment: (state) => { state.value += 1 },
decrement: (state) => { state.value -= 1 },
incrementByAmount: (state, action) => { state.value += action.payload },
}
})
export const { increment, decrement, incrementByAmount } = counterSlice.actions
export default counterSlice.reducer
// App.jsx (React component)
import { useSelector, useDispatch } from 'react-redux'
import { increment } from './counterSlice'
export function Counter() {
const value = useSelector(state => state.counter.value)
const dispatch = useDispatch()
return (
<button onClick={() => dispatch(increment())}>
مقدار فعلی: {value}
</button>
)
}
🔹 Zustand
کتابخانهای minimal و مدرن برای state management که خیلی ساده و سریع کار میکند، بدون نیاز به کانتکس یا provider ها.
ویژگیها:
- بدون boilerplate، بسیار ساده و خوانا
- امکانات strong typing در TypeScript
- عملکرد بسیار سریع (selective re-render)
- ترکیب عالی با hooks و مدل فانکشنال
// useCounterStore.js
import { create } from 'zustand'
export const useCounterStore = create((set) => ({
value: 0,
increment: () => set((state) => ({ value: state.value + 1 })),
decrement: () => set((state) => ({ value: state.value - 1 })),
incrementByAmount: (amount) => set((state) => ({ value: state.value + amount })),
}))
// App.jsx (React component)
import { useCounterStore } from './useCounterStore'
export function Counter() {
const { value, increment } = useCounterStore()
return (
<button onClick={increment}>
مقدار فعلی: {value}
</button>
)
}
✨ نکات کلیدی معماری:
- Redux Toolkit برای پروژههای Enterprise یا اپهایی که نیاز به ساختار پیچیده، Middleware و قابلیت دیباگینگ پیشرفته دارند، عالی است.
- Zustand برای اپلیکیشنهای فانکشنال، کوچک تا متوسط و تیمهایی که به انعطاف و سادگی اهمیت میدهند، انتخابی ایدهآل است.
به تجربه شما، کدام ابزار بهترین trade-off را بین سادگی و مقیاسپذیری ارائه میدهد؟
@DeveloperAdvocate 🥑1 035
ابزاری که هر توسعهدهنده EF Core باید بشناسد: EF Core Power Tools
در پروژههای بزرگ، نگهداری مدلهای دادهای و همگامسازی آنها با دیتابیس همیشه چالشبرانگیز است. اگر تا حالا با مشکلات مکرر reverse engineering، ایجاد دیاگرامهای بصری، یا تولید Seed Data مواجه شدهاید، EF Core Power Tools میتواند نقطه عطف جریان کاری شما باشد.
✅ ویژگیهای کلیدی:
- Reverse Engineer: با دو کلیک، اسکیمای دیتابیس را به مدلهای POCO (با پشتیبانی از Owned Types، Value Conversions و ...) تبدیل کنید. سفارشیسازی نامها، namespaces، فعال/غیرفعال سازی DataAnnotation و حتی تولید DbContext اختصاصی یک قابلیت حیاتی است.
- Visualization: مدل خود را با یک دیاگرام تعاملی بررسی کنید. این دیاگرام فقط برای مستندسازی نیست؛ بلکه برای تحلیل وابستگیها، کشف سیکلها و درک ساختار domain واقعاً ضروری است.
- Migrations & Seed Data: اسکریپتهای migration را با یک کلیک مشاهده و مدیریت کنید. حتی میتوانید داده اولیه (Seed) از جدولهای واقعی تولید کنید.
- Compare: تفاوت بین DbContext و اسکیمای دیتابیس روی هوا! وقتی migrationهای زیادی دارید یا پروژه شما microservice-base است، این امکان gold است.
برای شروع:
1. از Marketplace یا ویژوال استودیو، افزونه EF Core Power Tools را نصب کنید.
2. روی پروژه راستکلیک ➡️ EF Core Power Tools را انتخاب کنید.
نمونهای از reverse engineer:
// Connection String را وارد کنید و نوع خروجی را انتخاب نمایید
Reverse engineer to DbContext and POCO classes…
یا برای visualization:
Show EF Core Model Diagram
🎛 نکته حرفهای: این ابزار بهطور کامل از EF Core 7.x/8.x و حتی مدلهای Annotated، Fluent و ترکیبی پشتیبانی میکند.
اگر بهدنبال مدیریت و تکامل مدلهای دامنهای پیشرفته در معماریهای Clean، DDD و یا microservices هستید، EF Core Power Tools را به جعبه ابزار خود اضافه کنید. سرعت، دقت و کیفیت توسعه شما را چند پله بالاتر میبرد.
@DeveloperAdvocate 🥑1 035
در بسیاری از کامپوننتهای React، بهویژه کامپوننتهایی که روی اجزای بصری سفارشی با عناصر HTML ابتدایی (مثلاً div، span) ساخته میشوند، یک مشکل رایج accessibility این است که کاربران فقط با صفحهکلید یا فناوریهای کمکی، نمیتوانند با این اجزا تعامل درستی داشته باشند. بیایید مثالی رایج را با هم ببینیم:
فرض کنید یک دکمه سفارشی با استفاده از div نوشتهایم:
<div onClick={handleClick}>
ذخیره
</div>
این div نه با کلید Enter فعال میشود و نه توسط screen readerها به عنوان دکمه شناسایی میگردد. برای حل این، استفاده از ARIA attributes و نقش درست ضروری است:
1. نقش یا role را به button تغییر دهید تا screen reader اکسپلیسیت بفهمد این عنصر یک دکمه است.
2. خصوصیت tabindex را برابر 0 قرار دهید تا با Tab روی آن فوکوس شود.
3. رویداد onKeyDown را مدیریت کنید تا با فشردن Enter یا Space هم عملکرد دکمه اجرا شود.
4. برای خوانایی بیشتر، میتوانید aria-label یا aria-pressed (در دکمههای toggle) اضافه کنید.
نمونهی اصلاح شده:
<div
role="button"
tabIndex={0}
aria-label="ذخیره"
onClick={handleClick}
onKeyDown={e => {
if (e.key === "Enter" || e.key === " ") {
handleClick();
}
}}
style={{
display: "inline-block",
padding: "8px 16px",
cursor: "pointer",
background: "#1976D2",
color: "#fff",
borderRadius: "4px"
}}
>
ذخیره
</div>
این الگو را مخصوصاً برای دکمههای custom یا کامپوننتهای interactive خود در نظر بگیرید، چون هم تجربهی کاربری بهتری برای همه (بهویژه افراد با نیازهای ویژه) فراهم میکند و هم در تستهای automated accessibility (مثل axe) سربلند بیرون میآید. 🛡️
#React #Accessibility #a11y #SeniorTips
@DeveloperAdvocate 🥑1 035
🇮🇷 الگوی Sidecar در معماریهای مدرن بهویژه Kubernetes، راهکاری قدرتمند برای افزودن قابلیتهای غیرعملکردی مثل لاگگیری و مانیتورینگ به سرویسها است، بدون آنکه به کد سرویس اصلی دست بزنید.
فرض کنید یک سرویس ASP.NET Core دارید که باید لاگهایش به یک سیستم متمرکز (مانند ELK) منتقل شود. بهجای تزریق مستقیم لاگر به کد یا استفاده از Agent سراسری، میتوانید یک کانتینر «سایدکار» کنار سرویس اصلی اجرا کنید. سایدکار لاگر، در همان پاد اجرا میشود و مسئول جمعآوری و ارسال لاگها به سمت مقصد نهایی است.
مزیت؟ تفکیک concerns و افزایش انعطاف: هر سرویس صرفاً لاگ خود را روی فایل، stdout یا یک endpoint مینویسد، و کار انتقال/پردازش لاگها را سایدکار انجام میدهد. میتوان این سایدکار را بهشکل کاملاً جنریک پیادهسازی و حتی برای چندین سرویس استفاده کرد.
نمونه پیکربندی ساده برای جمعآوری لاگها از یک دایرکتوری (مثلاً
/logs):
# در فایل Kubernetes Pod definition
apiVersion: v1
kind: Pod
metadata:
name: logging-sidecar-demo
spec:
containers:
- name: main-app
image: my-dotnet-app
volumeMounts:
- name: log-volume
mountPath: /logs
- name: fluent-bit-sidecar
image: fluent/fluent-bit
volumeMounts:
- name: log-volume
mountPath: /fluent-bit/logs
args: [
"-i", "tail", "-p", "path=/fluent-bit/logs/*.log",
"-o", "es", "-p", "host=elasticsearch", "-p", "port=9200"
]
volumes:
- name: log-volume
emptyDir: {}
در C# هم، تنها کافی است لاگها را روی مسیری بنویسید که سایدکار به آن دسترسی دارد:
var loggerFactory = LoggerFactory.Create(builder =>
{
builder.AddFile("/logs/app.log"); // لاگر متداول فایلبیس
});
var logger = loggerFactory.CreateLogger<Program>();
logger.LogInformation("سلام از سرویس اصلی DotNet!");
به این ترتیب بدون تغییر کد اصلی و با استفاده از قابلیتهای دپلوی کانتینری، logging و حتی قابلیتهایی مثل tracing یا metric collection را به شکل plug-and-play به سرویسها اضافه میکنید. این جداسازی، هم توسعه و هم نگهداشت را سریعتر و امنتر میکند.
@DeveloperAdvocate 🥑1 035
توسعه مبتنی بر تست (TDD) نه فقط یک تکنیک، بلکه یک شیوه تفکر است که کیفیت، اعتماد و طراحی کد را ارتقا میدهد. در تیمهای حرفهای، TDD باعث میشود وابستگی به دیباگر کاهش پیدا کند و ماژولاریتی پروژه ذاتاً حفظ شود.
🔍 یک چرخه ساده و عملی TDD که در پروژههای .NET امتحان خود را پس داده:
۱. Fail (یک تست بنویس که فعلاً شکست میخورد):
برای مثال، قصد داریم تابعی بنویسیم که آیا یک رشته Palindrome است یا خیر.
[Fact]
public void IsPalindrome_ReturnsTrue_ForPalindromeString()
{
Assert.True(StringUtils.IsPalindrome("level"));
}
۲. Make it Pass (کمترین کد ممکن برای پاسشدن تست):
public static class StringUtils
{
public static bool IsPalindrome(string s)
{
return s == new string(s.Reverse().ToArray());
}
}
۳. Refactor (بازسازی هوشمند بدون شکست تست):
اینجا میتوانید سادهسازی، بهبود نامگذاری یا حذف تکرار را اعمال کنید، در حالی که همۀ تستها باید سبز بمانند.
public static bool IsPalindrome(string s)
{
if (string.IsNullOrEmpty(s)) return false;
var reversed = new string(s.Reverse().ToArray());
return s.Equals(reversed, StringComparison.OrdinalIgnoreCase);
}
هر بار که فیچر جدید مینویسید، خود را مجبور کنید چرخه Red–Green–Refactor را طی کنید، حتی اگر راه حل در ابتدا ساده باشد. بعد از مدتی، کدتان نه تنها تستپوشش عالی خواهد داشت بلکه از نظر طراحی نیز منعطف و خوشساخت باقی میماند.
⛔️ مقاومت تیم؟ زیاد شنیدم: «وقتگیر است!» واقعیت: TDD هزینه دیباگ، رفع باگهای پنهان و رگرسیون را به شدت کاهش میدهد و از همه مهمتر: به شما جرئت تغییر بزرگ در سیستم میدهد.
اگر تا امروز TDD را فقط یک توصیهی زیبای آکادمیک میدیدید، همین امروز این چرخه را در یک فیچر جدید امتحان کنید؛ تاثیرش را هرگز فراموش نخواهید کرد.
@DeveloperAdvocate 🥑1 035
👷♂️ معماری Modular Monolith در #NET
اگر به دنبال سادگی دیپلوی مونولیت و تمرکز روی جداسازی مسئولیتها هستید، معماری Modular Monolith بهترین گزینه برای شماست: هر ماژول با توجه به قانون "High Cohesion - Low Coupling" توسعه مییابد و ارتباطات شفاف و سطح پایین با سایر ماژولها دارد.
🔹 پترن پیشنهادشده برای ساختار فولدر:
/src
/BuildingBlocks
/Modules
/Catalog
/Catalog.API
/Catalog.Application
/Catalog.Domain
/Catalog.Infrastructure
/Sales
/Sales.API
/Sales.Application
/Sales.Domain
/Sales.Infrastructure
/SharedKernel
/WebApi
۱️⃣ هر ماژول Domain-Driven طراحی میشود (🔸DDD in practice!)
۲️⃣ فقط لایههای API میتوانند Controller داشته باشند.
۳️⃣ ارتباط ماژولها فقط از طریق Contract و Mediator یا Domain Events (غیرمستقیم) انجام شود.
نمونه رجیستری ماژول در Program.cs:
// WebApi/Program.cs
builder.Services.AddCatalogModule(builder.Configuration);
builder.Services.AddSalesModule(builder.Configuration);
// دقت کنید: هر ماژول متد Extension خودش را برای رجیستری ارائه میدهد
درون Catalog.Application:
namespace Catalog.Application;
public static class ServiceRegistration
{
public static IServiceCollection AddCatalogModule(this IServiceCollection services, IConfiguration config)
{
// رجیستر سرویسهای Application و Infrastructure ماژول
services.AddMediatR(cfg => cfg.RegisterServicesFromAssembly(typeof(ServiceRegistration).Assembly));
services.AddScoped<IProductService, ProductService>();
// ...
return services;
}
}
ارتباط ماژولها با Mediator:
// Sales.Application پروژه
public record ProductReserved(Guid OrderId, Guid ProductId, int Quantity) : INotification;
public class ReserveProductHandler : INotificationHandler<ProductReserved>
{
public Task Handle(ProductReserved notification, CancellationToken cancellationToken)
{
// رویداد را پردازش کن (میتواند به Catalog پیام دهد)
return Task.CompletedTask;
}
}
🔥 پیشنهاد کلیدی:
- Data Access هر ماژول باید فقط در همان دایره (مثلاً Sales.Infrastructure) باشد.
- اگر پایگاه داده Shared است، با اسکیما و Transaction مدیریت کنید.
- SharedKernel فقط باید انتزاعی و فاقد رفتارهای بیزنسی باشد.
🎯 این ساختار، مهاجرت به Microservices را هموار میکند!
#DotNet #SoftwareArchitecture #ModularMonolith
@DeveloperAdvocate 🥑1 035
شکلدهی ماژولار مونولیث در .NET: یک راهنمای عملی
✅ اگر به دنبال معماری تمیز، انعطافپذیر و قابل توسعه هستید، انتخاب Modular Monolith یک راهکار متعادل بین سادگی مونولیث و مدیریت پیچیدگی معماری میکروسرویس است. ساختار پیشنهادی زیر را برای اپلیکیشنهای enterprise در .NET پیشنهاد میکنم:
۱️⃣ فضای Solution و پروژهها
- Solution اصلی: نام دامنه (مثلاً
MyCompany.MyApp)
- پروژه Web/API: فقط درخواست را دریافت و ارسال میکند (لایه تست).
- هر ماژول = یک پروژه Class Library با رعایت اصل دایره ممنوع (No Circular Dependencies).
- پروژههای Cross-Cutting مثل Infrastructure یا Shared Kernel.
- پروژه Application برای واسطها، Mediatorها و سرویس Application.
۲️⃣ مثال چیدمان:
📦MyCompany.MyApp.sln
├─ Web (ASP.NET Core API)
├─ Modules
├─ Catalog
├─ Catalog.API (در صورت نیاز)
├─ Catalog.Application
├─ Catalog.Domain
├─ Catalog.Infrastructure
├─ Orders
├─ Orders.Application
├─ Orders.Domain
├─ Orders.Infrastructure
├─ BuildingBlocks (Shared Kernel، Base Classes و ...)
├─ Infrastructure (cross-cutting: Auth, Messaging و ...)
۳️⃣ رعایت اصل استقلال ماژولها
ماژولها، کل bounded context بوده و تنها از طریق API Contracts با یکدیگر صحبت میکنند (Service Layer/Internal API یا پیامرسانی). هر ماژول Domain Model و قوانین مخصوص به خود را دارد.
۴️⃣ Dependency Management
هر ماژول فقط و فقط به BuildingBlocks و پروژههای سطح پایینتر (مثلاً از Domain به Application) ارجاع میدهد. از Dependency Injection برای مرزبندی سرویسها استفاده کنید.
۵️⃣ تزریق ماژولها در Startup:
// Web/Program.cs
builder.Services.AddCatalogModule();
builder.Services.AddOrdersModule();
داخل هر ماژول یک فایل اکستنشن برای رجیستر سرویسها:
public static class CatalogModuleInstaller
{
public static IServiceCollection AddCatalogModule(this IServiceCollection services)
{
services.AddScoped<IProductRepository, ProductRepository>();
// سایر dependencyها…
return services;
}
}
۶️⃣ ارتباط داخلی ماژولها:
حتماً از Contractهای ساده (interfaces یا DTO) و پیامرسانی داخلی استفاده کنید. به هیچ وجه مستقیم به Serviceهای داخلی یک ماژول دیگر وابسته نشوید.
۷️⃣ نمونه کد:
// داخل Catalog.Application.Commands
public class CreateProductCommand : IRequest<Guid>
{
public string Name { get; set; }
public Money Price { get; set; }
}
// داخل Catalog.Application.Handlers
public class CreateProductHandler : IRequestHandler<CreateProductCommand, Guid>
{
// Inject Repositories و ...
public Task<Guid> Handle(CreateProductCommand request, CancellationToken ct)
{
// logic ...
}
}
۸️⃣ تستپذیری و توسعهپذیری
با این ساختار، تست واحد، Integration و حتی تست End-to-End برای هر ماژول بسیار آسان و ایزوله است.
🚩 جمعبندی:
ترکیب اصول DDD، تفکیک ماژولار، Contractهای داخلی و کنترل دقیق Dependencyها، قدرت یک Modular Monolith را در .NET به اوج میرساند و گذار به میکروسرویس را در آینده تسهیل میکند.
@DeveloperAdvocate 🥑1 035
🔹 #TDD: چرا و چگونه؟
در اکوسیستم تیمهای حرفهای، کدنویسی بدون تست مانند ساختمانسازی بدون فونداسیون است. اگر به دنبال تحویل محصول پایدار و قابل اطمینان هستید، توسعهی مبتنی بر تست (TDD) دیگر یک انتخاب نیست، بلکه ضرورت است— بهویژه در پروژههای داتنت که ابزارهای تست قدرتمندی چون xUnit و NSubstitute در اختیار داریم.
## یک ورکفلو عملی و مینیمال
۱. آزمون بنویس (Red): یک رفتار کوچک و مستقل تعریف کن. مثلا:
[Fact]
public void Sum_ShouldReturnSumOfTwoNumbers()
{
var calc = new Calculator();
var result = calc.Sum(2, 3);
result.Should().Be(5);
}
۲. کدمینیمال برای رفع خطا (Green): فقط به اندازهی رفع خطای تست، پیادهسازی کن:
public int Sum(int a, int b)
{
return a + b;
}
۳. بازآرایی (Refactor): بهینهسازی کن، بدون شکستن تستها. اگر رشد داشتیم:
public int Sum(params int[] values)
{
return values.Sum();
}
## نکات کلیدی
- تستها باید همیشه سریع باشند تا جرات تجربه کردن را داشته باشیم.
- قبل از شروع توسعهی فیچر اصلی، اول قراردادها و سناریوهای شکست/پیروزی را در قالب Test Cases بنویس.
- اگر تستزنی برای اینترفیسها یا Database Access Layer لازم شد، از mocking frameworks نظیر Moq یا NSubstitute استفاده کن تا تستها pure unit باقی بمانند.
- TDD فقط جریان تولید تست نیست؛ یک design feedback loop است که شما را به سمت معماری ماژولار و interface-driven هدایت میکند.
✅ TDD یک مهارت است؛ با کمی تمرین، تجربهی پیادهسازی نرمافزار تمیز و منعطف را لمس خواهی کرد. آیا در تیم شما، Red-Green-Refactor واقعاً جا افتاده؟
@DeveloperAdvocate 🥑1 035
🎯 Feature Flags: یک سلاح پیشرفته برای کاهش ریسک دپلوی و فعالسازی Canary Release
در دیپلویهای امروزی، ریسک پیادهسازی ویژگیهای جدید بدون تست بلادرنگ روی کاربران واقعی، غیرقابل قبول است. Feature Flagها به ما قدرت میدهند تا بهشکل تدریجی، کنترلشده و در صورت لزوم بازگشتپذیر، فیچرها را منتشر کنیم.
یک سناریوی کلاسیک:
- فیچر جدید را با یک فلگ پیادهسازی کنید.
- فقط برای گروه خاصی فعال کنید (مثلاً ۵٪ کاربران یا فقط internalها).
- مانیتور کنید، اگر مشکلی نبود، ترافیک بیشتری را به فیچر جدید منتقل کنید.
در .NET پیشنهادم استفاده از Microsoft.FeatureManagement است که پیادهسازی clean، ساده و extensible ارائه میدهد.
فرض کنید میخواهیم فیچری با نام
EnableNewCheckout را فقط برای کاربران کاناری فعال کنیم. در appsettings:
"FeatureManagement": {
"EnableNewCheckout": false
}
حل کنترلشده فلگ:
using Microsoft.FeatureManagement;
public class CheckoutController : Controller
{
private readonly IFeatureManager _featureManager;
public CheckoutController(IFeatureManager featureManager)
{
_featureManager = featureManager;
}
public async Task<IActionResult> Index()
{
if (await _featureManager.IsEnabledAsync("EnableNewCheckout"))
{
return View("NewCheckout");
}
return View("ClassicCheckout");
}
}
برای Canary Release واقعی، کافی است targeting را به فلگ اضافه کنید:
"FeatureManagement": {
"EnableNewCheckout": {
"EnabledFor": [
{
"Name": "Targeting",
"Parameters": {
"Audience": {
"Users": [ "user1", "user2" ],
"Groups": [ "BetaTesters" ],
"DefaultRolloutPercentage": 5 // فعال فقط برای ۵٪ کاربران
}
}
}
]
}
}
و قلاب User Context:
public class CustomTargetingContextAccessor : ITargetingContextAccessor
{
private readonly IHttpContextAccessor _httpContextAccessor;
public CustomTargetingContextAccessor(IHttpContextAccessor httpContextAccessor)
=> _httpContextAccessor = httpContextAccessor;
public ValueTask<TargetingContext> GetContextAsync()
{
var user = _httpContextAccessor.HttpContext?.User;
return ValueTask.FromResult(new TargetingContext
{
UserId = user?.Identity?.Name,
Groups = user?.IsInRole("BetaTesters") == true ? new[] { "BetaTesters" } : Array.Empty<string>()
});
}
}
در نتیجه:
- هر فیچر را امن، تدریجی و بازگشتپذیر فعال کنید.
- بر پایه دادههای ریلتایم، سریع rollback کنید.
- تجربه Canary Release را در قلب DevOps خود اتوماتیک سازید.
🔗 برای مطالعه بیشتر: https://learn.microsoft.com/en-us/azure/azure-app-configuration/feature-management-overview
@DeveloperAdvocate 🥑1 035
🎯 تفاوت Authentication و Authorization؛ با یک مثال و کاربرد فنی
فرض کنید وارد شرکت میشوید:
- Authentication (احراز هویت): نگهبان در ورودی با کارت شناسایی بررسی میکند واقعاً شما کی هستید. اگر کارت معتبر باشد، وارد میشوید.
- Authorization (سطح دسترسی): بعد از ورود، برای رفتن به بخش مالی به کارت شما نگاه میکنند تا ببینند اجازه دسترسی به آن بخش را دارید یا خیر.
در نرمافزار، Authentication معمولاً به معنای بررسی هویت کاربر (مثلاً ورود با نام کاربری و رمزعبور، یا OAuth) است. Authorization تعیین میکند کاربر تصدیقشده به کدام منابع و عملیات مجوز دارد.
در ASP.NET Core میتوانید این جداسازی را بهوضوح ببینید:
// احراز هویت: تعیین scheme و فعالسازی
services.AddAuthentication("CookieAuth")
.AddCookie("CookieAuth", options => {
options.LoginPath = "/Account/Login";
});
// سطح دسترسی: تعریف رول یا policy
services.AddAuthorization(options =>
{
options.AddPolicy("AdminOnly", policy => policy.RequireRole("Admin"));
});
در Controller:
// ابتدا باید احراز هویت شود، سپس سطح دسترسی بررسی خواهد شد
[Authorize(Policy = "AdminOnly")]
public IActionResult SecretSection() => View();
🔑 خلاصه: Authentication سؤال میکند «چه کسی هستید؟»، Authorization میپرسد «به چه چیزهایی اجازه دسترسی دارید؟». تفکیک این دو اصل برای معماریهای امن، کلیدی است.
@DeveloperAdvocate 🥑1 035
گاهی در پروژههایی که معماری مبتنی بر EF Core دارند، نیاز است برای برخی کوئریهای حیاتی (مثلاً joinهای سنگین یا reportهای سفارشی) از داینامیک بودن و سرعت Dapper بهره بگیریم—بدون خدشه به انسجام و maintainability سرتاسر دامنه.
یک رویکرد پیشنهادی: استفاده هدفمند از Dapper روی همان DbContext، ضمن رعایت لایهبندی. فرض کنید یک سرویس دارید که معمولاً EFCore را مصرف میکند اما برای یک کوئری خاص، مستقیماً با Dapper ارتباط میگیرد.
نمونه:
public class UserReportService {
private readonly AppDbContext _dbContext;
public UserReportService(AppDbContext dbContext) {
_dbContext = dbContext;
}
public async Task<IReadOnlyList<UserReportDto>> GetActiveUserReportsAsync(CancellationToken ct) {
var connection = _dbContext.Database.GetDbConnection();
// مهم: connection باز کردن/بستن را مدیریت کنید
var isOpen = connection.State == ConnectionState.Open;
if (!isOpen) await connection.OpenAsync(ct);
var sql = @"
SELECT u.Id, u.Name, COUNT(o.Id) AS OrderCount
FROM Users u
LEFT JOIN Orders o ON u.Id = o.UserId AND o.Status = @orderStatus
WHERE u.IsActive = 1
GROUP BY u.Id, u.Name
";
var param = new { orderStatus = "Completed" };
var result = await connection.QueryAsync<UserReportDto>(sql, param, commandTimeout: 30, commandType: CommandType.Text);
if (!isOpen) await connection.CloseAsync();
return result.ToList();
}
}
public class UserReportDto {
public int Id { get; set; }
public string Name { get; set; }
public int OrderCount { get; set; }
}
🔹 چند نکته کلیدی:
- ارتباط connection از خود DbContext بگیرید تا به صورت share شده در یک transaction فعالیت کند (ویژه سناریوهای UnitOfWork/Transaction).
- Dapper برای projection مستقیم به DTO عالیست؛ ولی mutation (Save/Update/Delete) را به EFCore بسپارید تا change tracking و ویژگیهای جدیتر را از دست ندهید.
- روی disposable بودن connection و همزمانی access حساس باشید—در ASP.NET Core، ترجیح دهید این کارها را در service (نه controller) انجام دهید و از scoped DbContext بهره بگیرید.
ترکیب Dapper و EFCore: پرفورمنس هرجا لازم است، قدرت مدل دامنه هرجا مهم است. قدرت انتخاب با معمار!
@DeveloperAdvocate 🥑1 035
یکی از بهترین رویکردهای مدیریت امن Secrets در یک اپلیکیشن .NET، بارگذاری مستقیم secrets از Azure Key Vault به IConfiguration در زمان اجراست، بدون ذخیرهسازی آنها به صورت فایل یا متغیر محیطی. پترن زیر با حداقل دسترسی و تضمین گردش امن Secrets، استفاده از Managed Identity (بدون نیاز به ذخیره یا مدیریت credentials اضافی) را نشان میدهد:
using Azure.Identity;
var builder = WebApplication.CreateBuilder(args);
// فرض بر این است که اپلیکیشن روی Azure اجرا میشود و Managed Identity فعال است
string keyVaultUrl = builder.Configuration["KeyVault:Url"];
builder.Configuration.AddAzureKeyVault(new Uri(keyVaultUrl), new DefaultAzureCredential());
var app = builder.Build();
حال، هر Secret با نام مطابق Convention بخشبندیشده (مثلاً Db--ConnectionString برای Db:ConnectionString) به طور مستقیم در IConfiguration قابل استفاده است:
string connectionString = app.Configuration["Db:ConnectionString"];
برای HashiCorp Vault، پکیج محبوب VaultSharp پیشنهاد میشود. رویکرد زیر، خواندن Secrets از Vault و تزریق امن آنها به IConfiguration را به شکل extensible نشان میدهد:
using VaultSharp;
using VaultSharp.V1.AuthMethods.Token;
using VaultSharp.V1.Commons;
// ثبت provider اختصاصی
public static class VaultConfigurationExtensions
{
public static IConfigurationBuilder AddVaultSecrets(this IConfigurationBuilder builder, string vaultUri, string token, string secretPath)
{
var authMethod = new TokenAuthMethodInfo(token);
var vaultClient = new VaultClient(new VaultClientSettings(vaultUri, authMethod));
var secret = vaultClient.V1.Secrets.KeyValue.V2.ReadSecretAsync(secretPath).GetAwaiter().GetResult();
var data = secret.Data.Data;
builder.AddInMemoryCollection(data.ToDictionary(k => k.Key, v => v.Value?.ToString()));
return builder;
}
}
// استفاده:
builder.Configuration.AddVaultSecrets("https://my-vault:8200", "<VAULT_TOKEN>", "secret/data/prod");
✨ نکته مهم:
Secrets را هیچگاه در فایل appsettings یا محیط CI ذخیره نکنید. Chain کردن sourceها و استفاده از Providerهای secrets، اصل Principle of Least Privilege را در معماری .NET رعایت میکند. فعالسازی Rotation و Auditing نیز برای امنیت اجباریست.
#DotNet #DevOps #KeyVault #Vault #BestPractices
@DeveloperAdvocate 🥑1 035
🔹 شناخت چرخه عمر کامپوننتها در Blazor — ورای مقدمات
در معماری مدرن مبتنی بر کامپوننت مانند Blazor، درک عمیق چرخه عمر (Lifecycle) هر کامپوننت نهفقط کلید مدیریت State و ایزولاسیون Dependencyهاست، بلکه بستری بیبدیل برای بهینگی رندر و تعامل پروژههای بزرگ فولاستک را فراهم میکند.
هر کامپوننت در Blazor چهار گام حیاتی را در طول عمر تجربه میکند:
1️⃣ Initialization
۲ پروسه مرتبط وجود دارد:
-
OnInitialized (Synchronous)
- OnInitializedAsync (Asynchronous)
اینجا جایی است که میتوانید دادههای اولیه را بارگذاری یا dependency injection کنید. شایان ذکر است که سازنده (constructor) صرفاً برای DI و مقداردهی دستی استفاده میشود، اما logic اولیه کامپوننت را باید در این متدها قرار داد (و نه در constructor).
2️⃣ Parameter Setting
هر بار که پارامترها از والد تغییریابند، Blazor SetParametersAsync را اجرا میکند.
اگر پارامتری نیاز به واکنش یا مقداردهی مجدد دارد، از OnParametersSet یا نسخه async آن استفاده کنید.
تکنیک pro-level: اگر کامپوننتتان state محاسبهشده براساس پارامترها دارد، این منطق را اینجا پیاده کنید.
3️⃣ Render Flow
هر بار StateHasChanged فراخوانی شود یا پارامتری تغییر کند، Blazor BuildRenderTree را اجرا و DOM مجازی را آپدیت میکند. شما رفتار رندر را با override کردن ShouldRender کنترل کنید:
protected override bool ShouldRender()
{
return importantStateChanged;
}
این Pattern برای جلوگیری از رندرهای غیرضروری در پروژههای حجیم حیاتی است.
4️⃣ After Render
متدهای OnAfterRender و OnAfterRenderAsync پس از هر بار رندر شدن کامپوننت فراخوانی میشوند. این نقطه طلایی برای تعامل با JS interop یا دسترسی مستقیم به DOM است:
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender)
{
await JS.InvokeVoidAsync("myInitFunction");
}
}
مطمئن شوید که عملیات سنگین یا Side Effectها را فقط زمانی اجرا کنید که firstRender == true باشد تا از rerunهای ناخواسته جلوگیری شود.
✨ نکته معماری: اگر State یا eventها خارج از قاعده چرخه عمر مدیریت شوند، به مشکلاتی نظیر memory leak، قرینگی وضعیت نادرست یا رفتار غیرقابلپیشبینی کامپوننت برمیخورید.
⏳ جمعبندی برای Fullstackها:
با مدلسازی دقیق کامپوننتها برپایه چرخه عمر، نهتنها Performance و Maintainability تضمین میشود، بلکه Integration با APIهای backend و JS نیز بیدردسر و توسعهپذیر خواهد بود — دقیقاً همان جایی که Blazor در قامت یک اکوسیستم فولاستک میدرخشد.
@DeveloperAdvocate 🥑