1 035
订阅者
无数据24 小时
无数据7 天
-130 天
帖子存档
\n\n\n🔐 اجرای CSP فقط یک هدر نیست—بلکه تفکری معمارانه برای دفاع لایهای است. آنرا جدی بگیرید!\n\n@DeveloperAdvocate 🥑","datePublished":"2025-06-24T11:30:51Z","dateModified":"2025-06-24T11:30:51Z","author":{"@type":"Organization","name":"Developer Advocate","url":"https://telemetr.io/ch/channels/1781873126-developeradvocate","image":"https://img.tlmtr.io/c/1WAyB8/5938065502930847822?ty=x"},"publisher":{"@type":"Organization","name":"Developer Advocate","url":"https://telemetr.io/ch/channels/1781873126-developeradvocate","image":"https://img.tlmtr.io/c/1WAyB8/5938065502930847822?ty=x"},"commentCount":0,"interactionStatistic":[{"@type":"InteractionCounter","interactionType":"https://schema.org/ViewAction","userInteractionCount":58},{"@type":"InteractionCounter","interactionType":"https://schema.org/ShareAction","userInteractionCount":1}]}},{"@type":"ListItem","position":9,"item":{"@type":"SocialMediaPosting","@id":"https://telemetr.io/ch/channels/1781873126-developeradvocate/posts/2294","url":"https://telemetr.io/ch/channels/1781873126-developeradvocate/posts/2294","mainEntityOfPage":"https://telemetr.io/ch/channels/1781873126-developeradvocate/posts/2294","headline":"🎯 Credential Stuffing چیست و چطور با آن مقابله کنیم؟ Credential Stuffing یکی از حملات متداول مبتنی بر اتوماسی…","articleBody":"🎯 Credential Stuffing چیست و چطور با آن مقابله کنیم؟\n\nCredential Stuffing یکی از حملات متداول مبتنی بر اتوماسیون است که در آن مهاجم، با استفاده از مجموعهای از نام کاربری/کلمه عبورهای لو رفته (مثلاً از دیتابیسهای افشا شده)، تلاش میکند به صورت انبوه وارد یک سرویس شود. موفقیت این حملات روی کاربران عموماً به علت استفاده مجدد رمزهای عبور در چند سرویس مختلف است.\n\n👁️🗨️ سناریو: فرض کنید endpoint لاگین سرویس شما بدون هیچ rate limiting و کنترل اضافهای، صرفاً ورودی کاربر را دریافت و احراز هویت میکند. این سطح از سادگی شانس موفقیت مهاجم را بهشدت افزایش میدهد.\n\nبرای بهبود امنیت endpoint لاگین، چند لایه اصلی دفاع پیشنهاد میشود:\n\n1. Rate Limiting/Throttling: محدودسازی تعداد تلاش ورود از یک آدرس یا IP مشخص در بازه زمانی. مثلا بیش از ۵ تلاش طی ۵ دقیقه مجاز نباشد.\n2. CAPTCHA: پس از چند تلاش ناموفق، کاربر برای ادامه باید CAPTCHA حل کند.\n3. Passwordless/MFA: فعالسازی احراز هویت چندعاملی یا روشهای بدون رمز عبور.\n4. Motivated Response: ارتباط دادن رفتار مشکوک به بلاک کوتاهمدت/دائمی IP، یا ارسال هشدار به مالک حساب.\n5. Monitoring & Logging: بررسی لاگها برای شناسایی رفتارهای مشکوک و هماهنگ با SIEM.\n\nنمونه ساده پیادهسازی Limiting برای endpoint لاگین در ASP.NET Core:\npublic class SimpleRateLimiter\n{\n private readonly MemoryCache _cache = new MemoryCache(new MemoryCacheOptions());\n private readonly int _maxAttempts = 5;\n private readonly TimeSpan _timeWindow = TimeSpan.FromMinutes(5);\n\n public bool IsAllowed(string key)\n {\n var value = _cache.GetOrCreate(key, entry =>\n {\n entry.AbsoluteExpirationRelativeToNow = _timeWindow;\n return 0;\n });\n\n int current = (int)value;\n if (current >= _maxAttempts)\n return false;\n\n _cache.Set(key, current + 1, TimeSpan.FromMinutes(5));\n return true;\n }\n}\n\nدر middleware لاگین:\nvar limiter = new SimpleRateLimiter();\nif (!limiter.IsAllowed(Request.HttpContext.Connection.RemoteIpAddress.ToString()))\n return Unauthorized(\"Too many login attempts, please wait.\");\n\n\nراهبردهای جدیتر مانند استفاده از distributed cache (مثل Redis)، سرویسهای مدیریت bot detection، و بررسی credential stuffing با ابزارهایی چون Azure AD Identity Protection توصیه میشود. همچنین کاربران را به استفاده از رمز عبور منحصر به فرد و فعالسازی MFA تشویق کنید.\n\n#Security #ASPNetCore #CredentialStuffing\n\n@DeveloperAdvocate 🥑","datePublished":"2025-06-24T10:31:00Z","dateModified":"2025-06-24T10:31:00Z","author":{"@type":"Organization","name":"Developer Advocate","url":"https://telemetr.io/ch/channels/1781873126-developeradvocate","image":"https://img.tlmtr.io/c/1WAyB8/5938065502930847822?ty=x"},"publisher":{"@type":"Organization","name":"Developer Advocate","url":"https://telemetr.io/ch/channels/1781873126-developeradvocate","image":"https://img.tlmtr.io/c/1WAyB8/5938065502930847822?ty=x"},"commentCount":0,"interactionStatistic":[{"@type":"InteractionCounter","interactionType":"https://schema.org/ViewAction","userInteractionCount":58},{"@type":"InteractionCounter","interactionType":"https://schema.org/LikeAction","userInteractionCount":1},{"@type":"InteractionCounter","interactionType":"https://schema.org/ShareAction","userInteractionCount":1}]}},{"@type":"ListItem","position":10,"item":{"@type":"SocialMediaPosting","@id":"https://telemetr.io/ch/channels/1781873126-developeradvocate/posts/2293","url":"https://telemetr.io/ch/channels/1781873126-developeradvocate/posts/2293","mainEntityOfPage":"https://telemetr.io/ch/channels/1781873126-developeradvocate/posts/2293","headline":"🟢 تکنیکهای پیشرفته پیرامون Primary Constructors در C# 12: بهترین رویکردها و دامها استفاده از Primary Const…","articleBody":"🟢 تکنیکهای پیشرفته پیرامون Primary Constructors در C# 12: بهترین رویکردها و دامها\n\nاستفاده از Primary Constructor در کلاسهای C# (از نسخه ۱۲ به بعد) کد شما را تمیزتر و خواناتر میکند، اما اگر بدون دقت استفاده شود میتواند به code smell بینجامد.\n\n⚙️ بهترین رویکردها:\n- برای Immutable Objects عالی است: زمانهایی که فقط مقداردهی فیلدها یا propertyها را لازم دارید، Primary Constructor همراه با init یا readonly فوقالعاده تمیز و ساده است.\n public class Customer(string name, string email)\n {\n public string Name { get; } = name;\n public string Email { get; } = email;\n }\n \n- Dependency Injection را زیبا میکند: وقتی سرویسها را بدون نیاز به منطقی پیچیده inject میکنید، Constructor succinctتر میشود.\n public class OrderService(ILogger logger, IOrderRepository repository) { /* ... */ }\n \n- معماری Clean و DDD: میتوانید Aggregate Rootها و Value Objectها را با minimum ceremony بسازید و وابستگی را شفاف منتقل کنید.\n\n⛔️ دامهای رایج:\n- افزایش Coupling: مراقب باشید که پارامترهای Primary Constructor فقط در همین کلاس زندگی میکنند. اگر کلاسهای مشتق بخواهند به این مقادیر دسترسی داشته باشند باید تکرار کنند یا راههای workaround بزنند (مثل ایجاد Protected Property).\n public class BaseProcessor(string contextPath) { /* ... */ }\n public class FileProcessor(string contextPath, string filePath) : BaseProcessor(contextPath) { /* ... */ }\n \n اینجا contextPath باید هم به Base و هم به Derived داده شود (Duplication محتمل است).\n- کاهش Refactorability: اگر پارامترها زیاد شوند یا تغییر کنند، امضای کلاس را عوض میکنید و همه مصرفکنندهها تحت تاثیر قرار میگیرند. برای کلاسهای با عمر زیاد یا قرارداد پایدار، مراقب باشید.\n- سختتر شدن Serialization/Deserialization: لزوماً همه serializerها، به ویژه در legacy یا ابزارهای خاص، هنوز با Primary Constructor هماهنگ نشدهاند.\n- محدودیت Partial: نمیتوانید سایر بخشهای Partial کلاس را به این پارامترها مستقیماً دسترسی دهید.\n\n🎯 Tip معماری: توصیه میشود این قابلیت را برای record typeها یا کلاسهای کوچک stateless استفاده کنید؛ در کاربردهای پیچیده، ترجیح دهید از traditional constructor بهره ببرید تا انعطاف بیشتر در دستکاری dependency injection و منطق اعتبارسنجی داشته باشید.\n\nنتیجه: Primary Constructor قدرت زیادی دارد، ولی استفاده افراطی میتواند maintenance codebase را سخت کند. نگاه بلندمدت داشته باشید و هر کاربرد را بر اساس context انتخاب کنید.\n\n@DeveloperAdvocate 🥑","datePublished":"2025-06-24T09:31:07Z","dateModified":"2025-06-24T09:31:07Z","author":{"@type":"Organization","name":"Developer Advocate","url":"https://telemetr.io/ch/channels/1781873126-developeradvocate","image":"https://img.tlmtr.io/c/1WAyB8/5938065502930847822?ty=x"},"publisher":{"@type":"Organization","name":"Developer Advocate","url":"https://telemetr.io/ch/channels/1781873126-developeradvocate","image":"https://img.tlmtr.io/c/1WAyB8/5938065502930847822?ty=x"},"commentCount":0,"interactionStatistic":[{"@type":"InteractionCounter","interactionType":"https://schema.org/ViewAction","userInteractionCount":60},{"@type":"InteractionCounter","interactionType":"https://schema.org/ShareAction","userInteractionCount":1}]}},{"@type":"ListItem","position":11,"item":{"@type":"SocialMediaPosting","@id":"https://telemetr.io/ch/channels/1781873126-developeradvocate/posts/2292","url":"https://telemetr.io/ch/channels/1781873126-developeradvocate/posts/2292","mainEntityOfPage":"https://telemetr.io/ch/channels/1781873126-developeradvocate/posts/2292","headline":"🚀 ساخت یک GitHub Action سفارشی با Node.js برای مدیریت ورژن در پروژهها در پروژههای CI/CD، گاهی نیاز دارید اط…","articleBody":"🚀 ساخت یک GitHub Action سفارشی با Node.js برای مدیریت ورژن در پروژهها\n\nدر پروژههای CI/CD، گاهی نیاز دارید اطمینان حاصل کنید که فایل AssemblyInfo یا appsettings شما پس از هر build یا release، بهطور خودکار بر اساس ورژن جاری شاخه یا تگ، بهروزرسانی شود. بیایید یک GitHub Action جاوااسکریپتی بنویسیم که نسخه را با تگ جاری در فایل دلخواه همگامسازی میکند (مثلاً برای AssemblyInfo.cs).\n\nساختار پوشه:\n.github/\n actions/\n sync-version/\n action.yml\n index.js\n package.json\n\n\nنمونه محتوا:\n\naction.yml:\nname: Sync Version to AssemblyInfo\ndescription: Update version in AssemblyInfo.cs from git tag\nruns:\n using: 'node16'\n main: 'index.js'\ninputs:\n file-path:\n description: 'Path to AssemblyInfo.cs'\n required: true\n version-regex:\n description: 'Regex pattern for AssemblyVersion'\n required: false\n default: '(?<=\\[assembly: AssemblyVersion\\(\")(.+?)(?=\"\\)\\])'\n\n\nindex.js:\nconst core = require('@actions/core');\nconst fs = require('fs');\n\ntry {\n const filePath = core.getInput('file-path', { required: true });\n const versionRegex = new RegExp(core.getInput('version-regex'), 'g');\n const gitRef = process.env.GITHUB_REF || '';\n // استخراج نسخه از تگ: refs/tags/v1.2.3 -> 1.2.3\n const versionMatch = gitRef.match(/refs\\/tags\\/v?(\\d+\\.\\d+\\.\\d+)/);\n if (!versionMatch) {\n core.setFailed('No version tag found in GITHUB_REF.');\n return;\n }\n const version = versionMatch[1];\n\n let text = fs.readFileSync(filePath, 'utf8');\n text = text.replace(versionRegex, version);\n fs.writeFileSync(filePath, text, 'utf8');\n core.info(`Version in ${filePath} synced to ${version}`);\n} catch (error) {\n core.setFailed(error.message);\n}\n\n\nاستفاده در workflow:\n- name: Sync version in AssemblyInfo\n uses: ./.github/actions/sync-version\n with:\n file-path: 'src/MyProject/Properties/AssemblyInfo.cs'\n\n\nاین اقدام نهتنها از خطاهای انسانی جلوگیری میکند، بلکه انتشار ورژنهای قابل اتکاتر و repeatable را تضمین میکند. الهامبخش یک DevOps واقعی باشید و اکشنهای خود را بجای پلاگینهای عمومی، دقیقاً مطابق نیازمندیهای معماریتان بسازید!\n\n@DeveloperAdvocate 🥑","datePublished":"2025-06-24T08:30:55Z","dateModified":"2025-06-24T08:30:55Z","author":{"@type":"Organization","name":"Developer Advocate","url":"https://telemetr.io/ch/channels/1781873126-developeradvocate","image":"https://img.tlmtr.io/c/1WAyB8/5938065502930847822?ty=x"},"publisher":{"@type":"Organization","name":"Developer Advocate","url":"https://telemetr.io/ch/channels/1781873126-developeradvocate","image":"https://img.tlmtr.io/c/1WAyB8/5938065502930847822?ty=x"},"commentCount":0,"interactionStatistic":[{"@type":"InteractionCounter","interactionType":"https://schema.org/ViewAction","userInteractionCount":59},{"@type":"InteractionCounter","interactionType":"https://schema.org/ShareAction","userInteractionCount":1}]}},{"@type":"ListItem","position":12,"item":{"@type":"SocialMediaPosting","@id":"https://telemetr.io/ch/channels/1781873126-developeradvocate/posts/2291","url":"https://telemetr.io/ch/channels/1781873126-developeradvocate/posts/2291","mainEntityOfPage":"https://telemetr.io/ch/channels/1781873126-developeradvocate/posts/2291","headline":"یکی از جنبههای مغفول مانده در معماری نرمافزارهای مبتنی بر .NET، مدیریت صحیح Flowهای ناهمگام (Asynchronous F…","articleBody":"یکی از جنبههای مغفول مانده در معماری نرمافزارهای مبتنی بر .NET، مدیریت صحیح Flowهای ناهمگام (Asynchronous Flows) در لایههای مختلف است. اغلب دیده میشود که async/await صرفاً به لایههای پایینی تعلق میگیرد (مثلاً Data Access)، اما ارزش واقعی زمانی آشکار میشود که contractهای لایههای دامین و اپلیکیشن هم کاملاً async طراحی شوند. بدین ترتیب، Application Serviceها، Command Handlerها و حتی Domain Serviceها به جای مسدودسازی (blocking)، جریانهای داده را به شکلی کاملاً asynchronous مدیریت میکنند:\n\npublic interface IOrderService\n{\n Task PlaceOrderAsync(OrderCommand command, CancellationToken token);\n}\n\npublic class PlaceOrderCommandHandler : IRequestHandler\n{\n private readonly IOrderService _orderService;\n\n public PlaceOrderCommandHandler(IOrderService orderService)\n {\n _orderService = orderService;\n }\n\n public async Task Handle(PlaceOrderCommand command, CancellationToken cancellationToken)\n {\n return await _orderService.PlaceOrderAsync(command, cancellationToken);\n }\n}\n\n\nدر این رویکرد، حتی اگر فعلاً بعضی عملیاتها کاملاً همگام باشند، Contractها از ابتدا آماده رشد مقیاسپذیر هستند. به عقیده شما، آیا پیادهسازی end-to-end async در دامنه و application layer همیشه مطلوب است، یا میتواند باعث پیچیدگی غیرضروری و کاهش خوانایی کد شود؟ تجربه خود را به اشتراک بگذارید.\n\n@DeveloperAdvocate 🥑","datePublished":"2025-06-24T07:30:50Z","dateModified":"2025-06-24T07:30:50Z","author":{"@type":"Organization","name":"Developer Advocate","url":"https://telemetr.io/ch/channels/1781873126-developeradvocate","image":"https://img.tlmtr.io/c/1WAyB8/5938065502930847822?ty=x"},"publisher":{"@type":"Organization","name":"Developer Advocate","url":"https://telemetr.io/ch/channels/1781873126-developeradvocate","image":"https://img.tlmtr.io/c/1WAyB8/5938065502930847822?ty=x"},"commentCount":0,"interactionStatistic":[{"@type":"InteractionCounter","interactionType":"https://schema.org/ViewAction","userInteractionCount":67},{"@type":"InteractionCounter","interactionType":"https://schema.org/ShareAction","userInteractionCount":2}]}},{"@type":"ListItem","position":13,"item":{"@type":"SocialMediaPosting","@id":"https://telemetr.io/ch/channels/1781873126-developeradvocate/posts/2290","url":"https://telemetr.io/ch/channels/1781873126-developeradvocate/posts/2290","mainEntityOfPage":"https://telemetr.io/ch/channels/1781873126-developeradvocate/posts/2290","headline":"یک اشتباه رایج در استفاده از LINQ که پرفورمنس را بهطور جدی تحت تاثیر قرار میدهد، ترکیب فیلتر کردن و پروجکشن…","articleBody":"یک اشتباه رایج در استفاده از LINQ که پرفورمنس را بهطور جدی تحت تاثیر قرار میدهد، ترکیب فیلتر کردن و پروجکشن (Select) بهصورت نابهینه است.\n\nفرض کنید لیستی از کاربران داریم و میخواهیم نام کاربران فعال را بگیریم:\n\nvar activeUserNames = users\n .Where(u => u.IsActive)\n .Select(u => u.Name)\n .ToList();\n\n\nاین صحیح است، اما اشتباه رایج بهشکل زیر اتفاق میافتد:\n\nvar userNames = users\n .Select(u => u.Name)\n .Where(name => users.First(x => x.Name == name).IsActive)\n .ToList();\n\n\nدر این اشتباه، ابتدا تمام user.Nameها انتخاب میشود و سپس برای هر Name باید دوباره کل مجموعه کاربران را جستجو کنیم. این به معنای اجرای N بار First و درنتیجه O(N^2)!\n\nراه بهینه همیشه این است که فیلترینگ را قبل از پروجکشن انجام بدهیم تا دادههای غیرضروری هرگز وارد مرحله بعدی نشوند:\n\nvar activeUserNames = users\n .Where(u => u.IsActive)\n .Select(u => u.Name)\n .ToList();\n\n\nهمیشه ترتیب توابع LINQ اهمیت دارد. هر بار که دیدید Select جلوتر از Where آمده، یک بار دیگر فکر کنید—ممکن است در حال انجام پردازشهای تکراری باشید!\n\n✔️ قانون طلایی: \nتا میتوانید داده کمتر را به مراحل بعدی بفرستید. همیشه Where را زودتر از Select اعمال کنید، مگر اینکه دلیل خاصی داشته باشید.\n\n@DeveloperAdvocate 🥑","datePublished":"2025-06-24T06:30:52Z","dateModified":"2025-06-24T06:30:52Z","author":{"@type":"Organization","name":"Developer Advocate","url":"https://telemetr.io/ch/channels/1781873126-developeradvocate","image":"https://img.tlmtr.io/c/1WAyB8/5938065502930847822?ty=x"},"publisher":{"@type":"Organization","name":"Developer Advocate","url":"https://telemetr.io/ch/channels/1781873126-developeradvocate","image":"https://img.tlmtr.io/c/1WAyB8/5938065502930847822?ty=x"},"commentCount":0,"interactionStatistic":[{"@type":"InteractionCounter","interactionType":"https://schema.org/ViewAction","userInteractionCount":67},{"@type":"InteractionCounter","interactionType":"https://schema.org/ShareAction","userInteractionCount":2}]}},{"@type":"ListItem","position":14,"item":{"@type":"SocialMediaPosting","@id":"https://telemetr.io/ch/channels/1781873126-developeradvocate/posts/2289","url":"https://telemetr.io/ch/channels/1781873126-developeradvocate/posts/2289","mainEntityOfPage":"https://telemetr.io/ch/channels/1781873126-developeradvocate/posts/2289","headline":"وقتی صحبت از افزایش سرعت توسعه و همسانسازی محیط توسعه بین اعضای تیمهای full-stack (مثلاً تیمی با .NET Backe…","articleBody":"وقتی صحبت از افزایش سرعت توسعه و همسانسازی محیط توسعه بین اعضای تیمهای full-stack (مثلاً تیمی با .NET Backend و React Frontend) میشود، DevContainers یک راهکار بینظیر است. با استفاده از فایل devcontainer.json، میتوان یک کانتینر توسعه استاندارد ساخت که هم .NET SDK را دارد و هم Node.js را برای React؛ به این ترتیب نیازی به نصب ابزارها روی سیستم توسعهدهندگان نیست و مشکلاتی مثل اختلاف نسخهها از بین میرود. در ادامه، یک پیکربندی عملی و پیشرفته برای چنین پروژهای را میبینید:\n\n{\n \"name\": \"Full-Stack .NET & React DevContainer\",\n \"image\": \"mcr.microsoft.com/devcontainers/dotnet:8.0\", // داکر ایمیج رسمی .NET 8\n \"features\": {\n \"ghcr.io/devcontainers/features/node:1\": {\n \"version\": \"18\"\n }\n },\n \"postCreateCommand\": \"dotnet tool restore && npm install --prefix ClientApp\",\n \"customizations\": {\n \"vscode\": {\n \"extensions\": [\n \"ms-dotnettools.csharp\",\n \"dbaeumer.vscode-eslint\",\n \"esbenp.prettier-vscode\"\n ]\n }\n },\n \"ports\": [\n 5000, // پورت پیشفرض ASP.NET\n 3000 // پورت پیشفرض React - قابل تنظیم با توجه به پروژه\n ],\n \"forwardPorts\": [5000, 3000],\n \"remoteUser\": \"vscode\"\n}\n\n\n🔹 نکتههای پیشرفته:\n- بوسیله بخش features، Node.js به محیط داتنت افزوده شده و ماژولهای npm قابل اجرا هستند، بدون نیاز به دو ایمیج یا کانتینر جدا.\n- پس از ساخت کانتینر، دستورات dotnet tool restore و npm install به ترتیب ابزارهای global داتنت و پکیجهای React (در مسیر ClientApp) را نصب میکنند.\n- با اضافه کردن افزونههای کلیدی VS Code، تجربه توسعه یکپارچهسازی میشود.\n- پورتها بر اساس معماری پروژه قابل تغییر هستند، اما پیشنهاد میشود اتصال بکاند به ۵۰۰۰ و فرنـتاند به ۳۰۰۰ باشد تا هاتریلـود هر دو سمت به نرمی کار کند.\n\nبا استانداردسازی DevContainer، bootstrap محیط توسعه برای هر عضو تیم فقط با یک کلیک ممکن است—و این یعنی خداحافظی با جملهی «برای من روی ماشینم کار میکنه»! 🚀\n\n@DeveloperAdvocate 🥑","datePublished":"2025-06-24T05:30:56Z","dateModified":"2025-06-24T05:30:56Z","author":{"@type":"Organization","name":"Developer Advocate","url":"https://telemetr.io/ch/channels/1781873126-developeradvocate","image":"https://img.tlmtr.io/c/1WAyB8/5938065502930847822?ty=x"},"publisher":{"@type":"Organization","name":"Developer Advocate","url":"https://telemetr.io/ch/channels/1781873126-developeradvocate","image":"https://img.tlmtr.io/c/1WAyB8/5938065502930847822?ty=x"},"commentCount":0,"interactionStatistic":[{"@type":"InteractionCounter","interactionType":"https://schema.org/ViewAction","userInteractionCount":60},{"@type":"InteractionCounter","interactionType":"https://schema.org/ShareAction","userInteractionCount":2}]}},{"@type":"ListItem","position":15,"item":{"@type":"SocialMediaPosting","@id":"https://telemetr.io/ch/channels/1781873126-developeradvocate/posts/2288","url":"https://telemetr.io/ch/channels/1781873126-developeradvocate/posts/2288","mainEntityOfPage":"https://telemetr.io/ch/channels/1781873126-developeradvocate/posts/2288","headline":"🔹 قالب گفتگو با مدیر محصول درباره تأثیر فنی و بیزینسی دیون فنی یکی از مهارتهای کلیدی معماران نرمافزار، توضی…","articleBody":"🔹 قالب گفتگو با مدیر محصول درباره تأثیر فنی و بیزینسی دیون فنی\n\nیکی از مهارتهای کلیدی معماران نرمافزار، توضیح شفاف تأثیر دیون فنی به زبان بیزینس است. این اسکریپت، برای جلسات با مدیر محصول قابل استفاده است:\n\n---\n«الان بخشهایی از کد ما، مشابه و تکراری هستند و اگر نیاز به تغییر داشته باشیم (مثلاً تغییر استراتژی قیمتگذاری)، باید این تغییر در چندین نقطه انجام شود. این به معنای افزایش قابل توجه زمان تحویل و همچنین افزایش ریسک باگ است.\n\nاگر اجازه بدهید بخشی از زمان تیم را صرف بازپرداخت این دیون فنی کنیم (مثلاً بازآرایش این منطق و استخراج آن به یک سرویس مستقل)،:\n\n۱. افزایش سرعت ارائه ویژگیهای جدید: هر ویژگی جدید یا تغییر آینده، سریعتر و کمهزینهتر پیادهسازی خواهد شد.\n۲. کاهش نرخ رگرسیون: تیم تضمین کیفیت میتواند اعتماد بیشتری به تغییرات سریعتر داشته باشد چون نقاط آسیبپذیری کمتری وجود خواهد داشت.\n۳. پایداری بیشتر در تولید: دیون فنی بالا = احتمال خطای بیشتر = وضعیت «آتشسوزی» و نگرانیهای مکرر در محیط لایو.\n\nاگر امروز این کارها را به تعویق بیندازیم، هزینه هر تغییر جدید، افزایشی خطی یا حتی نمایی پیدا میکند. نمونه عملی این موضوع در بخش فلان بود که تغییر ساده نمایش تاریخ باعث شد چندین فیچر دیگر سهواً خراب شوند. مثال کد فعلی:\n// قیمتگذاری سفارش، به تکرار در چند سرویس مختلف\nif (order.Type == OrderType.Express) {\n price += 20000;\n // کدهای مشابه در چندین کلاس\n}\n\nراهحل پیشنهادی:\npublic interface IPricingStrategy {\n int Calculate(Order order);\n}\n\npublic class ExpressPricingStrategy : IPricingStrategy {\n public int Calculate(Order order) => order.BasePrice + 20000;\n}\n// تزریق اینترفیس به جای تکرار منطق\n\n\n🔸 با سرمایهگذاری روی بازپرداخت دیون فنی، به تیم اجازه میدهیم واقعاً «لبه رقابتی» بیزینس را سریعتر و دقیقتر پیادهسازی کند تا صرفاً «تعمیرکار همیشگی» محصول بماند. اگر خواستید، میتوانم ROI تقریبی بازپرداخت این بخش را براساس فیچرهای آینده تخمین بزنم.»\n\n@DeveloperAdvocate 🥑","datePublished":"2025-06-24T04:30:55Z","dateModified":"2025-06-24T04:30:55Z","author":{"@type":"Organization","name":"Developer Advocate","url":"https://telemetr.io/ch/channels/1781873126-developeradvocate","image":"https://img.tlmtr.io/c/1WAyB8/5938065502930847822?ty=x"},"publisher":{"@type":"Organization","name":"Developer Advocate","url":"https://telemetr.io/ch/channels/1781873126-developeradvocate","image":"https://img.tlmtr.io/c/1WAyB8/5938065502930847822?ty=x"},"commentCount":0,"interactionStatistic":[{"@type":"InteractionCounter","interactionType":"https://schema.org/ViewAction","userInteractionCount":75},{"@type":"InteractionCounter","interactionType":"https://schema.org/ShareAction","userInteractionCount":2}]}},{"@type":"ListItem","position":16,"item":{"@type":"SocialMediaPosting","@id":"https://telemetr.io/ch/channels/1781873126-developeradvocate/posts/2287","url":"https://telemetr.io/ch/channels/1781873126-developeradvocate/posts/2287","mainEntityOfPage":"https://telemetr.io/ch/channels/1781873126-developeradvocate/posts/2287","headline":"فرق Monitoring و Observability را چگونه باید در معماری نرمافزار جدی گرفت؟ 🔍 Monitoring (پایش) معادل سیستم عص…","articleBody":"فرق Monitoring و Observability را چگونه باید در معماری نرمافزار جدی گرفت؟\n\n🔍 Monitoring (پایش) معادل سیستم عصبی واکنشی است: شما مجموعهای محدود از متریکها (مثل CPU, memory یا latency) را با ابزارهایی مانند Prometheus جمعآوری میکنید تا شرایط سرویس را رصد کنید و هشدار دریافت کنید. فرض اصلی پایش این است که میدانید به دنبال چه چیزی هستید و معمولاً با Grafana داشبوردهای سفارشی برای نمایش این دادهها میسازید.\n\n🦉 Observability (قابلیت مشاهده) اما یک سطح بالاتر است—توانایی سیستم برای نمایش رفتار کلی خود از مسیر دادههای telemetry (یعنی ترکیب متریکها، لاگها و تریسها). اینجا فقط به هشدار یا آلارم بسنده نمیکنید، بلکه میتوانید پاسخ سوالات ناشناخته و اتفاقات پیشبینینشده را نیز استخراج کنید. Observability یعنی اینکه وقتی یک رفتار غیرعادی رخ میدهد، با دادههای کافی و مرتبط، بتوان عمق اشکال را کشف کرد، نه اینکه فقط از سطح مشکل آگاه شوید.\n\n🎯 مثال عینی: \nفرض کنید یک API تحت بار بالا کند شده. Monitoring با Prometheus و Grafana صرفاً افزایش latency را اطلاع میدهد. اما اگر structured logging (مثلاً Serilog) و distributed tracing (مانند OpenTelemetry با Jaeger) را اضافه کنید، میتوانید بفهمید کندی دقیقاً ناشی از کدام dependency یا کدام endpoint است.\n\nدر .NET، پیادهسازی استاندارد observability با ترکیب متریکهای OpenTelemetry، لاگهای ساختارمند Serilog، و ارسال داده به سیستمهایی مانند Grafana و Jaeger انجام میشود:\n\nbuilder.Services.AddOpenTelemetry()\n .WithMetrics(metrics =>\n {\n metrics.AddAspNetCoreInstrumentation();\n metrics.AddPrometheusExporter(); // export metrcis for Prometheus\n })\n .WithTracing(tracing =>\n {\n tracing.AddAspNetCoreInstrumentation();\n tracing.AddJaegerExporter(); // distributed tracing\n });\n\n\n💡 توصیه معماران: Monitoring خوب برای واکنش سریع حیاتی است، اما observability قوی، ریشهیابی (Root Cause Analysis) و پیشبینی رفتار سرویس را ممکن میسازد. به سمت معماری observability-first حرکت کنید تا سرویسهایتان واقعاً قابل اطمینان و قابل پشتیبانی باقی بمانند.\n\n@DeveloperAdvocate 🥑","datePublished":"2025-06-24T03:30:56Z","dateModified":"2025-06-24T03:30:56Z","author":{"@type":"Organization","name":"Developer Advocate","url":"https://telemetr.io/ch/channels/1781873126-developeradvocate","image":"https://img.tlmtr.io/c/1WAyB8/5938065502930847822?ty=x"},"publisher":{"@type":"Organization","name":"Developer Advocate","url":"https://telemetr.io/ch/channels/1781873126-developeradvocate","image":"https://img.tlmtr.io/c/1WAyB8/5938065502930847822?ty=x"},"commentCount":0,"interactionStatistic":[{"@type":"InteractionCounter","interactionType":"https://schema.org/ViewAction","userInteractionCount":68},{"@type":"InteractionCounter","interactionType":"https://schema.org/ShareAction","userInteractionCount":2}]}},{"@type":"ListItem","position":17,"item":{"@type":"SocialMediaPosting","@id":"https://telemetr.io/ch/channels/1781873126-developeradvocate/posts/2286","url":"https://telemetr.io/ch/channels/1781873126-developeradvocate/posts/2286","mainEntityOfPage":"https://telemetr.io/ch/channels/1781873126-developeradvocate/posts/2286","headline":"الگوی Outbox در معماری میکروسرویسها: چطور قابلیت اطمینان رخدادها را تضمین کنیم؟ در بسیاری از سیستمهای مبتنی…","articleBody":"الگوی Outbox در معماری میکروسرویسها: چطور قابلیت اطمینان رخدادها را تضمین کنیم؟\n\nدر بسیاری از سیستمهای مبتنی بر رخداد (Event-Driven Microservices)، نیاز داریم تضمین کنیم که دادهها هم در دیتابیس محلی ثبت شوند و هم پیام رویداد مرتبط با آن، دقیقا یکبار به باس پیام (مثلا Kafka یا RabbitMQ) ارسال شود. اما واقعیت این است که تراکنش توزیعشده بین دیتابیس و Message Broker اغلب پرهزینه یا حتی غیرممکن است.\n\nراهحل کلاسیک: Outbox Pattern 🔥\n\nدر این الگو، عملیات نوشتن دیتا و رخداد مرتبط، باهم و در قالب یک تراکنش ACID فقط در دیتابیس سرویس (مثلا SQL Server) انجام میشوند. سپس یک پروسه جداگانه (Event Publisher) از جدول Outbox رخدادها را خوانده و به باس پیام ارسال میکند. با این کار، همیشه تضمین میکنیم که داده و رویداد باهم در Atomicity کامل ایجاد شوند؛ حتی اگر سرویس یا شبکه قطع شود.\n\nیک پیادهسازی ساده و تمیز این الگو با EF Core:\n\npublic class IntegrationEvent\n{\n public Guid Id { get; set; }\n public string Type { get; set; }\n public string Payload { get; set; }\n public DateTime OccurredAt { get; set; }\n}\n\npublic class AppDbContext : DbContext\n{\n public DbSet OutboxEvents { get; set; }\n // ... سایر موجودیتها\n}\n\n// سرویس دامین:\npublic async Task PlaceOrderAsync(Order order)\n{\n using var trx = await db.Database.BeginTransactionAsync();\n\n db.Orders.Add(order);\n\n db.OutboxEvents.Add(new IntegrationEvent {\n Id = Guid.NewGuid(),\n Type = \"OrderPlaced\",\n Payload = Serialize(order),\n OccurredAt = DateTime.UtcNow\n });\n\n await db.SaveChangesAsync();\n await trx.CommitAsync();\n}\n\n\nیک Worker جداگانه رخدادهای Outbox را بر اساس Id و وضعیت ارسال میکند و بعد از اطمینان از تحویل، آنها را حذف یا Update میکند.\n\nمزیت بارز: رخدادهای شما هیچوقت گم نمیشوند یا دوبار ارسال نمیشوند🔥. این الگو به ویژه برای ترکیب با Event Sourcing/ CQRS بسیار قدرتمند است و در معماریهای جدی میکروسرویس باید جدیاش گرفت!\n\n#Microservices #Outbox #EventDriven #DotNetArchitect\n\n@DeveloperAdvocate 🥑","datePublished":"2025-06-24T02:30:57Z","dateModified":"2025-06-24T02:30:57Z","author":{"@type":"Organization","name":"Developer Advocate","url":"https://telemetr.io/ch/channels/1781873126-developeradvocate","image":"https://img.tlmtr.io/c/1WAyB8/5938065502930847822?ty=x"},"publisher":{"@type":"Organization","name":"Developer Advocate","url":"https://telemetr.io/ch/channels/1781873126-developeradvocate","image":"https://img.tlmtr.io/c/1WAyB8/5938065502930847822?ty=x"},"commentCount":0,"interactionStatistic":[{"@type":"InteractionCounter","interactionType":"https://schema.org/ViewAction","userInteractionCount":59},{"@type":"InteractionCounter","interactionType":"https://schema.org/ShareAction","userInteractionCount":1}]}},{"@type":"ListItem","position":18,"item":{"@type":"SocialMediaPosting","@id":"https://telemetr.io/ch/channels/1781873126-developeradvocate/posts/2285","url":"https://telemetr.io/ch/channels/1781873126-developeradvocate/posts/2285","mainEntityOfPage":"https://telemetr.io/ch/channels/1781873126-developeradvocate/posts/2285","headline":"🌐 تفاوت Concurrency و Parallelism در #NET اغلب مفاهیم Concurrency (همزمانی) و Parallelism (پردازش موازی) به…","articleBody":"🌐 تفاوت Concurrency و Parallelism در #NET\n\nاغلب مفاهیم Concurrency (همزمانی) و Parallelism (پردازش موازی) به اشتباه به جای هم به کار میروند، اما این دو تفاوت ماهیتی دارند.\n\n- Concurrency: چندین کار به طور بالقوه همزمان در حال پیشرفتاند؛ اما لزوماً در یک زمان اجرا نمیشوند — میتواند روی یک هسته باشد و context switch صورت گیرد.\n- Parallelism: چندین کار واقعاً به طور همزمان روی چندین هسته انجام میشوند.\n\n👨💻 مثال عملی — تفکیک معنایی با کد:\n\nusing System;\nusing System.Threading;\nusing System.Threading.Tasks;\n\nclass Program\n{\n static void Main()\n {\n Console.WriteLine(\"Concurrency (همزمانی):\"); \n ConcurrencyExample();\n\n Console.WriteLine(\"\\nParallelism (موازی):\");\n ParallelismExample();\n }\n\n static void ConcurrencyExample()\n {\n for (int i = 1; i <= 3; i++)\n {\n // هر Task بلافاصله استارت میزند اما Scheduler میتواند آنها را روی یک Thread اجرا کند\n Task.Run(() => DoWork($\"Task {i} (concurrent)\"));\n }\n Thread.Sleep(1500); // منتظر ماندن برای تکمیل نمایش\n }\n\n static void ParallelismExample()\n {\n // Parallel.For واقعی روی چند هسته اجرا میشود (در صورت وجود منابع)\n Parallel.For(1, 4, i =>\n {\n DoWork($\"Parallel Task {i}\");\n });\n }\n\n static void DoWork(string name)\n {\n Console.WriteLine($\"{name} شروع شد در ThreadId: {Thread.CurrentThread.ManagedThreadId}\");\n Thread.Sleep(500); // شبیهسازی پردازش\n Console.WriteLine($\"{name} پایان یافت در ThreadId: {Thread.CurrentThread.ManagedThreadId}\");\n }\n}\n\n\nدر اجرای نمونه بالا:\n- در بخش Concurrency هر Task میتواند روی همان Thread اجرا شود (وابسته به ThreadPool) — کارها پشت سر هم اما با هم در گردش هستند.\n- در بخش Parallelism، Parallel.For کارها را بین چندین Thread واقعی توزیع میکند تا بطور حقیقی همزمان کار کنند.\n\n📌 همیشه هر concurrent code موازی نیست، اما هر parallel code لزوماً concurrent هم هست. تفاوت را در طراحی سیستمهای چندنخی جدی بگیرید!\n\n@DeveloperAdvocate 🥑","datePublished":"2025-06-24T01:30:59Z","dateModified":"2025-06-24T01:30:59Z","author":{"@type":"Organization","name":"Developer Advocate","url":"https://telemetr.io/ch/channels/1781873126-developeradvocate","image":"https://img.tlmtr.io/c/1WAyB8/5938065502930847822?ty=x"},"publisher":{"@type":"Organization","name":"Developer Advocate","url":"https://telemetr.io/ch/channels/1781873126-developeradvocate","image":"https://img.tlmtr.io/c/1WAyB8/5938065502930847822?ty=x"},"commentCount":0,"interactionStatistic":[{"@type":"InteractionCounter","interactionType":"https://schema.org/ViewAction","userInteractionCount":47},{"@type":"InteractionCounter","interactionType":"https://schema.org/ShareAction","userInteractionCount":1}]}},{"@type":"ListItem","position":19,"item":{"@type":"SocialMediaPosting","@id":"https://telemetr.io/ch/channels/1781873126-developeradvocate/posts/2284","url":"https://telemetr.io/ch/channels/1781873126-developeradvocate/posts/2284","mainEntityOfPage":"https://telemetr.io/ch/channels/1781873126-developeradvocate/posts/2284","headline":"🎯 استفاده پیشرفته از Delegates و Events در مدرنترین سبک C# در بسیاری از پروژههای enterprise، مدیریت رخدادها…","articleBody":"🎯 استفاده پیشرفته از Delegates و Events در مدرنترین سبک C#\n\nدر بسیاری از پروژههای enterprise، مدیریت رخدادهای async و decoupled اغلب حیاتی است. ترکیب Delegates و Events با الگوهای مدرن میتواند کد را هم تستپذیرتر و هم توسعهپذیرتر کند. در این مثال واقعی، با استفاده از EventHandler و lamda expressions رویکردی تمیز، type-safe و حتی تستپذیر ساختهایم:\n\nفرض کنید یک سرویس notification داریم که رخداد ارسال پیام را توسط event به دیگر بخشها اطلاع میدهد، و subscriberها میتوانند با استفاده از lambdaها هندلر خود را inject کنند.\n\npublic class MessageSentEventArgs : EventArgs\n{\n public string Recipient { get; }\n public string Body { get; }\n\n public MessageSentEventArgs(string recipient, string body)\n {\n Recipient = recipient;\n Body = body;\n }\n}\n\npublic class NotificationService\n{\n public event EventHandler? MessageSent;\n\n public void Send(string recipient, string body)\n {\n // ارسال پیام فرضی\n // ...\n\n OnMessageSent(new MessageSentEventArgs(recipient, body));\n }\n\n protected virtual void OnMessageSent(MessageSentEventArgs e)\n {\n MessageSent?.Invoke(this, e);\n }\n}\n\n\nحال میتوانیم با syntax مدرن C#، به سادگی، subscriberها را ثبت و تست کنیم:\n\nvar notifications = new NotificationService();\n\n// ثبت dynamic handler با lambda inline\nnotifications.MessageSent += (sender, e) =>\n{\n Console.WriteLine($\"🔔 پیام به {e.Recipient}: {e.Body}\");\n};\n\n// اجرای رخداد\nnotifications.Send(\"ali@domain.com\", \"سلام علی!\");\n\n/*\n🔔 پیام به ali@domain.com: سلام علی!\n*/\n\n\nنکات پیشرفته:\n- Nullability را رعایت کنید (EventHandler?) تا با async context و تستها دچار مشکل نشوید.\n- لایه تزریق Dependency برای تغذیه event handlers فراهم کنید، برای example با استفاده از DI Container:\n services.AddSingleton();\n notifications.MessageSent += scopedHandler.HandleAsync;\n \n- مراقب memory leak باشید: اگر طول عمر subscriberها کوتاهتر از publisher است، در زمان مناسب unsubscribe کنید.\n\nاین الگو نه فقط برای GUI بلکه برای سرویسهای Distributed، کار با Domain Events و microservices نیز ایدهآل است.\n\n@DeveloperAdvocate 🥑","datePublished":"2025-06-24T00:30:51Z","dateModified":"2025-06-24T00:30:51Z","author":{"@type":"Organization","name":"Developer Advocate","url":"https://telemetr.io/ch/channels/1781873126-developeradvocate","image":"https://img.tlmtr.io/c/1WAyB8/5938065502930847822?ty=x"},"publisher":{"@type":"Organization","name":"Developer Advocate","url":"https://telemetr.io/ch/channels/1781873126-developeradvocate","image":"https://img.tlmtr.io/c/1WAyB8/5938065502930847822?ty=x"},"commentCount":0,"interactionStatistic":[{"@type":"InteractionCounter","interactionType":"https://schema.org/ViewAction","userInteractionCount":46},{"@type":"InteractionCounter","interactionType":"https://schema.org/ShareAction","userInteractionCount":3}]}},{"@type":"ListItem","position":20,"item":{"@type":"SocialMediaPosting","@id":"https://telemetr.io/ch/channels/1781873126-developeradvocate/posts/2283","url":"https://telemetr.io/ch/channels/1781873126-developeradvocate/posts/2283","mainEntityOfPage":"https://telemetr.io/ch/channels/1781873126-developeradvocate/posts/2283","headline":"❄️ مقابله با Cold Start در Serverless (.NET) – رویکرد حرفهای یکی از چالشهای حساس در سرویسهای Serverless مث…","articleBody":"❄️ مقابله با Cold Start در Serverless (.NET) – رویکرد حرفهای\n\nیکی از چالشهای حساس در سرویسهای Serverless مثل AWS Lambda و Azure Functions، مشکل Cold Start است، بهویژه وقتی اپلیکیشن شما با .NET توسعه یافته است. Cold Start یعنی وقتی instance فانکشن برای اولین بار یا پس از idle شدن پاسخ میدهد، باید محیط عملیاتی را بالا بیاورد؛ این یعنی بارگذاری کامل CLR، جِت کردن کد، Dependency Injection و... که گاهی میتواند تا چند ثانیه طول بکشد و تجربهی کاربری را تحت فشار بگذارد.\n\nنکات پیشرفته مدیریت Cold Start برای .NET:\n\n🔸 Minimal hosting و runtime \nحتماً از الگوی minimal API و وارد کردن ریفرنسها به شکل targeted استفاده کنید. بیجهت کل nugetهای سنگین را فقط برای یک فیچر وارد نکنید.\n\n🔸 Configuration خارج از زمان اجرا \nتنظیمات سنگین (مثلاً بارگذاری پیوندهای دیتابیس، مقداردهی اولیه HttpClientها و ...) را در استاتیک کانستراکتور یا Singletonها و خارج از متد اصلی اجرا کنید. مثال:\nprivate static readonly HttpClient _httpClient = new HttpClient\n{\n Timeout = TimeSpan.FromSeconds(5)\n};\n\n\n🔸 جِت Ahead-of-Time (AOT) \nدر AWS Lambda میتوانید از Amazon.Lambda.RuntimeSupport و Native AOT (dotnet publish -r linux-x64 -c Release /p:PublishAot=true) استفاده کنید که بسیاری از delayهای زمان startup را حذف میکند.\n\ndotnet publish -r linux-x64 -c Release /p:PublishAot=true\n\n\nدر Azure Functions، با .NET 8 و مدل isolated process میتوانید از AOT بهره ببرید و زمان Cold Start را بهشدت کاهش دهید.\n\n🔸 حواستان به Dependency Injection باشد \nاستفاده سنگین از DI Containerهایی مثل Autofac یا حتی ServiceCollection میتواند startup time را افزایش دهد. اگر فقط یک یا دو سرویس inject میکنید، کد wiring را دستی بنویسید تا از reflection و اسکن اتوماتیک جلوگیری کنید.\n\n🔸 فانکشنهای سبک و سریع \nیک فانکشن = یک کار کوچک. منابع بزرگ (EF Core DbContext با کانفیگ سنگین؟) را بیرون بکشید و از استفاده بیش از اندازه از لایههای abstraction بپرهیزید.\n\n🔸 Provisioned Concurrency (فقط AWS) \nاگر نیاز به پاسخگو بودن همیشگی دارید، با فعال کردن این feature حداقل n instance گرم از لامبدا همیشه نگه داشته میشود – ولی هزینهی شما بالاتر میرود.\n\n🔸 Always On در Azure \nدر پلن Premium یا Dedicated، با فعالسازی Always On فانکشن شما هیچگاه cold نمیشود؛ اما توجه کنید که این راهحل هم هزینهبر است و spirit سرورلس را نقض میکند.\n\nدر مجموع، Cold Start صرفاً یک موضوع زیرساختی نیست؛ Design و معماری اپلیکیشن شما نقش بنیادین دارد. فانکشن را slim، stateless و upstream-friendly نگه دارید تا حتی در بدترین حالت، Cold Start غیرمحسوس شود.\n\n@DeveloperAdvocate 🥑","datePublished":"2025-06-23T23:30:55Z","dateModified":"2025-06-23T23:30:55Z","author":{"@type":"Organization","name":"Developer Advocate","url":"https://telemetr.io/ch/channels/1781873126-developeradvocate","image":"https://img.tlmtr.io/c/1WAyB8/5938065502930847822?ty=x"},"publisher":{"@type":"Organization","name":"Developer Advocate","url":"https://telemetr.io/ch/channels/1781873126-developeradvocate","image":"https://img.tlmtr.io/c/1WAyB8/5938065502930847822?ty=x"},"commentCount":0,"interactionStatistic":[{"@type":"InteractionCounter","interactionType":"https://schema.org/ViewAction","userInteractionCount":47},{"@type":"InteractionCounter","interactionType":"https://schema.org/ShareAction","userInteractionCount":2}]}}]}



















1 035
✳️ معماری Multi-Tenancy با EF Core: نمونه عملی با Query Filter
در سناریوهای Multi-Tenancy به سبک "Shared Database, Shared Schema"، یکی از بهترین راهکارها برای جداسازی دادههای هر مشتری (Tenant) استفاده از Query Filterها در EF Core است؛ جایی که هیچ دادهای خارج از محدوده Tenant فعلی فچ نمیشود.
بیایید یک مثال واقعی و قابل گسترش را با هم ببینیم:
فرض کنیم TenantId در هر Entity وجود دارد و Context باید در تمام Queryها آن را اِعمال کند. TenantId را از طریق یک Service یا HttpContext میگیریم.
public interface ITenantProvider
{
Guid TenantId { get; }
}
public class TenantProvider : ITenantProvider
{
private readonly IHttpContextAccessor _httpContextAccessor;
public TenantProvider(IHttpContextAccessor accessor)
{
_httpContextAccessor = accessor;
}
public Guid TenantId =>
Guid.Parse(_httpContextAccessor.HttpContext.User.FindFirst("tenant_id").Value);
}
داخل DbContext، یک Query Filter جنریک برای هر Entity که ITenantEntity را پیادهسازی کند، تعریف میکنیم:
public interface ITenantEntity
{
Guid TenantId { get; set; }
}
public class Product : ITenantEntity
{
public int Id { get; set; }
public string Name { get; set; }
public Guid TenantId { get; set; }
// سایر فیلدها...
}
public class AppDbContext : DbContext
{
private readonly ITenantProvider _tenantProvider;
public AppDbContext(DbContextOptions<AppDbContext> options, ITenantProvider tenantProvider)
: base(options)
{
_tenantProvider = tenantProvider;
}
public DbSet<Product> Products { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
// فیلتر همه Entityهای tenant-aware:
foreach (var entityType in modelBuilder.Model.GetEntityTypes()
.Where(e => typeof(ITenantEntity).IsAssignableFrom(e.ClrType)))
{
// مقدار پارامتر Query Filter را برای EF Core توضیح میدهیم
var method = typeof(AppDbContext).GetMethod(nameof(SetGlobalQueryForTenant), BindingFlags.NonPublic | BindingFlags.Instance)
.MakeGenericMethod(entityType.ClrType);
method.Invoke(this, new object[] { modelBuilder });
}
}
// این تابع با Reflection برای هر entity فراخوانی میشود
private void SetGlobalQueryForTenant<TEntity>(ModelBuilder modelBuilder) where TEntity : class, ITenantEntity
{
modelBuilder.Entity<TEntity>().HasQueryFilter(x => x.TenantId == _tenantProvider.TenantId);
}
}
✅ نتیجه: تمام کوئریهای شما فقط دادههای مختص Tenant فعلی را نشان خواهند داد—بدون دردسر و فراموشی فیلترها در جاهای مختلف کد! این روش هم امنتر است، هم DRY، و هم قابل تست.
📎 نکته حرفهای: هنگام Insert هم TenantId را از TenantProvider گرفته و ست کنید تا دادهها همواره جدا بمانند. این Pattern پایهای بسیار مستحکم برای سناریوهای SaaS و Enterprise است.
@DeveloperAdvocate 🥑1 035
💡 نگاهی عمیق به Primary Constructorها در C# (Best Practices & Pitfalls)
در #CSharp12 و پس از آن، Primary Constructorها به عنوان یک قابلیت جدید برای کلاسها (علاوهبر recordها) معرفی شدند و الگوی طراحی Domain-driven را تا حد زیادی سادهتر میکنند. اما استفادهی نادرست میتواند کد شما را شکننده و نامفهوم کند.
🔹 Best Practices
- Dependency Injection را شفاف پیادهسازی کنید
Primary Constructorهای کلاسهای Service یا Aggregates، بهترین جا برای تزریق وابستگیها هستند. خوانایی و ادغام با ابزارهای DI (مثلاً در ASP.NET Core) عالیست.
public class OrderService(ILogger<OrderService> logger, IUnitOfWork unitOfWork)
{
public void PlaceOrder(Order order)
{
logger.LogInformation("Placing order...");
unitOfWork.Save(order);
}
}
- وابستگیها را فقط Read-Only نگه دارید
پارامترهای Primary Constructor عملاً بعنوان میدان (field) فقط خواندنی (readonly) عمل میکنند. اجازه ندهید کد، آنها را از طریق Property set کند.
public class Product(string name, decimal price)
{
public string Name => name;
public decimal Price => price;
}
- با میراث (Inheritance) و مبهمسازی (Ambiguity) مراقب باشید
اگر Base Class هم از Primary Constructor استفاده کند، پارامترها باید پاس داده شوند؛ مراقب Signature تصادفی باشید.
public class Entity(Guid id)
{
public Guid Id => id;
}
public class User(Guid id, string username) : Entity(id)
{
public string Username => username;
}
🔸 Potential Pitfalls
- میدان (Field) های نامرئی و Binding مشکلساز
پارامترها به صورت مستقیم Field کلاس نیستند و Reflection/Binder ابزارها (مثل بعضی ORMها) هنوز با آنها مشکل دارند. برای Entityهای EF Core، Primary Constructor توصیه نمیشود.
- همنشینی با سایر Constructorها
اگر به Constructorهای متعددی نیاز دارید (Overloading)، باید از Constructorهای عادی استفاده کنید یا منطق را دوبارهنویسی کنید؛ چون Primary Constructor فقط یک بار قابل تعریف است.
- کاهش تستپذیری و Mock
پارامترهای Private مانع از تزریق ماک در تستهای واحد میشوند، مگر اینکه Interfaceها را خارج از Primary Constructor مانور دهید.
- شکنندگی در تغییرات API
اضافه/حذف پارامتر از امضای Constructor، مصرفکنندهها را Fail میکند. برای کلاسهای داخلی (Internal Domain models) عالی، برای کلاسهای Public API با احتیاط!
✅ جمعبندی توصیهای:
در لایهی Domain و Service کاربرد حرفهای دارد؛ اما برای POCOها و تکنولوژیهایی که به Reflection و ذینفع بودن Constructorها متکیاند، با احتیاط استفاده کنید. سعی کنید مدلهای Public-facing را همچنان با Constructorهای سنتی و Properties بنویسید تا اعتبار و پایداری حفظ شود.
@DeveloperAdvocate 🥑1 035
✨ ویژگی کمتر شناختهشده PostgreSQL: جستجوی Full-Text بر روی JSONB
بسیاری از توسعهدهندگان با قدرت JSONB در PostgreSQL آشنا هستند، اما شاید کمتر کسی بداند که میتوانید جستجوی full-text را مستقیماً بر روی فیلدهای دلخواه در ستونهای JSONB انجام دهید—ابزاری قدرتمند برای دیتابیسهای مدرن با ساختارهای نیمهساختیافته.
فرض کنید یک جدول محصولات با اطلاعات منعطف دارید:
CREATE TABLE products (
id SERIAL PRIMARY KEY,
data JSONB
);
نمونهای از داده:
{
"title": "Logitech MX Master 3",
"description": "Wireless mouse for advanced users",
"category": "Mouse"
}
اگر بخواهید full-text index روی title و description بگذارید:
CREATE INDEX idx_products_search
ON products
USING GIN (
to_tsvector('english',
coalesce(data->>'title', '') || ' ' || coalesce(data->>'description', '')
)
);
حالا کوئری full-text search:
SELECT *
FROM products
WHERE to_tsvector('english',
coalesce(data->>'title', '') || ' ' || coalesce(data->>'description', '')
) @@ plainto_tsquery('english', 'wireless advanced');
🔷 نتیجه: full-text search روی مقادیر دلخواه در JSONB با کارایی GIN Index—بدون schema migration!
این روش برای مدلهای دادهی داینامیک، نظیر microserviceها یا ثبت رویدادها (event logs) ایدهآل است.
📚 اگر از Entity Framework Core استفاده میکنید:
context.Products
.Where(p =>
EF.Functions.ToTsVector("english",
(string)p.Data["title"] + " " + (string)p.Data["description"]
)
.Matches(EF.Functions.PlainToTsQuery("english", "wireless advanced"))
)
.ToList();
این قابلیت را به workflow تحلیلیتان بیفزایید تا مزیت رقابتی بگیرید!
@DeveloperAdvocate 🥑1 035
در معماری App Router جدید Next.js، ویژگی Server Components نوآوری جذابیست که مستقیم روی مدرنسازی رندر و بهینهسازی عملکرد تمرکز دارد. Server Components به شما اجازه میدهد منطق UI را در سرور اجرا کنید و فقط HTML مینیمال مورد نیاز را به کلاینت ارسال کنید؛ هیچ جاوااسکریپتی برای این بخشها به مرورگر فرستاده نمیشود، در نتیجه کارایی و امنیت تا حد قابل توجهی افزایش مییابد.
در عمل، شما میتوانید کامپوننتهایی را که دادهمحور، سنگین یا به منابع سرور وابستهاند (مانند ارتباط با دیتابیس یا APIهای خصوصی)، به صورت کاملاً سروری پیادهسازی کنید؛ بدون اینکه bundle کلاینت متورم شود یا دادههای حساس آپلود شود. شکوفایی ماجرا زمانیست که Server و Client Components را هوشمندانه ترکیب کنید تا تعادلی بین UX و کارایی ایجاد گردد.
نحوه استفاده:
// این یک Server Component است و فقط در سرور اجرا میشود.
export default async function LatestPosts() {
const posts = await getLatestPostsFromDatabase();
return (
<ul>
{posts.map(post => <li key={post.id}>{post.title}</li>)}
</ul>
);
}
// این یک Client Component است، مثلاً برای تعامل لحظهای.
'use client';
export function LikeButton({ postId }) {
// منطق state و event handling اینجا قرار میگیرد
// ...
}
✅ نکته معماری: Server Components موجب جدایی concerns و بهشدت کاهش coupling میان لایههای UI و Data میشوند. مثل ساختار بهینه View Models در ASP.NET Core Razor Pages، اما این بار در سطح جاوااسکریپت مدرن. بهرهگیری بهینه از این قابلیت، مستقیماً منجر به افزایش maintainability و مقیاسپذیری برنامههای وب میشود.
@DeveloperAdvocate 🥑1 035
👨💻 #TypeSafety #Fullstack
یکی از دغدغههای اصلی معماران نرمافزار، تضمین type-safety «از دیتابیس تا UI» است؛ چیزی که در اکوسیستم TypeScript با ابزارهایی مثل tRPC سادهتر شده. اما آیا میشود اشتراک تایپ بین backend مبتنی بر .NET و frontend مبتنی بر React+TS را نیز به همین کارآمدی رساند؟ بله!
⚡️ راهکار عملی: کدجنریشن تایپهای TypeScript مستقیماً از DTOهای #CSharp و مصرف آنها در کلاینت React.
فرض کنید یک API دارید:
public class ProductDto
{
public Guid Id { get; set; }
public required string Name { get; set; }
public decimal Price { get; set; }
}
[ApiController]
[Route("api/products")]
public class ProductController : ControllerBase
{
[HttpGet]
public ActionResult<IEnumerable<ProductDto>> Get() => ...;
}
با ابزار TypeSync یا NJsonSchema.CodeGeneration.TypeScript میتوانید این DTO را به TypeScript interface تبدیل کنید:
TypeSync --input MyProject.dll --output types/api.d.ts
// یا:
dotnet njsonschema codegenerate-ts --input MyProject.dll --output api.ts
خروجی چیزی مشابه زیر خواهد بود:
export interface ProductDto {
id: string;
name: string;
price: number;
}
در frontend با React + TypeScript:
import { ProductDto } from './types/api';
async function fetchProducts(): Promise<ProductDto[]> {
const res = await fetch('/api/products');
return res.json();
}
// مصرف در komponent:
const { data } = useQuery<ProductDto[]>('products', fetchProducts);
✨ مزایا:
- تغییر در مدل backend بلافاصله در frontend منعکس میشود (کاملاً type-safe).
- اطمینان از اینکه API contract هیچ وقت out-of-sync نمیشود.
- توسعه سریعتر و خطای بسیار کمتر.
👀 نکته عمیق: اگر دنبال تجربهای تا حد زیادی شبیه tRPC واقعی هستید (نظیر ساختار endpointها بر اساس متد بهجای REST)، پروژههای open source مثل AspNetCore.TypeScript.ApiGenerator یا حتی طراحی لایهی thin gRPC + auto-generation را بررسی کنید؛ اما همین مدل مبتنی بر کدجنریشن فعلی در بسیاری از پروژههای enterprise با موفقیت و در مقیاس بزرگ استفاده میشود.
⚪️ اگر تجربه یا طرحی نوینتر در این حوزه دارید، خوشحال میشوم بدانم!
@DeveloperAdvocate 🥑1 035
در .NET ۷ به بعد، توصیه مایکروسافت این است که به جای
[DllImport] از [LibraryImport] (در فضای نام System.Runtime.InteropServices) استفاده کنید. این اتریبیوت جدید مبتنی بر سورس جنریشن است و مزایای جدی مانند پرفورمنس بالا، قابل اطمینان بودن و کاهش نقش Reflection را به همراه میآورد.
نمونه معادل P/Invoke با نسل قدیم و جدید:
// روش سنتی
[DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
public static extern int MessageBoxW(IntPtr hWnd, string text, string caption, uint type);
// روش مدرن
[LibraryImport("user32.dll", SetLastError = true, StringMarshalling = StringMarshalling.Utf16)]
public static partial int MessageBoxW(IntPtr hWnd, string text, string caption, uint type);
نکات کلیدی:
- متد حتماً باید partial باشد. پیادهسازی آن توسط generator تولید میشود.
- StringMarshalling = StringMarshalling.Utf16 معادل CharSet.Unicode است اما به شکل تایپسیفتر و واضحتر.
- SetLastError همان نقش قبلی را دارد.
- برای این که سورس جنریشن کار کند، باید پروژه حداقل روی .NET ۷ باشد و Package زیر را اضافه کنید:
<PackageReference Include="System.Runtime.InteropServices" Version="7.0.0" />
- با این روش دیگر نیاز به Reflection و جستجوی داینامیک نیست و در نتیجه startup سریعتر و امنیت بیشتر داریم.
به طور خلاصه: اگر به interop با native code نیاز دارید، LibraryImport را جایگزین کامل و آینده-proof برای DllImport بدانید—هم برای performance و هم developer experience.
@DeveloperAdvocate 🥑1 035
Minimal APIs: معماری ساده، قدرت بینظیر 🚀
اگر هنوز با Minimal APIs در .NET کار نکردید، بخشی از آیندهی توسعهی بکاند را از دست دادهاید! با حذف Ceremonyهای اضافی، میتوانید APIهایی بسازید که هم سریعند، هم قابلنگهداری، و هم خوانا. مثال عمیق زیر را ببینید:
var builder = WebApplication.CreateBuilder(args);
// Dependency Injection با سبک جدید
builder.Services.AddDbContext<AppDbContext>(o => /* ... */);
builder.Services.AddScoped<IWeatherService, WeatherService>();
var app = builder.Build();
// OpenAPI integration (در ۳ خط!)
app.MapGet("/weather/{city}", async (string city, IWeatherService svc) =>
{
var report = await svc.GetWeatherAsync(city);
return report is null ? Results.NotFound() : Results.Ok(report);
})
.WithName("GetWeather") // قابلیتهای پیشرفته مثل tagging و versioning!
.Produces<WeatherReport>(StatusCodes.Status200OK)
.Produces(StatusCodes.Status404NotFound);
app.Run();
ویژگیها و فلسفه:
- Endpointsها تابع هستند—کاملاً تایپ-سیف! هیچ Controller یا Attribute اضافی ندارید.
- DI به سادگی داخل Delegate تزریق میشود. حتی DbContext.
- تستنویسی ساده با minimal ceremony.
- افزایش performance نسبت به WebAPI کلاسیک.
- قابلیت ترکیب با Middlewareها و قابلیتهای استاندارد ASP.NET.
معماری پیشنهادی برای production:
Routeهای کوچک و متمرکز (Single Responsibility). قابلیت انطباق آسان با DDD یا Clean Architecture با Delegation Serviceهای Rich.
📌 پیشنهاد: از Minimal APIs برای سرویسهای BFF، microserviceهای سبک، و حتی MVP پروتوتایپها بهره ببرید. اما همیشه جداکنید Business Logic را از Endpointها—کد تمیز همیشه برنده است!
@DeveloperAdvocate 🥑1 035
👮♂️ سیاست امنیتی محتوا (CSP) در ASP.NET Core
در دنیای وب مدرن، اجرای CSP یکی از مؤثرترین راهکارها برای مقابله با XSS، تزریق اسکریپت و جلوگیری از بارگذاری منابع ناخواسته است. در ASP.NET Core افزودن این هدر به اپلیکیشن، بدون وابستگی به کتابخانههای جانبی، بسیار ساده است.
در سادهترین حالت، CSP را با یک middleware سفارشی در
Startup.cs اضافه کنید:
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();
});
این خطمشی:
- فقط منابع از دامنه خود را مجاز میکند (default-src 'self')
- فقط اسکریپتها و استایلهای داخلی را اجازه میدهد
- استفاده از object (مانند Flash یا Java applet) را کاملاً غیرفعال میکند
🔎 نکته: برای سناریوهای پیشرفته، whitelist کردن دامینها یا اجازه برای عملکرد خاص (مثل تنظیم nonce یا hash برای inline scripts)، فقط کافی است مقدار هدر را سفارشی و داینامیک بسازید.
مثال با تولید nonce برای هر پاسخ HTTP:
app.Use(async (context, next) =>
{
var nonce = Convert.ToBase64String(Guid.NewGuid().ToByteArray());
context.Items["CspNonce"] = nonce;
context.Response.Headers.Add("Content-Security-Policy",
$"default-src 'self'; script-src 'self' 'nonce-{nonce}';");
await next();
});
حال در Razor View خود:
<script nonce="@Context.Items["CspNonce"]">
// اسکریپت امن اینجا قرار میگیرد
</script>
🔐 اجرای CSP فقط یک هدر نیست—بلکه تفکری معمارانه برای دفاع لایهای است. آنرا جدی بگیرید!
@DeveloperAdvocate 🥑1 035
🎯 Credential Stuffing چیست و چطور با آن مقابله کنیم؟
Credential Stuffing یکی از حملات متداول مبتنی بر اتوماسیون است که در آن مهاجم، با استفاده از مجموعهای از نام کاربری/کلمه عبورهای لو رفته (مثلاً از دیتابیسهای افشا شده)، تلاش میکند به صورت انبوه وارد یک سرویس شود. موفقیت این حملات روی کاربران عموماً به علت استفاده مجدد رمزهای عبور در چند سرویس مختلف است.
👁️🗨️ سناریو: فرض کنید endpoint لاگین سرویس شما بدون هیچ rate limiting و کنترل اضافهای، صرفاً ورودی کاربر را دریافت و احراز هویت میکند. این سطح از سادگی شانس موفقیت مهاجم را بهشدت افزایش میدهد.
برای بهبود امنیت endpoint لاگین، چند لایه اصلی دفاع پیشنهاد میشود:
1. Rate Limiting/Throttling: محدودسازی تعداد تلاش ورود از یک آدرس یا IP مشخص در بازه زمانی. مثلا بیش از ۵ تلاش طی ۵ دقیقه مجاز نباشد.
2. CAPTCHA: پس از چند تلاش ناموفق، کاربر برای ادامه باید CAPTCHA حل کند.
3. Passwordless/MFA: فعالسازی احراز هویت چندعاملی یا روشهای بدون رمز عبور.
4. Motivated Response: ارتباط دادن رفتار مشکوک به بلاک کوتاهمدت/دائمی IP، یا ارسال هشدار به مالک حساب.
5. Monitoring & Logging: بررسی لاگها برای شناسایی رفتارهای مشکوک و هماهنگ با SIEM.
نمونه ساده پیادهسازی Limiting برای endpoint لاگین در ASP.NET Core:
public class SimpleRateLimiter
{
private readonly MemoryCache _cache = new MemoryCache(new MemoryCacheOptions());
private readonly int _maxAttempts = 5;
private readonly TimeSpan _timeWindow = TimeSpan.FromMinutes(5);
public bool IsAllowed(string key)
{
var value = _cache.GetOrCreate(key, entry =>
{
entry.AbsoluteExpirationRelativeToNow = _timeWindow;
return 0;
});
int current = (int)value;
if (current >= _maxAttempts)
return false;
_cache.Set(key, current + 1, TimeSpan.FromMinutes(5));
return true;
}
}
در middleware لاگین:
var limiter = new SimpleRateLimiter();
if (!limiter.IsAllowed(Request.HttpContext.Connection.RemoteIpAddress.ToString()))
return Unauthorized("Too many login attempts, please wait.");
راهبردهای جدیتر مانند استفاده از distributed cache (مثل Redis)، سرویسهای مدیریت bot detection، و بررسی credential stuffing با ابزارهایی چون Azure AD Identity Protection توصیه میشود. همچنین کاربران را به استفاده از رمز عبور منحصر به فرد و فعالسازی MFA تشویق کنید.
#Security #ASPNetCore #CredentialStuffing
@DeveloperAdvocate 🥑1 035
🟢 تکنیکهای پیشرفته پیرامون Primary Constructors در C# 12: بهترین رویکردها و دامها
استفاده از Primary Constructor در کلاسهای C# (از نسخه ۱۲ به بعد) کد شما را تمیزتر و خواناتر میکند، اما اگر بدون دقت استفاده شود میتواند به code smell بینجامد.
⚙️ بهترین رویکردها:
- برای Immutable Objects عالی است: زمانهایی که فقط مقداردهی فیلدها یا propertyها را لازم دارید، Primary Constructor همراه با
init یا readonly فوقالعاده تمیز و ساده است.
public class Customer(string name, string email)
{
public string Name { get; } = name;
public string Email { get; } = email;
}
- Dependency Injection را زیبا میکند: وقتی سرویسها را بدون نیاز به منطقی پیچیده inject میکنید، Constructor succinctتر میشود.
public class OrderService(ILogger logger, IOrderRepository repository) { /* ... */ }
- معماری Clean و DDD: میتوانید Aggregate Rootها و Value Objectها را با minimum ceremony بسازید و وابستگی را شفاف منتقل کنید.
⛔️ دامهای رایج:
- افزایش Coupling: مراقب باشید که پارامترهای Primary Constructor فقط در همین کلاس زندگی میکنند. اگر کلاسهای مشتق بخواهند به این مقادیر دسترسی داشته باشند باید تکرار کنند یا راههای workaround بزنند (مثل ایجاد Protected Property).
public class BaseProcessor(string contextPath) { /* ... */ }
public class FileProcessor(string contextPath, string filePath) : BaseProcessor(contextPath) { /* ... */ }
اینجا contextPath باید هم به Base و هم به Derived داده شود (Duplication محتمل است).
- کاهش Refactorability: اگر پارامترها زیاد شوند یا تغییر کنند، امضای کلاس را عوض میکنید و همه مصرفکنندهها تحت تاثیر قرار میگیرند. برای کلاسهای با عمر زیاد یا قرارداد پایدار، مراقب باشید.
- سختتر شدن Serialization/Deserialization: لزوماً همه serializerها، به ویژه در legacy یا ابزارهای خاص، هنوز با Primary Constructor هماهنگ نشدهاند.
- محدودیت Partial: نمیتوانید سایر بخشهای Partial کلاس را به این پارامترها مستقیماً دسترسی دهید.
🎯 Tip معماری: توصیه میشود این قابلیت را برای record typeها یا کلاسهای کوچک stateless استفاده کنید؛ در کاربردهای پیچیده، ترجیح دهید از traditional constructor بهره ببرید تا انعطاف بیشتر در دستکاری dependency injection و منطق اعتبارسنجی داشته باشید.
نتیجه: Primary Constructor قدرت زیادی دارد، ولی استفاده افراطی میتواند maintenance codebase را سخت کند. نگاه بلندمدت داشته باشید و هر کاربرد را بر اساس context انتخاب کنید.
@DeveloperAdvocate 🥑1 035
🚀 ساخت یک GitHub Action سفارشی با Node.js برای مدیریت ورژن در پروژهها
در پروژههای CI/CD، گاهی نیاز دارید اطمینان حاصل کنید که فایل AssemblyInfo یا appsettings شما پس از هر build یا release، بهطور خودکار بر اساس ورژن جاری شاخه یا تگ، بهروزرسانی شود. بیایید یک GitHub Action جاوااسکریپتی بنویسیم که نسخه را با تگ جاری در فایل دلخواه همگامسازی میکند (مثلاً برای AssemblyInfo.cs).
ساختار پوشه:
.github/
actions/
sync-version/
action.yml
index.js
package.json
نمونه محتوا:
action.yml:
name: Sync Version to AssemblyInfo
description: Update version in AssemblyInfo.cs from git tag
runs:
using: 'node16'
main: 'index.js'
inputs:
file-path:
description: 'Path to AssemblyInfo.cs'
required: true
version-regex:
description: 'Regex pattern for AssemblyVersion'
required: false
default: '(?<=\[assembly: AssemblyVersion\(")(.+?)(?="\)\])'
index.js:
const core = require('@actions/core');
const fs = require('fs');
try {
const filePath = core.getInput('file-path', { required: true });
const versionRegex = new RegExp(core.getInput('version-regex'), 'g');
const gitRef = process.env.GITHUB_REF || '';
// استخراج نسخه از تگ: refs/tags/v1.2.3 -> 1.2.3
const versionMatch = gitRef.match(/refs\/tags\/v?(\d+\.\d+\.\d+)/);
if (!versionMatch) {
core.setFailed('No version tag found in GITHUB_REF.');
return;
}
const version = versionMatch[1];
let text = fs.readFileSync(filePath, 'utf8');
text = text.replace(versionRegex, version);
fs.writeFileSync(filePath, text, 'utf8');
core.info(`Version in ${filePath} synced to ${version}`);
} catch (error) {
core.setFailed(error.message);
}
استفاده در workflow:
- name: Sync version in AssemblyInfo
uses: ./.github/actions/sync-version
with:
file-path: 'src/MyProject/Properties/AssemblyInfo.cs'
این اقدام نهتنها از خطاهای انسانی جلوگیری میکند، بلکه انتشار ورژنهای قابل اتکاتر و repeatable را تضمین میکند. الهامبخش یک DevOps واقعی باشید و اکشنهای خود را بجای پلاگینهای عمومی، دقیقاً مطابق نیازمندیهای معماریتان بسازید!
@DeveloperAdvocate 🥑1 035
یکی از جنبههای مغفول مانده در معماری نرمافزارهای مبتنی بر .NET، مدیریت صحیح Flowهای ناهمگام (Asynchronous Flows) در لایههای مختلف است. اغلب دیده میشود که async/await صرفاً به لایههای پایینی تعلق میگیرد (مثلاً Data Access)، اما ارزش واقعی زمانی آشکار میشود که contractهای لایههای دامین و اپلیکیشن هم کاملاً async طراحی شوند. بدین ترتیب، Application Serviceها، Command Handlerها و حتی Domain Serviceها به جای مسدودسازی (blocking)، جریانهای داده را به شکلی کاملاً asynchronous مدیریت میکنند:
public interface IOrderService
{
Task<OrderResult> PlaceOrderAsync(OrderCommand command, CancellationToken token);
}
public class PlaceOrderCommandHandler : IRequestHandler<PlaceOrderCommand, OrderResult>
{
private readonly IOrderService _orderService;
public PlaceOrderCommandHandler(IOrderService orderService)
{
_orderService = orderService;
}
public async Task<OrderResult> Handle(PlaceOrderCommand command, CancellationToken cancellationToken)
{
return await _orderService.PlaceOrderAsync(command, cancellationToken);
}
}
در این رویکرد، حتی اگر فعلاً بعضی عملیاتها کاملاً همگام باشند، Contractها از ابتدا آماده رشد مقیاسپذیر هستند. به عقیده شما، آیا پیادهسازی end-to-end async در دامنه و application layer همیشه مطلوب است، یا میتواند باعث پیچیدگی غیرضروری و کاهش خوانایی کد شود؟ تجربه خود را به اشتراک بگذارید.
@DeveloperAdvocate 🥑1 035
یک اشتباه رایج در استفاده از LINQ که پرفورمنس را بهطور جدی تحت تاثیر قرار میدهد، ترکیب فیلتر کردن و پروجکشن (Select) بهصورت نابهینه است.
فرض کنید لیستی از کاربران داریم و میخواهیم نام کاربران فعال را بگیریم:
var activeUserNames = users
.Where(u => u.IsActive)
.Select(u => u.Name)
.ToList();
این صحیح است، اما اشتباه رایج بهشکل زیر اتفاق میافتد:
var userNames = users
.Select(u => u.Name)
.Where(name => users.First(x => x.Name == name).IsActive)
.ToList();
در این اشتباه، ابتدا تمام user.Nameها انتخاب میشود و سپس برای هر Name باید دوباره کل مجموعه کاربران را جستجو کنیم. این به معنای اجرای N بار First و درنتیجه O(N^2)!
راه بهینه همیشه این است که فیلترینگ را قبل از پروجکشن انجام بدهیم تا دادههای غیرضروری هرگز وارد مرحله بعدی نشوند:
var activeUserNames = users
.Where(u => u.IsActive)
.Select(u => u.Name)
.ToList();
همیشه ترتیب توابع LINQ اهمیت دارد. هر بار که دیدید Select جلوتر از Where آمده، یک بار دیگر فکر کنید—ممکن است در حال انجام پردازشهای تکراری باشید!
✔️ قانون طلایی:
تا میتوانید داده کمتر را به مراحل بعدی بفرستید. همیشه Where را زودتر از Select اعمال کنید، مگر اینکه دلیل خاصی داشته باشید.
@DeveloperAdvocate 🥑1 035
وقتی صحبت از افزایش سرعت توسعه و همسانسازی محیط توسعه بین اعضای تیمهای full-stack (مثلاً تیمی با .NET Backend و React Frontend) میشود، DevContainers یک راهکار بینظیر است. با استفاده از فایل
devcontainer.json، میتوان یک کانتینر توسعه استاندارد ساخت که هم .NET SDK را دارد و هم Node.js را برای React؛ به این ترتیب نیازی به نصب ابزارها روی سیستم توسعهدهندگان نیست و مشکلاتی مثل اختلاف نسخهها از بین میرود. در ادامه، یک پیکربندی عملی و پیشرفته برای چنین پروژهای را میبینید:
{
"name": "Full-Stack .NET & React DevContainer",
"image": "mcr.microsoft.com/devcontainers/dotnet:8.0", // داکر ایمیج رسمی .NET 8
"features": {
"ghcr.io/devcontainers/features/node:1": {
"version": "18"
}
},
"postCreateCommand": "dotnet tool restore && npm install --prefix ClientApp",
"customizations": {
"vscode": {
"extensions": [
"ms-dotnettools.csharp",
"dbaeumer.vscode-eslint",
"esbenp.prettier-vscode"
]
}
},
"ports": [
5000, // پورت پیشفرض ASP.NET
3000 // پورت پیشفرض React - قابل تنظیم با توجه به پروژه
],
"forwardPorts": [5000, 3000],
"remoteUser": "vscode"
}
🔹 نکتههای پیشرفته:
- بوسیله بخش features، Node.js به محیط داتنت افزوده شده و ماژولهای npm قابل اجرا هستند، بدون نیاز به دو ایمیج یا کانتینر جدا.
- پس از ساخت کانتینر، دستورات dotnet tool restore و npm install به ترتیب ابزارهای global داتنت و پکیجهای React (در مسیر ClientApp) را نصب میکنند.
- با اضافه کردن افزونههای کلیدی VS Code، تجربه توسعه یکپارچهسازی میشود.
- پورتها بر اساس معماری پروژه قابل تغییر هستند، اما پیشنهاد میشود اتصال بکاند به ۵۰۰۰ و فرنـتاند به ۳۰۰۰ باشد تا هاتریلـود هر دو سمت به نرمی کار کند.
با استانداردسازی DevContainer، bootstrap محیط توسعه برای هر عضو تیم فقط با یک کلیک ممکن است—و این یعنی خداحافظی با جملهی «برای من روی ماشینم کار میکنه»! 🚀
@DeveloperAdvocate 🥑1 035
🔹 قالب گفتگو با مدیر محصول درباره تأثیر فنی و بیزینسی دیون فنی
یکی از مهارتهای کلیدی معماران نرمافزار، توضیح شفاف تأثیر دیون فنی به زبان بیزینس است. این اسکریپت، برای جلسات با مدیر محصول قابل استفاده است:
---
«الان بخشهایی از کد ما، مشابه و تکراری هستند و اگر نیاز به تغییر داشته باشیم (مثلاً تغییر استراتژی قیمتگذاری)، باید این تغییر در چندین نقطه انجام شود. این به معنای افزایش قابل توجه زمان تحویل و همچنین افزایش ریسک باگ است.
اگر اجازه بدهید بخشی از زمان تیم را صرف بازپرداخت این دیون فنی کنیم (مثلاً بازآرایش این منطق و استخراج آن به یک سرویس مستقل)،:
۱. افزایش سرعت ارائه ویژگیهای جدید: هر ویژگی جدید یا تغییر آینده، سریعتر و کمهزینهتر پیادهسازی خواهد شد.
۲. کاهش نرخ رگرسیون: تیم تضمین کیفیت میتواند اعتماد بیشتری به تغییرات سریعتر داشته باشد چون نقاط آسیبپذیری کمتری وجود خواهد داشت.
۳. پایداری بیشتر در تولید: دیون فنی بالا = احتمال خطای بیشتر = وضعیت «آتشسوزی» و نگرانیهای مکرر در محیط لایو.
اگر امروز این کارها را به تعویق بیندازیم، هزینه هر تغییر جدید، افزایشی خطی یا حتی نمایی پیدا میکند. نمونه عملی این موضوع در بخش فلان بود که تغییر ساده نمایش تاریخ باعث شد چندین فیچر دیگر سهواً خراب شوند. مثال کد فعلی:
// قیمتگذاری سفارش، به تکرار در چند سرویس مختلف
if (order.Type == OrderType.Express) {
price += 20000;
// کدهای مشابه در چندین کلاس
}
راهحل پیشنهادی:
public interface IPricingStrategy {
int Calculate(Order order);
}
public class ExpressPricingStrategy : IPricingStrategy {
public int Calculate(Order order) => order.BasePrice + 20000;
}
// تزریق اینترفیس به جای تکرار منطق
🔸 با سرمایهگذاری روی بازپرداخت دیون فنی، به تیم اجازه میدهیم واقعاً «لبه رقابتی» بیزینس را سریعتر و دقیقتر پیادهسازی کند تا صرفاً «تعمیرکار همیشگی» محصول بماند. اگر خواستید، میتوانم ROI تقریبی بازپرداخت این بخش را براساس فیچرهای آینده تخمین بزنم.»
@DeveloperAdvocate 🥑1 035
فرق Monitoring و Observability را چگونه باید در معماری نرمافزار جدی گرفت؟
🔍 Monitoring (پایش) معادل سیستم عصبی واکنشی است: شما مجموعهای محدود از متریکها (مثل CPU, memory یا latency) را با ابزارهایی مانند Prometheus جمعآوری میکنید تا شرایط سرویس را رصد کنید و هشدار دریافت کنید. فرض اصلی پایش این است که میدانید به دنبال چه چیزی هستید و معمولاً با Grafana داشبوردهای سفارشی برای نمایش این دادهها میسازید.
🦉 Observability (قابلیت مشاهده) اما یک سطح بالاتر است—توانایی سیستم برای نمایش رفتار کلی خود از مسیر دادههای telemetry (یعنی ترکیب متریکها، لاگها و تریسها). اینجا فقط به هشدار یا آلارم بسنده نمیکنید، بلکه میتوانید پاسخ سوالات ناشناخته و اتفاقات پیشبینینشده را نیز استخراج کنید. Observability یعنی اینکه وقتی یک رفتار غیرعادی رخ میدهد، با دادههای کافی و مرتبط، بتوان عمق اشکال را کشف کرد، نه اینکه فقط از سطح مشکل آگاه شوید.
🎯 مثال عینی:
فرض کنید یک API تحت بار بالا کند شده. Monitoring با Prometheus و Grafana صرفاً افزایش latency را اطلاع میدهد. اما اگر structured logging (مثلاً Serilog) و distributed tracing (مانند OpenTelemetry با Jaeger) را اضافه کنید، میتوانید بفهمید کندی دقیقاً ناشی از کدام dependency یا کدام endpoint است.
در .NET، پیادهسازی استاندارد observability با ترکیب متریکهای OpenTelemetry، لاگهای ساختارمند Serilog، و ارسال داده به سیستمهایی مانند Grafana و Jaeger انجام میشود:
builder.Services.AddOpenTelemetry()
.WithMetrics(metrics =>
{
metrics.AddAspNetCoreInstrumentation();
metrics.AddPrometheusExporter(); // export metrcis for Prometheus
})
.WithTracing(tracing =>
{
tracing.AddAspNetCoreInstrumentation();
tracing.AddJaegerExporter(); // distributed tracing
});
💡 توصیه معماران: Monitoring خوب برای واکنش سریع حیاتی است، اما observability قوی، ریشهیابی (Root Cause Analysis) و پیشبینی رفتار سرویس را ممکن میسازد. به سمت معماری observability-first حرکت کنید تا سرویسهایتان واقعاً قابل اطمینان و قابل پشتیبانی باقی بمانند.
@DeveloperAdvocate 🥑1 035
الگوی Outbox در معماری میکروسرویسها: چطور قابلیت اطمینان رخدادها را تضمین کنیم؟
در بسیاری از سیستمهای مبتنی بر رخداد (Event-Driven Microservices)، نیاز داریم تضمین کنیم که دادهها هم در دیتابیس محلی ثبت شوند و هم پیام رویداد مرتبط با آن، دقیقا یکبار به باس پیام (مثلا Kafka یا RabbitMQ) ارسال شود. اما واقعیت این است که تراکنش توزیعشده بین دیتابیس و Message Broker اغلب پرهزینه یا حتی غیرممکن است.
راهحل کلاسیک: Outbox Pattern 🔥
در این الگو، عملیات نوشتن دیتا و رخداد مرتبط، باهم و در قالب یک تراکنش ACID فقط در دیتابیس سرویس (مثلا SQL Server) انجام میشوند. سپس یک پروسه جداگانه (Event Publisher) از جدول Outbox رخدادها را خوانده و به باس پیام ارسال میکند. با این کار، همیشه تضمین میکنیم که داده و رویداد باهم در Atomicity کامل ایجاد شوند؛ حتی اگر سرویس یا شبکه قطع شود.
یک پیادهسازی ساده و تمیز این الگو با EF Core:
public class IntegrationEvent
{
public Guid Id { get; set; }
public string Type { get; set; }
public string Payload { get; set; }
public DateTime OccurredAt { get; set; }
}
public class AppDbContext : DbContext
{
public DbSet<IntegrationEvent> OutboxEvents { get; set; }
// ... سایر موجودیتها
}
// سرویس دامین:
public async Task PlaceOrderAsync(Order order)
{
using var trx = await db.Database.BeginTransactionAsync();
db.Orders.Add(order);
db.OutboxEvents.Add(new IntegrationEvent {
Id = Guid.NewGuid(),
Type = "OrderPlaced",
Payload = Serialize(order),
OccurredAt = DateTime.UtcNow
});
await db.SaveChangesAsync();
await trx.CommitAsync();
}
یک Worker جداگانه رخدادهای Outbox را بر اساس Id و وضعیت ارسال میکند و بعد از اطمینان از تحویل، آنها را حذف یا Update میکند.
مزیت بارز: رخدادهای شما هیچوقت گم نمیشوند یا دوبار ارسال نمیشوند🔥. این الگو به ویژه برای ترکیب با Event Sourcing/ CQRS بسیار قدرتمند است و در معماریهای جدی میکروسرویس باید جدیاش گرفت!
#Microservices #Outbox #EventDriven #DotNetArchitect
@DeveloperAdvocate 🥑1 035
🌐 تفاوت Concurrency و Parallelism در #NET
اغلب مفاهیم Concurrency (همزمانی) و Parallelism (پردازش موازی) به اشتباه به جای هم به کار میروند، اما این دو تفاوت ماهیتی دارند.
- Concurrency: چندین کار به طور بالقوه همزمان در حال پیشرفتاند؛ اما لزوماً در یک زمان اجرا نمیشوند — میتواند روی یک هسته باشد و context switch صورت گیرد.
- Parallelism: چندین کار واقعاً به طور همزمان روی چندین هسته انجام میشوند.
👨💻 مثال عملی — تفکیک معنایی با کد:
using System;
using System.Threading;
using System.Threading.Tasks;
class Program
{
static void Main()
{
Console.WriteLine("Concurrency (همزمانی):");
ConcurrencyExample();
Console.WriteLine("\nParallelism (موازی):");
ParallelismExample();
}
static void ConcurrencyExample()
{
for (int i = 1; i <= 3; i++)
{
// هر Task بلافاصله استارت میزند اما Scheduler میتواند آنها را روی یک Thread اجرا کند
Task.Run(() => DoWork($"Task {i} (concurrent)"));
}
Thread.Sleep(1500); // منتظر ماندن برای تکمیل نمایش
}
static void ParallelismExample()
{
// Parallel.For واقعی روی چند هسته اجرا میشود (در صورت وجود منابع)
Parallel.For(1, 4, i =>
{
DoWork($"Parallel Task {i}");
});
}
static void DoWork(string name)
{
Console.WriteLine($"{name} شروع شد در ThreadId: {Thread.CurrentThread.ManagedThreadId}");
Thread.Sleep(500); // شبیهسازی پردازش
Console.WriteLine($"{name} پایان یافت در ThreadId: {Thread.CurrentThread.ManagedThreadId}");
}
}
در اجرای نمونه بالا:
- در بخش Concurrency هر Task میتواند روی همان Thread اجرا شود (وابسته به ThreadPool) — کارها پشت سر هم اما با هم در گردش هستند.
- در بخش Parallelism، Parallel.For کارها را بین چندین Thread واقعی توزیع میکند تا بطور حقیقی همزمان کار کنند.
📌 همیشه هر concurrent code موازی نیست، اما هر parallel code لزوماً concurrent هم هست. تفاوت را در طراحی سیستمهای چندنخی جدی بگیرید!
@DeveloperAdvocate 🥑1 035
🎯 استفاده پیشرفته از Delegates و Events در مدرنترین سبک C#
در بسیاری از پروژههای enterprise، مدیریت رخدادهای async و decoupled اغلب حیاتی است. ترکیب Delegates و Events با الگوهای مدرن میتواند کد را هم تستپذیرتر و هم توسعهپذیرتر کند. در این مثال واقعی، با استفاده از EventHandler<TEventArgs> و lamda expressions رویکردی تمیز، type-safe و حتی تستپذیر ساختهایم:
فرض کنید یک سرویس notification داریم که رخداد ارسال پیام را توسط event به دیگر بخشها اطلاع میدهد، و subscriberها میتوانند با استفاده از lambdaها هندلر خود را inject کنند.
public class MessageSentEventArgs : EventArgs
{
public string Recipient { get; }
public string Body { get; }
public MessageSentEventArgs(string recipient, string body)
{
Recipient = recipient;
Body = body;
}
}
public class NotificationService
{
public event EventHandler<MessageSentEventArgs>? MessageSent;
public void Send(string recipient, string body)
{
// ارسال پیام فرضی
// ...
OnMessageSent(new MessageSentEventArgs(recipient, body));
}
protected virtual void OnMessageSent(MessageSentEventArgs e)
{
MessageSent?.Invoke(this, e);
}
}
حال میتوانیم با syntax مدرن C#، به سادگی، subscriberها را ثبت و تست کنیم:
var notifications = new NotificationService();
// ثبت dynamic handler با lambda inline
notifications.MessageSent += (sender, e) =>
{
Console.WriteLine($"🔔 پیام به {e.Recipient}: {e.Body}");
};
// اجرای رخداد
notifications.Send("ali@domain.com", "سلام علی!");
/*
🔔 پیام به ali@domain.com: سلام علی!
*/
نکات پیشرفته:
- Nullability را رعایت کنید (EventHandler<T>?) تا با async context و تستها دچار مشکل نشوید.
- لایه تزریق Dependency برای تغذیه event handlers فراهم کنید، برای example با استفاده از DI Container:
services.AddSingleton<NotificationService>();
notifications.MessageSent += scopedHandler.HandleAsync;
- مراقب memory leak باشید: اگر طول عمر subscriberها کوتاهتر از publisher است، در زمان مناسب unsubscribe کنید.
این الگو نه فقط برای GUI بلکه برای سرویسهای Distributed، کار با Domain Events و microservices نیز ایدهآل است.
@DeveloperAdvocate 🥑1 035
❄️ مقابله با Cold Start در Serverless (.NET) – رویکرد حرفهای
یکی از چالشهای حساس در سرویسهای Serverless مثل AWS Lambda و Azure Functions، مشکل Cold Start است، بهویژه وقتی اپلیکیشن شما با .NET توسعه یافته است. Cold Start یعنی وقتی instance فانکشن برای اولین بار یا پس از idle شدن پاسخ میدهد، باید محیط عملیاتی را بالا بیاورد؛ این یعنی بارگذاری کامل CLR، جِت کردن کد، Dependency Injection و... که گاهی میتواند تا چند ثانیه طول بکشد و تجربهی کاربری را تحت فشار بگذارد.
نکات پیشرفته مدیریت Cold Start برای .NET:
🔸 Minimal hosting و runtime
حتماً از الگوی minimal API و وارد کردن ریفرنسها به شکل targeted استفاده کنید. بیجهت کل nugetهای سنگین را فقط برای یک فیچر وارد نکنید.
🔸 Configuration خارج از زمان اجرا
تنظیمات سنگین (مثلاً بارگذاری پیوندهای دیتابیس، مقداردهی اولیه HttpClientها و ...) را در استاتیک کانستراکتور یا Singletonها و خارج از متد اصلی اجرا کنید. مثال:
private static readonly HttpClient _httpClient = new HttpClient
{
Timeout = TimeSpan.FromSeconds(5)
};
🔸 جِت Ahead-of-Time (AOT)
در AWS Lambda میتوانید از Amazon.Lambda.RuntimeSupport و Native AOT (dotnet publish -r linux-x64 -c Release /p:PublishAot=true) استفاده کنید که بسیاری از delayهای زمان startup را حذف میکند.
dotnet publish -r linux-x64 -c Release /p:PublishAot=true
در Azure Functions، با .NET 8 و مدل isolated process میتوانید از AOT بهره ببرید و زمان Cold Start را بهشدت کاهش دهید.
🔸 حواستان به Dependency Injection باشد
استفاده سنگین از DI Containerهایی مثل Autofac یا حتی ServiceCollection میتواند startup time را افزایش دهد. اگر فقط یک یا دو سرویس inject میکنید، کد wiring را دستی بنویسید تا از reflection و اسکن اتوماتیک جلوگیری کنید.
🔸 فانکشنهای سبک و سریع
یک فانکشن = یک کار کوچک. منابع بزرگ (EF Core DbContext با کانفیگ سنگین؟) را بیرون بکشید و از استفاده بیش از اندازه از لایههای abstraction بپرهیزید.
🔸 Provisioned Concurrency (فقط AWS)
اگر نیاز به پاسخگو بودن همیشگی دارید، با فعال کردن این feature حداقل n instance گرم از لامبدا همیشه نگه داشته میشود – ولی هزینهی شما بالاتر میرود.
🔸 Always On در Azure
در پلن Premium یا Dedicated، با فعالسازی Always On فانکشن شما هیچگاه cold نمیشود؛ اما توجه کنید که این راهحل هم هزینهبر است و spirit سرورلس را نقض میکند.
در مجموع، Cold Start صرفاً یک موضوع زیرساختی نیست؛ Design و معماری اپلیکیشن شما نقش بنیادین دارد. فانکشن را slim، stateless و upstream-friendly نگه دارید تا حتی در بدترین حالت، Cold Start غیرمحسوس شود.
@DeveloperAdvocate 🥑