Developer Advocate
Ir al canal en Telegram
1 035
Suscriptores
Sin datos24 horas
Sin datos7 días
-130 días
Archivo de publicaciones
1 035
در توسعه مدرن، تعادل بین سرعت ارائه قابلیت جدید و حفظ کیفیت کدبیس حیاتی است. روش Trunk-Based Development (TBD) بهعنوان جایگزینی قدرتمند برای Feature Branching رایج، به تیمها اجازه میدهد به جای شاخههای متعدد، مستقیماً روی شاخه اصلی (main/master) کار کرده و به دفعات بسیار در روز تغییرات را مبتنی بر Feature Flags و Feature Toggles مستقر کنند.
این رویکرد فرهنگ CI قویتری ایجاد میکند: ادغام تغییرات کوچک و مکرر باعث میشود احتمال تعارض کمتر شده، و خطایابی سریعتر انجام شود. نکته کلیدی، جداسازی فعالسازی قابلیتها (با فلگ) از استقرار کد است؛ به این معنا که قابلیتهای نیمهآماده به کمک Feature Toggleها قابل پنهانسازی هستند تا زمانیکه برای کاربران نهایی فعال شوند.
یک پیادهسازی ساده Feature Toggle در #CSharp از طریق IConfiguration:
public interface IFeatureToggle
{
bool IsEnabled(string featureName);
}
public class AppSettingsFeatureToggle : IFeatureToggle
{
private readonly IConfiguration _config;
public AppSettingsFeatureToggle(IConfiguration config) => _config = config;
public bool IsEnabled(string featureName)
=> _config.GetValue<bool>($"FeatureToggles:{featureName}");
}
// در DI ثبت کنید
services.AddSingleton<IFeatureToggle, AppSettingsFeatureToggle>();
// استفاده در سرویس:
if (_featureToggle.IsEnabled("NewCheckoutFlow"))
{
// منطق جدید
}
else
{
// منطق فعلی
}
در عمل TBD بیشتر مناسب پروژههای enterprise است که Release Cycle سریع و پایدار میخواهند. ولی نیازمند Discipline در تست خودکار، Culture دادن به تیم و تعریف استانداردهایی نظیر Short-lived Feature Branches (بر اساس Pull Requestهای بسیار کوچک و سریع) است.
مطالعه بیشتر (انگلیسی):
https://martinfowler.com/articles/feature-toggles.html
Advanced CI/CD & Automation
@DeveloperAdvocate 🥑1 035
❤️ تست Snapshot برای کامپوننتهای UI: راهکاری برای اطمینان از پایداری رابط کاربری در .NET ❤️
تست snapshot یکی از تکنیکهای قدرتمند برای شناسایی رگرسیون در UI است؛ اما این تکنیک همواره معطوف به React یا Jest نبوده و مدلسازی آن در دنیای .NET (مثلاً برای Blazor یا WinForms/WPF) مزیتهای جالبی بههمراه دارد. ایده اصلی این است که خروجی رندر کامپوننت (مثلاً markup نهایی HTML یا tree گرافیکی) را با یک snapshot معتبرِ قبلی مقایسه کنیم تا تغییرات غیرمنتظره سریعاً تشخیص داده شوند.
در مثال زیر، یک تست snapshot ساده برای یک کامپوننت Blazor را با استفاده از xUnit و یک پلایگن کمحجم custom بررسی میکنیم:
using Xunit;
using Bunit;
using System.IO;
public class CounterComponentSnapshotTest : TestContext
{
[Fact]
public void CounterMarkup_Should_Match_Snapshot()
{
// Arrange
var component = RenderComponent<Counter>();
var markup = component.Markup;
var snapshotPath = "Snapshots/CounterComponent.snapshot";
if (!File.Exists(snapshotPath))
{
// اولین بار، snapshot را ذخیره کن (یا با فلگ خاص تست را fail کن)
File.WriteAllText(snapshotPath, markup);
Assert.True(false, "Snapshot created. Please review & re-run test.");
}
var expectedMarkup = File.ReadAllText(snapshotPath);
Assert.Equal(expectedMarkup, markup);
}
}
این رویکرد، بخصوص برای atomic componentها و سیستمهای طراحی (Design Systems) فوقالعاده کاربردی است. البته توجه داشته باشید که snapshot testing، ضعفهایی هم دارد (مثلاً sensitivity به whitespace یا وابستگی به ترتیب attributeها). برای حرفهایتر کار کردن:
- snapshotها را در بازبینی کد (code review) مدیریت کنید.
- پیشپردازش (normalize) ساده روی خروجی انجام دهید تا تغییرات بیاهمیت باعث تست fail نشوند.
- برای تست snapshot در اپلیکیشنهای پیچیدهتر، از snapshot serializers اختصاصی بهره بگیرید.
🔗 مطالعه تکمیلی: https://martinfowler.com/articles/mocksArentStubs.html (درک عمیقتر تست مبتنی بر golden file/snapshot و تفاوت آن با رویکردهای کلاسیک mock/verify)
Testing & Quality Assurance (.NET)
@DeveloperAdvocate 🥑1 035
در دنیای DevSecOps، آشنایی دقیق با رویکردهای تست امنیتی برای معماران و توسعهدهندگان پیشرفته حیاتی است. سه ستون اصلی:
۱️⃣ SAST (Static Application Security Testing): تحلیل استاتیک کد، معمولاً در زمان build و بدون اجرا. نقاط قوت: کشف آسیبپذیری در منطق و flow داده پیش از deploy. اگر پارامترهای ورودی کاربر بیاعتبار شده باشد، SAST معمولاً راحت کشف میکند:
// مثال ساده تزریق SQL که SAST کشف میکند
public void GetUser(string username)
{
var cmd = $"SELECT * FROM Users WHERE Username = '{username}'";
// ...
}
۲️⃣ DAST (Dynamic Application Security Testing): تست داینامیک روی برنامه در حال اجرا (black-box). ایدهآل برای کشف آسیبپذیریهایی مثل XSS یا مسیرهای باز امنیتی که صرفاً تحلیل کد کشف نمیکند. استفاده از اسکریپت تست یا ابزارهایی مانند OWASP ZAP توصیه میشود. DAST معمولاً رفتار خروجی برنامه در شرایط غیرمنتظره را بررسی میکند.
۳️⃣ Penetration Testing: شبیهسازی حمله توسط متخصصین انسانی. بهترین روش برای کشف آسیبپذیریهای ترکیبی و کانفیگهای اشتباه محیط اجرا. بسیاری از اکسپلویتها فقط در این فاز کشف میشوند؛ به ویژه ضعفهایی که در interaction سرویسها یا misconfiguration رخ میدهد.
نکته عملی:
تیم توسعه حرفهای باید SAST را در pipeline CI/CD قرار دهد، DAST را روی staging اتوماتیک کند و هر فصل لااقل یک Pen Test سفارش دهد. مشارکت فعال Devها در رفع findings بسیار کلیدی است. برای شروع، ابزارهای رایگان مانند SonarQube (SAST) و OWASP ZAP (DAST) را تست کنید.
مطالعهٔ بیشتر (انگلیسی، Microsoft):
https://learn.microsoft.com/en-us/azure/security/develop/security-testing-overview
Testing & Quality Assurance (.NET)
@DeveloperAdvocate 🥑1 035
🔍 Refactoring برای مهندسین حرفهای: شناسایی Code Smells و اعمال الگوهای Refactoring
در پروژههای enterprise با گذر زمان، کد مستعد بروز «code smell» میشود: نشانههایی از مشکلات طراحی که زنگ خطر را به صدا درمیآورند. تشخیص سریع این بوها و اعمال refactoring، کیفیت و maintainability را تضمین میکند.
نمونههای رایج Code Smells:
- Long Method: توابع طولانی که چندین وظیفه متفاوت را پوشش میدهند.
- Feature Envy: کلاسی که به دادههای کلاس دیگر بیش از حد وابسته است.
- Switch Statements: تکرار پرشمار از
switch یا if های مبتنی بر enum، که معمولا نشانه نبودن abstraction کافی است.
- Primitive Obsession: استفاده بیش از حد از انواع ابتدایی (مانند string یا int) بجای Value Objectها.
بیایید یک مثال عملی ببینیم؛ فرض کنید با Smellای مواجه هستیم: Long Method
public void ProcessOrder(Order order)
{
// محاسبه قیمت کل
decimal total = order.Lines.Sum(item => item.Price * item.Quantity);
// اعمال تخفیف
if (order.CouponCode == "DISCOUNT10")
total *= 0.9m;
// چک اعتبار مشتری
if (order.Customer.Credit < total)
throw new InvalidOperationException("اعتبار کافی نیست.");
// ارسال ایمیل تایید
_mailer.SendConfirmation(order.Customer.Email, total);
}
🔧 Refactoring پیشنهادی:
هر وظیفه را به متدی جداگانه یا حتی یک کلاس مجزا منتقل کنید.
public void ProcessOrder(Order order)
{
decimal total = CalculateTotal(order);
total = ApplyDiscount(order, total);
EnsureCustomerCredit(order.Customer, total);
_mailer.SendConfirmation(order.Customer.Email, total);
}
decimal CalculateTotal(Order order) => order.Lines.Sum(l => l.Price * l.Quantity);
decimal ApplyDiscount(Order order, decimal total)
{
if (order.CouponCode == "DISCOUNT10") return total * 0.9m;
return total;
}
void EnsureCustomerCredit(Customer customer, decimal total)
{
if (customer.Credit < total)
throw new InvalidOperationException("اعتبار کافی نیست.");
}
نتیجه؟ خوانایی. تستپذیری. و مانع رشد «kitchen sink method» در قلب اپلیکیشن.
شناسایی و رفع code smell فقط زیباشناسی نیست، ریسک فنی را کاهش میدهد و توسعه آینده را ایمن میکند.
برای مطالعه عمیقتر روی code smells و refactoring patterns، این مقاله ارزشمند Martin Fowler را از دست ندهید:
https://refactoring.guru/refactoring/smells
Productivity & Self-Improvement
@DeveloperAdvocate 🥑1 035
وقتی بحث معماری اپلیکیشنهای مبتنی بر رندرینگ سمت سرور (.NET 8 RSC) و اجزای کلاینتی (بهویژه در Blazor و ترکیب با WASM) پیش میآید، رعایت مرز و نحوه تعامل این دو، یکی از چالشهای جدی است. بهترین رویهها برای ترکیب این دو کامپوننت:
🔸 ۱. مسئولیت مشخص:
کامپوننتهای سروری («Server Components») باید فقط داده و منطق مربوط به بکاند را مدیریت کنند؛ هیچ state یا eventی که به تعامل کاربر وابسته است، نباید اینجا بیاید. در مقابل، کامپوننتهای کلاینتی مسئول تعامل بلادرنگ و UI دینامیک هستند.
🔸 ۲. فقط داده، نه Callback:
ارسال delegation یا callbacks از server component به client component شدنی نیست، چون سرور فقط مقدار اولیه state را فراهم میکند و پس از رندر ارتباط قطع میشود. پس استراتژی توصیهشده:
- دادهها را به کامپوننت کلاینت پاس بدهید
- وقایع را از client component داخل سرور با Patternهایی مثل API یا SignalR کنترل کنید
نمونه:
// Server Component
<UserProfile Data="userDto" />
// UserProfile.razor (Client Component)
@code {
[Parameter]
public UserDto Data { get; set; }
void OnLikeClicked()
{
// پیام را از طریق API به سرور بازمیگردانیم
await ApiClient.LikeUser(Data.Id);
}
}
🔸 ۳. Mapping مدلها و serialization:
مدلهای منتقلشده باید خالص و قابل serialized شدن باشند (Recordها یا POCOها). بهتر است بین مدل دامنه و مدل DTO اختصاصی برای انتقال داده تمایز قائل شوید تا decoupling (جداسازی وابستگیها) رعایت شود.
🔸 ۴. مرورگر به عنوان پل ارتباطی:
Eventهای پیچیده که نیاز به اصطلاحاً “Hydration” دارند (مثلاً modalها یا فرمهای تعاملی)، باید در کامپوننت کلاینتی مدیریت شوند. تنها مقدار اولیه در سرور تولید میشود. هرگونه نیاز به event propagation باید با JS interop یا API انجام شود.
🔸 ۵. مراقب هزینه hydration باشید:
وابستگی زیاد به client component باعث افزایش حجم JS و هزینه hydration میشود. تا جای ممکن بخشهای non-interactive را به server component بسپارید و فقط interactiveها را جدا کنید.
⬆️ برای جزئیات بیشتر و رویکردهای پیشرفتهتر این مقاله عالی را ببینید:
https://devblogs.microsoft.com/dotnet/blazor-unified-rendering/#component-type-interoperability
#Blazor #ServerClientInterop #SoftwareArchitecture
Advanced React Concepts
@DeveloperAdvocate 🥑1 035
🔐 آپلود امن فایلها: لوپهای رایج و بهترین رویکردها
بسیاری از نفوذها ریشه در عدم اعتبارسنجی و مدیریت ضعیف فایلهای بارگذاریشده دارند. نکات کلیدی برای جلوگیری از File Injection در .NET:
1️⃣ اعتبارسنجی محتوای فایل، نه فقط پسوند: بهجای اعتماد به پسوند، امضای باینری و MIME Type را بررسی کنید. مثال برای اعتبارسنجی سرآیند (Magic Number):
public bool IsPng(Stream fileStream)
{
byte[] signature = new byte[8];
fileStream.Read(signature, 0, 8);
var pngSignature = new byte[] { 137, 80, 78, 71, 13, 10, 26, 10 };
return signature.SequenceEqual(pngSignature);
}
2️⃣ محدودسازی سایز فایل: بیتوجهی به اندازه، سرور را تا حد Denial of Service آسیبپذیر میکند. در کانفیگ ASP.NET Core:
services.Configure<FormOptions>(options => {
options.MultipartBodyLengthLimit = 2 * 1024 * 1024; // ۲ مگابایت
});
3️⃣ غیرقابل اجرا بودن مسیر ذخیرهسازی: فایلها را هرگز در wwwroot یا مسیرهای public قرار ندهید؛ بلکه مثلا /app_data/uploads (خارج از root) باشد و به هیچ صورت از آنجا اجرا نشود.
4️⃣ رینیم کردن فایل: از نام اولیه فایل صرفنظر کنید؛ جایگزین با Guid یا Hashed نام:
var safeFileName = $"{Guid.NewGuid()}{Path.GetExtension(file.FileName)}";
5️⃣ اسکن آنتیویروس: اتصال به آنتیویروس سازمان پیش از ذخیرهسازی فایل حساس.
6️⃣ عدم پردازش فایل بلافاصله: تأخیر یا صفبندی فایل برای بررسی و تبدیل (Sandbox processing).
📚 منبع پیشنهادی برای درک عمیقتر:
https://learn.microsoft.com/en-us/aspnet/core/security/file-uploads
#Security #.NET #BestPractices #FileUpload
Application Security (AppSec)
@DeveloperAdvocate 🥑1 035
👨💻 مدیریت استثناها در UI با Error Boundaries — تجربهای بینقص برای کاربر
در معماریهای مدرن UI، حتی بهترین کامپوننتها هم ممکن است با استثناهای پیشبینینشده مواجه شوند— مخصوصا در React یا Blazor. «خطاهای غیرقابل دسترس» اگر به درستی مدیریت نشوند، میتوانند کل اپلیکیشن را مختل کنند. Error Boundary مکانیزمی است که با آن میتوانید بخشی از درخت کامپوننت را در مقابل کرش حفظ کنید.
در Blazor، پیادهسازی ErrorBoundary بسیار ساده و در عین حال قدرتمند است:
@using Microsoft.AspNetCore.Components
<ErrorBoundary>
<ChildContent>
<MyComponent />
</ChildContent>
<ErrorContent>
<div class="alert alert-danger">
مشکلی پیش آمده است. مجدداً تلاش کنید یا با پشتیبانی تماس بگیرید.
</div>
</ErrorContent>
</ErrorBoundary>
برای شخصیسازی رفتار (مثلاً لاگ کردن خطاها)، یک ErrorBoundary سفارشی بنویسید:
using Microsoft.AspNetCore.Components;
public class CustomErrorBoundary : ErrorBoundary
{
protected override Task OnErrorAsync(Exception exception)
{
// ارسال لاگ خطا به سرویس مانیتورینگ
LogService.Log(exception);
return base.OnErrorAsync(exception);
}
}
نکات کلیدی برای معماران:
- فقط UI سطوح منطقیای را Error Boundary کنید تا UX طبیعی بماند.
- همیشه Context کاربر را برای نمایش پیام مرتبط نگه دارید.
- خطاها را لاگ کنید و به یک پلتفرم مانیتورینگ وصل کنید (مثلاً Application Insights).
مطالعه بیشتر (Microsoft Docs):
https://learn.microsoft.com/en-us/aspnet/core/blazor/fundamentals/handle-errors
Advanced React Concepts
@DeveloperAdvocate 🥑1 035
🎯 چگونه از اقیانوس اطلاعات تکنولوژی غرق نشویم؟
بروز ماندن در اکوسیستم .NET و دنیای فناوری با این حجم از منابع و سرعت تغییرات، دیگر با مدلهای سنتی مثل RSS یا فید لینکی ساده کارآمد نیست. تجربهها نشان دادهاند ترکیب فیلترینگ هوشمند با ابزارهای درست و یک فرآیند شخصیسازیشده میتواند اطلاعات حیاتی و عمیق را از نویز جدا کند.
🔹 استراتژی شخصیسازی جریان ورودی:
۱. انتخاب منابع با ارزش افزوده: دنبال کردن معماریها و مخازن رسمی مثل devblogs.microsoft.com یا مجله معماری مارتین فاولر، به جای کانالهای عمومی تلگرام و توییتر.
۲. سیستم تگگذاری و سیستماتیکسازی: ابزارهایی مثل Feedly یا Inoreader به شما امکان دستهبندی و برچسبگذاری فیدها را میدهند تا بر مبنای نیاز پروژه یا علاقه، خبرها و مقالات را مدیریت کنید.
🔹 مثال اسکریپتی برای فیلترینگ هوشمند:
فرض کنید فقط به مقالات حوزه C# 12 و معماری مدرن علاقهمندید. از RSS Toolkit میتوانید یک پالایشگر ساده با سیشارپ بنویسید:
using System.ServiceModel.Syndication;
using System.Xml;
var reader = XmlReader.Create("https://devblogs.microsoft.com/dotnet/feed/");
var feed = SyndicationFeed.Load(reader);
foreach(var item in feed.Items)
{
if((item.Title.Text.Contains("C# 12") || item.Summary.Text.Contains("architecture")) &&
!item.Title.Text.Contains("preview"))
{
Console.WriteLine($"Title: {item.Title.Text}\nLink: {item.Links[0].Uri}\n");
}
}
این کد نمونه، یک فیلتر شخصی برای نمایش مقالات داتنت، حول محور C# 12 و معماری نوشته است؛ و previewها را حذف میکند!
🔹 توصیه حرفهای:
یک پنجره زمانی مشخص (مثلا هر جمعه ۴۵ دقیقه) فقط برای مطالعه عمیق و یادداشت برداری اختصاص دهید و سرتیترها را همان ابتدا با کد فیلترینگ بالا غربال کنید تا از پراکندگی جلوگیری شود.
📌 مقاله پیشنهادی:
Staying Current in a Rapidly Changing World (Martin Fowler)
این مقاله دیدگاهی معماری و راهبردی به مدیریت تغییرات سریع تکنولوژی ارائه میدهد.
Productivity & Self-Improvement
@DeveloperAdvocate 🥑1 035
📌 CTEهای بازگشتی: شاهکلیدِ مدیریت دادههای سلسلهمراتبی در SQL
استفاده از Common Table Expressions (CTE)های بازگشتی روشی قدرتمند برای سادهسازی درخواست روی دادههای سلسلهمراتبی است؛ مثل منوها، دستهبندیها یا ساختار سازمانی. فرض کنید یک جدول با ساختار Parent-Child داریم:
Id | Name | ParentId ---|-----------|--------- 1 | CEO | NULL 2 | CTO | 1 3 | Dev Lead | 2 4 | Developer | 3کافیست یک CTE بازگشتی بنویسید تا کل زیرشاخههای یک نود خاص را واکشی کنید:
WITH OrgChart AS (
SELECT Id, Name, ParentId, 0 AS Level
FROM Employees
WHERE Id = 1 -- ریشه موردنظر (مثلاً مدیرعامل)
UNION ALL
SELECT e.Id, e.Name, e.ParentId, oc.Level + 1
FROM Employees AS e
INNER JOIN OrgChart AS oc ON e.ParentId = oc.Id
)
SELECT * FROM OrgChart
ORDER BY Level, Name
در این مثال، بخش اول CTE نقطه شروع (ریشه) را مشخص میکند و بخش دوم با ترکیب رکوردهای فعلی با رکوردهای پدرشان، درخت را گسترش میدهد.
🔹 تجربه در معماری:
CTEهای بازگشتی جایگزین فوقالعادهای برای کدنویسی دستی traversal در لایه application (مثلاً در #CSharp) هستند. این کار باعث انتقال بار پردازشی به SQL و کمتر شدن Latency بین اپلیکیشن و پایگاه داده میشود. اما مراقب باشید! برای دادههای بسیار بزرگ، ممکن است کارایی دچار مشکل شود؛ پس همواره با شاخصگذاری مناسب و محدود کردن عمق traversal کار کنید.
🔎 مطالعه بیشتر:
Recursive Queries Using Common Table Expressions (MSDN)
#SQL #CTE #DataModeling #Architects #Persian
Advanced Database Concepts
@DeveloperAdvocate 🥑1 035
Event Grid (در Azure) و EventBridge (در AWS) راهکاری مدرن برای معماریهای رویدادمحور در محیطهای ابری و Serverless هستند—ابزاری قدرتمند که decoupling واقعی بین Domainها و سرویسها را به زیبایی ممکن میسازد. در معماری microservices، ارتباط مبتنی بر رویداد نهتنها latency سیستم را کاهش میدهد بلکه scalability افقی و قابلیت observability مطلوبتری ایجاد میکند.
مدل Publish/Subscribe باعث میشود هر سرویس صرفاً به رویدادهایی واکنش نشان دهد که برایش اهمیت دارند. به این ترتیب event consumers (مانند یک ماژول پردازش سفارش) میتوانند بدون هیچ وابستگی مستقیم به event producer تغییر کنند و توسعه یا دیپلوی مستقل داشته باشند.
در .NET استفاده از Event Grid (مثلاً با Azure.Messaging.EventGrid) بسیار ساده و در عین حال منعطف است:
using Azure.Messaging.EventGrid;
using Azure.Messaging.EventGrid.SystemEvents;
// ارسال یک event ساده
var topicEndpoint = "https://<topic-name>.westeurope-1.eventgrid.azure.net/api/events";
var topicKey = "<your-access-key>";
var client = new EventGridPublisherClient(new Uri(topicEndpoint), new AzureKeyCredential(topicKey));
await client.SendEventAsync(
new EventGridEvent(
subject: "order/created",
eventType: "OrderCreated",
dataVersion: "1.0",
data: new { OrderId = 123, Amount = 985000 }
)
);
از مزیتهای کلیدی این سرویسها:
- At-least-once delivery و تضمین Reliability در ارسال رویدادها
- توانایی فیلتر کردن، مسیردهی و ترنسفرم رویدادها با قابلیت Declarative
- ادغام native با منطق امنیتی و احراز هویت ابری
- مانیتورینگ و Tracing پیشرفته (برای مثال با Azure Monitor یا AWS CloudWatch)
یک الگوی رایج برای توسعهپذیری و resiliency بیشتر، پیادهسازی Outbox Pattern در کنار Event Grid است تا اطمینان حاصل شود که رویدادها حتی در صورت بروز خطا در بانک اطلاعاتی یا ارتباطات شبکه گم نمیشوند.
📚 بیشتر بخوانید:
Cloud Events, Event Grid, and Event-Driven Architectures - Microsoft Docs
Cloud Computing (Azure/AWS/GCP) & Serverless
@DeveloperAdvocate 🥑1 035
در دنیای مدرن توسعه نرمافزار، داشتن SDLC ایمن دیگر تنها دغدغه تیمهای امنیت نیست؛ توسعهدهندگان در خط اول دفاع قرار دارند. سه ستون اصلی تست امنیتی که باید در هر پروژه جدی به آنها فکر کنیم:
🔹 SAST (Static Application Security Testing)
تحلیل کد منبع یا اسمبلی بهصورت آفلاین، بهمنظور شناسایی آسیبپذیریهایی مانند Injection، XSS یا bad crypto. نمونه ابزارها: SonarQube، GitHub Advanced Security. در .NET، استفاده از آنالیزورهای Roslyn بسیار مفید است. مثال:
// مثال کد آسیبپذیر در برابر SQL Injection
var query = $"SELECT * FROM Users WHERE Name = '{userInput}'";
using var command = new SqlCommand(query, connection);
ابزارهای SAST به شما کمک میکنند این نوع اشتباهات را پیش از دپلوی، شناسایی کنید.
🔹 DAST (Dynamic Application Security Testing)
تست برنامه در حال اجرا از بیرون (اما بدون دسترسی به کد). ابزارهایی مثل OWASP ZAP یا Burp Suite رفتار برنامه را هنگام پاسخگویی به ریکوئستها بررسی میکنند. این تست، آسیبپذیریهایی مانند authentication flaw، exposed error messages و misconfigurations را کشف میکند.
🔹 Penetration Testing
بیشتر شبیه هک واقعی است و معمولاً توسط تیمهایی با نگاه «مخرب» اجرا میشود تا مسیرهای حمله واقعی را کشف کنند. کمک میکند نقاط کور در فرآیند امنیت و تستهای اتوماتیک را بیابید.
🚩 نکته معمارانه:
تستهای SAST و DAST را به CI/CD اضافه کنید و findings را تبدیل به استانداردهای کدنویسی کنید. معماریهای مدرن مثل clean architecture، با جداسازی سطوح، حمله را دشوارتر و اجرای تستهای امنیتی را سادهتر میکنند.
مطالعه بیشتر:
Static and Dynamic Analysis in the CI/CD pipeline (Microsoft Learn)
#Security #Architecture #SDLC #DevSecOps
Testing & Quality Assurance (.NET)
@DeveloperAdvocate 🥑1 035
حاکمیت داده (Data Governance) مانند معماری خوب برای دادههای سازمانی است؛ تضمینکننده کیفیت، امنیت و انطباق دادهها به عنوان دارایی استراتژیک. در اکوسیستم .NET، پیادهسازی اصول حاکمیت داده اغلب با تعریف واضح سیاستها، کنترل دسترسی مبتنی بر نقش (RBAC)، ممیزی تغییرات، و مانیتورینگ کیفیت داده همراه است.
برای نمونه، ترکیب EF Core با Value Object، Policy-based Authorization و Data Annotation میتواند کنترل موثری بر اعتبار و دسترسپذیری دادهها ایجاد کند. مثال زیر یک ساختار ساده برای اطمینان از اعتبار (Data Validation) و ثبت تاریخچه تغییرات را نمایش میدهد:
public class AuditableEntity
{
public DateTime CreatedAt { get; set; }
public string CreatedBy { get; set; }
public DateTime? ModifiedAt { get; set; }
public string ModifiedBy { get; set; }
}
public class Customer : AuditableEntity
{
[Required]
[MaxLength(100)]
public string Name { get; set; }
[EmailAddress]
public string Email { get; set; }
}
// Context override for audit logging
public override int SaveChanges()
{
var entries = ChangeTracker.Entries<AuditableEntity>();
foreach (var entry in entries)
{
if (entry.State == EntityState.Added)
{
entry.Entity.CreatedAt = DateTime.UtcNow;
entry.Entity.CreatedBy = _currentUserService.Username;
}
else if (entry.State == EntityState.Modified)
{
entry.Entity.ModifiedAt = DateTime.UtcNow;
entry.Entity.ModifiedBy = _currentUserService.Username;
}
}
return base.SaveChanges();
}
توصیه پیشرفته: طراحی سطوح دسترسی بر اساس Policy و ادغام آن با ابزارهایی مانند Azure Purview یا Microsoft Information Protection، امکان پیادهسازی استراتژیهای Data Classification و Data Loss Prevention را در سازمانهای بزرگ فراهم میکند.
مطلب مفصل و فنی درباره Data Governance و مفاهیم پیشرفتهتر را اینجا ببینید:
https://learn.microsoft.com/en-us/azure/architecture/data-guide/governance/data-governance-overview
Advanced Database Concepts
@DeveloperAdvocate 🥑1 035
در معماری مدرن، امنیت باید بخشی جداییناپذیر از فرآیند توسعه نرمافزار (SDLC) باشد؛ نه پساندازش برای مرحله تست یا عملیات! بیایید چند اصل کلیدی Security by Design را مرور کنیم:
۱. تهدیدمحوری (Threat Modeling): از همان روز نخست، سناریوهای تهدید مثل XSS، CSRF، Injection و ضعف در احراز هویت را شناسایی کنید. ابزارهایی مانند Microsoft Threat Modeling Tool به مهندسین کمک میکند در جلسات طراحی، حملات را پیشدستانه مدل کنند.
۲. الگوهای Secure by Default: ارزش پیشفرضها را دستکم نگیرید. کانفیگهای دیفالت سیستم باید کمینهترین دسترسی و باز بودن را داشته باشند. در ASP.NET Core، تمام endpointها را با
[Authorize] قفل کنید و فقط بر اساس نیاز باز کنید:
[Authorize]
[ApiController]
[Route("api/[controller]")]
public class OrdersController : ControllerBase
{
[HttpGet]
[AllowAnonymous] // تنها در صورت لزوم
public IActionResult PublicCatalog() { ... }
[HttpPost]
public IActionResult PlaceOrder() { ... }
}
۳. Validate Everything: همیشه ورودیها را اعتبارسنجی کنید—حتی دادههایی که از مایکروسرویسهای مجاور میگیرند. با System.ComponentModel.DataAnnotations و FluentValidation در لایه API و Domain:
public class RegisterUserDto
{
[Required, EmailAddress]
public string Email { get; set; }
[Required, MinLength(8)]
public string Password { get; set; }
}
۴. DevSecOps: ابزارهای تست امنیتی مثل Snyk، SonarQube و GitHub Advanced Security را به CI/CD خود وارد کنید تا آسیبپذیریها زودتر کشف شوند.
۵. Dependency Hygiene: کتابخانهها و پکیجها را با dotnet list package --vulnerable، مداوم بررسی و بهروز نگهدارید.
۶. Secrets Management: کلیدهای API، connection stringها و secrets را هرگز در سورسکد قرار ندهید؛ از Azure Key Vault یا AWS Secrets Manager استفاده کنید. در ASP.NET Core:
var builder = new ConfigurationBuilder()
.AddAzureKeyVault(new Uri(keyVaultUri), new DefaultAzureCredential());
در نهایت، امنیت صرفاً یک محصول جانبی نیست؛ بلکه بخشی از فرآیند مهندسی و فرهنگ تیم شماست.
مطلب مرجع و عمیقتر:
https://learn.microsoft.com/en-us/security/devsecops/secure-development-lifecycle
Quality Attributes & Cross-Cutting Concerns
@DeveloperAdvocate 🥑1 035
🧬 بررسی عمیق Generics: کوواریانس، کنترا-واریانس و ریاضیات جنریک در #CSharp
درک دقیق واریانس (کوواریانس و کنترا-واریانس) در generics، نقطه قوت طراحی انعطافپذیر و امن است. بگذارید به این مفاهیم از دریچه اینترفیسها و generic math نگاه کنیم:
🔹 Covariance (
out): امکان تبدیل IEnumerable<Derived> به IEnumerable<Base> را میدهد، زیرا فقط خروجی تعریف میکنید:
IEnumerable<string> strings = new List<string>();
IEnumerable<object> objects = strings; // OK: covariance
🔹 Contravariance (in): مناسب برای پارامترهای ورودی؛ مثلاً یک Action<object> میتواند جایگزین Action<string> باشد:
Action<object> actObj = o => Console.WriteLine(o);
Action<string> actStr = actObj; // OK: contravariance
🔹 نکته کلیدی: واریانس تنها روی اینترفیسها و delegates اعمال میشود؛ روی کلاسها ممکن نیست.
---
⚡️ Generic Math:
.NET 7 با معرفی اینترفیسهایی مانند INumber<T> ریاضیات جنریک را ممکن کرده است؛ انواع عددی مشترک میتوانند بر پایه abstraction یکسان رفتار کنند. نتیجه: الگوریتمهای عددی مستقل از نوع!
static T Add<T>(T a, T b) where T : INumber<T>
=> a + b;
var sum = Add(3, 5); // int
var sum2 = Add(2.1, 3.5); // double
کوواریانس و کنترا-واریانس در تعریف اپراتورها و جبر مجردی با ریاضیات جنریک نقش اساسی دارند – دنیایی از انتزاع و قدرت!
---
🔗 مطالعه بیشتر در Understanding covariance and contravariance in C# (Microsoft Docs)
Advanced C# Language Features
@DeveloperAdvocate 🥑1 035
در معماریهای مدرن، اصرار بر یک دیتابیس برای همهچیز اغلب منجر به مصالحههای سنگین میشود. فلسفه Polyglot Persistence به شما امکان میدهد برای هر نیاز، دیتابیس مناسبش را انتخاب کنید؛ مثلاً ذخیره تراکنشها در SQL برای تضمین ACID، مدیریت پروفایلهای منعطف در Document DBها و مدلسازی روابط پیچیده کاربرها در Graph DB. چالش اصلی، طراحی معماری صحیح برای کاهش تداخل بین دیتابیسها و حفظ انسجام داده است.
راهکار عملی: لایه domain را به گونهای طراحی کنید که هر entity مسئول مدیریت دادهاش در دیتابیس مناسب باشد. پرسوجوی cross-database را به application layer منتقل کنید و با الگوهایی چون CQRS یا Event Sourcing، جداسازی مدل رید و رایت را بهتر مدیریت کنید.
یک نمونه عملی با استفاده از EF Core برای SQL و استفاده از MongoDB.Driver برای مستندات:
// Service Layer Composition (Dependency Injection)
public class UserProfileService
{
private readonly AppDbContext _sqlContext; // EF Core
private readonly IMongoCollection<UserProfileDocument> _mongoCollection;
public UserProfileService(AppDbContext sqlContext, IMongoDatabase mongoDatabase)
{
_sqlContext = sqlContext;
_mongoCollection = mongoDatabase.GetCollection<UserProfileDocument>("UserProfiles");
}
public async Task<UserProfileAggregate> GetUserProfileAsync(Guid userId)
{
// Transactional data from SQL
var user = await _sqlContext.Users.FindAsync(userId);
// Flexible profile data from MongoDB
var profileDoc = await _mongoCollection.Find(p => p.UserId == userId).FirstOrDefaultAsync();
// Example composition
return new UserProfileAggregate(user, profileDoc);
}
}
کلید موفقیت در Polyglot Persistence، انتخاب دقیق Bounded Contextها و طراحی contractها برای همگامسازی داده (مثلاً با پیامرسانی Event-driven). مواظب باشید که دادههای مجزا باعث اتفاقات غیرمنتظره در consistency یا performance نشوند.
مطالعه عمیقتر:
https://martinfowler.com/bliki/PolyglotPersistence.html
Advanced Database Concepts
@DeveloperAdvocate 🥑1 035
در معماریهای مدرن مبتنی بر microservices، وجود یک API Gateway قوی و منعطف حیاتیست. YARP (Yet Another Reverse Proxy) محصول مایکروسافت، یک reverse proxy کاملاً extensible و مبتنی بر .NET است که با حداقل effort امکانات Routing، Load Balancing (حتی sticky session با cookies!) و حتی تغییر dynamic مسیرها را فراهم میکند. YARP را میتوانید مستقیم از NuGet نصب و با کمی پیکربندی YAML یا کد، بهراحتی در اپ خود ادغام کنید.
مثال ساده راهاندازی YARP:
// Program.cs (.NET 6+)
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddReverseProxy()
.LoadFromConfig(builder.Configuration.GetSection("ReverseProxy"));
var app = builder.Build();
app.MapReverseProxy();
app.Run();
و نمونهای از تنظیمات در appsettings.json:
"ReverseProxy": {
"Routes": [
{
"RouteId": "route1",
"ClusterId": "cluster1",
"Match": {
"Path": "/api/{**catch-all}"
}
}
],
"Clusters": {
"cluster1": {
"Destinations": {
"dest1": { "Address": "https://service1.local/" }
},
"LoadBalancingPolicy": "RoundRobin"
}
}
}
ویژگی جذابتر: شما میتوانید بهراحتی قابلیتهایی نظیر authentication, authorization یا custom route transforms را صرفاً با middlewareهای معمول ASP.NET و یا با extension pointهای YARP مثل IReverseProxyTransformProvider پیادهسازی کنید.
برای مطالعه عمیقتر و دیدن مثالهای قابلاستفاده در پروژههای enterprise حتماً این مقالهی رسمی از docs مایکروسافت را بخوانید:
https://learn.microsoft.com/en-us/aspnet/core/fundamentals/reverse-proxy?view=aspnetcore-7.0
ASP.NET Core (Advanced)
@DeveloperAdvocate 🥑1 035
در معماریهای توزیعشده، تراکنشهای سنتی پایگاه داده به راحتی جوابگو نیستند؛ در عوض باید از الگوهایی مانند "تراکنشهای جبرانی" (Compensating Transactions) برای بازگردانی و همسانسازی وضعیت نودها هنگام شکست عملیات استفاده کنیم. این الگو جزو اصول SAGA است و بهویژه زمانی اهمیت دارد که پیامد عملیاتهای توزیعی را نمیتوان با یک rollback ساده خنثی کرد.
فرض کنید در یک فرآیند طولانی سفارش اینترنتی، بعد از ثبت سفارش در سرویس Order، باید موجودی را در سرویس Inventory کاهش دهید. حال اگر در مرحله دوم خطا رخ دهد، باید عملی معکوس (compensate) برای Order اجرا کنید:
// نمونهای ساده از تعریف یک کامند جبرانی
public interface ICommand { }
public class PlaceOrderCommand : ICommand {
public Guid OrderId { get; set; }
}
public class CompensateOrderCommand : ICommand {
public Guid OrderId { get; set; }
}
public class OrderHandler {
public async Task Handle(ICommand command) {
switch (command) {
case PlaceOrderCommand placeCmd:
// عملیات ثبت سفارش
break;
case CompensateOrderCommand compCmd:
// انجام عملیات جبرانی، مثلاً لغو سفارش
break;
}
}
}
نکته کلیدی: هر action باید یک action جبرانی مجزا داشته باشد که بتواند state را semantically خنثی کند (نه صرفاً rollback دیتابیس!). در سطحی پیشرفتهتر، میتوانید با ابزارهایی مانند MassTransit و NServiceBus حتی SAGA State Machine را پیادهسازی کنید.
برای یادگیری عمیقتر، این مقاله Martin Fowler را توصیه میکنم:
https://martinfowler.com/articles/patterns-of-distributed-systems/compensating-transaction.html
Microservices Patterns (Advanced)
@DeveloperAdvocate 🥑1 035
در گفتگو با ذینفعان کسبوکار درباره بدهی فنی، توضیح تأثیر آن بر اهداف تجاری حیاتی است. به جای تمرکز صرف بر اصطلاحات فنی، بهتر است ارتباط مستقیم بدهی فنی با سرعت ارائه فیچرها، هزینه نگهداری و ریسکهای بلندمدت را نشان دهید.
یک تکنیک مؤثر، استفاده از سناریوهای واقعی است: فرض کنید backlog فیچرهای جدید داریم اما هر تغییر ساده چندین روز زمان میبرد، چون کد legacy فاقد تست یا ساختار مناسب است. حتی میتوانید تخمینی از افزایش هزینه توسعه بر اثر بدهی فنی ارائه دهید؛ مثلاً:
// مدلسازی تخمینی تأثیر هزینه بدهی فنی در توسعه فیچرها
public class FeatureDevelopmentCost
{
public int BaseDevelopmentHours { get; set; } // زمان ایدهآل
public double TechnicalDebtMultiplier { get; set; } // ضریب بدهی فنی
public double GetActualDevelopmentHours()
{
return BaseDevelopmentHours * TechnicalDebtMultiplier;
}
}
// مثال: توسعه یک فیچر ساده با بدهی فنی
var cost = new FeatureDevelopmentCost
{
BaseDevelopmentHours = 16,
TechnicalDebtMultiplier = 1.75 // بدهی فنی باعث 75% افزایش زمان شده
};
Console.WriteLine(cost.GetActualDevelopmentHours()); // 28 ساعت
در مکالمات، از متریسهای ملموس مانند Lead Time، تعداد باگهای بحرانی، یا افزایش هزینه DevOps جهت مانیتور اثر بدهی فنی استفاده کنید و تاثیر منفی آن بر تحویل بهموقع فیچرها یا رضایت مشتری را برجسته سازید.
برای راهحل، مدلهای کوتاهمدت (مانند Refactoring تدریجی طی هر Sprint) و بلندمدت (بازنگری معماری یا جداسازی Contextها با الگوی Strangler) را پیشنهاد دهید. همچنین میتوانید "Tech Debt Register" ایجاد کرده و هر آیتم را براساس معیارهایی مثل ارزش کسبوکاری و هزینه حل اولویتبندی کنید.
مقاله پیشنهادی برای مطالعه بیشتر:
https://martinfowler.com/articles/technicalDebtQuantification.html
Technical Leadership & Mentoring
@DeveloperAdvocate 🥑1 035
🚩 وقتی از فایل آپلود صحبت میکنیم، یعنی حملهگرترین سطح حملات سطح اپلیکیشن را باز کردیم؛ بخصوص اگر validate و sanitize نکنیم. در .NET برای جلوگیری از Malicious File Injection باید چند لایه دفاع در نظر بگیریم:
۱. محدودیت نوع فایل: فقط MIME و پسوند را چک نکنید! با این کد، MIME را validate میکنیم و حتی میتوانیم محتوا را هم بررسی کنیم (Sniffing):
public static bool IsValidFile(IFormFile file, IEnumerable<string> allowedMimeTypes)
{
if (file == null || file.Length == 0)
return false;
if (!allowedMimeTypes.Contains(file.ContentType))
return false;
var extension = Path.GetExtension(file.FileName);
var validExtensions = new[] { ".jpg", ".jpeg", ".png", ".pdf" };
if (!validExtensions.Contains(extension.ToLowerInvariant()))
return false;
// محتوا (magic number) چک شود
if (extension.Equals(".jpg", StringComparison.OrdinalIgnoreCase))
{
using var reader = new BinaryReader(file.OpenReadStream());
var first2Bytes = reader.ReadBytes(2);
// JPEG: FF D8
if (first2Bytes[0] != 0xFF || first2Bytes[1] != 0xD8)
return false;
}
return true;
}
۲. محدودیت سایز: فایل بزرگ، سرور را به زانو درمیآورد. همیشه سایز را محدود کنید. در ASP.NET Core حداکثر حجم را به روش زیر تعیین کنید:
services.Configure<FormOptions>(o => {
o.MultipartBodyLengthLimit = 5 * 1024 * 1024; // 5MB
});
۳. نوشتن فایل در فضای جدا: هیچگاه فایل را در wwwroot یا public-access directory ننویسید. قبل از ذخیره، یک نام تصادفی امن بدهید:
var safeFileName = Path.GetRandomFileName() + Path.GetExtension(file.FileName);
۴. نخواندن فایل به صورت مستقیم امنتر است: فایلهایی مثل PDF را اگر میخواهید preview کنید، هرگز با iframe به صورت مستقیم به یوزر نشان ندهید. استریم کنید و Content-Type را خودتان تنظیم کنید تا آسیب XSS و Content Sniffing نبینید.
۵. ویروسیابی: استفاده از سرویسهایی مثل ClamAV یا Microsoft Security Scan میتواند لایه دفاع نهایی باشد.
✉️ جمعبندی: آپلود امن، ترکیبی از validation، isolation و defense-in-depth است—هر کوتاهی مساوی است با backdoor شدن سرور.
مطالعه بیشتر:
Upload files safely with ASP.NET Core (Microsoft Docs)
Application Security (AppSec)
@DeveloperAdvocate 🥑1 035
در معماریهای مدرن .NET، یکپارچهسازی Logging ساختاریافته و انتقال رویدادها به SIEM (مانند Splunk، Azure Sentinel یا Elastic SIEM) برای کشف و پاسخ به رخدادهای امنیتی حیاتی است. نکته کلیدی: هر لاگ اپلیکیشن باید فراداده (metadata) غنی، سطوح severity و context امنیتی قابل جستجو داشته باشد—این یعنی تقریباً هر لاگ باید شامل کاربر مرتبط، action، IP یا claim مهم و یک Correlation Id یکتا باشد. استفاده از ساختار JSON لازمهی تجزیهٔ اتوماتیک در SIEMهاست.
در .NET با Serilog و sink لازم (مثلاً sink مخصوص Splunk یا Elasticsearch)، میتوانید جریان لاگها را به کل اکوسیستم SIEM بفرستید:
using Serilog;
Log.Logger = new LoggerConfiguration()
.Enrich.FromLogContext()
.Enrich.WithProperty("Application", "MyCriticalAPI")
.WriteTo.Console()
.WriteTo.Elasticsearch(new ElasticsearchSinkOptions(new Uri("https://elasticsearch:9200"))
{
AutoRegisterTemplate = true,
IndexFormat = "applogs-{0:yyyy.MM.dd}"
})
.CreateLogger();
// نمونه لاگ همراه با متادیتای امنیتی و CorrelationId
using (LogContext.PushProperty("UserId", currentUser.Id))
using (LogContext.PushProperty("IPAddress", currentRequest.IP))
using (LogContext.PushProperty("CorrelationId", correlationId))
{
Log.Information("User authenticated via MFA");
}
همچنین توصیه میشود تمام دسترسیهای حساس (Privilege Escalation، ویرایشها، لاگین/لاگآوت و غیره) با الگوهای ساختاریافته و کلیدواژههای قابل جستجو لاگ شوند تا بتوان براحتی در SIEM روی آنها Rule تعریف کرد (مثل تشخیص brute-force یا رفتار مشکوک).
✅ برای آشنایی عمیقتر با یکپارچهسازی لاگهای اپلیکیشن با SIEM و رعایت Best Practiceهای امنیتی، مستندات رسمی مایکروسافت را مطالعه کنید:
https://learn.microsoft.com/en-us/azure/architecture/example-scenario/logging/dotnet-application-logs-siem
#معماری #Security #SIEM #Logging #BestPractices
Application Security (AppSec)
@DeveloperAdvocate 🥑