Developer Advocate
الذهاب إلى القناة على Telegram
1 035
المشتركون
لا توجد بيانات24 ساعات
لا توجد بيانات7 أيام
-130 أيام
أرشيف المشاركات
1 035
در حال حاضر سرورهای هیچ یک از دیتاسنترهای معروف ایران مثل آسیاتک، آروان، شاتل و … با خارج از ایران ارتباط ندارن و کاملا مسدود هستند.
تنها ۲ دیتاسنتر گمنام دولتی که اسمش به گوش کسی نرسیده و فقط از سایتهای دولتی میزبانی میکنند ارتباط فعال دارند که خب مشخصه چه افرادی از این سرورها دارند و اصلا قابل خریداری نیست.
در حال حاضر هیچ راهی (واقعا هیچراهی) برای اتصال نیست تا زمانی که شرایط عادی بشه. شما هم پولتونو دور نریزید و بهترین کار صبر کردنه. هر سروری هم وصله خیلی موقته در حد یکی ۲ ساعت.
1 035
🔐 Credential Stuffing چیست و چگونه از Endpoint ورود حفاظت کنیم؟
یکی از حملات رایج به سرویسهای احراز هویت، Credential Stuffing است: مهاجم با استفاده از دیتابیسهای قدیمیِ لو رفتهی نام کاربری و رمز عبور (معمولاً به صورت لیستهای بزرگ)، سعی میکند از همان اطلاعات در سرویس جدید استفاده کند. از آنجا که بسیاری از کاربران رمزهای تکراری دارند، احتمال ورود موفق وجود دارد.
برای دفاع پیشرفته در .NET (مثلاً ASP.NET Core)، توجه به چند لایه الزامی است:
1️⃣ محدودیت نرخ (Rate Limiting):
هر IP یا شناسه حساب کاربری باید محدودیت منطقی در تعداد تلاش ورود داشته باشد. در .NET 8 به سادگی میتوانید با Attribute زیر این کار را انجام دهید:
[EnableRateLimiting("login-policy")]
[HttpPost("login")]
public IActionResult Login([FromBody] LoginModel model) => ...
و سپس در Program.cs:
builder.Services.AddRateLimiter(options =>
{
options.AddPolicy("login-policy", context =>
RateLimitPartition.GetFixedWindowLimiter(
partitionKey: context.Connection.RemoteIpAddress?.ToString() ?? "unknown",
factory: key => new FixedWindowRateLimiterOptions
{
PermitLimit = 5,
Window = TimeSpan.FromMinutes(1),
QueueProcessingOrder = QueueProcessingOrder.OldestFirst,
QueueLimit = 2
}));
});
2️⃣ کپچا (Recaptcha یا سایر):
اضافه کردن کپچا به صفحه ورود بعد از چند تلاش ناموفق، الگوریتمهای خودکار را متوقف میکند.
3️⃣ بررسی IP و GeoLocation مشکوک:
تحلیل رفتار کاربر و کشف الگوهای ورود غیرمنتظره میتواند هشدار داخلی ایجاد کند.
4️⃣ مدیریت لاگین Failها:
وقتی تعداد ورود ناموفق برای یک کاربر یا IP زیاد شد، غیر فعالسازی موقت حساب کاربری (Account Lockout) یا تاخیر تدریجی (Progressive Delay) ایدهآل هستند.
services.Configure<IdentityOptions>(options =>
{
options.Lockout.MaxFailedAccessAttempts = 5;
options.Lockout.DefaultLockoutTimeSpan = TimeSpan.FromMinutes(10);
options.Lockout.AllowedForNewUsers = true;
});
5️⃣ ادغام با سرویسهای تشخیص breached credential (مثلاً HaveIBeenPwned) و هشدار به کاربر.
🎯 نکتهی کلیدی: با ابزار قدرتمند Rate Limiter و Policyهای Lockout در .NET، میتوانید سطح حمله را بسیار پایین بیاورید. دفاع حرفهای نیازمند ترکیب لایههاست تا بار امنیت فقط روی یک دیوار نیفتد.
#Security #ASPNetCore #CredentialStuffing #BestPractices
@DeveloperAdvocate 🥑1 035
🎯 امنیت وب: پیادهسازی سریع Content Security Policy (CSP) در ASP.NET Core
یکی از بهترین راهها برای مقابله با XSS، اضافهکردن هدر CSP به ریسپانسهای HTTP است. با استفاده از Middleware سفارشی، میتوان این هدر را بهسادگی روی کل اپلیکیشن ست کرد و سیاستهای دلخواه را اعمال نمود. نمونهی زیر یک سیاست ساده (و نسبتاً سختگیرانه) را فقط برای اسکریپتها، استایلها و تصاویر اعمال میکند:
public class CspMiddleware
{
private readonly RequestDelegate _next;
public CspMiddleware(RequestDelegate next) => _next = next;
public async Task Invoke(HttpContext context)
{
var csp = "default-src 'self'; script-src 'self'; style-src 'self'; img-src 'self' data:;";
context.Response.Headers.Append("Content-Security-Policy", csp);
await _next(context);
}
}
// و حالا در متد Configure اپ فایل Startup.cs:
app.UseMiddleware<CspMiddleware>();
✅ نکته حرفهای:
- از 'self' برای محدودسازی منابع به دامین خودتان استفاده کنید.
- برای گسترش سیاست (مثلاً اجازهی بارگذاری فونت از CDN)، صرفاً دستور مدنظر را به رشتهی سیاست اضافه کنید:
font-src 'self' https://fonts.googleapis.com;
- در محیط دیباگ و توسعه، CSP را برای گزارش خطا با سوییچ report-uri فعال کنید.
💎 این الگو بسیار منعطف است و میتوانید سیاست را بر اساس نیاز خودتان Dynamic کنید یا فقط روی مسیرهای خاص اعمالش کنید. CSP یکی از اولین خطوط دفاعی در مقابل حملات injection است؛ حتماً در معماری امنیتیتان لحاظ کنید.
@DeveloperAdvocate 🥑1 035
مینیمال API ها در .NET: قدرت و سادگی در کنار هم
اگر به دنبال ساخت بکاندهای سبک، مقیاسپذیر و سریع هستید، مینیمال API ها در .NET 7+/8 را جدی بگیرید. این قابلیت با حذف لایههای اضافی (مانند Controller و Action) و سادهسازی Dependency Injection و مسیریابی، تجربهای شبیه به FastAPI یا ExpressJS اما با قدرت سیستم تایپ C# ارائه میدهد.
نمونهای از تعریف یک API ساده با DI، ولیدیشن و OpenAPI:
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddDbContext<AppDbContext>();
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
var app = builder.Build();
app.UseSwagger();
app.UseSwaggerUI();
app.MapPost("/todo", async (TodoItemDto dto, AppDbContext db) =>
{
if (string.IsNullOrWhiteSpace(dto.Title))
return Results.BadRequest("Title is required.");
var todo = new TodoItem { Title = dto.Title, IsDone = false };
db.Todos.Add(todo);
await db.SaveChangesAsync();
return Results.Created($"/todo/{todo.Id}", todo);
});
app.Run();
record TodoItemDto(string Title);
class TodoItem { public int Id { get; set; } public string Title { get; set; } public bool IsDone { get; set; } }
نکتههای معماری:
• تزریق وابستگی کاملاً یکپارچه است؛ وابستگیها را همانند پارامترهای متد دریافت کنید.
• ساختار داده را به صورت record یا class مشخص کنید؛ قابلیت استفاده از model binding وجود دارد.
• تستپذیری بسیار بالا: هر endpoint را میتوان به صورت delegate تست و شبیهسازی کرد.
• با افزودن auth middleware یا Policyها پیچیدگی را مرحله به مرحله کنترل کنید.
در پروژههای میکروسرویس، Edge API، serverless و prototyping، مینیمال API یک انتخاب بینقص است؛ سادگی را بدون قربانی کردن قابلیت تست و انعطاف تجربه خواهید کرد.
#dotnet #minimalAPI #architecture
@DeveloperAdvocate 🥑1 035
📌 Integration Testing حرفهای بر روی .NET API با دیتابیس تست مستقل
مهمترین اصل integration test معتبر: اجرای تستها روی دیتابیسی که کاملاً ایزوله است و هر بار از نو ساخته میشود. بهترین راهکار در .NET استفاده از EF Core InMemory یا کلاً SQL Server Docker-based برای نزدیکی بیشتر به production است.
یک رویکرد پیشرفته:
- استفاده از xUnit + WebApplicationFactory برای راهاندازی کل API
- تزریق DbContext تستی
- راهاندازی و امتیگرت کردن دیتابیس تست با هر اجرا
نمونه کد:
public class CustomWebApplicationFactory<TStartup> : WebApplicationFactory<TStartup> where TStartup : class
{
protected override void ConfigureWebHost(IWebHostBuilder builder)
{
builder.ConfigureServices(services =>
{
// Remove اصلی DbContext
var descriptor = services.SingleOrDefault(
d => d.ServiceType == typeof(DbContextOptions<AppDbContext>));
if (descriptor != null)
services.Remove(descriptor);
// افزودن DbContext تست با InMemory
services.AddDbContext<AppDbContext>(options =>
{
options.UseInMemoryDatabase("TestDb_" + Guid.NewGuid());
});
// دیتابیس تست را مقداردهی اولیه کن
var sp = services.BuildServiceProvider();
using (var scope = sp.CreateScope())
{
var db = scope.ServiceProvider.GetRequiredService<AppDbContext>();
db.Database.EnsureCreated();
// داده تست اولیه وارد کن (seed)
}
});
}
}
اکنون میتوانید کل API را با HttpClient تست کنید و از دیتابیس مجزا برای هر تست بهره ببرید:
public class MyApiIntegrationTests : IClassFixture<CustomWebApplicationFactory<Startup>>
{
private readonly HttpClient _client;
public MyApiIntegrationTests(CustomWebApplicationFactory<Startup> factory)
{
_client = factory.CreateClient();
}
[Fact]
public async Task Test_Create_Item()
{
var content = new StringContent(
JsonConvert.SerializeObject(new { Title = "Integration" }),
Encoding.UTF8, "application/json");
var response = await _client.PostAsync("/api/items", content);
response.EnsureSuccessStatusCode();
// چک کردن دیتابیس via context یا با فراخوانیهای API بعدی
}
}
✅ با این رویکرد، هر تست کاملاً مستقل، repeatable و نزدیکترین رفتار به محیط واقعی production را خواهد داشت. برای سطح حرفهای، مهاجرت به دیتابیس واقعی (مثلاً با Testcontainers برای SQL Server/PostgreSQL) هم قابل توصیه است — ولی الگوی فوق یک نقطه شروع عالی و ساده برای CI/CD پایدار میسازد.
@DeveloperAdvocate 🥑1 035
🚀 Type-Safe APIs در دنیای .NET و React — روشی حرفهای برای E2E type safety
شاید با tRPC در اکوسیستم Node.js آشنا باشید — اما اگر عاشق تایپ سیفتی سرتاسری (end-to-end type safety) بین Backend (مثلاً ASP.NET Core) و React هستید، باید به سراغ ابزارهایی مانند TypeGen، R2TS یا نسل جدید TypeSync بروید تا تعریف قراردادها را در C# انجام دهید و TypeScript را به شکل اتوماتیک از آن بسازید.
مسیر کار به صورت اصولی 👇
1️⃣ قراردادها را در بکاند تعریف کنید:
یک کلاس ساده C# که API contract شماست:
public class UserDto
{
public int Id { get; set; }
public required string Name { get; set; }
}
2️⃣ آنتیشن یا comment مخصوص ابزار سینک قرار دهید:
برای TypeGen مثلاً:
[ExportTsClass] // یا
/// <auto-generate-ts>
public class UserDto { ... }
3️⃣ اسکریپت تبدیل اجرای شبانه:
اسکریپت CI/CD یا prebuild local:
typegen generate
4️⃣ Consume در React:
نیازی به تعریف مجدد Interface در TypeScript نیست! فقط این فایل auto-generated را ایمپورت کنید:
import { UserDto } from './contracts/UserDto';
const loadUser = async (): Promise<UserDto> => {
const res = await fetch('/api/user/1');
return res.json();
};
✅ حالا اگر فیلدی در DTO اضافه/حذف کنید، تغییر بلافاصله به TypeScript منتقل و کاملاً type-safe میشوید. هیچ نیاز به Duplication یا نگهداری interface موازی نیست.
⭐️ نکته طلایی:
با این رویکرد، شما به tRPC-like developer experience دست مییابید (بدون GraphQL یا ابزارهای اضافه). ساختار قرارداد در جایی که بیشترین context وجود دارد (یعنی C# مدل) نگه داشته میشود، و هرچیز در FE خودبهخود بهروز و type-safe میماند.
پایینتر حتماً نگاه کنید به:
- TypeGen (برای پروژههای بزرگ فوقالعاده است)
- R2TS (اگر نسل کدهای سبکتر میخواهید)
- NSwag (آپشن برای تولید TypeScript از Swagger/OpenAPI جدید)
- TypeSync (برای کنترل custom mapping در پروژههای enterprise)
👓 آیا تجربه تولید TypeScript اتوماتیک از مدلهای .NET دارید؟ کدام ابزار و روش را ترجیح میدهید؟
@DeveloperAdvocate 🥑1 035
🎯 لایه ضدخرابی (ACL) در معماری: ساده اما حیاتی
فرض کنید باید زیرساخت دامین خود را از ناپایداری، اشتباهات مدل یا قراردادهای ضعیف یک سرویس خارجی محافظت کنید. Anti-Corruption Layer دقیقا همینجاست که میدرخشد! بیایید با یک مثال عملی ایده را شفاف کنیم:
تصور کنید سیستم داخلی ما با مدل Order کار میکند، اما سرویس خارجی تحویلدهنده، مدل OrderDto متفاوتی دارد. هدف: ایزولهسازی کامل مدل دامین از مدل و رفتار خارجی.
۱️⃣ انتقالدهنده (Translator) بین مدلها
۲️⃣ درگاه انتزاعی برای سرویسی که کنترلش با ما نیست
// مدل دامین داخلی
public class Order
{
public Guid Id { get; }
public DateTime CreatedAt { get; }
public decimal Total { get; }
// ... منطق دامین اینجا
public Order(Guid id, DateTime createdAt, decimal total) =>
(Id, CreatedAt, Total) = (id, createdAt, total);
}
// مدل خارجی از سرویس تحویلدهنده
public class OrderDto
{
public string orderId { get; set; }
public string timestamp { get; set; }
public float price { get; set; }
}
🔰 لایه ضدخرابی = Mapper + آداپتور
// Translator: مسئول تبدیل به مدل امن داخلی
public static class OrderTranslator
{
public static Order FromDto(OrderDto dto)
{
return new Order(
Guid.Parse(dto.orderId),
DateTime.Parse(dto.timestamp),
(decimal)dto.price
);
}
}
// پورت انتزاعی: استفاده فقط از مدل دامین
public interface IOrderProvider
{
Task<Order> GetOrderAsync(Guid id, CancellationToken ct = default);
}
// آداپتور که سرویس خارجی را wrap میکند:
public class OrderServiceAdapter : IOrderProvider
{
private readonly IExternalOrderClient _externalClient;
public OrderServiceAdapter(IExternalOrderClient externalClient)
=> _externalClient = externalClient;
public async Task<Order> GetOrderAsync(Guid id, CancellationToken ct = default)
{
var dto = await _externalClient.FetchOrderAsync(id.ToString(), ct);
return OrderTranslator.FromDto(dto);
}
}
💡 نکته طلایی: اگر API خارجی دچار تغییر یا خطا شود، این تغییرات فقط در قشر ACL شکسته میشوند و شما مدل دامین را پاک، ایمن و مستقل نگه میدارید.
🧬 این راهبرد نه فقط الگوی DDD را تقویت میکند، بلکه مهاجرت یا جایگزینی سرویسهای خارجی آتی را هم بسیار آسان میسازد. همیشه لایه ضدخرابی، حتی برای سناریوهای بهظاهر ساده!
@DeveloperAdvocate 🥑1 035
مینیمال APIها در .NET: ابزار قدرتمند برای معماریهای سبک و مدرن 🚀
اگر هنوز با مینیمال APIها (Minimal APIs) در .NET 6+ کار نکردهاید، وقت آن است که جدیتر نگاه کنید؛ رویکردی که با الهام از فلسفه simplicity-first، امکان ساخت سرویسهای back-end بسیار سبک، سریع و قابل تست را مهیا میکند بدون نیاز به boilerplate سنتی ASP.NET Core (مانند Controllerها و Attributeها).
نمونهای از ساخت یک endpoint کامل، با dependency injection و ولیدیشن مدل، همه در چند خط:
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddDbContext<AppDbContext>();
builder.Services.AddScoped<IUserRepository, UserRepository>();
var app = builder.Build();
// Map endpoint با DI و ولیدیشن مدل
app.MapPost("/users", async (UserDto dto, IUserRepository repo) =>
{
if (string.IsNullOrWhiteSpace(dto.Name))
return Results.BadRequest(new { error = "Name is required" });
var user = new User { Name = dto.Name, Email = dto.Email };
await repo.AddAsync(user);
return Results.Created($"/users/{user.Id}", user);
})
.WithName("CreateUser")
.Produces<User>(StatusCodes.Status201Created)
.Produces(StatusCodes.Status400BadRequest);
app.Run();
مزایا برای معماران و تیمهای حرفهای:
- Startup فوقالعاده سریع (حتی در serverless)
- Boilerplate بسیار کم – هر endpoint، یک تابع!
- Dependency Injection بصورت توکار، حتی بدون نیاز به attributeها
- نهایت کنترل روی رفتار HTTP pipeline (بدون abstraction اضافی MVC)
- کاملاً type-safe و قابل تست – میتوانید مستقیماً handlerها را unit test کنید
مینیمال APIها یک انتخاب عالی برای:
- gatewayهای سبک microservice
- پروژههای Serverless
- اثباتگرهای سریع مفهوم (POC)
- سرویسهای Edge & IoT
چالش جالب: سعی کنید یک RESTful سرویس کامل را فقط با مینیمال APIها و بدون هیچ Controller پیادهسازی کنید؛ معماری و تکامل کدتان شما را شگفتزده خواهد کرد!
@DeveloperAdvocate 🥑1 035
✅ امنیت: اضافهکردن Header ساده CSP در ASP.NET Core
یکی از مؤثرترین راهکارهای مقابله با XSS، استفاده از Content Security Policy است. بیایید یک هدر CSP اولیه اما کاربردی با محدودیت بارگذاری اسکریپت فقط از مبدأ خودمان، در اپلیکیشن ASP.NET Core اضافه کنیم:
// در متد Configure کلاس Startup یا Program:
app.Use(async (context, next) =>
{
context.Response.Headers.Add(
"Content-Security-Policy",
"default-src 'self'; script-src 'self'; style-src 'self'; object-src 'none';"
);
await next();
});
این هدر چه میکند؟
- فقط اجازه بارگذاری منابع (JS، CSS، و...) از همان Origin را میدهد ('self')
- اجرای inline scripts یا sourceهای خارجی (مثلاً CDN بدون اجازه خاص) را مسدود میکند
- بارگذاری هر نوع شی خارجی (مثل Flash) را کاملاً غیرمجاز میکند
🔎 نکته معماری:
- برای محیط production، توصیه میشود هر policy را بسته به نیاز و معماری frontend تنظیم کنید (مثلاً اگر از CDN معتبری استفاده میکنید، دامنه آن را اضافه کنید).
- خطایابی CSP را با استفاده از ابزارهایی مثل گزارشهای DevTools و violation-reporting انجام دهید.
🧩 این ساختار ساده پایهای برای امنیت مدرن frontend است؛ آن را متناسب با الزامات امنیتی سیستم خود توسعه دهید و هرگز فراموش نکنید CSP تنها یکی از قطعات پازل امنیت شماست!
@DeveloperAdvocate 🥑1 035
🎯 Platform Engineering چیست و نمونهای از Internal Developer Platform (IDP)
مدتهاست DevOps بهعنوان حلقه اتصال توسعه و عملیات مطرح است؛ اما با رشد تیمها و پیچیدگی زیرساختها، نیاز به یک لایه میانی جدید حس شد: Platform Engineering. هدف این حوزه، ساخت «پلتفرم توسعه داخلی» (IDP) است؛ بستری که تیمهای توسعه مستقلانه، سریع و امن سرویسهای خود را دیپلوی و نگهداری کنند، بدون نیاز به درگیر شدن با پیچیدگیهای زیرساخت.
یک IDP معمولاً ابزارهایی چون GitOps، کلاسترینگ، CI/CD سلفسرویس، دیتابیس مدیریتشده و ابزار مانیتورینگ را در قالب API، CLI یا UI سادهشده عرضه میکند.
🌟 نمونه ساده از Internal Developer Platform با ASP.NET Core و GitHub Actions:
1. API برای دیپلوی سرویس: فرض کنید سرویسهای شما بهشکل containerized (مانند Docker) هستند. یک API ایجاد میکنید تا devها فقط با یک call یا git push سرویسشان را دیپلوی کنند.
// Controller: /api/deploy
[ApiController]
[Route("api/[controller]")]
public class DeployController : ControllerBase
{
[HttpPost]
public IActionResult Deploy([FromBody] DeployRequest req)
{
// اعتبارسنجی درخواست و سطح دسترسی
if (!IsAuthorized(req.Team, req.Service))
return Unauthorized();
// اجرای pipeline (مثلاً با GitHub Actions dispatch)
var triggered = TriggerDeployment(req.Service, req.Branch);
return triggered ? Ok("Deployment started") : StatusCode(500, "Failed");
}
private bool TriggerDeployment(string service, string branch)
{
// سادهسازی: شبیهسازی فراخوانی API گیتهاب برای dispatch workflow
// واقعی: از Octokit یا REST API گیتهاب استفاده کنید
var http = new HttpClient();
http.DefaultRequestHeaders.Authorization =
new AuthenticationHeaderValue("Token", "<GITHUB_TOKEN>");
var body = new { ref = branch, inputs = new { service = service } };
var resp = http.PostAsJsonAsync(
"https://api.github.com/repos/your-org/platform-infra/actions/workflows/deploy.yml/dispatches",
body).Result;
return resp.IsSuccessStatusCode;
}
}
public record DeployRequest(string Team, string Service, string Branch);
2. سورس کد pipeline (نمونه GitHub Actions):
# .github/workflows/deploy.yml
name: Deploy Service
on:
workflow_dispatch:
inputs:
service:
required: true
type: string
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Build & Push Docker Image
run: |
docker build -t myregistry/${{ github.event.inputs.service }}:latest .
docker push myregistry/${{ github.event.inputs.service }}:latest
- name: Update Service in Kubernetes
run: |
kubectl set image deployment/${{ github.event.inputs.service }} \
${SERVICE}="myregistry/${{ github.event.inputs.service }}:latest"
🔹 با این معماری ساده، توسعهدهنده فقط یک درخواست POST ارسال میکند و سرویسش بدون دخالت مستقیم با زیرساخت، خودکار build و deploy میشود — مهمترین اصل Platform Engineering یعنی سلفسرویس!
---
آیا در سازمان شما Platform Engineering وجود دارد؟ راهکارهای بومی خود را به اشتراک بگذارید 👇
@DeveloperAdvocate 🥑1 035
🔹 Virtual DOM در React: چرا و چگونه؟
وقتی صحبت از عملکرد React میشود، یکی از کلیدیترین مفاهیم، Virtual DOM است. برخلاف تصور اولیه، Virtual DOM صرفاً یک نسخه سبک وزن و درختی از DOM واقعی در حافظه است که React مدیریت و پردازش میکند.
چالش اصلی: DOM واقعی مرورگر به شدت کند است؛ هر بار که باید تغییری روی درخت DOM انجام دهیم (مثلاً اضافهکردن یک عنصر یا تغییر کلاس CSS)، مرورگر باید محاسبه و render جدید انجام دهد که برای اپلیکیشنهای مدرن با دادههای پویا، به شدت هزینهبر خواهد بود.
✅ راه حل React:
۱. رندر مجدد در Virtual DOM
هر بار که state یا props شما تغییر میکند، React یک نسخه جدید و کامل از درخت Virtual DOM میسازد.
۲. مقایسه (Diffing)
React با الگوریتم diffing اختصاصی خودش (درخت جدید و قدیمی را مقایسه میکند) حداقل مجموعهای از تغییرات لازم را استخراج میکند.
۳. اعمال تغییرات minimal به DOM واقعی (Reconciliation)
در نهایت، فقط همان تغییراتی که واقعاً تفاوت دارند، به DOM واقعی منتقل میشوند.
⏩ این معماری باعث میشود React بهجای صدها عملیات پرهزینه روی DOM، تنها چند آپدیت هدفمند انجام دهد.
یک تشبیه: فرض کنید باید لیستی از ۱۰٬۰۰۰ آیتم را بهروزرسانی کنید، اما تنها یک آیتم تغییر کرده است؛ Virtual DOM تضمین میکند فقط همان یک آیتم دوباره رندر شود، نه کل لیست.
برای معماران .NET:
اگر پیشزمینهای در توسعه WPF دارید، الگوی Virtual DOM بسیار شبیه کارکرد Visual Tree و Dependency Propertyهاست، جایی که یک state مرکزی، view را بهشکل دکلرهتیو مدیریت میکند و تغییرات minimal و هوشمند به رابط کاربری اعمال میشود.
ℹ️ نکته فنی: اگر در اپهای React حجم داده زیاد است یا درخت کامپوننتها عمیق و پیچیده شده، میتوانید به کمک memoization، کلیدهای مناسب (
key) و PureComponent عمق بیخودی renderها را کمینه کنید و از قدرت Virtual DOM حداکثر استفاده را ببرید.
🔬 از نگاه معماری، Virtual DOM یعنی decoupling کامل UI state و تصویردهی، و قدم بزرگی به سوی UIهای پیشبینانه و reactive.
@DeveloperAdvocate 🥑1 035
در دپلوی اپلیکیشنهای .NET روی Kubernetes، مفهومی به نام Pod یکی از ارکان معماری استراتژیک است. Pod کوچکترین واحد قابل دیپلوی در K8s است که میتواند یک یا چند کانتینر (معمولاً یک کانتینر اصلی) را در خود جای دهد. فرض کنید یک وبAPI با ASP.NET Core ساختهاید و آن را داکرایز کردید. معادل هر instance این API، یک Pod دارید؛ و K8s این Podها را مدیریت میکند.
اما چرا به جای دیپلوی مستقیم کانتینرها از Pod استفاده کنیم؟ دقت کنید که Pod امکاناتی مثل اشتراک منابع (Volume، Network Namespace) و همبستگی Lifecycle بین کانتینرها را فراهم میکند. مثلاً اگر کنار اپ اصلی خود یک sidecar برای لاگ گرفتن یا مدیریت کانفیگ نیاز دارید، هر دو را در یک Pod قرار میدهید. این pattern برای اپهای .NET بهویژه مفید است وقتی یک ابزار Monitoring (مثل dotnet-counters یا تریسینگ اختصاصی) را همزمان با سرویس اصلی اجرا میکنید.
در یک Deployment میتوانید تعداد Pod مورد نیاز را تنظیم کنید و K8s با یک Replication Controller روی تعداد instanceها نظارت میکند:
apiVersion: apps/v1
kind: Deployment
metadata:
name: dotnet-api-deployment
spec:
replicas: 3
selector:
matchLabels:
app: dotnet-api
template:
metadata:
labels:
app: dotnet-api
spec:
containers:
- name: api
image: yourregistry.azurecr.io/dotnet-api:1.0.0
ports:
- containerPort: 80
برای ارتباط بین Podها (مثلاً frontend و backend)، از Service استفاده میکنید. یک Service نوع ClusterIP یا LoadBalancer به شما IP ثابت و Discovery داخلی میدهد. تفاوت مهم: Podها ephemeral هستند، همهچیز در Service هماهنگ میشود تا حتی پس از بازسازی Pods هم ترافیک بهدرستی route شود.
در پروژههای حرفهای .NET توصیه میکنم Event-driven logging و health checkها را بر اساس liveness/readiness probes روی هر Pod تنظیم کنید، تا K8s بتواند بهصورت خودکار instance معیوب را حذف و جایگزین کند:
livenessProbe:
httpGet:
path: /health/live
port: 80
initialDelaySeconds: 30
periodSeconds: 10
جمعبندی: هر Pod نمایندهی یک instance از اپلیکیشن .NET شماست؛ اما قدرت واقعی زمانی آشکار میشود که قابلیتهای orchestration K8s مثل scaling، self-healing، و سرویس دیسکاوری را برپایه Pods و Services به کار میگیرید و چرخه عمر یکپارچه برای ماژولهای داتنتی خود پیاده میکنید.
@DeveloperAdvocate 🥑1 035
✨ آشنایی عمیق با صفت
[CollectionBuilder] در .NET 8: ساخت Literalهای سفارشی برای کالکشنها
در .NET 8 با معرفی صفت [CollectionBuilder] میتوانید سینتکس literal برای کالکشنهای سفارشی خود ارائه کنید—دقیقاً مانند سینتکس {} که برای List<T> یا HashSet<T> در C# 12 در دسترس است.
🔍 مثال عملی: فرض کنید کالکشن Immutable خود با نام MyImmutableSet<T> دارید و میخواهید با { ... } آن را مقداردهی کنید.
ابتدا یک کلاس Builder بسازید:
public static class MyImmutableSetBuilder<T>
{
public static MyImmutableSet<T> Create(ReadOnlySpan<T> items)
{
var result = new MyImmutableSet<T>();
foreach (var item in items)
result = result.Add(item);
return result;
}
}
سپس کالکشن خود را با صفت [CollectionBuilder] وابسته کنید:
using System.Collections.Frozen;
using System.Runtime.CompilerServices;
[CollectionBuilder(typeof(MyImmutableSetBuilder<>), "Create")]
public sealed class MyImmutableSet<T>
{
private readonly FrozenSet<T> _set;
public MyImmutableSet() : this(FrozenSet<T>.Empty) { }
private MyImmutableSet(FrozenSet<T> set) => _set = set;
public MyImmutableSet<T> Add(T value) => new MyImmutableSet<T>(_set.Add(value));
public bool Contains(T value) => _set.Contains(value);
}
حالا میتوانید از سینتکس literal استفاده کنید:
var primes = new MyImmutableSet<int> { 2, 3, 5, 7, 11 };
// primes از نوع MyImmutableSet<int> مقداردهی شده با عناصر بالا است!
نکات کلیدی:
- متد builder باید static و public باشد.
- پارامتر single ReadOnlySpan<T> بگیرد یا params T[] (برای عملکرد بهتر از Span استفاده کنید).
- این قابلیت پتانسیل DSLهای type-safe و توصیفی بیشتری در C# بدون boilerplate اضافی را ایجاد میکند.
🔗 مرجع: CollectionBuilderAttribute در مایکروسافت داکیومنتز
#DotNet8 #CSharp12 #AdvancedDotNet
@DeveloperAdvocate 🥑1 035
یک ترفند کارآمد با TanStack Query (React Query): بهروزرسانی خوشبینانه (Optimistic Update) برای تجربه کاربران در اپهای ریاکت
فرض کنید جدول کاربر (users) دارید و میخواهید با حذف سریع یک کاربر، بلافاصله نتیجه را به کاربر نشان دهید ــ حتی قبل از پاسخ سرور. اینجا بهروزرسانی خوشبینانه وارد میشود.
نمونه پیادهسازی با استفاده از
useMutation و invalidate کردن کوئری:
import { useMutation, useQueryClient } from '@tanstack/react-query';
function useDeleteUser() {
const queryClient = useQueryClient();
return useMutation(
async (userId) => {
// فرض: تابعی که روی سرور کاربر را حذف میکند
await deleteUserOnServer(userId);
},
{
// ۱. تغییر فوری cache با onMutate
onMutate: async (userId) => {
await queryClient.cancelQueries(['users']);
const previousUsers = queryClient.getQueryData(['users']);
// حذف کاربر به صورت خوشبینانه
queryClient.setQueryData(['users'], (old) =>
old?.filter((user) => user.id !== userId)
);
return { previousUsers };
},
// ۲. اگر خطا، بازگشت به داده قبلی
onError: (err, userId, context) => {
queryClient.setQueryData(['users'], context.previousUsers);
},
// ۳. سینک دوباره (تازهسازی)
onSettled: () => {
queryClient.invalidateQueries(['users']);
}
}
);
}
این الگو چند ویژگی کلیدی دارد:
- با onMutate، داده لوکال را آنی آپدیت میکنیم (UI سریع و واکنشگر).
- در صورت خطا، rollback پشت صحنه به وضعیت قبلی با context.
- بعد از عملیات (موفق یا ناموفق) تازهسازی داده از سرور برای همخوانی نهایی.
⏩ بهینهتر این است که با تنظیمات updateData و کوئریهای مجزا، کلیت cache را سمت کلاینت مدیریت کنید، بهویژه در سیستمهایی با real-time sync یا حجم بالای تعاملات.
#ReactQuery #OptimisticUpdate #Frontend #TanStackQuery #ReactAdvanced
@DeveloperAdvocate 🥑1 035
🎯 Logging Best Practices – ساختارمندسازی لاگها و اهمیت آن در معماریهای مدرن
در معماریهای مدرن (مبتنی بر microservices یا distributed systems)، لاگکردن صرفاً نوشتن متنی از اتفاقات نیست؛ بلکه باید دادهها را به شکل ساختارمند (Structured Logging) ثبت کنید تا بشود به صورت عملیاتی و سیستمی دادهها را تحلیل و پایش کرد.
🔹 Structured Logging چیست؟
به جای لاگ ساده مانند:
_logger.LogInformation($"User {userId} deleted order {orderId} at {time}");
پیشنهاد میشود لاگ را به صورت کلید-مقدار (Key-Value Pair) ذخیره کنید:
_logger.LogInformation("Order deleted by user", new { userId, orderId, timestamp = time });
در پکیجهایی مثل Serilog، میتوانید ساختارمند لاگ کنید:
Log.Information("Order {OrderId} deleted by user {UserId} at {Timestamp}", orderId, userId, time);
این مدل، لاگ را بهصورت JSON ذخیره و ارسال میکند:
{
"OrderId": 1234,
"UserId": 5678,
"Timestamp": "2024-06-01T12:34:56Z",
"Message": "Order 1234 deleted by user 5678 at 2024-06-01T12:34:56Z"
}
🔹 چرا Structured Logging اهمیت دارد؟
۱. امکان جستجو و فیلتر حرفهای: با ذخیره لاگها به فرمت ساختارمند (JSON, BSON, …) میتوانید لاگها را در سیستمهایی مانند ELK Stack یا Seq به راحتی فیلتر، جستجو و تجزیه تحلیل کنید.
۲. Correlation سادهتر: ردگیری درخواستها و شناسایی bottleneckها وقتی هر event و context آن (مانند traceId، userId و ...) لاگ شده راحتتر میشود.
۳. تشخیص سریعتر مشکلات: قابلیت ساخت dashboardها و alertها مبتنی بر فیلدهای ساختارمند موجب واکنش سریعتر در زمان رخداد خطا یا event حیاتی میشود.
🔸 Best practices:
- همیشه از ساختار جایگزین رشتههای concatenated استفاده کنید.
- هر رخداد مهم اپلیکیشن باید متادیتای کافی (context) داشته باشد (مانند IP، userId، correlationId و…).
- Sensitive data را لاگ نکنید؛ مراقب GDPR و الزامات قانونی باشید!
- سطح مناسبی از log level (Information, Warning, Error) را رعایت کنید.
✨ اگر هنوز از structured logging استفاده نمیکنید، همین امروز شروع کنید! ابزارهایی مثل Serilog (برای .NET)، ELK/EFK Stack و Seq کار شما را بسیار ساده میکنند.
Structured logs = Real observability 🚀
@DeveloperAdvocate 🥑1 035
WebAssembly (WASM): آیندهای بیمرز برای داتنت
WebAssembly، یک باینری قابلاجرای cross-platform در مرورگر است که ماشین مجازی جاوااسکریپت را دور زده و امکان اجرای کد native با سرعت نزدیک به ماشین محلی را روی وب فراهم میکند.
قدرتمندی WASM زمانی در اکوسیستم داتنت به اوج میرسد که با پروژههایی مانند Blazor WebAssembly تلفیق میشود؛ جاییکه شما میتوانید #C# و حتی کتابخانههای native خود را، بدون dependency به پلاگینهای اضافی، مستقیماً روی مرورگر اجرا کنید. این یعنی تجربهی SPAهای پیچیده با قدرت زبانهای مدرن و ساختار معماری میکروسرویسها، فارغ از چالشهای سنتی وب مانند سازگاری با مرورگر یا بهینهسازی جاوااسکریپت.
یک نکته فنی: پابرجاماندن state در Blazor WebAssembly بهصورت in-browser و اجرا شدن چرخههای رویدادی (event loop) در محیطی سندباکسشده، به شما اجازه میدهد تا الگوهایی مانند Dependency Injection و حتی multi-threading (از طریق WebAssembly threading API) را پیادهسازی کنید.
نمونه ساده از پایداری state و تعامل با جاوااسکریپت:
@inject IJSRuntime JS
<button @onclick="ShowAlert">Show Alert</button>
@code {
private async Task ShowAlert()
{
await JS.InvokeVoidAsync("alert", "با داتنت روی WebAssembly خوش آمدید!");
}
}
در آینده، با رشد WASI (WebAssembly System Interface) میتوانیم WASM را فراتر از مرورگر، مثلاً برای اجرای سرویسهای lightweight در لبه (edge) یا حتی سرور بدون محدودیت سیستمعامل، به کار بگیریم. این یک مسیر جذاب برای unified applications با core مشترک داتنت خواهد بود.
@DeveloperAdvocate 🥑1 035
🌐 پردازش رشتهای بدون تخصیص اضافی با استفاده از Span<T> در #dotnet
در بسیاری از سناریوهای پردازش متن (مثلاً استخراج یا اسلیس کردن بخشهایی از یک string)، فراخوانی متدهایی مثل
.Substring() باعث تخصیص شیء جدید در heap میشود—اتفاقی که با حجم زیاد داده بیرحمانه روی GC فشار میآورد.
راهحل مدرن برای این مسئله، بهرهگیری از Span<T> است: ساختاری stack-only که به شما اجازه میدهد بخشهایی از داده را بدون هیچ تخصیص اضافی (zero-allocation) دستکاری کنید.
مثال پیشرفته: استخراج همه اعداد از یک string ورودی بدون تخصیص هیچ رشته جدید.
static void ExtractAndProcessNumbers(ReadOnlySpan<char> input, Action<ReadOnlySpan<char>> process)
{
int start = -1;
for (int i = 0; i < input.Length; i++)
{
if (char.IsDigit(input[i]))
{
if (start == -1)
start = i;
}
else if (start != -1)
{
process(input.Slice(start, i - start));
start = -1;
}
}
if (start != -1)
process(input.Slice(start, input.Length - start));
}
مثال استفاده:
string line = "Order #24995 was delivered at 14:32.";
ExtractAndProcessNumbers(line, numSpan =>
{
// numSpan را بدون هیچ تخصیصی به string، پردازش کنید
Console.WriteLine(numSpan.ToString());
});
Span<T> شبیه یک view روی دادهی اصلی است؛ هیچ copy اتفاق نمیافتد. فقط مراقب باشید که این قابلیت صرفاً برای دادههایی مثل stack-allocated buffer یا قطعههایی از string (در scope خودش) مجاز است.
نکته معماری: اگر به zero-allocation در async و عملیات طولانی نیاز دارید، به سراغ Memory<T> و ReadOnlyMemory<T> بروید—زیرا Span<T> فقط برای سینک و روی stack است.
🔗 استفاده از این تکنیک در لایههای parsing، validation و ETL اپلیکیشنهای حجیم، بهرهوری حافظه و کارایی بینظیری به ارمغان میآورد.
@DeveloperAdvocate 🥑1 035
🎯 ارتقای عملکرد EF Core: استفاده هوشمندانه از Projection با
.Select()
در بسیاری از پروژهها مشاهده میشود که توسعهدهندگان مستقیماً مدلهای دامنه را از دیتابیس واکشی میکنند و سپس در لایههای مختلف، صرفاً چند خاصیت از آنها را به کلاینت ارسال میکنند. این کار نهتنها باعث افزایش بیدلیل حجم داده منتقلشده و حافظه مصرفی میشود، بلکه زمان پاسخدهی را نیز بالا میبرد.
ترفند حرفهای: همواره در زمان کوئری زدن با EF Core، داده را به همان مدل یا DTO موردنیاز پروجکت کنید تا SQL تولیدشده دقیقاً فقط فیلدهای مورد احتیاج را SELECT کند.
مثال:
var projections = await context.Users
.Where(u => u.IsActive)
.Select(u => new UserSummaryDto
{
Id = u.Id,
DisplayName = u.FirstName + " " + u.LastName,
Email = u.Email
})
.ToListAsync();
در اینجا دیتابیس فقط سه فیلد را برمیگرداند. در مقابل اگر .Select() را حذف کنید یا مستقیماً User را واکشی کنید، تمام فیلدها—including navigation properties—بارگذاری میشوند.
🔹 توجه: این رویکرد بهویژه زمانی که جدول شما دارای فیلدهای حجیم (مثلاً فیلدهای عکس، توضیحات طولانی یا navigation های زیاد) است، بهشدت حافظه و زمان واکشی را بهبود میبخشد.
در سیستمهای بزرگ، رعایت این نکته میتواند تفاوت چشمگیری در عملکرد API شما ایجاد کند.
@DeveloperAdvocate 🥑