🔬 آموزش صفر تا صد Process در ASP.NET Core (آموزش عمیق)

process ,asp.net core همه سطوح
parsakarimidev.ir

🔬 آموزش صفر تا صد Process در ASP.NET Core (آموزش عمیق)

process ,asp.net coreفریم‌ورک سطح: همه سطوح 1405/07/14

آموزش صفر تا صد Process در ASP.NET Core

⚡ خلاصه سریع — اصل کاری‌ها

این‌ها مهم‌ترین چیزهایی هستن که اگه هیچی ندونی، فقط اینا رو بدونی کافیه:

  • نکته ۱: برنامه ASP.NET Core در واقع یک اپلیکیشن کنسول است که با WebApplication شروع می‌شه.
  • نکته ۲: تمام درخواست‌ها از یک لوله (Pipeline) به نام Middleware رد می‌شن.
  • نکته ۳: پردازش‌های پس‌زمینه باید با BackgroundService انجام بشن تا سرور بلاک نشه.
  • نکته ۴: همیشه از async و await استفاده کن تا Thread اصلی سرور آزاد بمونه.
  • نکته ۵: Run() پردازش رو متوقف می‌کنه و جلوی اجرای بقیه کدها رو می‌گیره.
  • نکته ۶: Use() دو پارامتر داره: یکی برای ورود به لوله، یکی برای خروج (next).
  • نکته ۷: Map() مسیرهای جداگانه برای لوله پردازش ایجاد می‌کنه.
  • نکته ۸: DI Container مسئول ساختن و پاک کردن سرویس‌ها در طول Process هست.
  • نکته ۹: IHostApplicationLifetime برای مدیریت شروع و توقف برنامه استفاده می‌شه.
  • نکته ۱۰: کلاس Program.cs نقطه شروع و پایان کل Process در دات‌نت هست.

۳ اشتباه رایج که باید ازشون پرهیز کنی:

  1. استفاده از .Result یا .Wait() در متدهای غیرهمزمان (async) که باعث Deadlock می‌شه.
  2. اجرای کارهای زمان‌بر (مثل ارسال ایمیل) داخل کنترلر به جای Background Queue.
  3. ترتیب اشتباه در ثبت Middlewareها (مثلاً ثبت Exception Handler در انتهای لوله).

اگر فقط ۵ دقیقه وقت داری، این‌ها رو یاد بگیر:

  1. (مهم‌ترین مفهوم) ساختار Pipeline و Middleware — جریان درخواست‌ها.
  2. (پرتکرارترین دستور) app.Run(async (context) => { ... });
  3. (کلید شروع کار) dotnet new web و باز کردن Program.cs.

۱. مقدمه

  • این موضوع چیه؟
    وقتی یک برنامه ASP.NET Core رو اجرا می‌کنی، اتفاقی که می‌افته اینه که یک "پردازش" (Process) سیستمی شروع می‌شه. این پردازش شامل راه‌اندازی وب‌سرور (مثل Kestrel)، خواندن تنظیمات، آماده‌سازی لوله درخواست‌ها (Middleware Pipeline) و گوش دادن به پورت‌های شبکه است. درک Process یعنی بدونیم دات‌نت چطور یک درخواست HTTP رو می‌گیره، پردازش می‌کنه و جواب رو برمی‌گردونه.

  • چرا باید یادش بگیری؟
    اگه فقط کنترلر بنویسی، برنامه‌ت کار می‌کنه؛ اما وقتی مشکلات جدی پیش میاد (مثل کندی سرور، هنگ کردن برنامه، نشتی حافظه، یا نیاز به اجرای کارهای زمان‌بر مثل پاکسازی دیتابیس)، بدون درک Process گیج می‌شی. این دانش تفاوت یک برنامه‌نویس مبتدی و مهندس ارشد رو مشخص می‌کنه.

  • کجا استفاده می‌شه؟
    در هر جایی که ASP.NET Core اجرا بشه! از پروژه‌های کوچک API گرفته تا میکروسرویس‌های بزرگ سازمانی.

۲. پیش‌نیازها

  • آشنایی پایه با زبان #C (متدها، کلاس‌ها، و async/await).
  • نصب .NET SDK (نسخه ۸ یا بالاتر پیشنهاد می‌شه).
  • یک ویرایشگر کد (Visual Studio یا VS Code).

۳. نصب و راه‌اندازی

برای شروع، یک پروژه خالی API بساز:

dotnet new web -n ProcessDemo
cd ProcessDemo
dotnet run

اولین پروژه شما! فایل Program.cs رو باز کن. این فایل، نقطه شروع Process شماست.

۴. مفاهیم پایه

پردازش در ASP.NET Core روی ۳ پایه استوار است:

۱. وب‌سرور (Kestrel):
دات‌نت خودش یک وب‌سرور داخلی داره به نام Kestrel. وقتی dotnet run می‌زنی، Kestrel روی یک پورت (مثل ۵۰۰۰) شروع به گوش دادن می‌کنه.

۲. لوله درخواست (Request Pipeline / Middleware):
هر درخواستی که از مرورگر میاد، وارد یک لوله می‌شه. تو می‌تونی تیکه‌هایی (Middleware) به این لوله اضافه کنی که درخواست رو بخون، تغییر بده، یا جواب بده.

۳. تزریق وابستگی (DI):
برای اینکه Process سبک بمونه، دات‌نت یه سیستم داره که سرویس‌ها رو موقع نیاز می‌سازه و بعد از استفاده نابود می‌کنه.

مثال ساده Program.cs:

var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();

app.MapGet("/", () => "Hello, Process!");

app.Run();

توضیح: builder.Build() پردازش رو آماده می‌کنه. app.MapGet یک مسیر ثبت می‌کنه. app.Run() وب‌سرور رو استارت می‌زنه و پردازش رو روی آن پورت قفل می‌کنه.

۵. مثال‌های کد ساده

برای درک جریان (Flow) در Process، بیایم Middlewareها رو بررسی کنیم:

var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();

// Middleware اول
app.Use(async (context, next) =>
{
    Console.WriteLine("-> ورود به لوله");
    await next(); // فراخوانی میان‌افزار بعدی
    Console.WriteLine("<- خروج از لوله");
});

// Middleware دوم
app.Run(async (context) =>
{
    Console.WriteLine("  پردازش درخواست");
    await context.Response.WriteAsync("سلام از داخل Process!");
});

app.Run();

توضیح: وقتی درخواستی میاد، اول "ورود به لوله" چاپ می‌شه، بعد کنترل به Middleware بعدی می‌ره (next)، آنجا "پردازش درخواست" چاپ می‌شه و جواب برمی‌گرده، سپس کنترل برمی‌گرده و "خروج از لوله" چاپ می‌شه.

۶. مثال واقعی و کاربردی

سناریو: می‌خوایم وقتی سرور استارت شد، یک پردازش پس‌زمینه (Background Process) اجرا بشه که هر ۱۰ ثانیه دیتابیس رو چک کنه.

اول کلاس Background Service رو می‌نویسیم:

public class DatabaseCheckerService : BackgroundService
{
    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    {
        while (!stoppingToken.IsCancellationRequested)
        {
            Console.WriteLine("Checking DB at: " + DateTime.Now);
            // کار دیتابیس اینجا
            await Task.Delay(10000, stoppingToken); // صبر ۱۰ ثانیه‌ای
        }
    }
}

حالا در Program.cs ثبتش می‌کنیم:

var builder = WebApplication.CreateBuilder(args);

// ثبت پردازش پس‌زمینه
builder.Services.AddHostedService<DatabaseCheckerService>();

var app = builder.Build();
app.Run();

توضیح: وقتی app.Run() صدا زده می‌شه، دات‌نت به طور خودکار ExecuteAsync کلاس بالا رو اجرا می‌کنه و تا زمانی که سرور قطع نشه (Ctrl+C)، این پردازش در پس‌زمینه به کارش ادامه می‌ده.

۷. مباحث پیشرفته

مدیریت طول عمر برنامه (Application Lifetime):
گاهی نیاز داری بدونی کِی سرور در حال خاموش شدن است تا منابع رو آزاد کنی. دات‌نت از IHostApplicationLifetime برای این کار استفاده می‌کنه.

var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();

var lifetime = app.Lifetime;
lifetime.ApplicationStopping.Register(() =>
{
    Console.WriteLine("سرور در حال خاموش شدن است، منابع آزاد شدند!");
});

app.Run();

توضیح: ApplicationStopping رویدادی است که قبل از بسته شدن کامل Process صدا زده می‌شه. اینجا می‌تونی دیتابیس رو Close کنی یا لاگ‌ها رو Flush کنی.

۸. اشتباهات رایج

  1. استفاده از Thread.Sleep به جای Task.Delay:
    ❌ Thread.Sleep(5000); -> بلاک کردن یک Thread از Thread Pool سرور.
    ✅ await Task.Delay(5000); -> آزاد کردن Thread برای پردازش سایر درخواست‌ها.

  2. تلاش برای استفاده از DI در Middleware با تابع معمولی:
    Middlewareهایی که با app.Use نوشته می‌شن به سختی می‌تونن از سرویس‌های Scoped استفاده کنن. بهتره Middlewareها رو به صورت کلاس‌محور (Class-based) بنویسی.

  3. نادیده گرفتن CancellationToken:
    وقتی سرور در حال خاموش شدن است، اگه تو درخواست‌های طولانی CancellationToken رو چک نکنی، پردازش تو وسط کار نصفه می‌مونه و دیتا خراب می‌شه.

۹. بهترین روش‌ها (Best Practices)

  • همیشه Pipeline رو کوتاه نگه دار. Middlewareهای اضافی باعث کاهش Performance می‌شن.
  • از IHostedService برای کارهای زمان‌بر در شروع/توقف استفاده کن.
  • از BackgroundService برای پردازش‌های مداوم پس‌زمینه استفاده کن.
  • در nod慎重考虑: همیشه await رو فراموش نکن. یک async void یا فراموش کردن await می‌تونه باعث رفتار غیرقابل پیش‌بینی در Process بشه.

۱۰. منابع و ادامه مسیر


۱۱. مرجع کامل توابع و متدها (API Deep Dive)

در این بخش، مهم‌ترین متدها و کلاس‌هایی که Process رو در ASP.NET Core کنترل می‌کنن رو با جزئیات کامل بررسی می‌کنیم.

📌 نام متد: WebApplication.Run()

امضای متد (Signature):

public void Run(string? url = null);

ورودی‌ها (Parameters):

نام پارامتر نوع (Type) اجباری؟ توضیح دقیق مثال مقدار
url string? خیر آدرس URL که سرور باید روی آن گوش دهد. اگر null باشد، از تنظیمات (appsettings) استفاده می‌کند. "http://localhost:5000"

مقدار برگشتی (Return Value):

  • نوع: void
  • توضیح: چیزی برنمی‌گردونه، اما این متد روند اجرای متد Main رو بلاک (Block) می‌کنه تا زمانی که سرور خاموش بشه.

کاری که انجام می‌ده (گام به گام):
۱. سرور وب (Kestrel) رو راه‌اندازی می‌کنه و پورت‌ها رو باز می‌کنه.
۲. IHost رو استارت می‌زنه (شروع سرویس‌ها و Background Services).
۳. روند اجرای رو متوقف می‌کنه و در حالت گوش دادن به درخواست‌ها باقی می‌مونه.

مثال ساده و قابل اجرا:

var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.MapGet("/", () => "Server Running");
app.Run(); // اینجا سرور شروع به کار می‌کنه
Console.WriteLine("این خط بعد از خاموش شدن سرور چاپ می‌شه!");

خروجی:

info: Microsoft.Hosting.Lifetime[14]
      Now listening on: http://localhost:5000

مثال واقعی و کاربردی:

// اجرای سرور روی یک پورت خاص فارغ از تنظیمات فایل
app.Run("http://0.0.0.0:8080");

خطاها و Exceptions احتمالی:

Exception زمان رخ دادن راه‌حل
IOException پورت مورد نظر در حال استفاده توسط برنامه دیگه باشه پورت رو عوض کن یا برنامه‌ای که پورت رو اشغال کرده ببند.
InvalidOperationException سرور قبلاً استارت شده باشه یا کانفیگ نامعتبر باشه بررسی صحت فایل appsettings.json و کدهای استارت‌آپ.

Overloadها:

// بدون پارامتر - از تنظیمات پیش‌فرض استفاده می‌کنه
public void Run(); 

// با پارامتر URL
public void Run(string? url);

اشتباهات رایج:

  • ❌ نوشتن کدهای تنظیمات بعد از app.Run() -> ✅ تمام تنظیمات باید قبل از Run() باشن.
  • ❌ استفاده از app.RunAsync().Wait() -> ✅ از app.Run() همونطور که هست استفاده کن.

متدهای مرتبط:

  • StartAsync(): سرور رو استارت می‌کنه ولی بلاک نمی‌کنه.
  • StopAsync(): سرور رو متوقف می‌کنه.

📌 نام متد: IApplicationBuilder.Use()

امضای متد (Signature):

public IApplicationBuilder Use(Func<HttpContext, Func<Task>, Task> middleware);

ورودی‌ها (Parameters):

نام پارامتر نوع (Type) اجباری؟ توضیح دقیق مثال مقدار
middleware Func<HttpContext, Func<Task>, Task> بله یک تابع که کانتکست درخواست و تابع بعدی رو می‌گیره و Task برمی‌گردونه. async (context, next) => { await next(); }

مقدار برگشتی (Return Value):

  • نوع: IApplicationBuilder
  • توضیح: همون\Builder برمی‌گرده تا بتونی متدها رو زنجیروار (Chain) کنی.

کاری که انجام می‌ده (گام به گام):
۱. یک Middleware به لوله (Pipeline) اضافه می‌کنه.
۲. تابع ورودی رو در کلاینت Stack ثبت می‌کنه.
۳. هنگام درخواست، این تابع رو صدا می‌زنه و دو پارامتر (HttpContext و next) بهش پاس می‌ده.

مثال ساده و قابل اجرا:

app.Use(async (context, next) =>
{
    context.Response.Headers.Add("X-Custom-Header", "MyValue");
    await next.Invoke(); // ادامه لوله
});

مثال واقعی و کاربردی:
ثبت یک میان‌افزار برای لاگ‌گیری زمان اجرای درخواست:

public static class RequestTimingMiddlewareExtensions
{
    public static IApplicationBuilder UseRequestTiming(this IApplicationBuilder builder)
    {
        return builder.Use(async (context, next) =>
        {
            var watch = Stopwatch.StartNew();
            await next();
            watch.Stop();
            Console.WriteLine($"Request took {watch.ElapsedMilliseconds} ms");
        });
    }
}

خطاها و Exceptions احتمالی:

Exception زمان رخ دادن راه‌حل
InvalidOperationException فراموش کردن await next() در مسیرهایی که نیاز به ادامه داره همیشه await next(); رو در پایان Middleware بنویس.

Overloadها:

// نسخه ساده‌تر که فقط HttpContext رو می‌فرسته (برای مواقعی که next نیاز نیست)
// در واقع این UseFunc است.

اشتباهات رایج:

  • ❌ صدا نزدن await next() -> ✅ باعث می‌شه لوله کوتاه بشه و هیچ مسیری بعدش اجرا نشه.

متدهای مرتبط:

  • Run(): یک Middleware ترمینال (پایانی) اضافه می‌کنه (next نداره).
  • Map(): شاخه‌سازی (Branching) لوله.

📌 نام متد: BackgroundService.ExecuteAsync()

امضای متد (Signature):

protected abstract Task ExecuteAsync(CancellationToken stoppingToken);

ورودی‌ها (Parameters):

نام پارامتر نوع (Type) اجباری؟ توضیح دقیق مثال مقدار
stoppingToken CancellationToken بله توکنی که برای توقف پایدار (Graceful Shutdown) استفاده می‌شه. وقتی سرور خاموش می‌شه، این Token کنسل می‌شه. stoppingToken.ThrowIfCancellationRequested()

مقدار برگشتی (Return Value):

  • نوع: Task
  • توضیح: یک Task که نمایانگر پردازش پس‌زمینه است. تا زمانی که این Task کامل نشده، سرویس در حال اجراست.

کاری که انجام می‌ده (گام به گام):
۱. این متد توسط Host زمانی که Run صدا زده می‌شه، اجرا می‌شه.
۲. پردازش‌های مداوم پس‌زمینه رو اجرا می‌کنه.
۳. با لغو شدن stoppingToken، کارش رو متوقف می‌کنه.

مثال ساده و قابل اجرا:

public class MyBackgroundService : BackgroundService
{
    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    {
        while (!stoppingToken.IsCancellationRequested)
        {
            // کاری که هر ۵ ثانیه انجام می‌شه
            await Task.Delay(5000, stoppingToken);
        }
    }
}

مثال واقعی و کاربردی:
پردازش یک صف (Queue) از ایمیل‌ها:

public class EmailQueueProcessor : BackgroundService
{
    private readonly IBackgroundTaskQueue _queue;
    public EmailQueueProcessor(IBackgroundTaskQueue queue) => _queue = queue;

    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    {
        while (!stoppingToken.IsCancellationRequested)
        {
            var emailTask = await _queue.DequeueAsync(stoppingToken);
            try
            {
                await SendEmailAsync(emailTask); // ارسال ایمیل
            }
            catch (Exception ex)
            {
                // مدیریت خطا
            }
        }
    }
}

خطاها و Exceptions احتمالی:

Exception زمان رخ دادن راه‌حل
TaskCanceledException وقتی سرور خاموش می‌شه و در وسط Task.Delay هست. این طبیعیه؛ یا Catch کن یا طبق مثال از stoppingToken استفاده کن.
InvalidOperationException اگه متد همزمان (Sync) باشه و Task برنمی‌گردونه. حتماً async Task تعریف کن.

Overloadها:

  • نداره چون abstract است. اما می‌تونی StartAsync و StopAsync رو Override کنی.

اشتباهات رایج:

  • ❌ اجرای کد بلوکه‌کننده مثل Thread.Sleep در این متد -> ✅ باعث می‌شه کل Thread Pool سرور درگیر بشه.

متدهای مرتبط:

  • StartAsync: قبل از ExecuteAsync صدا زده می‌شه.
  • StopAsync: موقع توقف سرور صدا زده می‌شه.

۱۲. مقایسه کلاس‌ها/توابع مشابه

مقایسه Run vs Use vs Map در Pipeline:

ویژگی Use Run Map
توضیح اضافه‌کردن میان‌افزاری که درخواست رو به مرحله بعدی پاس می‌ده. اضافه‌کردن میان‌افزار پایانی که جواب رو می‌ده و تموم می‌کنه. ایجاد یک شاخه جدید از لوله اصلی بر اساس مسیر (Path).
پارامترها (context, next) (context) (pathMatch, branch)
ادامه Pipeline بله، با next() خیر، این آخرین نقطه است. بله، داخل شاخه جدید.
کاربرد برای لاگ، احراز هویت، تغییر هدر. برای تولید مستقیم Response یا خطای ۴۰۴. برای جداسازی منطق مسیرهای مختلف.

۱۳. سناریوهای واقعی (Real-World Scenarios)

سناریو ۱: خاموش کردن نرم (Graceful Shutdown) در داکر (Docker)
تو داکر وقتی docker stop می‌زنی، داکر به برنامه SIGTERM می‌فرسته. در دات‌نت این باعث می‌شه IHostApplicationLifetime.ApplicationStopping صدا زده بشه. اگه یه پردازش طولانی داری (مثل پردازش ویدیو)، باید چک کنی که سرور داره خاموش می‌شه تا دیتاها رو کامل نری و سیو کنی.

سناریو ۲: صف‌بندی پردازش‌ها (Task Queue)
یوزر یه فایل اکسل بزرگ آپلود می‌کنه و می‌خواد دیتابیس آپدیت بشه. اگه این کار رو داخل درخواست (Request) انجام بدی، کلاینت باید دقیقاً منتظر بمونه (Sync). راه درست اینه که: فایل سیو بشه، درخواست فوراً با Response "در حال پردازش" برگشته (۲۰۲ Accepted)، و پردازش واقعی به یک BackgroundService پاس داده بشه.


۱۴. کارایی و Performance

نکات کلیدی برای افزایش سرعت Process در ASP.NET Core:

  1. استفاده از async/await در همه‌جا: این به سرور اجازه می‌ده Threadها رو برای درخواست‌های دیگه آزاد کنه.
  2. تنظیم ThreadPool: اگه برنامه‌ت بار IO بالا داره، می‌تونی با SetMinThreads در Program.cs حداقل Threadهای ویندوز رو بالا ببری تا زمان Warmup سرور کم بشه.
  3. جلوگیری از نشتی حافظه (Memory Leaks): همیشه IDisposable سرویس‌ها رو به درستی پاک کن. سرویس‌های Transient رو با احتیاط استفاده کن.
  4. Response Caching: برای مسیرهایی که زیاد صدا زده می‌شن، Middleware کَش کردن (Caching) رو در Pipeline قرار بده تا پردازش کنترلر اصلاً اجرا نشه.

۱۵. چک‌لیست یادگیری

با این چک‌لیست مطمئن شو که همه چیز رو یاد گرفتی:

  • می‌تونم توضیح بدم که Program.cs دقیقاً چه کاری انجام می‌ده.
  • فرق بین app.Use و app.Run رو بلدم.
  • می‌تونم یک BackgroundService بنویسم و در Program.cs ثبت کنم.
  • می‌دونم CancellationToken چیه و چطور ازش برای توقف پردازش استفاده کنم.
  • راه‌اندازی لوله پردازش (Middleware Order) رو به ترتیب بلدم (مثلاً Exception Handler اول میاد).
  • خطر استفاده از .Result در async رو درک کردم.

Rejoining the server...

Rejoin failed... trying again in seconds.

Failed to rejoin.
Please retry or reload the page.

The session has been paused by the server.

Failed to resume the session.
Please retry or reload the page.