آموزش صفر تا صد 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 در داتنت هست.
۳ اشتباه رایج که باید ازشون پرهیز کنی:
- استفاده از
.Resultیا.Wait()در متدهای غیرهمزمان (async) که باعث Deadlock میشه. - اجرای کارهای زمانبر (مثل ارسال ایمیل) داخل کنترلر به جای Background Queue.
- ترتیب اشتباه در ثبت Middlewareها (مثلاً ثبت Exception Handler در انتهای لوله).
اگر فقط ۵ دقیقه وقت داری، اینها رو یاد بگیر:
- (مهمترین مفهوم) ساختار Pipeline و Middleware — جریان درخواستها.
- (پرتکرارترین دستور)
app.Run(async (context) => { ... }); - (کلید شروع کار)
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 کنی.
۸. اشتباهات رایج
استفاده از
Thread.Sleepبه جایTask.Delay:
❌Thread.Sleep(5000);-> بلاک کردن یک Thread از Thread Pool سرور.
✅await Task.Delay(5000);-> آزاد کردن Thread برای پردازش سایر درخواستها.تلاش برای استفاده از DI در Middleware با تابع معمولی:
Middlewareهایی که باapp.Useنوشته میشن به سختی میتونن از سرویسهای Scoped استفاده کنن. بهتره Middlewareها رو به صورت کلاسمحور (Class-based) بنویسی.نادیده گرفتن
CancellationToken:
وقتی سرور در حال خاموش شدن است، اگه تو درخواستهای طولانیCancellationTokenرو چک نکنی، پردازش تو وسط کار نصفه میمونه و دیتا خراب میشه.
۹. بهترین روشها (Best Practices)
- همیشه Pipeline رو کوتاه نگه دار. Middlewareهای اضافی باعث کاهش Performance میشن.
- از
IHostedServiceبرای کارهای زمانبر در شروع/توقف استفاده کن. - از
BackgroundServiceبرای پردازشهای مداوم پسزمینه استفاده کن. - در nod慎重考虑: همیشه
awaitرو فراموش نکن. یکasync voidیا فراموش کردنawaitمیتونه باعث رفتار غیرقابل پیشبینی در Process بشه.
۱۰. منابع و ادامه مسیر
- مستندات رسمی مایکروسافت - ASP.NET Core Fundamentals
- مفهوم Middleware در داتنت
- کار با Hosted Services
۱۱. مرجع کامل توابع و متدها (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:
- استفاده از
async/awaitدر همهجا: این به سرور اجازه میده Threadها رو برای درخواستهای دیگه آزاد کنه. - تنظیم
ThreadPool: اگه برنامهت بار IO بالا داره، میتونی باSetMinThreadsدرProgram.csحداقل Threadهای ویندوز رو بالا ببری تا زمان Warmup سرور کم بشه. - جلوگیری از نشتی حافظه (Memory Leaks): همیشه
IDisposableسرویسها رو به درستی پاک کن. سرویسهای Transient رو با احتیاط استفاده کن. - Response Caching: برای مسیرهایی که زیاد صدا زده میشن، Middleware کَش کردن (Caching) رو در Pipeline قرار بده تا پردازش کنترلر اصلاً اجرا نشه.
۱۵. چکلیست یادگیری
با این چکلیست مطمئن شو که همه چیز رو یاد گرفتی:
- میتونم توضیح بدم که
Program.csدقیقاً چه کاری انجام میده. - فرق بین
app.Useوapp.Runرو بلدم. - میتونم یک
BackgroundServiceبنویسم و درProgram.csثبت کنم. - میدونم
CancellationTokenچیه و چطور ازش برای توقف پردازش استفاده کنم. - راهاندازی لوله پردازش (Middleware Order) رو به ترتیب بلدم (مثلاً Exception Handler اول میاد).
- خطر استفاده از
.Resultدر async رو درک کردم.