💡اگر منتظر شنبهای، یعنی هنوز جدی نیستی.
شنبه یه bug توی سیستم ذهنته، fixش کن و run بزن.
شنبه یه bug توی سیستم ذهنته، fixش کن و run بزن.
سوال مصاحبهای که نحوه تفکر من درباره طراحی سیستم را تغییر داد 📝💡
<Milan Jovanović>
حدود شش یا هفت سال پیش، در یک مصاحبه برای یک موقعیت سطح میانی NET. شرکت کردم.
💡یکی از سوالها از آن زمان تا الان با من مانده است:
یک کاربر روی یک دکمه در UI کلیک میکند تا یک گزارش Excel یا PDF تولید شود. تولید گزارش حدود پنج دقیقه طول میکشد (زمان میتواند دلخواه باشد). کاربر باید منتظر بماند تا فرایند کامل شود. شما چگونه این جریان را بهینه میکنید؟
در آن زمان، تمرکز من روی چیزی بود که بیشترین تسلط را داشتم: performance ⏱️. شروع به فکر کردن درباره بهینهسازی تولید گزارش کردم. شاید بتوانم SQL queries را بهینه کنم، تبدیل دادهها را کاهش دهم یا بخشهایی از نتیجه را cache کنم. اگر میتوانستم فرایند را از پنج دقیقه به یک دقیقه برسانم، احساس موفقیت بزرگی میکردم.
اما حتی اگر پنج برابر سریعتر هم میشد، کاربر همچنان مجبور بود منتظر بماند. اگر browser کرش میکرد، همه چیز از دست میرفت. اگر شبکه قطع میشد، فرایند متوقف میشد. اگر تب را میبستند، تمام پیشرفت از بین میرفت.
در واقع این یک مشکل performance نبود، بلکه یک مشکل design بود. 🛠
چیزی که آن زمان از دست دادم ⚠️
با نگاه به گذشته، متوجه میشوم که ذهنم درگیر این بود که "کد را سریعتر کنم". نه که اشکالی داشته باشد، بهینهسازی عملکرد یک مهارت ارزشمند است. آنچه که آن موقع به وضوح نمیدیدم، مشکل بزرگتر بود. برنامه همه این کارها را synchronously انجام میداد و کاربر را تا پایان فرایند در حالت انتظار نگه میداشت. نهایتاً با چند راهنمایی از مصاحبهکننده متوجه شدم.
سوال بهتر این نبود که "چگونه میتوانم سریعتر کنم؟"
سوال واقعی این بود که: "چرا کاربر در وهله اول منتظر است؟" 🤔
اگر چیزی دقایق (یا حتی ساعتها و روزها) طول میکشد، نباید کاربر را بلاک کند. این کار باید در background و خارج از جریان اصلی درخواست انجام شود، در حالی که کاربر به کارهای خود ادامه میدهد.
نکته مهم 🔑
باز هم فراموش نکنید که کد را بهینه کنید. Database queries، پردازش دادهها و تولید فایل همگی اهمیت دارند. شاید indexی جا افتاده باشد، حلقهای ناکارآمد باشد یا کتابخانه بهتری برای ایجاد Excel وجود داشته باشد. اما این بهینهسازیها فقط بخشی از راهحل هستند، نه کل آن.
چگونه امروز این مشکل را حل میکنم ⚡️🖥
امروز، همچنان با همان دکمه UI شروع میکنم. کاربر روی «Generate Report» کلیک میکند، اما به جای انتظار، backend درخواست را میپذیرد، آن را جایی ذخیره میکند (مثلاً به عنوان یک رکورد job در دیتابیس) و بلافاصله پاسخ میدهد. این جوهرهی ساخت asynchronous APIs است. سپس job توسط یک background worker پردازش میشود.
این worker میتواند یک hosted service، یک Quartz job، یا حتی یک AWS Lambda Function فعالشده توسط پیام صف (queue message) باشد. این وظیفه را انجام میدهد: دادهها را جمعآوری میکند، فایل را میسازد و آن را در فضایی مثل S3 یا Azure Blob آپلود میکند. 📂☁️
زمانی که گزارش آماده شد، worker وضعیت job را به «completed» بهروزرسانی میکند و به کاربر اطلاع میدهد. این میتواند یک ایمیل با لینک دانلود یا پیام real-time SignalR باشد که در برنامه نمایش داده میشود. لینک به فایل ذخیرهشده اشاره دارد و از طریق backend به صورت امن ارائه میشود.
مزیت این روش 🌟
اکنون کاربر منتظر یک HTTP request طولانی نیست. سرور connections را برای دقیقهها باز نگه نمیدارد. اگر چیزی خراب شد، میتوان آن را به صورت خودکار retry کرد. همچنین امکان ردیابی پیشرفت یا لغو job وجود دارد. اگر صد کاربر به طور همزمان درخواست گزارش بدهند، سیستم بدون قفل شدن scale میشود.
تجربه برای کاربر سریعتر به نظر میرسد، حتی اگر زمان واقعی تولید گزارش تغییر نکرده باشد. زیرا در نهایت، کاربران به performance metrics اهمیت نمیدهند، بلکه responsiveness برایشان مهم است. ⚡️
چرا هنوز از این سوال استفاده میکنم 🧐
چند سال بعد، شروع به استفاده از همین سوال در مصاحبه با توسعهدهندگان دیگر کردم. نه برای فریب کسی، بلکه چون نشان میدهد افراد چگونه فکر میکنند.
برخی کاندیداها مستقیم به بهینهسازی کد و queries میروند، دقیقاً مثل من در گذشته. این نشان میدهد که آنها با performance tuning آشنا هستند. سپس میتوانم به سوالات فنی پیشرفته درباره algorithms، data structures یا database optimization بپردازم.
برخی لحظهای مکث میکنند و درباره تجربه کاربر، پردازش background و fault tolerance فکر میکنند. اینجاست که گفتگوی واقعی شروع میشود: queues، retries، اطلاعرسانی، اشتراک امن فایل و غیره. این سناریو میتواند به بحثهای گستردهتری درباره system design منجر شود.
هیچ پاسخ واحدی درست نیست. اما تفاوت بزرگی بین کسی که فقط روی کد تمرکز میکند و کسی که میتواند یک سیستم scalable طراحی کند وجود دارد.
درس مهم 🎓
وقتی اولین بار این سوال را شنیدم، به فکر سریعتر کردن کد بودم. اکنون به فکر بهتر کردن تجربه کاربر هستم.
بهینهسازی یک query یا حلقه میتواند کمک کند، اما مشکل انتظار، خطاها یا مقیاسپذیری را حل نمیکند. اگر کاربران زیادی همزمان همان گزارش را شروع کنند، طراحی synchronous به سرعت شکست میخورد. جریان asynchronous سیستم را پاسخگو و مقاوم نگه میدارد، بدون توجه به بار کاری.
این تغییر از بهینهسازی توابع به طراحی سیستمهای scalable تفاوت بین یک توسعهدهنده خوب و یک توسعهدهنده عالی است. 🏆
امیدوارم مفید بوده باشد.🤍
🧩 چالش برنامهنویسی امروز — خروجی چی میشه؟
What will be the output?
A) C
B
B
B) B
B
B
C) A
B
B
C) B
B
D
<Link>
🌐 مدل TCP/IP
مدل TCP/IP یک چارچوب (framework) است که برای مدلسازی ارتباطات در شبکه استفاده میشود. این مدل در اصل مجموعهای از پروتکلهای شبکه است که در لایههای مختلف سازماندهی شدهاند تا نحوهی ارتباطات در شبکه را مدل کنند.
این مدل شامل چهار لایه است:
🔹 Application
🔹 Transport
🔹 Network/Internet
🔹 Network Access
در حالی که مدل OSI دارای هفت لایه است، مدل چهار لایهای TCP/IP سادهتر بوده و امروزه در اینترنت و سیستمهای شبکهای بهطور گسترده استفاده میشود.
🎯 نقش TCP/IP
یکی از اهداف اصلی TCP/IP این است که اطمینان حاصل کند دادهای که از سمت فرستنده ارسال میشود، بهصورت صحیح و کامل به گیرنده میرسد.
برای انجام این کار، داده قبل از ارسال به بخشهای کوچکتری به نام Packet تقسیم میشود. هر Packet بهصورت جداگانه در شبکه سفر میکند و زمانی که به مقصد رسید، دوباره به ترتیب درست بازسازی (Reassemble) میشود.
🧩 نکته: این فرآیند باعث میشود احتمال خطا کاهش یابد و پیام نهایی بهطور کامل و دقیق به مقصد برسد.
🧱 لایههای مدل TCP/IP
1️⃣ Application Layer
بالاترین لایه در مدل TCP/IP است و نزدیکترین لایه به کاربر محسوب میشود.
در این لایه، تمام برنامههایی که ما استفاده میکنیم — مثل مرورگر وب، کلاینت ایمیل یا ابزارهای اشتراک فایل — به شبکه متصل میشوند.
این لایه مثل یک پل ارتباطی بین نرمافزارهای کاربر (مثل Chrome، Gmail یا WhatsApp) و لایههای پایینتر شبکه عمل میکند که واقعاً دادهها را ارسال و دریافت میکنند.
این لایه از پروتکلهای مختلفی پشتیبانی میکند مانند:
HTTP برای وبسایتها 🌍
FTP برای انتقال فایلها 📁
SMTP برای ارسال ایمیلها ✉️
DNS برای یافتن آدرس وبسایتها 🌐
همچنین وظایفی مانند قالببندی دادهها (Data Formatting)، رمزنگاری (Encryption) برای حفظ امنیت دادهها، و مدیریت نشستها (Session Management) برای کنترل ارتباطات فعال را بر عهده دارد.
⚙️ 2️⃣ Transport Layer
مسئول اطمینان از ارسال مطمئن و مرتب دادهها بین دستگاههاست. این لایه بررسی میکند که دادههایی مانند پیامها، فایلها یا ویدیوها که ارسال میشوند، بهصورت کامل و بدون خطا به مقصد برسند.
این لایه از دو پروتکل اصلی استفاده میکند: TCP و UDP — بسته به اینکه ارتباط نیاز به قابلیت اطمینان (Reliability) داشته باشد یا سرعت.
🔹️TCP (Transmission Control Protocol)
زمانی استفاده میشود که داده باید دقیق و کامل باشد؛ مانند زمانی که در حال بارگذاری یک صفحهی وب یا دانلود یک فایل هستیم.
خطاها را بررسی میکند، بخشهای گمشده را دوباره ارسال میکند و ترتیب دادهها را حفظ مینماید.
🔹️UDP (User Datagram Protocol)
سریعتر است اما تحویل داده را تضمین نمیکند.
این ویژگی باعث میشود برای مواردی مثل پخش زندهی ویدیو یا بازیهای آنلاین که سرعت مهمتر از دقت صددرصدی است، مناسب باشد.
🌍 3️⃣ Internet Layer
وظیفه دارد بهترین مسیر را برای انتقال دادهها در میان شبکههای مختلف پیدا کند تا داده بتواند به مقصد درست برسد.
این لایه مانند یک کنترلکنندهی ترافیک شبکه عمل میکند و کمک میکند Packetها از یک شبکه به شبکهی دیگر حرکت کنند تا به دستگاه مورد نظر برسند.
این لایه از Internet Protocol (IP) برای اختصاص آدرس منحصربهفرد (IP Address) به هر دستگاه استفاده میکند تا مشخص شود داده باید به کجا ارسال شود.
وظیفهی اصلی این لایه مسیریابی (Routing) است؛ یعنی تصمیمگیری دربارهی بهترین مسیر برای حرکت دادهها.
همچنین کارهایی مانند Packet Forwarding (انتقال داده از یک نقطه به نقطهی دیگر)، Fragmentation (شکستن دادههای بزرگ به بخشهای کوچکتر) و Addressing را نیز انجام میدهد.
📡 4️⃣ Network Access Layer
پایینترین لایه در مدل TCP/IP است. این لایه با اتصال فیزیکی واقعی بین دستگاههای موجود در یک شبکهی محلی (LAN) سر و کار دارد — مثل کامپیوترهایی که از طریق کابل یا Wi-Fi به هم متصل هستند.
این لایه اطمینان حاصل میکند که داده بتواند از طریق سختافزارهایی مانند سیمها، سوئیچها یا سیگنالهای بیسیم منتقل شود.
همچنین وظایف مهمی بر عهده دارد مانند:
• استفاده از MAC Address برای شناسایی دستگاهها 🖥
• ایجاد Frameها (قالب داده برای انتقال در لینک فیزیکی)
• بررسی خطاهای ابتدایی در هنگام ارسال داده
⚙️ نحوهٔ کار مدل TCP/IP
📤 زمانی که داده ارسال میشود (از Sender به Receiver)
Application Layer 🧩:
دادههای کاربر را با استفاده از پروتکلهایی مانند HTTP، FTP یا SMTP آماده میکند.
Transport Layer (TCP/UDP) 🚦:
داده را به بخشهای کوچکتر (Segments) تقسیم کرده و اطمینان حاصل میکند که انتقال یا قابل اعتماد (TCP) باشد یا سریعتر (UDP).
Internet Layer (IP) 🌍:
آدرسهای IP را اضافه میکند و بهترین مسیر را برای هر Packet انتخاب میکند.
Link Layer (Network Access Layer) 🔗:
Packet
ها را به Frame تبدیل کرده و از طریق شبکهی فیزیکی ارسال میکند.
📥 زمانی که داده دریافت میشود (در مقصد)
Link Layer ⚡️:
بیتها را از شبکه دریافت کرده و Frameها را بازسازی میکند تا به لایهی بعدی منتقل شوند.
Internet Layer 🌐:
آدرس IP را بررسی کرده، IP Header را حذف میکند و داده را به لایهی Transport میفرستد.
Transport Layer 📦:
Segments
را دوباره ترکیب میکند، خطاها را بررسی کرده و از کامل بودن داده مطمئن میشود.
Application Layer 🖥:
دادهی نهایی را به اپلیکیشن مناسب تحویل میدهد (برای مثال، نمایش یک صفحه وب در مرورگر).
💡 مقایسه TCP/IP و OSI — چرا TCP/IP پرکاربردتر است؟
مدل TCP/IP نسبت به OSI سادهتر، عملیتر و برای شبکههای واقعی و اینترنت بسیار رایجتر است.
🧠 دلیل | 💬 توضیح
━━━━━━━━━━━━━━━
⚙️ ساختار سادهتر (Simpler Structure)
🔹 فقط ۴ لایه دارد (در مقابل ۷ لایه در OSI)، بنابراین پیادهسازی و درک آن در سیستمهای واقعی بسیار سادهتر است.
🧩 طراحی مبتنی بر پروتکل (Protocol-Driven Design)
🔹 مدل TCP/IP بر اساس پروتکلهای واقعی ساخته شده است، در حالی که مدل OSI بیشتر یک چارچوب نظری و آموزشی است.
🚀 انعطافپذیری و مقاومت بالا (Flexibility & Robustness)
🔹 بهخوبی با سختافزارها و شبکههای مختلف سازگار میشود و قابلیتهایی مانند Error Handling, Routing و Congestion Control را پشتیبانی میکند.
🌍 استاندارد باز (Open Standard)
🔹 یک استاندارد آزاد و عمومی است که توسط هیچ سازمان خاصی کنترل نمیشود و به همین دلیل در سراسر جهان پذیرفته شده است.
🧱 کاربرد واقعی در مقابل مدل مفهومی (Actual Use vs Conceptual Model)
🔹 مدل OSI برای آموزش مفید است، اما در دنیای واقعی، این TCP/IP است که واقعاً در شبکهها بهکار میرود.
🌐 Advantages of TCP/IP Model (مزایای مدل TCP/IP)
✅ Interoperability (قابلیت همکاری):
مدل TCP/IP به سیستمها و شبکههای مختلف اجازه میدهد با هم ارتباط برقرار کنند، و باعث افزایش سازگاری و همکاری بین پلتفرمهای متنوع میشود.
✅ Scalability (مقیاسپذیری):
این مدل بسیار مقیاسپذیر است و هم برای شبکههای کوچک (LAN) و هم شبکههای بزرگ مانند اینترنت (WAN) مناسب است.
✅ Standardization (استانداردسازی):
بر اساس استانداردهای باز ساخته شده است؛ بنابراین دستگاهها و نرمافزارهای مختلف میتوانند بدون مشکل ناسازگاری با هم کار کنند.
✅ Flexibility (انعطافپذیری):
از انواع مختلف پروتکلهای مسیریابی، نوع دادهها و روشهای ارتباطی پشتیبانی میکند و به همین دلیل برای نیازهای متنوع شبکه قابل تطبیق است.
✅ Reliability (قابلیت اطمینان):
دارای ویژگیهای بررسی خطا و ارسال مجدد دادهها است که باعث میشود انتقال داده حتی در فواصل طولانی و شرایط نامطمئن شبکه نیز قابل اعتماد باشد.
⚠️ Disadvantages of TCP/IP Model (معایب مدل TCP/IP)
❌ Security Concerns (مسائل امنیتی):
TCP/IP
در ابتدا با تمرکز بر امنیت طراحی نشده بود. پروتکلهایی مانند SSL/TLS بعداً برای افزایش امنیت اضافه شدند، اما این موضوع باعث ایجاد برخی آسیبپذیریها در ساختار پایه شده است.
❌ Inefficiency for Small Networks (ناکارآمدی برای شبکههای کوچک):
در شبکههای کوچک، پیچیدگی و سربار (Overhead) این مدل ممکن است غیرضروری و ناکارآمد باشد.
❌ Limited by Address Space (محدودیت در فضای آدرسدهی):
نسخه قدیمی IPv4 فضای آدرس محدودی دارد که میتواند باعث کمبود آدرسها در شبکههای بزرگ شود (هرچند IPv6 این مشکل را برطرف کرده است).
❌ Data Overhead (سربار داده):
پروتکل TCP مقدار زیادی سربار کنترلی برای اطمینان از ارسال صحیح دادهها دارد که ممکن است بر عملکرد و سرعت تأثیر بگذارد.
📘 در مجموع، مدل TCP/IP ترکیبی از سادگی، پایداری و سازگاری جهانی است که باعث شده ستون فقرات ارتباطات اینترنت امروزی باشد. 🌍
🔖هشتگها:
#Networking #TCPIP #ComputerNetworks #InternetProtocols
چطور یک برنامهنویس بهتر بشم؟ 💻
مثل هر چیزی که درش خوب نیستیم،
باید تمرین کنیم! 💪
برنامهنویسی هم از این قاعده مستثنی نیست!
اما این به این معنی نیست که فقط ویدیو ببینی یا مقاله بخونی و امیدوار باشی یه روزی توی برنامهنویسی عالی بشی.
این چیزها مفید هستن، ولی خودشون باعث نمیشن واقعاً متخصص بشی.
چند مثال بامزه برای روشنتر شدن: 😄
نمیتونم فقط با دیدن ۲۰۰ تا ویدیوی بسکتبال، توی بسکتبال خوب بشم. 🏀
نمیتونم فقط با خوندن دربارهی بدنسازی، اتصال ذهن و عضلهی قوی پیدا کنم. 🏋️
نمیتونم ۱۰۰ تا آموزش رقص ببینم و انتظار داشته باشم همون اول عالی برقصم. 💃
پس باید دست به کار بشیم و تکرار کنیم تا بهتر بشیم.
این پایهی اصلی پیشرفته.
البته میتونیم تمرین رو با چیزهای دیگه هم تقویت کنیم مثل:
📚 کتابها
📰 مقالهها
🎥 ویدیوها
🎓 دورهها
اما در نهایت باید چیزی بسازی.
همیشه به بقیه میگم بسازن!
شاید بگی: «خیلی سخته، من نمیتونم همچین چیزی بسازم!»
اما اگه اون پروژه رو به بخشهای کوچیکتر و کوچیکتر تقسیم کنیم،
در نهایت به یه بخش کوچیک میرسیم که میتونی ازش شروع کنی.
از همون قسمت شروع کن، سعی کن بسازیش.
وقتی گیر کردی، از مقالهها و ویدیوها کمک بگیر.
بعدش قسمت بعدی رو بساز.
و همین کار رو تکرار کن. 🔁
مهم نیست چی داری میسازی،
تا وقتی که داری لذت میبری و یاد میگیری، در مسیر درستی هستی. ✨
با گذشت زمان، مهارت حل مسئله، تفکر انتقادی و درک مفاهیم کلیات قویتر میشه.
زبان برنامهنویسی برات مثل حافظهی عضلانی میشه، چون واقعاً داری ازش استفاده میکنی. 💪
بعد میتونی به سراغ زبانها و تکنولوژیهای دیگه بری
و همهی اون مفاهیم پایهای که یاد گرفتی رو با خودت ببری. 🚀
پس اگه هنوز «خوب» نیستی، نگران نباش.
تمرین کن، زمان بده، و اگر عاشق این کاری،
هیچوقت تسلیم نشو. ❤️🔥
🛡 ساخت APIهای امن با Role-Based Access Control در ASP.NET Core
🔐 در Authentication به شما میگوید کاربر کیست،
اما Authorization به شما میگوید کاربر چه کاری میتواند انجام دهد.
بیشتر توسعهدهندگان NET. در ابتدا با بررسیهای سادهی نقش (Role-Based Checks) شروع میکنند:
«آیا این کاربر Admin است؟» 👀
اما وقتی برنامه رشد میکند، خیلی زود متوجه میشوید که فقط نقشها کافی نیستند.
به مجوزهای جزئیتر (Granular Permissions) نیاز دارید که بتوان آنها را بهصورت انعطافپذیر ترکیب و تخصیص داد.
اینجاست که Role-Based Access Control (RBAC) میدرخشد. ✨
بهجای اینکه در همهجا در کد نقشها را چک کنید، شما مجوزهای خاص (Permissions) را تعریف میکنید
و نقشها را طوری طراحی میکنید که آن مجوزها را حمل کنند.
ممکن است یک کاربر نقش Manager داشته باشد،
اما چیزی که اهمیت دارد این است که آیا او مجوز users:delete را دارد یا نه. 🧾
بیایید ببینیم چطور میتوانیم یک سیستم احراز مجوز انعطافپذیر و مبتنی بر مجوز در ASP.NET Core بسازیم. 🚀
🧩 درک اجزای اصلی RBAC
مدل RBAC از سه جزء کلیدی تشکیل شده است که با هم کار میکنند:
👤 Users → اختصاص داده میشوند به → 🧑💼 Roles → که شامل هستند از → 🔑 Permissions
🔄 جریان کار به این صورت است:
🔹️Users (کاربران):
افراد واقعی که از سیستم شما استفاده میکنند.
🔹️Roles (نقشها):
گروهی از مجوزهای مرتبط (مثل Admin، Manager، Editor).
🔹️Permissions (مجوزها):
عملیات خاصی که کاربران میتوانند انجام دهند
(مثل users:read، orders:create، reports:delete).
✨ زیبایی RBAC در انعطافپذیری آن است.
میتوانید چندین نقش به یک کاربر اختصاص دهید
و نقشها را بدون تغییر در تخصیص کاربران ویرایش کنید.
به همهی Managerها میخواهید امکان export گزارشها بدهید؟
فقط مجوز reports:export را به نقش Manager اضافه کنید ✅
این روش بسیار قابل نگهداریتر است
تا اینکه بخواهید در کد بررسی کنید که آیا کسی Admin یا Super Manager است.
علاوه بر این، RBAC یک نقطهی گسترش اضافی فراهم میکند:
میتوانید برای برخی کاربران مجوزهای سفارشی ایجاد کنید
بدون اینکه نیاز به ساخت نقشهای جدید باشد. 💡
⚙️ ساخت یک Authorization Handler سفارشی در ASP.NET Core
در سیستم احراز مجوز (Authorization) در ASP.NET Core، مفاهیم اصلی بر پایهی Policyها و Requirementها ساخته میشوند.
در این بخش، میخواهیم یک Handler سفارشی بسازیم که مجوزها (Permissions) را از داخل Claims کاربر بررسی کند. 🔍
🧠 کد پیادهسازی Handler سفارشی
public class PermissionAuthorizationRequirement(params string[] allowedPermissions)
: AuthorizationHandler<PermissionAuthorizationRequirement>, IAuthorizationRequirement
{
public string[] AllowedPermissions { get; } = allowedPermissions;
protected override Task HandleRequirementAsync(
AuthorizationHandlerContext context,
PermissionAuthorizationRequirement requirement)
{
foreach (var permission in requirement.AllowedPermissions)
{
bool found = context.User.FindFirst(c =>
c.Type == CustomClaimTypes.Permission &&
c.Value == permission) is not null;
if (found)
{
context.Succeed(requirement);
break;
}
}
return Task.CompletedTask;
}
}
🧩 در پشت صحنه چه اتفاقی میافتد؟
این کلاس در واقع هر دو نقش Requirement و Handler را با هم ترکیب کرده است —
یعنی هم مشخص میکند چه مجوزهایی لازماند، و هم چگونه باید بررسی شوند.
این کار باعث میشود منطق مرتبط در یک جا نگهداری شود و از تکرار (Boilerplate) جلوگیری کند.
هندلر در میان Claims کاربر جستوجو میکند تا Claimی با نوع Permission پیدا کند
که مقدار آن با یکی از مجوزهای موردنیاز مطابقت داشته باشد.
این عمل به صورت OR است — یعنی اگر کاربر حتی یکی از مجوزهای خواستهشده را داشته باشد، اجازهی دسترسی خواهد داشت ✅
اگر Claim موردنظر یافت شود، با فراخوانی
context.Succeed(requirement)
به ASP.NET Core اطلاع میدهیم که شرط تأیید شده است، و دیگر نیازی به بررسی بقیه نیست.
در صورت نیاز، میتوانید این رفتار را به AND تغییر دهید تا تمام مجوزها الزامی باشند 🔒
🏷 تعریف Claim Type سفارشی
برای شناسایی نوع مجوز در Claims، ابتدا باید نوع Claim خود را تعریف کنید:
public static class CustomClaimTypes
{
public const string Permission = "permission";
}
🪪 افزودن Claims هنگام صدور JWT Token
هنگام ساخت توکن JWT یا تنظیم Claims کاربر، میتوانید مجوزها را از دیتابیس خوانده و در Claims اضافه کنید:
var permissions = await (
from role in dbContext.Roles
join permission in dbContext.RolePermissions on role.Id equals permission.RoleId
where roles.Contains(role.Name)
select permission.Name)
.Distinct()
.ToArrayAsync();
List<Claim> claims =
[
new(JwtRegisteredClaimNames.Sub, user.Id),
new(JwtRegisteredClaimNames.Email, user.Email!),
..roles.Select(r => new Claim(ClaimTypes.Role, r)),
..permissions.Select(p => new Claim(CustomClaimTypes.Permission, p))
];
var tokenDenoscriptor = new SecurityTokenDenoscriptor
{
Subject = new ClaimsIdentity(claims),
Expires = DateTime.UtcNow.AddMinutes(configuration.GetValue<int>("Jwt:ExpirationInMinutes")),
SigningCredentials = credentials,
Issuer = configuration["Jwt:Issuer"],
Audience = configuration["Jwt:Audience"]
};
var tokenHandler = new JsonWebTokenHandler();
string accessToken = tokenHandler.CreateToken(tokenDenoscriptor);
✨ ایجاد Clean APIها با استفاده از Extension Methodها در ASP.NET Core
کار با Authorization Policyهای خام درست است ولی معمولاً کد را طولانی و پیچیده میکند.
برای بهبود تجربهٔ توسعهدهنده، میتوانیم از Extension Methodها استفاده کنیم تا کد خواناتر و تمیزتر شود. 🧩
🧠 ایجاد Extension Method برای مجوزها
public static class PermissionExtensions
{
public static void RequirePermission(
this AuthorizationPolicyBuilder builder,
params string[] allowedPermissions)
{
builder.AddRequirements(new PermissionAuthorizationRequirement(allowedPermissions));
}
}
🔹 با این متد، میتوانید بهصورت مستقیم و تمیز در Policyها از ()RequirePermission استفاده کنید.
⚡️ استفاده در Minimal APIها
public static class Permissions
{
public const string UsersRead = "users:read";
public const string UsersUpdate = "users:update";
public const string UsersDelete = "users:delete";
}
app.MapGet("me", async (ApplicationDbContext dbContext, ClaimsPrincipal User) =>
{
var user = await dbContext.Users
.AsNoTracking()
.Where(u => u.Id == int.Parse(User.FindFirstValue(JwtRegisteredClaimNames.Sub)!))
.Select(u => new UserDto
{
u.Id,
u.Email,
u.FirstName,
u.LastName
})
.SingleOrDefaultAsync();
return Results.Ok(user);
})
.RequireAuthorization(policy => policy.RequirePermission(Permissions.UsersRead));
💡 حالا endpointها تمیز، خوانا و کاملاً منطبق با الگوی Clean Architecture هستند.
🧩 استفاده در MVC Controllerها
برای MVC Controllerها میتوانیم Attribute اختصاصی بسازیم تا کار با مجوزها سادهتر شود:
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class)]
public class RequirePermissionAttribute(params string[] permissions) : AuthorizeAttribute
{
public RequirePermissionAttribute(params string[] permissions)
: base(policy: string.Join(",", permissions))
{
}
}
🔧 سپس Policyها را در DI Container ثبت میکنیم:
builder.Services.AddAuthorizationBuilder()
.AddPolicy("users:read", policy => policy.RequirePermission(Permissions.UsersRead))
.AddPolicy("users:update", policy => policy.RequirePermission(Permissions.UsersUpdate));
📦 حالا استفاده از آن در Controller بسیار تمیز و ساده است:
[RequirePermission(Permissions.UsersUpdate)]
public async Task<IActionResult> UpdateUser(int id, UpdateUserRequest request)
{
// Your logic here
}
🚀 Extension Points برای محیط Production
پیادهسازی بالا کاملاً کاربردی است، اما میتوان آن را در محیطهای Production گسترش داد.
دو نقطهٔ کلیدی برای توسعه وجود دارد:
🧱 Type-Safe Permissions با استفاده از Enum
بهجای استفاده از رشتههای جادویی (Magic Strings)، میتوان از Enumها استفاده کرد تا از بررسی در زمان کامپایل (Compile-Time Safety) بهره ببریم:
public enum Permission
{
UsersRead,
UsersUpdate,
UsersDelete,
OrdersCreate,
ReportsExport
}
سپس هنگام صدور JWT Token یا بررسی مجوز،
باید Enum را به string تبدیل کنید و هنگام خواندن Claims مجدداً آن را به Enum برگردانید. 🔁
این کار باعث میشود اشتباهات تایپی و ناسازگاریهای احتمالی حذف شوند،
و سیستم امنتر، خواناتر و مقیاسپذیرتر شود. 💪
✨ Server-Side Permission Resolution در ASP.NET Core
در بسیاری از پروژهها، توسعهدهندگان تمام Permissionها را درون JWT Token ذخیره میکنند.
اما این روش باعث افزایش حجم Token و کاهش امنیت میشود.
راه بهتر این است که مجوزها را در سمت سرور (Server-Side) واکشی کنیم. ⚙️
🧠 استفاده از IClaimsTransformation برای افزودن Permissionها در سمت سرور
public class PermissionClaimsTransformation(IPermissionService permissionService)
: IClaimsTransformation
{
public async Task<ClaimsPrincipal> TransformAsync(ClaimsPrincipal principal)
{
if (principal.Identity?.IsAuthenticated != true)
{
return principal;
}
var userId = principal.FindFirst(ClaimTypes.NameIdentifier)?.Value;
if (userId == null)
{
return principal;
}
// واکشی مجوزها از دیتابیس و سپس ذخیره در Cache
// نکته مهم: حتماً نتایج را کش کنید تا در هر درخواست کوئری تکراری به دیتابیس نرود
var permissions = await permissionService.GetUserPermissionsAsync(userId);
var claimsIdentity = (ClaimsIdentity)principal.Identity;
foreach (var permission in permissions)
{
claimsIdentity.AddClaim(new Claim(CustomClaimTypes.Permission, permission));
}
return principal;
}
}
📦 سپس این کلاس را در DI Container ثبت میکنیم:
builder.Services.AddScoped<IClaimsTransformation, PermissionClaimsTransformation>();
🔹 با این روش، JWT شما سبک و امن باقی میماند،
در حالی که Authorization همچنان سریع و مبتنی بر Claims انجام میشود. ⚡️
🧩 جمعبندی (Takeaway)
الگوی RBAC (Role-Based Access Control)
فرآیند Authorization را از یک دردسر نگهداری به یک سیستم منعطف و مقیاسپذیر تبدیل میکند. 🚀
✅ از Permissions شروع کنید، نه Roles
تعریف کنید کاربر چه عملیاتی میتواند انجام دهد، نه اینکه چه نقشی دارد.
✅ Custom Authorization Handlerها
کنترل کامل روی نحوهٔ اعتبارسنجی مجوزها به شما میدهند.
✅ Extension Methodها
کد را تمیز، منسجم و خوانا میکنند.
✅ Type-Safe Enumها + Server-Side Permission Resolution
کد را پایدارتر، Tokenها را سبکتر و سیستم را قابل نگهداریتر میکنند.
✨ نتیجه؟
یک سیستم Authorization تمیز، تستپذیر، و منعطف
که بهسادگی با رشد برنامهٔ شما سازگار میشود. 💪
🔖هشتگها:
#ASPNetCore #RBAC #Authorization #DotNet #CleanArchitecture #CSharp
Forwarded from TondTech (مسعود بیگی)
اگر دنبال خلق ارزش با AI هستید مثل من و خیلی دیگه از بچه ها میتونید این کتاب رو دانلود کنید رایگان. یا نسخه چاپی شو با کمتر از یک پول پیتزا از ما بخرید
https://refhub.ir/fa/refrence_detail/ai-value-creators-beyond-the-generative-ai-user-mindset/
لایک و شیر و کامنت کنید برسه دست کسی که نیازش داره
https://refhub.ir/fa/refrence_detail/ai-value-creators-beyond-the-generative-ai-user-mindset/
لایک و شیر و کامنت کنید برسه دست کسی که نیازش داره
💡 بهترین شیوهها برای تست یکپارچهسازی با Testcontainers در NET.
تستهای یکپارچهسازی با Testcontainers قدرتمند هستند، اما اگر از الگوهای صحیح پیروی نکنید، به سرعت میتوانند به یک کابوس نگهداری تبدیل شوند. 😫
من تیمهایی را دیدهام که با تستهای شکننده (flaky)، مجموعه تستهای کند، و سردردهای پیکربندی دست و پنجه نرم میکنند که با رعایت شیوههای بهتر از همان ابتدا، قابل اجتناب بودند.
امروز الگوهایی را به شما نشان خواهم داد که تستهای Testcontainers را قابل اعتماد، سریع و آسان برای نگهداری میکنند.
Testcontainers چگونه تست یکپارچهسازی را تغییر میدهد؟
تستهای یکپارچهسازی سنتی اغلب به دیتابیسهای تست اشتراکی یا جایگزینهای درون-حافظهای (in-memory) تکیه میکنند که با رفتار محیط پروداکشن مطابقت ندارند. شما یا با آلودگی تست بین اجراها سر و کار دارید یا واقعگرایی را فدای سرعت میکنید.
تست کانتینرها این مشکل را با بالا آوردن کانتینرهای واقعی Docker 🐳 برای وابستگیهای شما حل میکند. تستهای شما در برابر PostgreSQL، Redis یا هر سرویس دیگری که در پروداکشن استفاده میکنید، اجرا میشوند. وقتی تستها کامل شدند، کانتینرها از بین میروند و هر بار یک محیط تمیز در اختیار شما قرار میدهند.
این جادو 🪄 از طریق API داکر اتفاق میافتد. Testcontainers کل چرخه حیات را مدیریت میکند: دریافت ایمیجها، شروع کانتینرها، انتظار برای آماده شدن، و پاکسازی. کد تست شما فقط باید بداند چگونه به آنها متصل شود.
پیشنیازها 📦
اول، مطمئن شوید که پکیجهای لازم را دارید:
Install-Package Microsoft.AspNetCore.Mvc.Testing
Install-Package Testcontainers.PostgreSql
Install-Package Testcontainers.Redis
ساختن کانتینرهای تست 🏗
در اینجا نحوه راهاندازی کانتینرهای خود با پیکربندی مناسب آمده است:
PostgreSqlContainer _postgresContainer = new PostgreSqlBuilder()
.WithImage("postgres:17")
.WithDatabase("devhabit")
.WithUsername("postgres")
.WithPassword("postgres")
.Build();
RedisContainer _redisContainer = new RedisBuilder()
.WithImage("redis:latest")
.Build();
برای شروع و توقف تمیز کانتینرها در سراسر مجموعه تست خود، IAsyncLifetime را در WebApplicationFactory خود پیادهسازی کنید:
public sealed class IntegrationTestWebAppFactory : WebApplicationFactory<Program>, IAsyncLifetime
{
// ... تعریف کانتینرها ...
public async Task InitializeAsync()
{
await _postgresContainer.StartAsync();
await _redisContainer.StartAsync();
// وابستگیهای دیگر را اینجا شروع کنید
}
public async Task DisposeAsync()
{
await _postgresContainer.StopAsync();
await _redisContainer.StopAsync();
}
}
این کار تضمین میکند که کانتینرها قبل از اجرای تستها آماده و پس از آن پاکسازی شوند. این یعنی هیچ وضعیت باقیمانده از داکر یا شرایط رقابتی (race conditions) وجود نخواهد داشت.
📌 نکته: نسخههای ایمیج خود را پین کنید (مانند postgres:17) تا از غافلگیریهای ناشی از تغییرات بالادستی جلوگیری کنید.
انتقال پیکربندی به اپلیکیشن شما ✅
بزرگترین اشتباهی که میبینم، هاردکد کردن connection stringها است. Testcontainers پورتهای داینامیک اختصاص میدهد. هیچ چیز را هاردکد نکنید.
در عوض، مقادیر را از طریق WebApplicationFactory.ConfigureWebHost تزریق کنید:
protected override void ConfigureWebHost(IWebHostBuilder builder)
{
builder.UseSetting("ConnectionStrings:Database", _postgresContainer.GetConnectionString());
builder.UseSetting("ConnectionStrings:Redis", _redisContainer.GetConnectionString());
}
📍نکته کلیدی استفاده از متد UseSetting برای انتقال داینامیک connection stringها است. این کار همچنین از هرگونه شرایط رقابتی یا تداخل با تستهای دیگری که ممکن است به صورت موازی اجرا شوند، جلوگیری میکند.
اشتراکگذاری تنظیمات پرهزینه با xUnit Collection Fixtures
💡فیکسچر تست (test fixture) چیست؟
یک فیکسچر یک زمینه اشتراکی برای تستهای شماست که به شما اجازه میدهد منابع پرهزینه مانند دیتابیسها یا message brokerها را یک بار راهاندازی کرده و در چندین تست از آنها استفاده مجدد کنید.
اینجاست که اکثر تیمها دچار مشکل میشوند. انتخاب بین class و collection fixtures بر عملکرد و ایزولهسازی تست تأثیر میگذارد.
Class Fixture 🏠 :
یک کانتینر برای هر کلاس تست:
زمانی استفاده کنید که تستها وضعیت سراسری را تغییر میدهند یا دیباگ کردن تعاملات تست دشوار میشود.
public class AddItemToCartTests : IClassFixture<DevHabitWebAppFactory>
{
// ...
}
Collection Fixture 🏢 :
یک کانتینر مشترک بین چندین کلاس تست:
زمانی استفاده کنید که تستهای شما وضعیت اشتراکی را تغییر نمیدهند یا میتوانید به طور قابل اعتماد بین تستها پاکسازی انجام دهید.
[CollectionDefinition(nameof(IntegrationTestCollection))]
public sealed class IntegrationTestCollection : ICollectionFixture<DevHabitWebAppFactory> {}
// سپس آن را به کلاسهای تست خود اعمال کنید:
[Collection(nameof(IntegrationTestCollection))]
public class AddItemToCartTests : IntegrationTestFixture
{
// ...
}
چه زمانی از کدام استفاده کنیم:🤔
🔹️Class fixtures
وقتی به ایزولهسازی کامل بین کلاسهای تست نیاز دارید (کندتر اما امنتر).
🔹️Collection fixtures
وقتی کلاسهای تست با یکدیگر تداخل ندارند (سریعتر اما نیازمند نظم).
نوشتن تستهای یکپارچهسازی قابل نگهداری ✍️
با پیکربندی صحیح زیرساخت، تستهای واقعی شما باید روی منطق بیزینس تمرکز کنند:
[Fact]
public async Task Should_ReturnFailure_WhenNotEnoughQuantity()
{
//Arrange
Guid customerId = await Sender.CreateCustomerAsync(Guid.NewGuid());
// ...
//Act
Result result = await Sender.Send(command);
//Assert
result.Error.Should().Be(TicketTypeErrors.NotEnoughQuantity(Quantity));
}
توجه کنید که چگونه تستها به جای دغدغههای زیرساختی، روی قوانین بیزینس تمرکز دارند. شما PostgreSQL یا Redis را mock نمیکنید، شما رفتار واقعی را تست میکنید.
نتیجهگیری 👍
تست کانتینرها تست یکپارچهسازی را با دادن اطمینانی که از تست در برابر وابستگیهای واقعی حاصل میشود، متحول میکند.
ساده شروع کنید: یک تست یکپارچهسازی را که در حال حاضر از mockها یا دیتابیسهای درون-حافظهای استفاده میکند، انتخاب کرده و آن را برای استفاده از Testcontainers تبدیل کنید. بلافاصله تفاوت در اطمینان را هنگامی که آن تست پاس میشود، متوجه خواهید شد.