Developer Advocate
Ir al canal en Telegram
1 035
Suscriptores
Sin datos24 horas
Sin datos7 días
-130 días
Archivo de publicaciones
1 035
در .NET، Expression Treeها ابزار قدرتمندی برای تولید داینامیک کوئریهای LINQ، پیادهسازی فیلترهای انعطافپذیر و حتی ساخت DSLهای سفارشی هستند. بیایید یک سناریوی عملی را بررسی کنیم: فرض کنید نیاز دارید تعدادی شرط را در زمان اجرا روی دیتاستی از کاربران اِعمال کنید و این شروط میتوانند بر اساس ورودی کاربر متفاوت باشند.
public class User
{
public string Name { get; set; }
public int Age { get; set; }
}
فرض کنیم میخواهیم با توجه به فیلترهای داینامیک، کوئریای شبیه این بسازیم:
- نام باید با "A" شروع شود (در صورت درخواست)
- سن باید بزرگتر از یک مقدار خاص باشد (در صورت وجود)
ساخت Expression Tree:
using System.Linq.Expressions;
// پارامتر معرف هر User
ParameterExpression param = Expression.Parameter(typeof(User), "u");
// فیلترها
string? nameStartsWith = "A";
int? minAge = 30;
Expression? predicate = null;
if (!string.IsNullOrEmpty(nameStartsWith))
{
var nameProp = Expression.Property(param, nameof(User.Name));
var startsWithMethod = typeof(string).GetMethod("StartsWith", new[] { typeof(string) })!;
var startsWith = Expression.Call(nameProp, startsWithMethod, Expression.Constant(nameStartsWith));
predicate = startsWith;
}
if (minAge.HasValue)
{
var ageProp = Expression.Property(param, nameof(User.Age));
var greaterThan = Expression.GreaterThan(ageProp, Expression.Constant(minAge.Value));
predicate = predicate != null
? Expression.AndAlso(predicate, greaterThan)
: greaterThan;
}
// تبدیل به Expression<Func<User, bool>>
var lambda = Expression.Lambda<Func<User, bool>>(predicate!, param);
و حالا میتوانید این Expression را مستقیماً به LINQ to Objects، EF یا هر IQueryable اعمال کنید:
var filtered = users.AsQueryable().Where(lambda);
این تکنیک زمانی که مجموعه شروط و فیلترها پویا و ناشناخته هستند، بسیار مفید و بهینه است؛ خصوصاً در اپلیکیشنهای Enterprise و سناریوهای CQRS برای تولید queryهای پیچیده سمت سرور.
✨ تجربه شما از استفاده داینامیک Expression Treeها در پروژههایتان چگونه بوده؟ تکنیکها و Patternهای مورد علاقهتان را به اشتراک بگذارید!
@DeveloperAdvocate 🥑1 035
🎛️ الگوی Circuit Breaker — افزونهای حیاتی برای تابآوری سرویسها
در معماریهای میکروسرویس و سیستمهای توزیعشده، شکستهای مکرر یک سرویس پاییندست میتواند کل اکوسیستم را تحت فشار قرار دهد. Circuit Breaker بهعنوان محافظی بین سرویسها قرار میگیرد تا از موج خطاها و مصرف غیرکارآمد منابع جلوگیری کند.
چگونه کار میکند؟
۱. فرض کنید یک میکروسرویس شما (A) به سرویس دیگری (B) وابسته است. اگر B مکرراً شکست بخورد، تمایل معمول این است که A به تلاش خود ادامه دهد که این کار منابع را هدر داده و Latency را افزایش میدهد.
۲. Circuit Breaker درخواستها را مانیتور میکند و اگر تعداد مشخصی خطا در بازه زمانی معین رخ دهد، مدار را باز میکند تا درخواستهای بعدی سریعاً با Exception پاسخ داده شوند و فشاری بر روی سرویس پاییندست وارد نشود.
۳. بعد از دورهی Cooling-off، مدار به حالت نیمهباز (Half-Open) میرود، یک درخواست عبور میدهد و اگر موفق بود، مدار مجدداً بسته میشود و سیستم به شرایط عادی بازمیگردد.
پیادهسازی با Polly در #داتنت:
Polly یکی از کتابخانههای محبوب جهت مدیریت استحکام عملیات است:
using Polly;
using Polly.CircuitBreaker;
var breaker = Policy
.Handle<HttpRequestException>()
.CircuitBreakerAsync(
exceptionsAllowedBeforeBreaking: 5,
durationOfBreak: TimeSpan.FromSeconds(30),
onBreak: (ex, breakDelay) =>
{
Console.WriteLine($"Circuit opened! Breaking for {breakDelay.TotalSeconds} seconds.");
},
onReset: () =>
{
Console.WriteLine("Circuit closed! All ok.");
},
onHalfOpen: () =>
{
Console.WriteLine("Circuit half-open. Trying test request...");
});
try
{
await breaker.ExecuteAsync(async () =>
{
// مثلا درخواست HttpClient به سرویس پاییندست
await httpClient.GetAsync("https://downstream-service/api/data");
});
}
catch (BrokenCircuitException)
{
// واکنش سریع بدون تلاش بیهوده به هنگام قطع مدار
Console.WriteLine("Downstream service is currently unavailable.");
}
نکات حرفهای:
- پارامتر exceptionsAllowedBeforeBreaking و durationOfBreak را متناسب با رفتار سامانه و حساسیت کسبوکار تعیین کنید.
- تلفیق Circuit Breaker با Retry و Timeout (مثلاً با Polly) کاملاً رایج است اما ترتیب را هوشمندانه تعیین کنید؛ معمولاً Timeout > Retry > CircuitBreaker.
- Metricهای Circuit Breaker را مانیتور کنید (باز، بسته، نیمهباز) و در ابزارهای observability گزارش دهید.
- بجای ارائه خطای خام به کاربر، پیام مناسبتری مثل "سرویس موقتاً در دسترس نیست" بازگردانید.
اجرای هوشمندانه Circuit Breaker نه فقط مقاومت سامانه را افزایش میدهد، بلکه تجربه کاربری و مقیاسپذیری را نیز به سطح بالاتری میبرد. 🚦
@DeveloperAdvocate 🥑1 035
یکی از مهمترین نکات حرفهای در نوشتن Dockerfile برای اپلیکیشنهای .NET استفاده هوشمندانه از multi-stage builds است. این روش نهتنها سایز ایمیج نهایی را به حداقل میرساند، بلکه attack surface را هم کاهش میدهد و از لحاظ امنیت و سرعت استقرار فوقالعاده مؤثر است.
مثال زیر یک الگو پیشنهادی مبتنی بر best practice است:
# Stage 1: Build
FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build
WORKDIR /src
COPY . .
RUN dotnet publish -c Release -o /app/publish
# Stage 2: Runtime-only
FROM mcr.microsoft.com/dotnet/aspnet:8.0-alpine AS final
WORKDIR /app
COPY --from=build /app/publish .
ENTRYPOINT ["dotnet", "YourApp.dll"]
توصیه مهم:
- استفاده از نسخه Alpine برای ایمیج رانتایم، سایز ایمیج را به شدت کاهش میدهد.
- تنها artifacts نهایی (خروجی پوشه publish) را به ایمیج رانتایم کپی کنید تا هیچکدام از سورسکد یا ابزارهای build به ایمیج نهایی راه پیدا نکنند.
- برای مایکروسرویسهای پیشرفته، استفاده از پارامترهایی مثل --no-restore و --no-cache در مرحله build برای بهبود سرعت و امنیت توصیه میشود.
در نهایت، همیشه .dockerignore قدرتمندی بسازید تا فایلهای غیرضروری مثل obj, bin و اسناد پیکربندی local وارد کانتینر نشوند—این کار build context را به شکل چشمگیر کاهش میدهد و سرعت CI/CD شما را بالا میبرد.
این ساختار Dockerfile در پروژههای پیچیده و مقیاسپذیر .NET، معادله توسعه-استقرار را متحول میکند.
@DeveloperAdvocate 🥑1 035
Redis فقط برای کشینگ نیست — پیامرسانی فوقسریع در .NET با Pub/Sub
بسیاری Redis را صرفاً ابزاری برای کش میشناسند، اما Pub/Sub آن به شما امکان میدهد یک message bus سبک، بیحاشیه و بسیار سریع بسازید—مخصوصاً برای توزیع دادهٔ بلادرنگ در مقیاس کوچک تا متوسط (مثل وقایع اپلیکیشن، اعلانها یا هماهنگی microserviceها).
در .NET با StackExchange.Redis، راهاندازی Pub/Sub واقعاً شفاف و ساده است. اما برای سناریوهای real-time production باید به نکاتی مانند delivery guarantee و memory footprint دقت کنید؛ چراکه Redis Pub/Sub هیچ پیامی را ذخیره نمیکند و پیام تنها به مشترکین متصل فعلی تحویل میشود.
یک نمونه پیادهسازی ساده:
using StackExchange.Redis;
// اتصال به Redis
var muxer = await ConnectionMultiplexer.ConnectAsync("localhost");
var pub = muxer.GetSubscriber();
// Publisher: نشر پیام
await pub.PublishAsync("telemetry", "CPU_USAGE:42");
// Subscriber: ثبت handler برای دریافت پیامها
await pub.SubscribeAsync("telemetry", (channel, message) => {
Console.WriteLine($"Received: {message}");
});
نکات حرفهای:
- اگر نیاز به شرایط At least once delivery یا persistence دارید، به سمت Redis Streams با مصرفکنندههای group فکر کنید.
- مراقب باشید Redis Pub/Sub هیچ نوع queue یا دیسکی ندارد: قطع ارتباط مساوی است با از دستدادن پیامهای آن بازه.
- میتوانید shared connection pool (به ویژه در high-throughput) و async event handling را برای بهینهسازی latency و منابع در distributed systemها به کار ببرید.
چه زمانی Pub/Sub Redis مناسب است؟
- Event broadcast با latency بسیار پایین (مثلاً Notificationهای UI و رویداد میان سرویسها).
- هماهنگی ساده و سریع میان instanceهای multi-node.
- سناریوهایی که persistency و ordering حیاتی نیست.
Redis Pub/Sub: یک ابزار ساده اما underestimated برای ارتباط real-time .NET های distributed—وقتی واقعاً "fast-and-light" را میخواهید، از آن غافل نشوید!
@DeveloperAdvocate 🥑1 035
🔸 تجربهی Storybook برای کامپوننتهای پیشرفته React
یکی از قابلیتهای کلیدی Storybook نمایش حالتهای مختلف یک کامپوننت پیچیده، مستقل از بیزینس لاجیک و Backend است. فرض کنید یک فرم لاگین پیچیده با چند state داریم: loading، خطا، موفقیت، و حالت پیشفرض.
در نمونه زیر، ابتدا کامپوننت LoginForm را به شکل Controlled ساختهایم و سپس با استفاده از argهای Storybook، چند سناریو کلیدی برای تست و مستندسازی UI تعریف کردهایم:
// LoginForm.jsx
import React from 'react';
export default function LoginForm({
onSubmit,
isLoading = false,
error = '',
success = false
}) {
return (
<form onSubmit={onSubmit}>
<input name="username" placeholder="Username" disabled={isLoading || success} />
<input name="password" placeholder="Password" type="password" disabled={isLoading || success} />
<button type="submit" disabled={isLoading || success}>
{isLoading ? 'منتظر بمانید...' : 'ورود'}
</button>
{error && <div style={{color:'red'}}>{error}</div>}
{success && <div style={{color:'green'}}>ورود موفقیتآمیز!</div>}
</form>
);
}
// LoginForm.stories.jsx
import LoginForm from './LoginForm';
export default {
title: 'Complex/LoginForm',
component: LoginForm,
argTypes: {
onSubmit: { action: 'submitted' }
}
};
const Template = (args) => <LoginForm {...args} />;
export const Default = Template.bind({});
Default.args = {
isLoading: false,
error: '',
success: false
};
export const Loading = Template.bind({});
Loading.args = {
isLoading: true,
error: '',
success: false
};
export const Error = Template.bind({});
Error.args = {
isLoading: false,
error: 'نام کاربری یا رمز عبور اشتباه است',
success: false
};
export const Success = Template.bind({});
Success.args = {
isLoading: false,
error: '',
success: true
};
💡 نکتهی معمارانه: با تعریفها و argهای storyها، تست ظاهر و حالات UX را بدون نیاز به کدنویسی اضافه، کاملاً قابل مشاهده و قابل ارزیابی میکنید؛ این است قدرت جداسازی Presentational Logic! Storybook را فاز سیستمی توسعه و مستندسازی فرض بگیرید، نه صرفاً ابزار تست.
@DeveloperAdvocate 🥑1 035
🔥 Security Focus: جلوگیری از آسیبپذیری XSS در .NET/React
یکی از متداولترین آسیبپذیریها از لیست OWASP Top 10، XSS (Cross-Site Scripting) است. در بسیاری از پروژههایی که از ASP.NET Core به عنوان بکاند و React برای فرانتاند استفاده میکنند، بستر برای بروز این آسیبپذیریها همچنان وجود دارد.
در XSS، مهاجم میتواند اسکریپت مخرب را وارد صفحه کند تا کوکی یا اطلاعات کاربر را بدزدد یا حتی رفتار برنامه را تغییر دهد. چند نکته پیشرفته برای حفاظت پروژه شما:
🔒 Backend: استفاده از AntiXSS و JsonSerializer
- در ASP.NET Core، استفاده از Razor Pages یا Blazor به طور پیشفرض همه خروجی HTML را encode میکند اما اگر جایی از
Mvc, IHtmlContent یا Raw() استفاده میکنید، مراقب باشید.
- دادههای ورودی را sanitize کنید.
- موقعی که دادهها را به کلاینت برمیگردانید، مطمئن شوید serialization امن انجام میشود:
// استفاده از System.Text.Encodings.Web برای ایمن کردن serialization خروجی
var options = new JsonSerializerOptions
{
Encoder = System.Text.Encodings.Web.JavaScriptEncoder.UnsafeRelaxedJsonEscaping // توجه، حالت پیشفرض امنتر است
};
var json = JsonSerializer.Serialize(model, options);
حتماً از حالت UnsafeRelaxed اجتناب کنید مگر آن که مطمئن به context داده باشید.
🔒 Frontend: جلوگیری از XSS در React
- هیچوقت داده غیرقابل اعتماد را با dangerouslySetInnerHTML رندر نکنید مگر پس از sanitize شدن.
- از کتابخانههایی مثل dompurify برای پاکسازی ورودی HTML استفاده کنید:
import DOMPurify from 'dompurify';
const safeHtml = DOMPurify.sanitize(userInputHtml);
<div dangerouslySetInnerHTML={{ __html: safeHtml }} />;
- حتی در استفاده معمولی، ریاکت خودش دادهها را escape میکند:
// امن، چون React به شکل پیشفرض HTML را escape میکند
<span>{userInput}</span>
🔒 یک گام جلوتر: CSP Header ایجاد کنید
در ASP.NET Core، هدر Content-Security-Policy را ست کنید تا اجرای اسکریپتهای خارج از منابع مجاز ممنوع شود:
app.Use(async (context, next) =>
{
context.Response.Headers.Add(
"Content-Security-Policy",
"default-src 'self'; script-src 'self'; object-src 'none';");
await next();
});
در این طرح شما چند لایه دفاع دارید: sanitization سمت کلاینت، encoding سمت سرور و سیاست سختگیرانه مرورگر.
⏩ جمعبندی: اصل طلایی این است که دادههای ورودی را هیچوقت به عنوان HTML اعتماد نکنید؛ همیشه encode/sanitize کنید و از CSP استفاده کنید.
#Security #XSS #ASPNetCore #React #BestPractices
@DeveloperAdvocate 🥑1 035
📦 DevOps: تست کد زیرساخت (Infrastructure Testing) با Terratest
در معماری مدرن، کدزیرساخت (Infrastructure as Code یا IaC) نقطه اتصال اپلیکیشن و محیط اجرای آن است. اما همانطور که برای کد برنامه تست مینویسیم، باید برای کدزیرساخت نیز تست خودکار داشته باشیم تا اطمینان یابیم منابع کلیدی مطابق انتظار Provision میشوند و Drift یا Misconfiguration رخ نداده است.
ابزاری مانند Terratest (بر پایه Go) این امکان را میدهد که سناریوهای تست باثبات برای Terraform، Packer یا Kubernetes بنویسیم و صحت ساختار، Dependencyها و دسترسیها را در دنیای واقعی اعتبارسنجی کنیم.
به مثال سادهای از تست یک ماژول Terraform توجه کنید:
package test
import (
"testing"
"github.com/gruntwork-io/terratest/modules/terraform"
)
func TestTerraformModule(t *testing.T) {
terraformOptions := &terraform.Options{
TerraformDir: "../terraform/my-module",
}
defer terraform.Destroy(t, terraformOptions)
terraform.InitAndApply(t, terraformOptions)
output := terraform.Output(t, terraformOptions, "resource_name")
if output != "expected-value" {
t.Fatalf("Output was %v, want expected-value", output)
}
}
چه چیزی مهم است؟
- تست، REAL RESOURCE را در محیط Test یا Sandbox دیپلوی و validate میکند.
- بهسادگی میتوان Assert و Cleanup داشت (همانند Unit testها اما روی زیرساخت).
- میتوانید توسعه IaC را شبیه TDD جلو ببرید و از امنیت و بازگشتپذیری مطمئن باشید.
در اکوسیستم .NET، میتوان با ابزارهایی مثل Kitchen-Terraform یا Pulumi Testing اندکی همین رویکرد را توسعه داد، یا در صورت نیاز Integration آنها را با ابزارهای تست فعلی (NUnit/xUnit/MSTest) برقرار نمود.
❓ آیا تجربهای از تست زیرساخت در پروژههایتان داشتهاید؟ تستهای Integration برای IaC را در کنار تست نرمافزار جدی بگیرید. این همان لایهای است که زیر خط کد شما زندگی و مقیاسپذیری واقعی را تضمین میکند.
@DeveloperAdvocate 🥑1 035
🚀 #CSharpTips
🔍 بررسی عمیق Constructorهای اولیه (Primary Constructors) در C# 12+
Primary Constructorها با ایجاد راهی مختصر و خوانا برای تعریف مقادیر اولیه در کلاسها و structها معرفی شدند. اما در استفاده از این قابلیت جدید باید به نکات مهمی توجه کنید تا ترکیب خوانایی و maintainability حفظ شود.
### 🟢 Best Practices
1. موارد استفاده ایدهآل:
- Data-holderهای ساده (مانند recordها یا DDD Value Objectها).
- زمانی که نیاز به immutable object دارید یا تعداد propertyهای کمی دارید.
- مدلهایی که عملاً نقش DTO دارند.
2. سازگاری با Dependency Injection:
- اگر injected dependencyها به صورت readonly قرار بگیرند، استفاده از primary constructor خوانایی کد و DI را تضمین میکند:
public class OrderService(IDatabase db, ILogger<OrderService> logger)
{
public void PlaceOrder(Order order)
{
db.Save(order);
logger.LogInformation("Order placed.");
}
}
3. سازگاری کامل با Init-only propertyها:
- زمانی که propertySetterها به صورت init هستند، میتوانید با ترکیب آنها و primary constructor، هم immutability و هم خوانایی را داشته باشید.
### 🔴 Potential Pitfalls
1. Smelly Design: افزایش پارامترهای Constructor
- اگر به تعداد زیادی پارامتر نیاز است (مثلاً بیش از ۴–۵)، احتمالاً کلاس شما مسئولیت زیادی دارد (SRP violated). primary constructor نباید سرعت شما برای refactoring را کم کند!
2. سایه خوردن پارامتر توسط property:
- مراقب باشید اگر property با همان نام تعریف شود، دسترسی به پارامتر مستقیم گم میشود:
public class Foo(string name)
{
public string name { get; } = name; // این کامپایل میشود، اما نامگذاری ممکن است سردرگمکننده باشد
}
3. Integration با ابزارهای Serialization:
- همهی serializerها (مثلاً Newtonsoft.Json یا System.Text.Json در نسخههای خاص) لزوماً از پارامترهای primary constructor پشتیبانی نمیکنند. Studio tooling و ابزارهای تست را بررسی کنید.
4. محدودیت در Constructor Overloading:
- فعلاً نمیتوانید چند primary constructor با امضاهای متفاوت داشته باشید؛ اگر نیاز به چندین مسیر ساخت دارید (مثل factory pattern)، کماکان باید constructorهای سنتی را تعریف کنید.
### 🌟 Summary
توصیه طلایی:
primary constructorها را برای کلاسهای ساده و مبتنی بر داده به کار ببرید و در کلاسهای انتزاعی یا بسیار پیچیده، با دقت سراغ آنها بروید. معماری تمیز قربانی زیبایی سینتکس نشود.
—
#dotnet #architecture #advancedcsharp
@DeveloperAdvocate 🥑1 035
🔍 قدرت بیرقیب PostgreSQL: Index-Only Scan برای JSONB
آیا میدانستید که امکان ایندکسگذاری و جستجوی فوق سریع بر روی کلیدهای دلخواه داخل JSONB در PostgreSQL وجود دارد؟ بسیاری از توسعهدهندگان تصور میکنند اینکه فیلدی را به صورت JSON ذخیره کنیم، یعنی جستجوگرفتن روی آن کند خواهد بود. اما PostgreSQL با ایندکس GiST یا GIN روی JSONB این تصور را نقض میکند؛ خصوصاً وقتی با Index-Only Scan ترکیب شود.
مثلاً فرض کنید ساختار دادهای مانند زیر در جدول دارید:
CREATE TABLE orders (
id SERIAL PRIMARY KEY,
data JSONB
);
-- نمونه داده
INSERT INTO orders(data) VALUES
('{"status": "pending", "customer": {"id": 22}}'),
('{"status": "done", "customer": {"id": 28}}'),
('{"status": "pending", "customer": {"id": 42}}');
فرض کنید میخواهید سفارشهای با status = 'pending' را سریع پیدا کنید. به راحتی میتوانید یک ایندکس جزئی بسازید:
CREATE INDEX idx_orders_status_pending
ON orders ((data->>'status'))
WHERE data->>'status' = 'pending';
حالا کوئری شما:
SELECT id FROM orders
WHERE data->>'status' = 'pending';
PostgreSQL این کوئری را با Index-Only Scan اجرا میکند؛ یعنی نیازی به خواندن دادههای اصلی جدول ندارد و فقط ایندکس برای استخراج id کافیست! سرعت این عمل وقتی میلیونها داده دارید، فوقالعاده است.
🎯 نکته حرفهای: از PostgreSQL 12 به بعد، حتی میتوانید روی مقادیر تو در تو درون JSONB هم همین نوع ایندکس را بسازید. مثلاً:
CREATE INDEX idx_customer_id
ON orders ((data->'customer'->>'id'));
این قابلیت را هیچوقت دستکم نگیرید—با معماری صحیح میتوانید بدون Sacrifice بر سرعت، ساختارهای دینامیک را هم مدل کنید.
#PostgreSQL #Database #JSONB #IndexOnlyScan
@DeveloperAdvocate 🥑1 035
در دنیای API-first، تایپسیفتی سرتاسری (end-to-end type safety) بین بکاند و فرانتاند یک امر حیاتی است—ویژه وقتی که تیمها روی دامنههای پیچیده کار میکنند. هرچند tRPC ذاتاً برای اکوسیستم NodeJS طراحی شده، اما میتوان همین رویکرد را با ابزارهایی چون TypeGen یا NSwag به پروژههای .NET وارد کرد.
سناریو: تعریف یک API همیشه تایپسیف در ASP.NET Core و مصرف مستقیم تایپها در ریاکت + تایپاسکریپت.
۱. تعریف DTO و سرویس در .NET:
public record WeatherForecast(DateTime Date, int TemperatureC, string Summary);
[ApiController]
[Route("api/[controller]")]
public class WeatherController : ControllerBase
{
[HttpGet]
public ActionResult<IEnumerable<WeatherForecast>> Get()
{
return Enumerable.Range(1, 5).Select(index =>
new WeatherForecast(DateTime.Now.AddDays(index), Random.Shared.Next(-20, 55), "Sunny")).ToArray();
}
}
۲. ژنریت کردن مدلهای تایپاسکریپت از روی C#:
با TypeGen یا NJsonSchema، مدلها را به راحتی به TypeScript تبدیل میکنیم.
فرض مثال با TypeGen:
dotnet tool install --global TypeGen.Cli
tg init // تنظیم فایل tgconfig.json
tg gen // تولید کد TypeScript
قطعه خروجی Sample:
export interface WeatherForecast {
date: Date;
temperatureC: number;
summary: string;
}
۳. مصرف مستقیم مدل تایپاسکریپت در فرانتاند (React):
import { WeatherForecast } from './models';
async function fetchForecast(): Promise<WeatherForecast[]> {
const res = await fetch('/api/weather');
if (!res.ok) throw new Error("Failed to fetch");
return res.json();
}
🎯 مزیت اصلی: همخوانی کامل امضا و ساختار دادهها. یکپارچگی مطلق بین بکاند و فرانتاند، حذف mismatchها، کوتاه کردن چرخه دیباگ، و تسهیل refactoring!
نکته حرفهای: با استفاده از ابزارهایی مانند NSwag یا Swashbuckle + NSwag.CodeGen میتوانید کل API client را همزمان با مدلها تولید کنید تا DX واقعاً tRPCمانند شود.
اگر معماری شما بر پایه DDD است، این رویکرد میتواند مدلسازی یوذر سنترال و API Module Contract را به سطح بالاتری ببرد—و این یعنی آینده قابل اتکا، مقیاسپذیر و لذتبخش توسعه حرفهای. 🚀
@DeveloperAdvocate 🥑1 035
حتماً برای شما هم پیش آمده که یک کامپوننت React پیچیده با حالات مختلف (مثل loading, error, empty, data) دارید که تست و مستندسازی UI آن بدون ابزار مناسب دشوار است. Storybook دقیقاً برای همین منظور ساخته شده است! بیایید یک داستان (Story) مؤثر برای کامپوننتی مثل
UserList که این حالات را مدیریت میکند، بسازیم:
ابتدا فرض کنید کامپوننت زیر را داریم:
// UserList.jsx
import React from 'react';
export default function UserList({ users, loading, error }) {
if (loading) return <div>در حال بارگذاری...</div>;
if (error) return <div style={{ color: 'red' }}>خطا: {error}</div>;
if (!users || users.length === 0) return <div>هیچ کاربری یافت نشد.</div>;
return (
<ul>
{users.map(u => <li key={u.id}>{u.name}</li>)}
</ul>
);
}
حال، استوریهای سناریومحور زیر میتواند توسعه و تست را قدرتمند کند:
// UserList.stories.jsx
import UserList from './UserList';
export default {
title: 'Components/UserList',
component: UserList,
};
const mockUsers = [
{id: 1, name: 'علی'},
{id: 2, name: 'زهرا'},
];
export const Default = () => <UserList users={mockUsers} loading={false} error={null} />;
export const Loading = () => <UserList users={[]} loading={true} error={null} />;
export const Error = () => <UserList users={[]} loading={false} error="اتصال قطع شد" />;
export const Empty = () => <UserList users={[]} loading={false} error={null} />;
برخی نکات پیشرفته برای Storybook:
- Storyها را بر اساس سناریوهای واقعی نامگذاری کنید (نه فقط props).
- اگر state بیشتری مدیریت میکنید، میتوانید از Storybook Controls (argTypes) برای ساخت "sandbox"های تعاملی استفاده کنید.
- Storybook امکان تست حالتهای edge (تحقق خطا یا داده ناقص) را آسان میکند که اغلب overlooked میشوند.
- جهت مستندسازی تیم و QA کیفیتیافته، storyها را همانقدر جدی بگیرید که Unit Testها را.
با این رویکرد، هم توسعه سریعتر خواهد شد، هم مستندسازی UI بهروز و قابلاعتماد میماند – مخصوصاً برای کامپوننتهای پیچیده!
@DeveloperAdvocate 🥑1 035
🎯 خودکارسازی تحلیل Canary Release با سنجهها
در استقرار Canary، ارزیابی کیفیت نسخه جدید حیاتی است. به جای تحلیل دستی، میتوانیم تحلیل Canaray را با اتکا به متریکها خودکار کنیم تا تصمیم Rollback/Promote به نسخه جدید کاملاً دادهمحور باشد.
🔍 مراحل کلیدی:
۱. انتخاب متریکها: نرخ خطا (Error Rate)، مدت پاسخ (Latency)، مصرف منابع (CPU/Memory)، نرخ تراکنش موفق و بسیاری متریکهای کسبوکار.
۲. جمعآوری دادهها: متریکهای Canary و Production هر دو محیط را موازی گردآوری کنید (توسط Prometheus, Application Insights یا Grafana).
۳. تحلیل خودکار: یک سرویس یا Job به صورت دورهای دادهها را تحلیل و نتیجهگیری میکند. به عنوان مثال: ≤۲۰٪ افزایش خطا نسبت به Production در بازه ۱۵ دقیقه، آلارم میدهد.
۴. خودکارسازی تصمیم: در CI/CD، Pipeline سرنوشت Canary را طبق نتایج تحلیل تعیین کند (Promotion یا Rollback).
نمونهی ساده تحلیل خودکار با Application Insights SDK:
using Microsoft.Azure.ApplicationInsights.Query;
using Microsoft.Azure.ApplicationInsights.Query.Models;
var client = new ApplicationInsightsDataClient(new ApiKeyClientCredentials("API_KEY"));
string appId = "YOUR-APP-ID";
string query = @"requests
| where timestamp > ago(15m)
| summarize canary_error = countif(cloud_RoleInstance == 'canary' and success == false),
prod_error = countif(cloud_RoleInstance == 'prod' and success == false)";
var result = await client.Query.ExecuteAsync(appId, query);
var canaryError = (int)result.Tables[0].Rows[0][0];
var prodError = (int)result.Tables[0].Rows[0][1];
double delta = (prodError == 0) ? (canaryError > 0 ? 1.0 : 0.0) : (double)(canaryError - prodError) / prodError;
if (delta > 0.2)
{
// Trigger rollback
}
else
{
// Promote canary
}
🔥 نکته: میتوانید تحلیل پیچیدهتری را با استفاده از ML یا تحلیلهای توزیعی پیادهسازی کنید؛ اما همین الگوریتم ساده تفاوت معنیدار را به سرعت مشخص میکند. تلفیق این رویکرد با ابزارهایی چون Argo Rollouts یا Azure DevOps، تحویل نسخههای پایدار و هوشمند را تضمین میکند.
#DevOps #CanaryAnalysis #Observability #CD #Metrics #.NET
@DeveloperAdvocate 🥑1 035
در نوشتن Dockerfile برای اپلیکیشنهای #dotnet، یکی از پیشرفتهترین راهکارها استفاده از چند مرحله (multi-stage build) برای جداسازی محیط build از محیط runtime است. بدین ترتیب، تصویر (image) نهایی شما فقط شامل باینریهای لازم و وابستگیهای اجرای پروژه خواهد بود؛ در نتیجه حجم image کمتر و سطح حمله سطح پایینتری خواهید داشت.
نمونه Dockerfile پیشنهادی:
# مرحله اول: build
FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build
WORKDIR /src
COPY . .
RUN dotnet publish "YourProject.csproj" -c Release -o /out /p:UseAppHost=false
# مرحله دوم: only runtime
FROM mcr.microsoft.com/dotnet/aspnet:8.0 AS final
WORKDIR /app
COPY --from=build /out ./
ENTRYPOINT ["dotnet", "YourProject.dll"]
کلید موفقیت این است:
- از /p:UseAppHost=false استفاده کنید تا فایلهای اضافی native تولید نشود.
- پوشه build را کاملاً جدا از runtime نگه دارید.
- فقط خروجی compiled را به runtime منتقل کنید.
حاصل کار: Imageهایی کوچک، امن، و آماده برای deploy سریع روی Kubernetes یا هر orchestrator حرفهای دیگر. این رویکرد در CI/CDهای مدرن نه تنها سرعت عمل را افزایش میدهد، بلکه امنیت Supply Chain را هم تضمین میکند.
@DeveloperAdvocate 🥑1 035
الگوی Circuit Breaker: مقاومت در برابر خرابیهای زنجیرهای
زمانی که سرویسهای شما به منابع خارجی (مثلاً سرویسهای دیگر یا دیتابیسها) وابستهاند، تلاشهای مکرر برای برقراری ارتباط با سرویس خراب میتواند باعث کندشدن کل سیستم، مصرف منابع و ایجاد “cascading failure” شود. Circuit Breaker این مشکل را با قطع موقت درخواستهای ناموفق حل میکند تا سیستم به حالت پایدارتری بازگردد.
سه حالت اصلی در Circuit Breaker:
۱. Closed: همه درخواستها عبور میکنند تا وقتی که خطاهای متوالی از یک آستانه بیشتر شود.
۲. Open: درخواستها بلاک میشوند و خطا برمیگردد تا بعد از مدت زمان معینی (timeout).
۳. Half-Open: تعداد محدودی درخواست عبور داده میشوند تا بررسی شود آیا سرویس بازیابی شده یا نه.
مثال کاربردی با Polly در داتنت:
var circuitBreakerPolicy = Policy
.Handle<HttpRequestException>()
.CircuitBreakerAsync(
exceptionsAllowedBeforeBreaking: 3, // تعداد خطا قبل از باز شدن مدار
durationOfBreak: TimeSpan.FromSeconds(30), // مدت زمان Open ماندن مدار
onBreak: (ex, breakDelay) =>
{
// ثبت لاگ یا اطلاعرسانی در زمان Break
},
onReset: () =>
{
// ثبت لاگ یا اطلاعرسانی در زمان Reset شدن مدار
},
onHalfOpen: () =>
{
// ثبت لاگ یا اطلاعرسانی در وضعیت Half-Open
}
);
// استفاده در فراخوانی سرویس:
await circuitBreakerPolicy.ExecuteAsync(async () =>
{
// کد فراخوانی HTTP یا منبع خارجی
});
💡 ترکیب Circuit Breaker با الگوهای Retry، Timeout و Fallback، فونداسیونی قدرتمند برای Resilience و تابآوری اپلیکیشن شما میسازد. حتماً مانیتورینگ و هشداردهی روی حالات Break و Half-Open لحاظ کنید تا بتوانید علل اصلی خطاها و زمانهای قطع را بهدرستی تحلیل کنید.
@DeveloperAdvocate 🥑1 035
در سناریوهای realtime، دو الگوی محبوب در ASP.NET Core برای بهروزرسانی کلاینت frontend وجود دارد: WebSocket و Server-Sent Events (SSE). هر کدام مزایا و محدودیتهای خاص خود را دارند. بیایید به پیادهسازی مینیمال هر دو بپردازیم:
———
🔥 WebSocket: اتصال دوطرفه (bi-directional)
در WebSocket، سرور و کلاینت میتوانند به صورت همزمان داده رد و بدل کنند.
در ASP.NET Core، کافیست endpoint مربوط را تنظیم کنید:
// Startup.cs یا Program.cs در endpointهای Minimal API
app.UseWebSockets();
app.Map("/ws", async context =>
{
if (context.WebSockets.IsWebSocketRequest)
{
using var webSocket = await context.WebSockets.AcceptWebSocketAsync();
var buffer = new byte[1024 * 4];
while (webSocket.State == WebSocketState.Open)
{
var result = await webSocket.ReceiveAsync(buffer, CancellationToken.None);
// Echo back the message
if (result.MessageType == WebSocketMessageType.Text)
{
await webSocket.SendAsync(
new ArraySegment<byte>(buffer, 0, result.Count),
WebSocketMessageType.Text,
true,
CancellationToken.None);
}
else if (result.MessageType == WebSocketMessageType.Close)
{
await webSocket.CloseAsync(
WebSocketCloseStatus.NormalClosure,
"Closing",
CancellationToken.None);
}
}
}
else
{
context.Response.StatusCode = 400;
}
});
کلاینت (مثلاً در JS):
const ws = new WebSocket('ws://localhost:5000/ws');
ws.onmessage = (evt) => console.log('Message:', evt.data);
ws.send('سلام از کلاینت!');
———
⚡ Server-Sent Events (SSE): ارتباط یکطرفه (Push از سرور به کلاینت)
اگر فقط نیاز است سرور رویدادی را به همه کلاینتها push کند (مثلا نوتیفیکیشن یا stream دیتا)، SSE سادگی و سازگاری بیشتری دارد.
در ASP.NET Core:
app.MapGet("/sse", async context =>
{
context.Response.Headers.Add("Content-Type", "text/event-stream");
var i = 0;
while (!context.RequestAborted.IsCancellationRequested)
{
await context.Response.WriteAsync($"data: پیام {++i} در {DateTime.UtcNow}\n\n");
await context.Response.Body.FlushAsync();
await Task.Delay(2000);
}
});
کلاینت (JS):
const evtSource = new EventSource('/sse');
evtSource.onmessage = (evt) => {
console.log('دریافت شد:', evt.data);
};
———
👑 نکته معماری:
- اگر نیاز به ارتباط دوطرفه و تعاملی (مانند chat یا multiplayer games) دارید، قطعاً WebSocket.
- برای Push ساده از سرور (مانند فید نوتیفیکیشن)، SSE سبکتر، ارزانتر و برای توسعه و نگهداری مناسبتر است (مخصوصاً با reverse proxyهایی مثل Nginx).
- SignalR ترکیبی از هر دو را با fallback و abstraction ارائه میدهد.
هر دو روش به سادگی با ASP.NET Core قابل استفادهاند و انتخابشان وابسته به نیازمندی دقیق بیزینس و معماری شماست.
@DeveloperAdvocate 🥑1 035
👷♂️ معماری - مقیاسپذیری
تفاوت Scaling افقی و عمودی، در انتخاب معماری ابری تعیینکننده است.
⬆️ مقیاسپذیری عمودی (Vertical Scaling):
افزایش ظرفیت یک ماشین؛ مثلاً ارتقا VM از 4 vCPU/8GB RAM به 16 vCPU/64GB در Azure VM یا ارتقا سایز دیتابیس از S2 به P4 در Azure SQL.
مزیت: ساده، مناسب برای اپلیکیشنهای legacy یا stateless با dependency قوی به state داخلی.
محدودیت: سقف دارد (حداکثر ظرفیت سختافزاری) و معمولا Downtime.
🌐 مقیاسپذیری افقی (Horizontal Scaling):
افزودن ماشینهای بیشتر؛ یعنی تعداد instanceها بالا میرود. به کمک Azure App Service یا Kubernetes، میتوانید ۱۰+ instance همزمان اجرا کنید.
مزیت: تقریبا نامحدود، مناسب microservices و معماری cloud-native.
محدودیت: نیازمند طراحی stateless (Session باید خارج از Instance نگهداری شود، مثلا Redis).
مثال کاربردی:
فرض کنید RESTful .NET API را روی Azure App Service اجرا میکنید. کافی است Auto-Scaling Rule تعریف کنید:
// پیکربندی نمونه برای Auto-Scale با ARM Template (pseudo)
{
"type": "Microsoft.Insights/autoscaleSettings",
"properties": {
"targetResourceUri": "[resourceId('Microsoft.Web/sites', variables('apiAppName'))]",
"minCount": 2,
"maxCount": 10,
"rules": [
{
"metricTrigger": {
"metricName": "CpuPercentage",
"operator": "GreaterThan",
"threshold": 70,
"timeGrain": "PT1M"
},
"scaleAction": {
"direction": "Increase",
"value": "1",
"cooldown": "PT5M"
}
}
]
}
}
در معماریهای مدرن، همواره افقی شدن بر عمودی شدن ارجح است—مگر آنکه محدودیت فنی خاصی وجود داشته باشد. همیشه سعی کنید IoC، استیت خارجی (مثل کش توزیعشده) و مقیاسپذیری افقی را پیشفرض بگیرید.
@DeveloperAdvocate 🥑1 035
در معماری مدرن مبتنی بر .NET، اجرای وظایف طولانی (Long-running Tasks) در بکگراند بحثی کلیدی است. دو ابزار اصلی برای این هدف:
IHostedService و BackgroundService. بیایید تفاوتها و سناریوهای بهینه استفاده را با دقت بررسی کنیم:
🔹 IHostedService:
اینترفیس پایهای که دو متد دارد:
- StartAsync(CancellationToken)
- StopAsync(CancellationToken)
شما باید کل چرخه حیات سرویس و کارهای Async را خودتان مدیریت کنید. مناسب برای سناریوهایی که کنترل کامل lifecycle یا ثبت دقیق eventها حیاتی است، مثل راهاندازی/توقف سرویسهای سمت زیرساخت (Message Bus Listener، Health Checks سفارشی و ...).
public class MyHostedService : IHostedService
{
private Task _backgroundTask;
private CancellationTokenSource _cts;
public Task StartAsync(CancellationToken cancellationToken)
{
_cts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
_backgroundTask = Task.Run(() => DoWork(_cts.Token));
return Task.CompletedTask;
}
public Task StopAsync(CancellationToken cancellationToken)
{
_cts.Cancel();
return _backgroundTask ?? Task.CompletedTask;
}
private async Task DoWork(CancellationToken token)
{
while (!token.IsCancellationRequested)
{
// Do long-running work here
}
}
}
🔹 BackgroundService:
یک انتزاع بالاتر مبتنی بر IHostedService. کافی است کلاس خود را از آن ارثبری کنید و متد انتزاعی ExecuteAsync(CancellationToken) را پیادهسازی کنید. Lifecycle و cancellation هوشمندانه مدیریت میشود. عالی برای Taskهایی با اجرای دائمی یا Polling (مانند Worker Queueها، Scraperها و ...)، بدون نیاز به boilerplate اضافی.
public class MyBackgroundService : BackgroundService
{
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
while (!stoppingToken.IsCancellationRequested)
{
// Do recurring background work
await Task.Delay(TimeSpan.FromMinutes(1), stoppingToken);
}
}
}
🔸 جمعبندی معماری:
- بیشترین کنترل → IHostedService
- سادگی و کاهش کد تکراری → BackgroundService
در اکثر کاربردهای اپلیکیشنی مدرن، BackgroundService انتخاب بهتر است.
ولی برای سناریوهای خاص (مثلاً orchestration سفت و سخت چرخه حیات)، مستقیماً سراغ IHostedService بروید.
نکته تکمیلی برای حرفهایها: همیشه انتزاع Cancellation و Logging را درست رعایت و تزریق کنید تا process قابل پیشبینی و مانیتور باشد.
سوال تخصصیتر داشتید، مطرح کنید تا با هم عمیقتر بررسی کنیم.
@DeveloperAdvocate 🥑1 035
در معماری دادهای، انتخاب بین SQL، NoSQL و NewSQL به شدت وابسته به نیازمندیهای پروژه، حجم داده، شکل دادهها، نیازمندیهای ACID و آیندهنگری اکوسیستم شماست. این درخت تصمیم، یک راهنمای عملی برای انتخاب الگوی دیتابیس مناسب است—بهخصوص وقتی قرار است یک معماری منعطف و مقیاسپذیر طراحی کنید.
🔰 ۱ ابتدا، نوع داده و ساختار آن را واکاوید:
▫️ آیا دادهها عمدتاً جدولی و به شدت ساختاریافتهاند؟
➔ بله: مرحله بعد
➔ خیر: مرحله ۲
▫️ آیا حجم دادهها بسیار زیاد یا پویا است؟
➔ بله: NewSQL یا NoSQL (مرحله ۳)
➔ خیر: SQL
🔰 ۲ اگر دادهها نیمهساختاریافته یا بدون ساختارند (مانند JSON، گراف یا اسناد):
▫️ آیا به تراکنشهای ACID یا JOIN پیچیده نیاز دارید؟
➔ بله: NewSQL (مانند CockroachDB, Google Spanner)
➔ خیر: NoSQL
— اگر دادهها اسندیاند → Document Store (MongoDB)
— اگر دادهها گرافاند → Graph DB (Neo4j)
🔰 ۳ آیا به اسکیل افقی، عملکرد بالا و شِارْدینگ نیاز دارید؟
▫️ آیا دادهها باید قویاً منسجم (Strong Consistency) بمانند؟
➔ بله: NewSQL
➔ خیر: NoSQL
▫️ آیا هنوز به تراکنشهای پیچیده، JOIN، و انعطافپذیری SQL نیاز دارید؟
➔ بله: NewSQL
➔ خیر: NoSQL
——
🎯 خلاصه برای معماران:
- SQL — اپلیکیشنهای سنتی و تحلیلی که دادهها ساختاریافته و حجم کم تا متوسط دارند؛ نیازمند تراکنشهای قوی.
- NoSQL — وقتی حجم داده Huge است یا نوع داده پویا و ساختارنیافته است؛ ترجیح زمانی که سرعت و مقیاسپذیری مهمترند تا یکپارچگی تراکنشی (AP در CAP).
- NewSQL — وقتی بخواهید مزایای SQL و ACID را، همراه با مقیاسپذیری افقی و Availability بالا داشته باشید؛ گزینهای عالی برای سیستمهای توزیعشده بانکی/فاینانس/IoT.
📎 نگاشت تصمیم را میتوانید به شکل زیر پیادهسازی کنید:
public enum DatabaseType { SQL, NoSQL, NewSQL }
public DatabaseType ChooseDatabase(bool isStructured, bool highVolume, bool needsTransactions,
bool needsJoins, bool horizontalScaling, bool strongConsistency, string dataModel)
{
if (isStructured)
{
if (highVolume || horizontalScaling)
return needsTransactions || needsJoins ? DatabaseType.NewSQL : DatabaseType.NoSQL;
return DatabaseType.SQL;
}
else
{
if (needsTransactions)
return DatabaseType.NewSQL;
if (dataModel == "document") return DatabaseType.NoSQL;
if (dataModel == "graph") return DatabaseType.NoSQL;
// Expand as needed
}
// Default fallback
return DatabaseType.NoSQL;
}
🧠 همیشه انتخاب دیتابیس، ترکیبی از trade-offهای consistency، availability، partitioning و نیازهای خاص پروژه است. کافیست تصمیمات را مستند کنید و Infrastructure as Code برای کانفیگ دیتابیس را جدی بگیرید تا بعدها قدرت مانور بیشتری داشته باشید.
@DeveloperAdvocate 🥑1 035
یکی از حرفهایترین رویکردها برای ارتقای کیفیت و یکنواختی کد در پروژههای #NET.، استفاده از Roslyn Analyzers است. این آنالایزرها نهتنها خطاهای رایج را بهصورت خودکار شناسایی میکنند، بلکه میتوانند قواعد شخصیسازیشده تیم شما را enforce کنند. به کمک فایل
editorconfig. حتی میتوانید granularترین سیاستهای استایل و معماری را تعریف و روی کل solution اجرا کنید.
برای فعالسازی آنالایزرهای رسمی مایکروسافت (و برخی ابزارهای Third-party مانند StyleCop.Analyzers)، کافی است این dependency را در پروژه خود اضافه کنید:
<ItemGroup>
<PackageReference Include="Microsoft.CodeAnalysis.NetAnalyzers" Version="8.0.0" PrivateAssets="all" />
</ItemGroup>
سپس از فایل .editorconfig برای شخصیسازی قوانین استفاده کنید. بهعنوان مثال، الزام به سبک نوشتاری خاص برای دسترسی متغیرها:
[*.cs]
dotnet_naming_rule.private_members_should_be_camel_case.severity = error
dotnet_naming_rule.private_members_should_be_camel_case.symbols = private_members
dotnet_naming_rule.private_members_should_be_camel_case.style = private_camel_case_style
dotnet_naming_symbols.private_members.applicable_kinds = field
dotnet_naming_symbols.private_members.accessibility = private
dotnet_naming_style.private_camel_case_style.capitalization = camel_case
تحلیلهای Roslyn نهفقط هنگام build، بلکه بهصورت لحظهای در IDE ظاهر میشوند و روند Code Review را متحول میکنند.
استفاده از آنها به سرعت تبدیل شدن به یک استاندارد سازمانی است—یک زیرساخت پویا که خطاهای انسانی را کاهش میدهد و به تیم تمرکز بر حل مسائل واقعی را میدهد، نه بحثهای بیپایان استایل!
@DeveloperAdvocate 🥑1 035
وباسمبلی (WebAssembly یا WASM) یک فرمت باینری سطح پایین برای اجرای کد نزدیک به سرعت نیتیو در مرورگر است که توسط همه مرورگرهای مدرن پشتیبانی میشود. WASM زبان اجرا نیست، بلکه یک تارگت کامپایل است: یعنی میتوان کد زبانهایی مثل ++C، Rust و حالا .NET C# را کامپایل و داخل مرورگر اجرا کرد.
این فناوری امکان اجرای اپلیکیشنهای پیچیدهای را که پیشتر فقط روی دسکتاپ ممکن بود، به وب میآورد — بدون نیاز به پلاگین یا جاوااسکریپت محور بودن.
اما پتانسیل WASM در دنیای .NET چیست؟
دقیقا همین جاست که پروژه Blazor WebAssembly وارد میشود. شما کل اپلیکیشن کلاینت را با .NET مینویسید و سپس مستقیماً روی WASM اجرا میکنید. این یعنی حتی منطق بیزینس، اعتبارسنجیها و بخشهایی از اکوسیستم گسترده .NET را سمت کلاینت دارید، کاملاً مستقل از جاوااسکریپت. سادگی استفاده از DI، قابلیت اشتراک گذاری کد بین سرور و کلاینت، و حتی لود Assemblyهای داینامیک در runtime مجموعه ابزار قدرتمندی در اختیار توسعهدهنده .NET قرار میدهد.
مثال بسیار ساده: ارتباط میان C# و جاوااسکریپت از طریق جاوااسکریپت اینترآپ (JSInterop):
// فراخوانی تابع جاوااسکریپت از C# - مثال: نمایش پیغام
await JS.InvokeVoidAsync("alert", "Hello from WASM and .NET!");
این ابتدای راه است؛ WASM به مرور پشتیبانی از multithreading و even GC را بهبود میبخشد.
پیشنهاد: اگر به توسعه کامپوننتها، اپلیکیشنهای آفلاین (PWA) یا حتی port کردن پروژههای WinForms/WPF به وب فکر میکنید، WASM و Blazor را جدی بگیرید. نسل بعدی اپلیکیشنهای وب، صرفاً جاوااسکریپتمحور نیست — با .NET عملکرد و آزادی عمل در سطح نیتیو را تجربه کنید.
@DeveloperAdvocate 🥑