🔬 آموزش جامع .NET Aspire از صفر تا صد (آموزش عمیق)

Aspire.net همه سطوح
parsakarimidev.ir

🔬 آموزش جامع .NET Aspire از صفر تا صد (آموزش عمیق)

Aspire.netفریم‌ورک سطح: همه سطوح 1405/07/03

آموزش جامع .NET Aspire از صفر تا صد

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

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

  • نکته ۱: .NET Aspire یک لایه ارکستراسیون و زیرساخت‌محور برای اپلیکیشن‌های ابری و میکروسرویسی در .NET است.
  • نکته ۲: AppHost پروژه اصلی Aspire است که منابع را تعریف می‌کند: پروژه‌ها، کانتینرها، Redis، SQL Server و...
  • نکته ۳: ServiceDefaults سرویس‌ها را با Health Check، OpenTelemetry و Resilience استاندارد می‌کند.
  • نکته ۴: WithReference اتصال بین سرویس‌ها را با تزریق خودکار Connection String برقرار می‌کند.
  • نکته ۵: Dashboard هنگام اجرا، وضعیت منابع، لاگ‌ها، متریک‌ها و trace ها را نمایش می‌دهد.
  • نکته ۶: Docker Desktop برای اجرای منابع کانتینری محلی مثل Redis و SQL Server لازم است.
  • نکته ۷: dotnet workload install aspire اولین قدم نصب رسمی Aspire است.
  • نکته ۸: برای شروع می‌توانی از قالب dotnet new aspire-starter استفاده کنی.

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

  1. اجرا کردن پروژه بدون Docker Desktop و سپس تعجب از خطای Redis/SQL Server.
  2. فراموش کردن MapDefaultEndpoints() وقتی از AddServiceDefaults() استفاده می‌کنی.
  3. استفاده از پورت ثابت دستی برای سرویس‌ها به‌جای اینکه بذاری Aspire پورت‌ها را مدیریت کند.

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

  1. AppHost همان ارکستراتور اصلی است.
  2. dotnet workload install aspire نصب را انجام بده.
  3. dotnet new aspire-starter -n MyApp && dotnet run پروژه را بساز و اجرا کن.

۱. مقدمه

.NET Aspire چیست؟

.NET Aspire یک فریم‌ورک جدید از خانواده .NET است که برای ساخت اپلیکیشن‌های توزیع‌شده، میکروسرویسی و ابرمحور طراحی شده. به زبان ساده، Aspire به شما کمک می‌کند:

  • چند سرویس مختلف را در یک راه‌حل تعریف کنی.
  • وابستگی‌ها مانند Redis، SQL Server، RabbitMQ، PostgreSQL و... را به‌صورت خودکار راه‌اندازی کنی.
  • Connection String و سرویس‌دیسکاوری را بین سرویس‌ها به‌صورت خودکار مدیریت کنی.
  • یک Dashboard برای مشاهده لاگ‌ها، خطاها، متریک‌ها و trace ها داشته باشی.
  • در نهایت همه چیز را برای استقرار روی Azure Container Apps یا Kubernetes آماده کنی.

پس Aspire یک "ارکستراتور توسعه و استقرار" است، نه فقط یک کتابخانه.

چرا باید یادش بگیری؟

در دنیای واقعی، وقتی چند سرویس داری، مشکلات زیر پیش می‌آید:

  • هر سرویس باید جداگانه اجرا شود.
  • باید Redis، SQL و... دستی با Docker اجرا شوند.
  • Connection Stringها باید دستی set شوند.
  • دیباگ و ردیابی خطا بین سرویس‌ها سخت است.

Aspire این مشکلات را خیلی کمتر می‌کند. تجربه توسعه شبیه اجرای یک پروژه واحد می‌شود، ولی زیرش یک سیستم توزیع‌شده واقعی است.

کجا استفاده می‌شود؟

  • ساخت میکروسرویس‌ها
  • اپلیکیشن‌های ابری آماده استقرار در Azure
  • سیستم‌های رویدادمحور با RabbitMQ یا Kafka
  • پروژه‌های چندسرویسی که از Redis Cache و SQL استفاده می‌کنند
  • تیم‌هایی که می‌خواهند توسعه محلی استاندارد و سریع داشته باشند

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

قبل از شروع، این‌ها را باید داشته باشی یا بدانی:

دانش قبلی

  • زبان C# و فریم‌ورک ASP.NET Core
  • مفاهیم ابتدایی Dependency Injection
  • آشنایی اولیه با Docker
  • آشنایی ابتدایی با REST API و Microservices
  • آشنایی ابتدایی با Configuration و Connection String در .NET

ابزارهای لازم

ابزار چرا لازم است حداقل نسخه
.NET SDK اجرای پروژه‌های Aspire 8 یا 9
Docker Desktop اجرای کانتینرهای Redis، SQL، RabbitMQ آخرین نسخه
Visual Studio 2022 توسعه راحت‌تر 17.9 به بالا
یا VS Code + C# Dev Kit توسعه سبک آخرین نسخه
Git مدیریت سورس و قالب‌ها آخرین نسخه

اگه Docker Desktop روی سیستم نصب نباشد، بخش‌هایی که نیاز به Redis یا SQL دارند اجرا نمی‌شوند.

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

گام ۱: نصب Workload رسمی Aspire

ترمینال را باز کن و دستور زیر را بزن:

dotnet workload update
dotnet workload install aspire

با این کار قالب‌ها و ابزارهای CLI مربوط به Aspire نصب می‌شوند.

گام ۲: ایجاد اولین پروژه

dotnet new aspire-starter -n MyFirstAspireApp
cd MyFirstAspireApp

ساختار پروژه به این شکل است:

MyFirstAspireApp.sln
├── MyFirstAspireApp.AppHost         // ارکستراتور اصلی
├── MyFirstAspireApp.ServiceDefaults // تنظیمات مشترک سرویس‌ها
├── MyFirstAspireApp.ApiService      // یک Web API ساده
└── MyFirstAspireApp.Web             // یک فرانتاند ساده

گام ۳: اجرای پروژه

dotnet run --project MyFirstAspireApp.AppHost

وقتی اجرا کنی:

  • AppHost بالا می‌آید.
  • سرویس‌های تعریف‌شده اجرا می‌شوند.
  • Dashboard روی آدرس https://localhost:15887 یا http://localhost:15888 باز می‌شود.
  • می‌توانی وضعیت هر منبع را به‌صورت زنده ببینی.

گام ۴: بررسی Dashboard

در Dashboard می‌توانی ببینی:

  • کدام منابع Running هستند.
  • لاگ هر سرویس به‌صورت زنده
  • trace مربوط به درخواست‌ها
  • متریک‌های CPU و Memory

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

AppHost چیست؟

پروژه AppHost همان قلب Aspire است. در فایل Program.cs این پروژه، منابع مختلف را تعریف می‌کنی:

var builder = DistributedApplication.CreateBuilder(args);

var cache = builder.AddRedis("cache");

var api = builder.AddProject<Projects.ApiService>("api")
    .WithReference(cache);

builder.Build().Run();

اینجا:

  • DistributedApplication.CreateBuilder ارکستراتور را می‌سازد.
  • AddRedis یک Redis محلی با Docker می‌سازد.
  • AddProject پروژه API را به عنوان یک منبع معرفی می‌کند.
  • WithReference یعنی API به Redis دسترسی دارد.

ServiceDefaults چیست؟

پروژه ServiceDefaults شامل تنظیمات مشترکی است که همه سرویس‌ها استفاده می‌کنند. در قالب Aspire، این پروژه متدهایی مثل:

  • AddServiceDefaults()
  • MapDefaultEndpoints()

را در اختیار سرویس‌ها می‌گذارد.

Resource چیست؟

در Aspire هر چیزی که در AppHost تعریف می‌کنی یک Resource است:

نوع Resource مثال
Project پروژه‌های ASP.NET Core
Container Redis، SQL Server، RabbitMQ
External سرویس‌های بیرونی یا ابری
Parameter رمزها و مقادیر پیکربندی

WithReference چه کاری می‌کند؟

وقتی می‌نویسی:

var api = builder.AddProject<Projects.ApiService>("api")
    .WithReference(cache);

Aspire به‌صورت خودکار:

  • Connection String مربوط به Redis را در Environment Variable سرویس api تزریق می‌کند.
  • نام پیش‌فرض متغیر ConnectionStrings__cache خواهد بود.
  • اگر منابع نیاز به صبر کردن داشته باشند، وابستگی را مدیریت می‌کند.

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

مثال ۱: یک API با Redis Cache

در AppHost:

var builder = DistributedApplication.CreateBuilder(args);

var cache = builder.AddRedis("cache");

var api = builder.AddProject<Projects.ApiService>("api")
    .WithReference(cache);

builder.Build().Run();

در ApiService باید بسته Aspire Redis Client را نصب کنی:

dotnet add package Aspire.StackExchange.Redis

سپس در Program.cs سرویس:

var builder = WebApplication.CreateBuilder(args);

builder.AddServiceDefaults();

// این متد ConnectionString به نام "cache" را می‌خواند
builder.AddRedisClient("cache");

var app = builder.Build();

app.MapDefaultEndpoints();

app.MapGet("/", async (IConnectionMultiplexer redis) =>
{
    var db = redis.GetDatabase();
    var value = await db.StringIncrementAsync("counter");
    return Results.Ok(new { visits = value });
});

app.Run();

توضیح:

  • AddRedisClient("cache") می‌داند که Connection String از ConnectionStrings__cache بیاید.
  • لازم نیست خودت Docker Redis را دستی اجرا کنی.
  • لازم نیست Connection String را در appsettings.json بذاری.

مثال ۲: API با SQL Server

در AppHost:

var builder = DistributedApplication.CreateBuilder(args);

var sql = builder.AddSqlServer("sql")
    .AddDatabase("shopdb");

var api = builder.AddProject<Projects.ApiService>("api")
    .WithReference(sql);

builder.Build().Run();

در ApiService بسته‌ها را نصب کن:

dotnet add package Aspire.Microsoft.EntityFrameworkCore.SqlServer
dotnet add package Microsoft.EntityFrameworkCore.Design

در Program.cs:

var builder = WebApplication.CreateBuilder(args);

builder.AddServiceDefaults();

builder.AddSqlServerDbContext<ShopDbContext>("shopdb");

var app = builder.Build();

app.MapDefaultEndpoints();

app.MapGet("/products", async (ShopDbContext db) =>
{
    return await db.Products.ToListAsync();
});

app.Run();

توضیح:

  • AddSqlServerDbContext<ShopDbContext>("shopdb") به‌صورت خودکار DbContext را با Connection String درست رجیستر می‌کند.
  • دیگر نیازی به پیکربندی دستی رشته اتصال نیست.

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

پروژه: فروشگاه کوچک eShopMini

این پروژه شامل:

  • Web به عنوان فرانت‌اند
  • CatalogApi برای مدیریت محصولات
  • OrderingApi برای ثبت سفارش
  • Redis برای Cache
  • SQL Server برای دیتابیس
  • RabbitMQ برای پیام‌رسانی بین سرویس‌ها

گام ۱: ساخت پروژه‌ها

dotnet new webapi -n CatalogApi
dotnet new webapi -n OrderingApi
dotnet new webapp -n Web
dotnet new aspire-starter -n eShopMini

سپس پروژه‌ها را در Solution قرار بده.

گام ۲: تعریف منابع در AppHost

در Program.cs پروژه AppHost:

var builder = DistributedApplication.CreateBuilder(args);

var sql = builder.AddSqlServer("sqlserver")
    .WithDataVolume()
    .AddDatabase("CatalogDb");

var redis = builder.AddRedis("redis");

var rabbit = builder.AddRabbitMQ("messaging");

var catalogApi = builder.AddProject<Projects.CatalogApi>("catalog-api")
    .WithReference(sql)
    .WithReference(redis);

var orderingApi = builder.AddProject<Projects.OrderingApi>("ordering-api")
    .WithReference(sql)
    .WithReference(rabbit);

var web = builder.AddProject<Projects.Web>("web")
    .WithExternalHttpEndpoints()
    .WithReference(catalogApi)
    .WithReference(orderingApi);

builder.Build().Run();

توضیح:

  • WithDataVolume یعنی داده‌های SQL Server در طول توسعه محلی از بین نروند.
  • AddDatabase("CatalogDb") یک دیتابیس منطقی داخل SQL Server می‌سازد.
  • WithExternalHttpEndpoints باعث می‌شود Web از بیرون قابل دسترسی باشد.
  • WithReference(catalogApi) باعث می‌شود آدرس سرویس کاتالوگ به Web تزریق شود.

گام ۳: پیکربندی CatalogApi

در Program.cs:

var builder = WebApplication.CreateBuilder(args);

builder.AddServiceDefaults();

builder.AddSqlServerDbContext<CatalogDbContext>("CatalogDb");
builder.AddRedisDistributedCache("redis");

var app = builder.Build();

app.MapDefaultEndpoints();

app.MapGet("/products", async (CatalogDbContext db) =>
{
    return await db.Products.ToListAsync();
});

app.MapGet("/products/{id}", async (int id, CatalogDbContext db, IDistributedCache cache) =>
{
    var cacheKey = $"product:{id}";
    var cached = await cache.GetStringAsync(cacheKey);

    if (cached is not null)
    {
        return Results.Ok(System.Text.Json.JsonSerializer.Deserialize<Product>(cached));
    }

    var product = await db.Products.FindAsync(id);

    if (product is null)
    {
        return Results.NotFound();
    }

    await cache.SetStringAsync(cacheKey, System.Text.Json.JsonSerializer.Serialize(product));

    return Results.Ok(product);
});

app.Run();

گام ۴: پیکربندی OrderingApi

در Program.cs:

var builder = WebApplication.CreateBuilder(args);

builder.AddServiceDefaults();

builder.AddSqlServerDbContext<OrderingDbContext>("CatalogDb");

builder.AddRabbitMQClient("messaging");

var app = builder.Build();

app.MapDefaultEndpoints();

app.MapPost("/orders", async (Order order, OrderingDbContext db, IConnection connection) =>
{
    db.Orders.Add(order);
    await db.SaveChangesAsync();

    await PublishOrderCreatedEvent(connection, order);

    return Results.Created($"/orders/{order.Id}", order);
});

app.Run();

این مثال نشان می‌دهد چطور Aspire وابستگی‌های واقعی را بدون پیکربندی دستی زیاد مدیریت می‌کند.

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

۱. استفاده از Parameter برای رمزها

var password = builder.AddParameter("sql-password", secret: true);

var sql = builder.AddSqlServer("sql", password);

حالا sql-password یک پارامتر امن است و هنگام استقرار قابل تنظیم است.

۲. استفاده از Container دلخواه

var postgres = builder.AddContainer("postgres", "postgres:16")
    .WithDataVolume()
    .AddDatabase("mydb");

می‌توانی هر ایمیج Docker را مستقیماً اضافه کنی.

۳. تعریف Dependency صریح با WaitFor

var api = builder.AddProject<Projects.Api>("api")
    .WaitFor(db)
    .WithReference(db);

WaitFor تضمین می‌کند API فقط وقتی شروع شود که دیتابیس آماده است.

۴. مقیاس‌دهی با WithReplicas

builder.AddProject<Projects.Api>("api")
    .WithReplicas(3);

هنگام استقرار روی Azure Container Apps، سه نمونه اجرا می‌شود.

۵. گرفتن Manifest برای استقرار

dotnet run --project eShopMini.AppHost --publish manifest

این دستور یک فایل Manifest می‌سازد که ابزارهای استقرار می‌توانند مصرف کنند.

۶. استقرار روی Azure

azd init
azd up

Aspire با Azure Developer CLI هماهنگ است و می‌تواند منابع را در Azure Container Apps بسازد.

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

۱. اجرا بدون Docker Desktop

بسیاری از منابع Aspire مثل Redis، SQL Server و RabbitMQ برای اجرا به Docker نیاز دارند. اگر Docker روشن نباشد، خطای مشابه زیر می‌بینی:

Docker is not running or is not reachable

۲. فراموش کردن MapDefaultEndpoints

اگر AddServiceDefaults() را بزنی ولی MapDefaultEndpoints() را صدا نزنی، Health Check و /health endpoint واقعاً exposed نمی‌شوند.

۳. استفاده از پورت ثابت دستی

builder.AddProject<Projects.Api>("api")
    .WithEndpoint("http", port: 5000);

چند سرویس ممکن است روی پورت 5000 تداخل پیدا کنند. بهتر است بگذاری Aspire پورت‌ها را مدیریت کند.

۴. اشتباه گرفتن نام Connection String

وقتی می‌نویسی:

builder.AddProject<Projects.Api>("api")
    .WithReference(redis);

نام متغیر محیطی ConnectionStrings__redis است. اگر در سرویس بنویسی AddRedisClient("cache")، کار نمی‌کند چون نام cache با نام منبع redis یکی نیست.

۵. فراموش کردن Reference پروژه

برای استفاده از Projects.Api در AppHost باید پروژه AppHost به آن پروژه Reference داشته باشد. اگر افزوده نشود، نوع Projects ساخته نمی‌شود.

۶. استفاده بیش از حد از WithExternalHttpEndpoints

همه سرویس‌ها لازم نیست از بیرون قابل دسترسی باشند. فقط Web یا BFF معمولاً باید external باشند.

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

۱. تنظیمات مشترک را در ServiceDefaults بگذار

به‌جای اینکه هر سرویس Health Check و OpenTelemetry را جداگانه پیکربندی کند، از AddServiceDefaults() استفاده کن.

۲. AppHost را ساده نگه دار

فقط تعریف منابع و روابط را در AppHost بنویس. منطق تجاری را داخل سرویس‌ها نگه دار.

۳. نام‌گذاری معنی‌دار برای منابع

  • redis
  • shopdb
  • catalog-api

این نام‌ها در Dashboard و متغیرهای محیطی استفاده می‌شوند.

۴. همیشه از WithReference استفاده کن

باگ‌ترین بخش میکروسرویس‌ها Connection String است. بذار Aspire آن را مدیریت کند.

۵. برای منابع Stateful از WithDataVolume استفاده کن

SQL Server یا PostgreSQL محلی باید دیتای خود را بین اجراهای مختلف حفظ کنند.

۶. رمزها را با AddParameter نگه دار

رمزها را مستقیم در کد AppHost قرار نده.

۷. از Health Check استاندارد استفاده کن

با MapDefaultEndpoints() سرویس‌هایت یک مسیر /health و /alive دارند.

۸. وابستگی‌ها را با WaitFor مشخص کن

اگر یکی از سرویس‌ها باید بعد از دیگری اجرا شود، از WaitFor استفاده کن.

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

منبع آدرس
مستندات رسمی .NET Aspire https://learn.microsoft.com/dotnet/aspire
مخزن نمونه‌ها https://github.com/dotnet/aspire-samples
پروژه eShop https://github.com/dotnet/eshop
.NET Aspire Community Toolkit https://github.com/CommunityToolkit/Aspire

پیشنهاد می‌کنم بعد از این آموزش:

  1. پروژه aspire-starter را بساز و اجرا کن.
  2. یک Redis و SQL Server به آن اضافه کن.
  3. پروژه eShop را clone کن و بررسی کن.
  4. یک Manifest خروجی بگیر.
  5. با azd up روی Azure استقرارش بده.

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

📌 نام متد: AddServiceDefaults()

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

public static IHostApplicationBuilder AddServiceDefaults(
    this IHostApplicationBuilder builder)

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

نام پارامتر نوع اجباری؟ توضیح دقیق مثال مقدار
builder IHostApplicationBuilder بله Application builder جاری برای پیکربندی سرویس builder

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

  • نوع: IHostApplicationBuilder
  • همان builder ورودی را برمی‌گرداند تا بتوانی متدهای بعدی را زنجیره‌ای صدا بزنی.
  • همیشه مقدار می‌دهد، مگر اینکه پیکربندی داخلی خطا بدهد.

کاری که انجام می‌ده (گام به گام):

۱. سرویس‌دیسکاوری را اضافه می‌کند.
۲. Health Check های پیش‌فرض را ثبت می‌کند.
۳. OpenTelemetry را برای Logging، Metrics و Tracing پیکربندی می‌کند.
۴. HttpClient را با Resilience استاندارد پیکربندی می‌کند.
۵. تنظیمات محلی توسعه را ساده می‌کند.

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

var builder = WebApplication.CreateBuilder(args);

builder.AddServiceDefaults();

var app = builder.Build();

app.MapDefaultEndpoints();

app.MapGet("/", () => "Hello from API");

app.Run();

خروجی هنگام اجرا:

Now listening on: http://localhost:5000

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

var builder = WebApplication.CreateBuilder(args);

builder.AddServiceDefaults();

builder.AddRedisClient("redis");
builder.AddSqlServerDbContext<ShopDbContext>("shopdb");

var app = builder.Build();

app.MapDefaultEndpoints();
app.MapControllers();

app.Run();

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

Exception زمان رخ دادن راه‌حل
InvalidOperationException اگر سرویس‌دیسکاوری بدون پیکربندی مناسب اجرا شود از قالب ServiceDefaults استفاده کن
ConfigurationException اگر OpenTelemetry endpoint نامعتبر باشد آدرس telemetry را بررسی کن

Overloadها:

این متد در قالب ServiceDefaults فقط یک امضا دارد، اما می‌توانی متدهای سفارشی خودت را بسازی.

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

  • ❌ صدا زدن AddServiceDefaults بعد از Build()
  • ✅ آن را قبل از ساخت app صدا بزن
  • ❌ استفاده از AddServiceDefaults بدون MapDefaultEndpoints
  • ✅ این دو را با هم استفاده کن

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

  • MapDefaultEndpoints()
  • AddRedisClient()
  • AddSqlServerDbContext()

📌 نام متد: MapDefaultEndpoints()

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

public static WebApplication MapDefaultEndpoints(
    this WebApplication app)

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

نام پارامتر نوع اجباری؟ توضیح دقیق مثال مقدار
app WebApplication بله اپلیکیشن ساخته‌شده که می‌خواهیم endpoints را اضافه کنیم app

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

  • نوع: WebApplication
  • همان app ورودی را برمی‌گرداند تا endpoint‌های بیشتری اضافه کنی.

کاری که انجام می‌ده (گام به گام):

۱. endpoint مربوط به سلامت (/health) را ثبت می‌کند.
۲. endpoint مربوط به زنده بودن (/alive) را ثبت می‌کند.
۳. این endpointها را برای استفاده Dashboard و Orchestrator آماده می‌کند.

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

var builder = WebApplication.CreateBuilder(args);

builder.AddServiceDefaults();

var app = builder.Build();

app.MapDefaultEndpoints();

app.Run();

خروجی با curl:

curl http://localhost:5000/health
Healthy

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

در همه سرویس‌های واقعی Aspire باید این متد را بعد از Build() فراخوانی کنی:

app.MapDefaultEndpoints();
app.MapControllers();
app.Run();

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

Exception زمان رخ دادن راه‌حل
InvalidOperationException اگر متد بعد از Run() صدا زده شود آن را قبل از Run() فراخوانی کن
NullReferenceException اگر app null باشد از Pipeline استاندارد استفاده کن

Overloadها:

فقط یک امضا دارد.

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

  • ❌ فراموش کردن این متد وقتی از AddServiceDefaults() استفاده می‌کنی
  • ✅ همیشه هر دو را با هم استفاده کن
  • ❌ اضافه کردن endpoint سفارشی به نام /health بعد از این متد
  • ✅ از نام‌های دیگر برای endpointهای سفارشی استفاده کن

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

  • AddServiceDefaults()
  • MapGet()
  • MapControllers()

📌 نام متد: AddProject<TProject>()

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

public static IResourceBuilder<ProjectResource> AddProject<TProject>(
    this IDistributedApplicationBuilder builder,
    string name)
    where TProject : IProjectMetadata, new()

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

نام پارامتر نوع اجباری؟ توضیح دقیق مثال مقدار
builder IDistributedApplicationBuilder بله builder ارکستراتور Aspire builder
name string بله نام منطقی یکتا برای پروژه "catalog-api"
TProject نوع جنریک بله نوع تولیدشده از پروژه، معمولاً از Projects Projects.CatalogApi

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

  • نوع: IResourceBuilder<ProjectResource>
  • builder مربوط به منبع پروژه را می‌دهد تا بتوانی WithReference و غیره را صدا بزنی.

کاری که انجام می‌ده (گام به گام):

۱. متادیتای پروژه را از TProject می‌خواند.
۲. یک ProjectResource جدید می‌سازد.
۳. آن را در مدل ارکستراتور ثبت می‌کند.
۴. builder قابل پیکربندی را برمی‌گرداند.

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

var api = builder.AddProject<Projects.ApiService>("api");

builder.Build().Run();

خروجی در Dashboard: یک منبع Project به نام api با وضعیت Running دیده می‌شود.

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

var api = builder.AddProject<Projects.CatalogApi>("catalog-api")
    .WithReference(db)
    .WithReference(redis)
    .WithReplicas(3);

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

Exception زمان رخ دادن راه‌حل
ArgumentException اگر نام تکراری باشد نام یکتا انتخاب کن
InvalidOperationException اگر TProject متادیتای معتبر نداشته باشد مطمئن شو پروژه Reference شده است
FileNotFoundException اگر فایل پروژه یافت نشود مسیر پروژه را بررسی کن

Overloadها:

در نسخه‌های اخیر، overload با Action<ProjectResourceOptions> هم وجود دارد:

builder.AddProject<Projects.Api>("api", options =>
{
    // تنظیمات اضافه پروژه
});

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

  • ❌ فراموش کردن Reference از AppHost به پروژه موردنظر
  • ✅ پروژه را به AppHost اضافه کن
  • ❌ استفاده از نام یکسان برای دو منبع
  • ✅ نام‌های یکتا و معنی‌دار بده

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

  • WithReference()
  • WithEndpoint()
  • WaitFor()
  • WithReplicas()

📌 نام متد: AddRedis()

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

public static IResourceBuilder<RedisResource> AddRedis(
    this IDistributedApplicationBuilder builder,
    string name,
    int? port = null,
    string? connectionString = null)

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

نام پارامتر نوع اجباری؟ توضیح دقیق مثال مقدار
builder IDistributedApplicationBuilder بله builder ارکستراتور builder
name string بله نام منطقی منبع "redis"
port int? خیر پورت مشخص برای Redis محلی 6379
connectionString string? خیر اگر داده شود، Redis موجود استفاده می‌شود به‌جای ساخت کانتینر "localhost:6379"

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

  • نوع: IResourceBuilder<RedisResource>
  • builder منبع Redis برای استفاده در WithReference.

کاری که انجام می‌ده (گام به گام):

۱. اگر connectionString داده شود، Redis را به عنوان منبع External ثبت می‌کند.
۲. در غیر این صورت، یک کانتینر Redis با Docker می‌سازد.
۳. آن را در مدل ارکستراتور ثبت می‌کند.
۴. builder را برای پیکربندی بیشتر برمی‌گرداند.

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

var redis = builder.AddRedis("redis");

var api = builder.AddProject<Projects.Api>("api")
    .WithReference(redis);

builder.Build().Run();

خروجی در Dashboard: یک منبع Container با نام redis بالا می‌آید.

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

استفاده از Redis خارجی:

var redis = builder.AddRedis("redis",
    connectionString: builder.Configuration["Redis:ConnectionString"]);

builder.AddProject<Projects.Api>("api")
    .WithReference(redis);

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

Exception زمان رخ دادن راه‌حل
ArgumentException نام تکراری نام یکتا بده
DockerException اگر Docker در دسترس نباشد Docker Desktop را اجرا کن
InvalidOperationException اگر port مشخص درگیر باشد پورت دیگری بده یا خالی بگذار

Overloadها:

AddRedis(name);
AddRedis(name, port);
AddRedis(name, connectionString);
AddRedis(name, port, connectionString);

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

  • ❌ استفاده از AddRedis("cache") و سپس AddRedisClient("redis") در سرویس
  • ✅ نام‌ها را یکسان نگه دار
  • ❌ اجرا بدون Docker و انتظار Redis محلی
  • ✅ Docker Desktop را روشن کن

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

  • WithReference()
  • AddRedisClient()
  • AddRedisDistributedCache()

📌 نام متد: AddSqlServer()

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

public static IResourceBuilder<SqlServerServerResource> AddSqlServer(
    this IDistributedApplicationBuilder builder,
    string name,
    string? password = null,
    int? port = null,
    string? connectionString = null)

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

نام پارامتر نوع اجباری؟ توضیح دقیق مثال مقدار
builder IDistributedApplicationBuilder بله builder ارکستراتور builder
name string بله نام منطقی SQL Server "sqlserver"
password string? خیر رمز عبور کاربر SA "P@ssw0rd"
port int? خیر پورت محلی مشخص 1433
connectionString string? خیر اتصال به SQL Server موجود "Server=...;..."

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

  • نوع: IResourceBuilder<SqlServerServerResource>
  • builder مربوط به SQL Server که می‌توانی AddDatabase را روی آن صدا بزنی.

کاری که انجام می‌ده (گام به گام):

۱. اگر connectionString داده شود، از SQL Server موجود استفاده می‌کند.
۲. در غیر این صورت کانتینر SQL Server با Docker می‌سازد.
۳. در صورت نیاز یک Database منطقی با AddDatabase اضافه می‌کند.
۴. Connection String مناسب را در اختیار سرویس‌ها می‌گذارد.

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

var sql = builder.AddSqlServer("sql")
    .AddDatabase("shopdb");

var api = builder.AddProject<Projects.Api>("api")
    .WithReference(sql);

builder.Build().Run();

خروجی در Dashboard: SQL Server و Database زیر آن نمایش داده می‌شود.

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

var password = builder.AddParameter("sql-password", secret: true);

var sql = builder.AddSqlServer("sql", password)
    .WithDataVolume();

var db = sql.AddDatabase("CatalogDb");

builder.AddProject<Projects.CatalogApi>("catalog-api")
    .WithReference(db);

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

Exception زمان رخ دادن راه‌حل
ArgumentException نام تکراری یا رمز نامعتبر نام یکتا و رمز معتبر بده
DockerException Docker در دسترس نیست Docker Desktop را روشن کن
InvalidOperationException AddDatabase قبل از ساخت server صدا زده شود از همان builder استفاده کن

Overloadها:

AddSqlServer(name);
AddSqlServer(name, password);
AddSqlServer(name, password, port);
AddSqlServer(name, connectionString: "...");

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

  • ❌ فراموش کردن AddDatabase و استفاده از WithReference(sql)
  • ✅ اگر می‌خواهی DbContext بگیری، باید Database را reference کنی
  • ❌ استفاده از رمز ضعیف که SQL Server قبول نکند
  • ✅ رمز با حروف بزرگ، کوچک، عدد و علامت استفاده کن

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

  • AddDatabase()
  • WithDataVolume()
  • AddSqlServerDbContext()

📌 نام متد: WithReference()

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

public static IResourceBuilder<TDestination> WithReference<TDestination>(
    this IResourceBuilder<TDestination> builder,
    IResourceBuilder<IResourceWithConnectionString> source,
    string? connectionName = null,
    bool optional = false)
    where TDestination : IResourceWithEnvironment

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

نام پارامتر نوع اجباری؟ توضیح دقیق مثال مقدار
builder IResourceBuilder بله منبع مقصد که باید Connection String بگیرد api
source IResourceBuilder بله منبع مبدأ که Connection String دارد redis
connectionName string? خیر نام سفارشی برای متغیر Connection String "MyCache"
optional bool خیر اگر true باشد، نبود منبع باعث خطا نمی‌شود true

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

  • نوع: IResourceBuilder<TDestination>
  • همان builder مقصد را می‌دهد تا بتوانی پیکربندی ادامه بدهی.

کاری که انجام می‌ده (گام به گام):

۱. بررسی می‌کند که منبع مبدأ Connection String دارد.
۲. نام متغیر محیطی را تعیین می‌کند.
۳. Connection String را به Environment Variable مقصد تزریق می‌کند.
۴. وابستگی ضمنی بین منابع را ثبت می‌کند.

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

var redis = builder.AddRedis("redis");

var api = builder.AddProject<Projects.Api>("api")
    .WithReference(redis);

builder.Build().Run();

خروجی در سرویس API به‌صورت متغیر محیطی:

ConnectionStrings__redis=localhost:6379

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

var legacyDb = builder.AddConnectionString("LegacyDb", "Server=legacy;Database=Old;...");

builder.AddProject<Projects.Migration>("migration")
    .WithReference(legacyDb, "LegacyDatabase", optional: true);

در این حالت سرویس Migration از متغیر ConnectionStrings__LegacyDatabase استفاده می‌کند.

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

Exception زمان رخ دادن راه‌حل
ArgumentException اگر منبع Connection String نداشته باشد منبع مناسب بده
InvalidOperationException اگر connectionName تکراری باشد نام یکتا بده
DistributedApplicationException اگر optional نباشد و منبع در دسترس نباشد optional را true کن یا منبع را درست کن

Overloadها:

WithReference(source);
WithReference(source, connectionName);
WithReference(source, connectionName, optional);

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

  • ❌ فکر کردن WithReference فقط dependency می‌سازد و Connection String تزریق نمی‌کند
  • ✅ WithReference هم dependency و هم Connection String را مدیریت می‌کند
  • ❌ استفاده از نام منبع اشتباه در AddRedisClient("wrongName")
  • ✅ نام را با connectionName یا نام منبع هماهنگ کن

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

  • WaitFor()
  • WithEnvironment()
  • AddConnectionString()

📌 نام متد: WaitFor()

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

public static IResourceBuilder<T> WaitFor<T>(
    this IResourceBuilder<T> builder,
    IResourceBuilder<IResource> resource)
    where T : IResource

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

نام پارامتر نوع اجباری؟ توضیح دقیق مثال مقدار
builder IResourceBuilder بله منبعی که باید منتظر بماند api
resource IResourceBuilder بله منبعی که باید آماده شود db

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

  • نوع: IResourceBuilder<T>
  • همان builder مقصد را می‌دهد.

کاری که انجام می‌ده (گام به گام):

۱. بررسی می‌کند که منبع مقصد و مبدأ معتبر باشند.
۲. وابستگی استارت‌آپ را ثبت می‌کند.
۳. هنگام اجرا، مقصد فقط بعد از Ready شدن مبدأ شروع می‌شود.

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

var db = builder.AddSqlServer("sql").AddDatabase("shopdb");

var api = builder.AddProject<Projects.Api>("api")
    .WaitFor(db)
    .WithReference(db);

builder.Build().Run();

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

var migrator = builder.AddProject<Projects.MigrationService>("migrator")
    .WaitFor(db)
    .WithReference(db);

در اینجا Migration Service فقط بعد از آماده‌شدن دیتابیس اجرا می‌شود.

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

Exception زمان رخ دادن راه‌حل
ArgumentException اگر منبع به خودش وابسته باشد منبع دیگری بده
InvalidOperationException اگر وابستگی چرخه‌ای ایجاد شود گراف وابستگی را ساده کن

Overloadها:

در برخی نسخه‌ها متد WaitForCompletion برای انتظار برای خاتمه یک Job وجود دارد.

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

  • ❌ استفاده از WaitFor به‌جای WithReference برای تزریق Connection String
  • ✅ WithReference را هم صدا بزن
  • ❌ استفاده از WaitFor برای منابعی که Health Check ندارند و همیشه Running هستند
  • ✅ برای ترتیب استارت‌آپ دقیق از Health Check استفاده کن

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

  • WithReference()
  • WithHealthCheck()

📌 نام متد: WithReplicas()

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

public static IResourceBuilder<T> WithReplicas<T>(
    this IResourceBuilder<T> builder,
    int replicas)
    where T : IResource

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

نام پارامتر نوع اجباری؟ توضیح دقیق مثال مقدار
builder IResourceBuilder بله منبعی که باید مقیاس‌پذیر شود api
replicas int بله تعداد نمونه‌های موردنیاز 3

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

  • نوع: IResourceBuilder<T>
  • همان builder را می‌دهد.

کاری که انجام می‌ده (گام به گام):

۱. تعداد replicas را در مدل منبع ذخیره می‌کند.
۲. هنگام استقرار روی Azure Container Apps یا Kubernetes، این تعداد اعمال می‌شود.
۳. در حالت محلی معمولاً یک نمونه اجرا می‌شود.

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

var api = builder.AddProject<Projects.Api>("api")
    .WithReplicas(3);

builder.Build().Run();

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

builder.AddProject<Projects.OrderingApi>("ordering-api")
    .WithReplicas(5);

هنگام azd up، سرویس OrderingApi با پنج نمونه مستقر می‌شود.

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

Exception زمان رخ دادن راه‌حل
ArgumentOutOfRangeException اگر replicas منفی باشد عدد نامنفی بده

Overloadها:

فقط یک امضا دارد.

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

  • ❌ فکر کردن که WithReplicas در اجرای محلی چند نمونه می‌سازد
  • ✅ در اجرای محلی معمولاً یک نمونه است؛ مقیاس‌دهی برای Deployment است
  • ❌ استفاده از WithReplicas برای دیتابیس
  • ✅ دیتابیس‌ها معمولاً نباید به این صورت مقیاس شوند

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

  • AddProject()
  • PublishAsAzureContainerApp()

📌 نام متد: WithEndpoint()

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

public static IResourceBuilder<T> WithEndpoint<T>(
    this IResourceBuilder<T> builder,
    string name,
    int? port = null,
    bool isProxied = true,
    bool isExternal = false,
    string? targetPort = null)
    where T : IResourceWithEndpoints

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

نام پارامتر نوع اجباری؟ توضیح دقیق مثال مقدار
builder IResourceBuilder بله منبع موردنظر api
name string بله نام endpoint "public-http"
port int? خیر پورت مشخص 8080
isProxied bool خیر آیا از طریق پروکسی Aspire در دسترس باشد true
isExternal bool خیر آیا از بیرون قابل دسترسی باشد false
targetPort string? خیر پورت داخلی هدف در کانتینر "5000"

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

  • نوع: IResourceBuilder<T>
  • همان builder را برمی‌گرداند.

کاری که انجام می‌ده (گام به گام):

۱. یک endpoint با نام مشخص می‌سازد.
۲. مشخص می‌کند داخلی یا خارجی باشد.
۳. در صورت نیاز پورت اختصاص می‌دهد.
۴. در orchestration و Service Discovery ثبت می‌شود.

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

builder.AddProject<Projects.Api>("api")
    .WithEndpoint("http", port: 5075);

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

builder.AddProject<Projects.Web>("web")
    .WithEndpoint("public", isExternal: true, targetPort: "8080");

در این حالت Web از بیرون قابل دسترسی است.

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

Exception زمان رخ دادن راه‌حل
ArgumentException اگر نام endpoint تکراری باشد نام یکتا بده
InvalidOperationException اگر پورت نامعتبر باشد پورت معتبر بده

Overloadها:

در برخی نسخه‌ها overload با Action<EndpointOptions> وجود دارد.

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

  • ❌ استفاده از WithEndpoint برای منبعی که endpoint ندارد
  • ✅ فقط برای Project و Container مناسب است
  • ❌ تنظیم isExternal = true برای API داخلی
  • ✅ فقط Web یا BFF را isExternal کن

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

  • WithExternalHttpEndpoints()
  • WithReference()
  • WithServiceBinding()

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

روش هدف اصلی تزریق Connection String ایجاد وابستگی مناسب برای
WithReference() اتصال بین منابع بله بله، به‌صورت ضمنی بیشتر ارتباط‌ها
WaitFor() ترتیب استارت‌آپ خیر بله، صریح وقتی فقط ترتیب مهم است
WithEnvironment() تزریق متغیر محیطی ساده خیر خیر مقادیر ساده
AddConnectionString() تعریف رشته اتصال دستی بله خیر سرویس‌های خارجی
AddParameter() تعریف پارامتر امن خیر خیر رمزها و مقادیر حساس
WithExternalHttpEndpoints() دسترسی بیرونی خیر خیر فرانت‌اند یا API عمومی
WithEndpoint() تعریف دقیق یک endpoint خیر خیر کنترل پورت و دسترسی

قانون سرانگشتی

  • اگر منبع Connection String دارد و مقصد باید متصل شود → WithReference
  • اگر فقط می‌خواهی صبر کند تا منبع آماده شود → WaitFor
  • اگر مقدار ساده مثل API_KEY را می‌خواهی تزریق کنی → WithEnvironment
  • اگر رمز یا مقدار امن داری → AddParameter

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

سناریو ۱: میکروسرویس فروشگاهی

var builder = DistributedApplication.CreateBuilder(args);

var sql = builder.AddSqlServer("sql")
    .WithDataVolume()
    .AddDatabase("ShopDb");

var redis = builder.AddRedis("redis");

var rabbit = builder.AddRabbitMQ("messaging");

var catalogApi = builder.AddProject<Projects.CatalogApi>("catalog-api")
    .WithReference(sql)
    .WithReference(redis);

var orderingApi = builder.AddProject<Projects.OrderingApi>("ordering-api")
    .WithReference(sql)
    .WithReference(rabbit);

var web = builder.AddProject<Projects.Web>("web")
    .WithExternalHttpEndpoints()
    .WithReference(catalogApi)
    .WithReference(orderingApi);

builder.Build().Run();

سناریو ۲: استفاده از منابع ابری به‌جای کانتینر محلی

var sql = builder.AddConnectionString("CatalogDb",
    "Server=mycloudsql.database.windows.net;Database=Catalog;...");

var redis = builder.AddConnectionString("redis",
    "mycloudredis.redis.cache.windows.net:6379,password=...");

var api = builder.AddProject<Projects.Api>("api")
    .WithReference(sql)
    .WithReference(redis);

builder.Build().Run();

سناریو ۳: پروژه Migration برای دیتابیس

var db = builder.AddSqlServer("sql")
    .WithDataVolume()
    .AddDatabase("ShopDb");

var migrator = builder.AddProject<Projects.MigrationService>("migrator")
    .WaitFor(db)
    .WithReference(db);

var api = builder.AddProject<Projects.Api>("api")
    .WaitFor(migrator)
    .WithReference(db);

builder.Build().Run();

سناریو ۴: استقرار روی Azure Container Apps

azd init
azd up

Aspire Manifest به‌صورت خودکار منابع را به Azure Container Apps تبدیل می‌کند.

۱۴. کارایی و Performance

۱. Health Check استاندارد

AddServiceDefaults() و MapDefaultEndpoints() باعث می‌شود سیستم از ارسال ترافیک به سرویس ناسالم جلوگیری کند.

۲. Resilience پیش‌فرض

با AddServiceDefaults()، HttpClient های شما به‌صورت خودکار Retry و Timeout بهتری دارند.

۳. جلوگیری از Cold Start غیرضروری با WaitFor

وقتی از WaitFor و Health Check استفاده کنی، سرویس‌ها قبل از آمادگی کامل درخواست نمی‌گیرند.

۴. مدیریت اتصال به Redis

بسته‌های Aspire مثل AddRedisClient() از Connection Multiplexing استفاده می‌کنند و بازدهی بهتری در سناریوهای پرترافیک دارند.

۵. استفاده از WithDataVolume در توسعه

این کار باعث می‌شود دیتای SQL Server یا PostgreSQL در هر اجرا از بین نرود و سرعت توسعه بهتر باشد.

۶. مقیاس‌دهی با WithReplicas

برای سرویس‌های Stateless می‌توانی تعداد replicas را بالا ببری تا throughput بیشتری داشته باشی.

۷. کاهش تاخیر با Service Discovery

Aspire به‌جای آدرس‌های سخت، از نام‌های منطقی استفاده می‌کند و سرویس‌دیسکاوری به‌صورت خودکار آدرس‌ها را رزولوشن می‌کند.

۸. غیرفعال کردن Dashboard در Production

در Production معمولاً Dashboard را غیرفعال می‌کنند تا منابع اضافی مصرف نشود:

builder.ConfigureAspireDashboard(disabled: true);

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

سطح مبتدی

  • .NET 8 یا 9 SDK نصب شده است.
  • Docker Desktop نصب و اجرا شده است.
  • dotnet workload install aspire را اجرا کرده‌ای.
  • پروژه با dotnet new aspire-starter ساخته‌ای.
  • AppHost را اجرا کرده‌ای و Dashboard را دیده‌ای.
  • فایل Program.cs در AppHost را می‌فهمی.

سطح متوسط

  • یک Redis به AppHost اضافه کرده‌ای.
  • یک SQL Server و Database اضافه کرده‌ای.
  • از WithReference برای اتصال منابع استفاده کرده‌ای.
  • از WithExternalHttpEndpoints برای Web استفاده کرده‌ای.
  • AddServiceDefaults و MapDefaultEndpoints را در سرویس‌ها استفاده کرده‌ای.
  • Connection String خودکار را در سرویس مصرف کرده‌ای.

سطح پیشرفته

  • از AddParameter برای رمزها استفاده کرده‌ای.
  • از WaitFor برای ترتیب استارت‌آپ استفاده کرده‌ای.
  • از WithDataVolume برای دیتابیس محلی استفاده کرده‌ای.
  • با WithReplicas مقیاس‌دهی را پیکربندی کرده‌ای.
  • Manifest خروجی گرفته‌ای.
  • با azd up روی Azure استقرار داده‌ای.

سطح حرفه‌ای

  • یک Custom Resource نوشته‌ای.
  • با OpenTelemetry و Trace کار کرده‌ای.
  • پروژه eShop را بررسی کرده‌ای.
  • استقرار Kubernetes یا ACA با Aspire را انجام داده‌ای.

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.