Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
64 changes: 0 additions & 64 deletions src/Templates/Boilerplate/Bit.Boilerplate/.docs/20- .NET Aspire.md
Original file line number Diff line number Diff line change
Expand Up @@ -333,70 +333,6 @@ This ensures **consistency** between development and production environments!

---

## ☁️ Going Production: Switching to Azure Managed Services

While local containers (Docker) are perfect for development, for production, you could switch to fully managed Azure services to ensure scalability, security, and high availability.

.NET Aspire makes this transition seamless by swapping the hosting resources in your `AppHost/Program.cs` without changing your application code.

### 1. Prerequisite Packages

Ensure you have the Azure integration packages installed in your **AppHost** project:

```xml
<PackageReference Include="Aspire.Hosting.Azure.Sql" />
<PackageReference Include="Aspire.Hosting.Azure.Redis" />
<PackageReference Include="Aspire.Hosting.Azure.PostgreSQL" />

```

### 2. Updating AppHost (`Program.cs`)

Replace your local container definitions with Azure resources.

#### 🛢️ Azure SQL Database

Instead of `AddSqlServer` (container), use `AddAzureSqlServer`:

```csharp
// ❌ Local Container
// var sqlServer = builder.AddSqlServer("sqlserver");

// ✅ Azure Managed Service
var sqlServer = builder.AddAzureSqlServer("sqlserver")
.AddDatabase("sqldb");

```

#### ⚡ Azure Cache for Redis

Instead of `AddRedis` (container), use `AddAzureRedis`:

```csharp
// ❌ Local Container
// var redis = builder.AddRedis("redis");

// ✅ Azure Managed Service
var redis = builder.AddAzureRedis("redis");

```

#### 🐘 Azure Database for PostgreSQL (Flexible Server)

To leverage high-performance vector search in production, use Azure PostgreSQL Flexible Server.

```csharp
// ❌ Local Container
// var postgres = builder.AddPostgres("postgres");

// ✅ Azure Managed Service
var postgres = builder.AddAzurePostgresFlexibleServer("postgres")
.AddDatabase("pgdb");

```

---

## Additional Resources

- 📚 **Official Documentation**: https://aspire.dev
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,15 +60,15 @@
<!--/+:msbuild-conditional:noEmit -->
<PackageVersion Condition="'$(signalR)' == 'true' OR '$(signalR)' == ''" Include="ModelContextProtocol.AspNetCore" Version="1.3.0" />
<PackageVersion Condition=" '$(aspire)' == 'true' OR '$(aspire)' == '' " Include="Aspire.Hosting.Maui" Version="13.3.3-preview.1.26264.13" />
<PackageVersion Condition=" '$(aspire)' == 'true' OR '$(aspire)' == '' " Include="Aspire.Hosting.Testing" Version="13.3.3" />
<PackageVersion Condition=" '$(aspire)' == 'true' OR '$(aspire)' == '' " Include="Aspire.Hosting.Testing" Version="13.3.4" />
<PackageVersion Condition=" '$(aspire)' == 'true' OR '$(aspire)' == '' " Include="CommunityToolkit.Aspire.Hosting.MailPit" Version="13.3.0" />
<PackageVersion Condition=" '$(aspire)' == 'true' OR '$(aspire)' == '' " Include="Aspire.Hosting.DevTunnels" Version="13.3.3" />
<PackageVersion Condition=" '$(aspire)' == 'true' OR '$(aspire)' == '' " Include="Aspire.Hosting.DevTunnels" Version="13.3.4" />
<PackageVersion Condition=" ('$(aspire)' == 'true' OR '$(aspire)' == '') AND ('$(database)' == 'Sqlite' OR '$(database)' == '') " Include="CommunityToolkit.Aspire.Hosting.Sqlite" Version="13.3.0" />
<PackageVersion Condition=" ('$(aspire)' == 'true' OR '$(aspire)' == '') AND ('$(database)' == 'SqlServer' OR '$(database)' == '') " Include="Aspire.Hosting.SqlServer" Version="13.3.3" />
<PackageVersion Condition=" ('$(aspire)' == 'true' OR '$(aspire)' == '') AND ('$(database)' == 'SqlServer' OR '$(database)' == '') " Include="Aspire.Hosting.Azure.Sql" Version="13.3.4" />
<PackageVersion Condition=" ('$(aspire)' == 'true' OR '$(aspire)' == '') AND ('$(database)' == 'SqlServer' OR '$(database)' == '') " Include="CommunityToolkit.Aspire.Hosting.SqlServer.Extensions" Version="13.3.0" />
<PackageVersion Condition=" ('$(aspire)' == 'true' OR '$(aspire)' == '') AND ('$(database)' == 'PostgreSQL' OR '$(database)' == '') " Include="Aspire.Hosting.PostgreSQL" Version="13.3.3" />
<PackageVersion Condition=" ('$(aspire)' == 'true' OR '$(aspire)' == '') AND ('$(database)' == 'MySql' OR '$(database)' == '') " Include="Aspire.Hosting.MySql" Version="13.3.3" />
<PackageVersion Condition=" ('$(aspire)' == 'true' OR '$(aspire)' == '') AND ('$(filesStorage)' == 'AzureBlobStorage' OR '$(filesStorage)' == '') " Include="Aspire.Hosting.Azure.Storage" Version="13.3.3" />
<PackageVersion Condition=" ('$(aspire)' == 'true' OR '$(aspire)' == '') AND ('$(database)' == 'PostgreSQL' OR '$(database)' == '') " Include="Aspire.Hosting.Azure.PostgreSQL" Version="13.3.4" />
<PackageVersion Condition=" ('$(aspire)' == 'true' OR '$(aspire)' == '') AND ('$(database)' == 'MySql' OR '$(database)' == '') " Include="Aspire.Hosting.MySql" Version="13.3.4" />
<PackageVersion Condition=" ('$(aspire)' == 'true' OR '$(aspire)' == '') AND ('$(filesStorage)' == 'AzureBlobStorage' OR '$(filesStorage)' == '') " Include="Aspire.Hosting.Azure.Storage" Version="13.3.4" />
<PackageVersion Condition=" ('$(aspire)' == 'true' OR '$(aspire)' == '') AND ('$(filesStorage)' == 'S3' OR '$(filesStorage)' == '') " Include="CommunityToolkit.Aspire.Hosting.Minio" Version="13.3.0" />
<PackageVersion Condition=" ('$(aspire)' == 'true' OR '$(aspire)' == '')" Include="Aspire.Hosting.Keycloak" Version="13.3.3-preview.1.26264.13" />
<PackageVersion Include="Microsoft.Extensions.Http.Resilience" Version="10.6.0" />
Expand Down Expand Up @@ -110,8 +110,9 @@
<PackageVersion Condition=" '$(filesStorage)' == 'S3' OR '$(filesStorage)' == '' " Include="FluentStorage.AWS" Version="6.0.3" />
<PackageVersion Condition=" '$(appInsights)' == 'true' OR '$(appInsights)' == '' " Include="Azure.Monitor.OpenTelemetry.AspNetCore" Version="1.5.0" />
<PackageVersion Condition=" '$(appInsights)' == 'true' OR '$(appInsights)' == '' " Include="Azure.Monitor.OpenTelemetry.Profiler" Version="1.0.1-beta.1" />
<PackageVersion Condition="'$(redis)' == 'true' OR '$(redis)' == ''" Include="Aspire.Hosting.Redis" Version="13.3.3" />
<PackageVersion Condition="'$(redis)' == 'true' OR '$(redis)' == ''" Include="Aspire.StackExchange.Redis" Version="13.3.3" />
<PackageVersion Condition="'$(redis)' == 'true' OR '$(redis)' == ''" Include="Aspire.Hosting.Redis" Version="13.3.4" />
<PackageVersion Condition="'$(redis)' == 'true' OR '$(redis)' == ''" Include="Aspire.Hosting.Azure.Redis" Version="13.3.4" />
<PackageVersion Condition="'$(redis)' == 'true' OR '$(redis)' == ''" Include="Aspire.StackExchange.Redis" Version="13.3.4" />
<PackageVersion Condition="'$(redis)' == 'true' OR '$(redis)' == ''" Include="ZiggyCreatures.FusionCache.Backplane.StackExchangeRedis" Version="2.6.0" />
<PackageVersion Condition="'$(redis)' == 'true' OR '$(redis)' == ''" Include="DistributedLock.Redis" Version="1.1.1" />
<PackageVersion Condition="'$(redis)' == 'true' OR '$(redis)' == ''" Include="Hangfire.Redis.StackExchange" Version="1.12.0" />
Expand All @@ -121,7 +122,7 @@
<!--/-:msbuild-conditional:noEmit -->
<PackageVersion Include="Humanizer" Version="3.0.10" />
<PackageVersion Include="QRCoder" Version="1.8.0" />
<PackageVersion Include="Magick.NET-Q16-AnyCPU" Version="14.13.0" />
<PackageVersion Include="Magick.NET-Q16-AnyCPU" Version="14.13.1" />
<PackageVersion Include="FluentEmail.Smtp" Version="3.0.2" />
<PackageVersion Include="FluentStorage" Version="6.0.4" />
<PackageVersion Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="10.0.8" />
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<Project Sdk="Aspire.AppHost.Sdk/13.3.2">
<Project Sdk="Aspire.AppHost.Sdk/13.3.4">

<PropertyGroup>
<OutputType>Exe</OutputType>
Expand All @@ -14,11 +14,12 @@
<PackageReference Include="Aspire.Hosting.Maui" />
<PackageReference Condition=" '$(database)' == 'MySql' OR '$(database)' == '' " Include="Aspire.Hosting.MySql" />
<PackageReference Condition="'$(redis)' == 'true' OR '$(redis)' == ''" Include="Aspire.Hosting.Redis" />
<PackageReference Condition="'$(redis)' == 'true' OR '$(redis)' == ''" Include="Aspire.Hosting.Azure.Redis" />
<PackageReference Include="CommunityToolkit.Aspire.Hosting.MailPit" />
<PackageReference Condition=" '$(database)' == 'Sqlite' OR '$(database)' == '' " Include="CommunityToolkit.Aspire.Hosting.Sqlite" />
<PackageReference Condition=" '$(database)' == 'SqlServer' OR '$(database)' == ''" Include="Aspire.Hosting.SqlServer" />
<PackageReference Condition=" '$(database)' == 'SqlServer' OR '$(database)' == ''" Include="Aspire.Hosting.Azure.Sql" />
<PackageReference Condition=" '$(database)' == 'SqlServer' OR '$(database)' == ''" Include="CommunityToolkit.Aspire.Hosting.SqlServer.Extensions" />
<PackageReference Condition=" '$(database)' == 'PostgreSQL' OR '$(database)' == '' " Include="Aspire.Hosting.PostgreSQL" />
<PackageReference Condition=" '$(database)' == 'PostgreSQL' OR '$(database)' == '' " Include="Aspire.Hosting.Azure.PostgreSQL" />
<PackageReference Condition=" '$(filesStorage)' == 'AzureBlobStorage' OR '$(filesStorage)' == '' " Include="Aspire.Hosting.Azure.Storage" />
<PackageReference Condition=" '$(filesStorage)' == 'S3' OR '$(filesStorage)' == '' " Include="CommunityToolkit.Aspire.Hosting.Minio" />
</ItemGroup>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,215 @@
//+:cnd:noEmit
using Aspire.Hosting.Maui;
//#if (database == "SqlServer" || database == "PostgreSQL" || redis == true || filesStorage == "AzureBlobStorage")
using Aspire.Hosting.Azure;
//#endif
using Aspire.Hosting.DevTunnels;
//#if (redis == true)
using Azure.Provisioning.RedisEnterprise;
//#endif

namespace Aspire.Hosting;

public static class IDistributedApplicationBuilderExtensions
{
//#if (redis == true)
/// <summary>
/// Adds a Redis instance configured for FusionCache hybrid caching (L2 cache) and SignalR backplane.
/// No persistence is needed for this cache instance.
/// </summary>
public static IResourceBuilder<AzureManagedRedisResource> AddRedisCache(this IDistributedApplicationBuilder builder)
{
return builder.AddAzureManagedRedis("redis-cache")
.RunAsContainer(redis => // Remove this RunAsContainer and related configuration to use actual Azure Redis instance
{
redis.WithRedisInsight()
.WithRedisCommander()
.WithImage("redis/redis-stack", "latest")
.WithArgs(
"--save", "", // Backend API has its own L1 in-memory cache, no need to have RDB snapshots for the L2 redis cache in case of failures.
"--appendonly", "no", // Disables AOF persistence as well for the same reason.
"--maxmemory-policy", "allkeys-lru" // Evict least recently used keys when memory limit is reached
);
}).ConfigureInfrastructure(infra =>
{
var db = infra.GetProvisionableResources()
.OfType<RedisEnterpriseDatabase>()
.Single();

db.Persistence = new()
{
IsAofEnabled = false,
IsRdbEnabled = false
};

db.EvictionPolicy = RedisEnterpriseEvictionPolicy.AllKeysLru;
});
}

/// <summary>
/// Adds a Redis instance configured for Hangfire background jobs and distributed locking.
/// This instance uses AOF persistence for durability.
/// </summary>
public static IResourceBuilder<AzureManagedRedisResource> AddRedisPersistent(this IDistributedApplicationBuilder builder)
{
return builder.AddAzureManagedRedis("redis-persistent")
.RunAsContainer(redis => // Remove this RunAsContainer and related configuration to use actual Azure Redis instance
{
redis.WithRedisInsight()
.WithRedisCommander()
.WithImage("redis/redis-stack", "latest")
.WithArgs(
"--appendonly", "yes", // Enable AOF (Append only file) for data durability
"--appendfsync", "always", // Sync to disk on every write for maximum durability. Temporarily disable it programmatically using C# code during bulk operations if needed.
"--save", "", // Disables RDB snapshots
"--maxmemory-policy", "noeviction" // Raise error when memory limit is reached instead of evicting keys
);
})
.ConfigureInfrastructure(infra =>
{
var db = infra.GetProvisionableResources()
.OfType<RedisEnterpriseDatabase>()
.Single();

// --appendonly yes + --appendfsync always
db.Persistence = new()
{
IsAofEnabled = true,
AofFrequency = PersistenceSettingAofFrequency.Always,
IsRdbEnabled = false // --save ""
};

// --maxmemory-policy noeviction
db.EvictionPolicy = RedisEnterpriseEvictionPolicy.NoEviction;
});
}
//#endif

//#if (database == "SqlServer")
/// <summary>
/// Adds a SQL Server instance with DbGate management UI and a database named <c>mssqldb</c>.
/// Uses SQL Server 2025 which supports embedded vector search.
/// </summary>
public static IResourceBuilder<AzureSqlDatabaseResource> AddSqlServer(this IDistributedApplicationBuilder builder)
{
return builder.AddAzureSqlServer("sqlserver")
.RunAsContainer(sqlServer => // Remove this RunAsContainer and related configuration to use actual Azure SQL Server instance
{
sqlServer.WithDbGate(config => config.WithDataVolume())
.WithDataVolume()
.WithImage("mssql/server", "2025-latest"); // Sql server 2025 supports embedded vector search.
})
.AddDatabase("mssqldb");
}
//#endif

//#if (database == "PostgreSQL")
/// <summary>
/// Adds a PostgreSQL Server instance with pgAdmin and a database named <c>postgresdb</c>.
/// Uses pgvector (pg18) image which supports embedded vector search.
/// </summary>
public static IResourceBuilder<AzurePostgresFlexibleServerDatabaseResource> AddPostgreSQL(this IDistributedApplicationBuilder builder)
{
return builder.AddAzurePostgresFlexibleServer("postgresserver")
.RunAsContainer(postgresDatabase => // Remove this RunAsContainer and related configuration to use actual Azure PostgreSQL instance
{
postgresDatabase.WithPgAdmin()
.WithV18DataVolume()
.WithOptimizedSetup()
.WithImage("pgvector/pgvector", "pg18"); // pgvector supports embedded vector search.
})
.AddDatabase("postgresdb");
}
//#endif

//#if (database == "MySql")
/// <summary>
/// Adds a MySQL server instance with phpMyAdmin and a database named <c>mysqldb</c>.
/// </summary>
public static IResourceBuilder<MySqlDatabaseResource> AddMySql(this IDistributedApplicationBuilder builder)
{
return builder.AddMySql("mysqlserver")
.WithPhpMyAdmin()
.WithDataVolume()
.AddDatabase("mysqldb");
}
//#endif

//#if (database == "Sqlite")
/// <summary>
/// Adds a SQLite database instance with a web-based management UI.
/// </summary>
public static IResourceBuilder<SqliteResource> AddSqlite(this IDistributedApplicationBuilder builder)
{
return builder.AddSqlite("sqlite", databaseFileName: "BoilerplateDb.db")
.WithSqliteWeb();
}
//#endif

//#if (filesStorage == "AzureBlobStorage")
public static IResourceBuilder<AzureBlobStorageResource> AddAzureStorage(this IDistributedApplicationBuilder builder)
{
return builder.AddAzureStorage("storage")
.RunAsEmulator(azurite => // Remove this RunAsEmulator and related configuration to use actual Azure Blob Storage instance
{
azurite
.WithDataVolume();
})
.AddBlobs("azureblobstorage");
}
//#endif

/// <summary>
/// Adds the .NET MAUI Blazor Hybrid project and configures it for all supported device targets
/// (Windows, macOS Catalyst, iOS Device, iOS Simulator, Android Device, Android Emulator).
/// Uses dev tunnels for OpenTelemetry data collection on mobile/remote targets.
/// </summary>
public static IResourceBuilder<MauiProjectResource> AddMaui(
this IDistributedApplicationBuilder builder,
IResourceBuilder<ProjectResource> serverWebProject,
IResourceBuilder<DevTunnelResource> tunnel)
{
var mauiapp = builder.AddMauiProject("mauiapp", @"../../Client/Boilerplate.Client.Maui/Boilerplate.Client.Maui.csproj");

if (OperatingSystem.IsWindows())
{
mauiapp.AddWindowsDevice()
.WithExplicitStart()
.WithReference(serverWebProject);
}

if (OperatingSystem.IsMacOS())
{
mauiapp.AddMacCatalystDevice()
.WithExplicitStart()
.WithReference(serverWebProject);
}

if (OperatingSystem.IsMacOS())
{
// Windows supports iOS Simulator and Physical devices if there's a mac connected to network, but the following runners only work on macOS for now.

mauiapp.AddiOSDevice()
.WithExplicitStart()
.WithOtlpDevTunnel() // Required for OpenTelemetry data collection
.WithReference(serverWebProject, tunnel);

mauiapp.AddiOSSimulator()
.WithExplicitStart()
.WithOtlpDevTunnel() // Required for OpenTelemetry data collection
.WithReference(serverWebProject, tunnel);
}

mauiapp.AddAndroidDevice()
.WithExplicitStart()
.WithOtlpDevTunnel() // Required for OpenTelemetry data collection
.WithReference(serverWebProject, tunnel);

mauiapp.AddAndroidEmulator()
.WithExplicitStart()
.WithOtlpDevTunnel() // Required for OpenTelemetry data collection
.WithReference(serverWebProject, tunnel);

return mauiapp;
}
}
Loading
Loading