Skip to content

Commit 1f53acc

Browse files
authored
✨ deploy to azure from aspire (#539)
* Added the ability to deploy to app service, azure sql, and app insights on azure. * 📖 Update README with Azure deployment instructions * 📖 Update README for improved Azure deployment instructions formatting * Use github markdown for notes * Fixed up formatting * Fixed integration tests * Remove commented-out SqlServer package reference from AppHost.csproj * Refactor environment variable handling in Azure App Service configuration
1 parent 1fd1d80 commit 1f53acc

File tree

8 files changed

+140
-42
lines changed

8 files changed

+140
-42
lines changed

Directory.Packages.props

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,9 @@
55
<ItemGroup>
66
<PackageVersion Include="Ardalis.Specification" Version="9.2.0"/>
77
<PackageVersion Include="Ardalis.Specification.EntityFrameworkCore" Version="9.2.0"/>
8+
<PackageVersion Include="Aspire.Hosting.Azure.ApplicationInsights" Version="9.4.0"/>
9+
<PackageVersion Include="Aspire.Hosting.Azure.AppService" Version="9.4.0-preview.1.25378.8"/>
10+
<PackageVersion Include="Aspire.Hosting.Azure.Sql" Version="9.4.0"/>
811
<PackageVersion Include="AspNetCore.HealthChecks.SqlServer" Version="9.0.0" />
912
<PackageVersion Include="AspNetCore.HealthChecks.UI" Version="9.0.0" />
1013
<PackageVersion Include="AspNetCore.HealthChecks.UI.Client" Version="9.0.0" />

README.md

Lines changed: 76 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -109,29 +109,32 @@ This is a template for creating a new project using [Clean Architecture](https:/
109109

110110
1. Install the SSW CA template
111111

112-
```bash
113-
dotnet new install SSW.CleanArchitecture.Template
114-
```
115-
116-
> NOTE: The template only needs to be installed once. Running this command again will update your version of the template.
112+
```bash
113+
dotnet new install SSW.CleanArchitecture.Template
114+
```
115+
116+
> [!NOTE]
117+
> The template only needs to be installed once. Running this command again will update your version of the template.
117118

118119
2. Create a new directory
119120

120-
```bash
121-
mkdir Northwind365
122-
cd Northwind365
123-
```
121+
```bash
122+
mkdir Northwind365
123+
cd Northwind365
124+
```
124125

125126
3. Create a new solution
126127

127-
```bash
128-
dotnet new ssw-ca
129-
```
128+
```bash
129+
dotnet new ssw-ca
130+
```
130131

131-
> NOTE: `name` is optional; if you don't specify it, the directory name will be used as the solution name and project namespaces.
132+
> [!NOTE]
133+
> `name` is optional; if you don't specify it, the directory name will be used as the solution name and project namespaces.
132134
133-
Alternatively, you can specify the `name` and `output` directory as follows:
134135
136+
Alternatively, you can specify the `name` and `output` directory as follows:
137+
135138
```bash
136139
dotnet new ssw-ca --name {{SolutionName}} --output .\
137140
```
@@ -140,21 +143,21 @@ dotnet new ssw-ca --name {{SolutionName}} --output .\
140143
141144
1. Create a query
142145
143-
```bash
144-
cd src/Application/UseCases
145-
mkdir {{FeatureName}}
146-
cd {{FeatureName}}
147-
dotnet new ssw-ca-query --name {{QueryName}} --entityName {{Entity}} --slnName {{SolutionName}}
148-
```
146+
```bash
147+
cd src/Application/UseCases
148+
mkdir {{FeatureName}}
149+
cd {{FeatureName}}
150+
dotnet new ssw-ca-query --name {{QueryName}} --entityName {{Entity}} --slnName {{SolutionName}}
151+
```
149152
150153
2. Create a command
151154
152-
```bash
153-
cd src/Application/UseCases
154-
mkdir {{FeatureName}}
155-
cd {{FeatureName}}
156-
dotnet new ssw-ca-command --name {{CommandName}} --entityName {{Entity}} --slnName {{SolutionName}}
157-
```
155+
```bash
156+
cd src/Application/UseCases
157+
mkdir {{FeatureName}}
158+
cd {{FeatureName}}
159+
dotnet new ssw-ca-command --name {{CommandName}} --entityName {{Entity}} --slnName {{SolutionName}}
160+
```
158161
159162
### Running the Solution
160163
@@ -176,9 +179,52 @@ dotnet new ssw-ca-command --name {{CommandName}} --entityName {{Entity}} --slnNa
176179
dotnet run
177180
```
178181
179-
> **NOTE:** The first time you run the solution, it may take a while to download the docker images, create the DB, and seed the data.
182+
> [!NOTE]
183+
> The first time you run the solution, it may take a while to download the docker images, create the DB, and seed the data.
184+
185+
3. Open https://localhost:7255/scalar/v1 in your browser to see it running ️🏃‍♂️
186+
187+
## Deploying to Azure
188+
189+
The template can be deployed to Azure via
190+
the [Azure Developer CLI (AZD)](https://learn.microsoft.com/en-us/azure/developer/azure-developer-cli/install-azd?tabs=winget-windows,brew-mac,script-linux&pivots=os-mac).
191+
This will setup the following:
192+
193+
- Azure App Services: API + MigrationService
194+
- Azure SQL Server + Database: Data storage
195+
- Application Insights + Log Analytics: For monitoring and logging
196+
- Managed Identities: For secure access to Azure resources
197+
- Azure Container Registry: For storing Docker images
198+
199+
### Steps to Deploy
200+
201+
1. Authenticate with Azure
202+
203+
```bash
204+
azd auth login
205+
```
206+
207+
2. Initialize AZD for the project
208+
209+
```bash
210+
azd init
211+
```
212+
213+
3. Update environment variables
214+
215+
```bash
216+
azd env set ASPNETCORE_ENVIRONMENT Development
217+
```
218+
219+
4. Deploy to Azure
220+
221+
```bash
222+
azd up
223+
```
180224
181-
4. Open https://localhost:7255/scalar/v1 in your browser to see it running ️🏃‍♂️
225+
> [!NOTE]
226+
> `azd up` combines `azd provision` and `azd deploy` commands to create the resources and deploy the application. If running this from a CI/CD
227+
> pipeline, you can use `azd provision` and `azd deploy` separately in the appropriate places.
182228
183229
## 🚀 Publishing Template
184230
@@ -191,7 +237,8 @@ Template will be published to NuGet.org when changes are made to `CleanArchitect
191237
3. `package` GitHub Action will run and publish the new version to NuGet.org
192238
4. Create a GitHub release to document the changes
193239
194-
> **NOTE:** We are now using CalVer for versioning. The version number should be in the format `YYYY.M.D` (e.g. `2024.2.12`).
240+
> [!NOTE]
241+
> We are now using CalVer for versioning. The version number should be in the format `YYYY.M.D` (e.g. `2024.2.12`).
195242
196243
<!-- TODO Issue #99: Getting Started using the dotnet new template -->
197244

src/Infrastructure/DependencyInjection.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ public static class DependencyInjection
1111
{
1212
public static void AddInfrastructure(this IHostApplicationBuilder builder)
1313
{
14-
builder.AddSqlServerDbContext<ApplicationDbContext>("Clean-Architecture",
14+
builder.AddSqlServerDbContext<ApplicationDbContext>("CleanArchitecture",
1515
null,
1616
options =>
1717
{

tests/WebApi.IntegrationTests/Common/Infrastructure/Web/WebApiTestFactory.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,6 @@ protected override void ConfigureWebHost(IWebHostBuilder builder)
2929
// x.Services.AddSingleton<ILoggerProvider>(new XUnitLoggerProvider(Output));
3030
});
3131

32-
builder.UseSetting("ConnectionStrings:clean-architecture", _dbConnection.ConnectionString);
32+
builder.UseSetting("ConnectionStrings:CleanArchitecture", _dbConnection.ConnectionString);
3333
}
3434
}

tools/AppHost/AppHost.cs

Lines changed: 51 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,71 @@
11
using AppHost.Commands;
2+
using Azure.Provisioning;
3+
using Azure.Provisioning.AppService;
24
using Projects;
35

46
var builder = DistributedApplication.CreateBuilder(args);
57

8+
// Configure the Azure App Service environment
9+
builder.AddAzureAppServiceEnvironment("plan").ConfigureInfrastructure(infra =>
10+
{
11+
var plan = infra.GetProvisionableResources()
12+
.OfType<AppServicePlan>()
13+
.Single();
14+
15+
plan.Sku = new AppServiceSkuDescription
16+
{
17+
Name = "B1", // Basic tier, 1 core
18+
};
19+
});
20+
621
var sqlServer = builder
7-
// If desired, set SQL Server Port to a constant value
8-
.AddSqlServer("sql" /*, port: 1800*/)
9-
.WithLifetime(ContainerLifetime.Persistent);
22+
.AddAzureSqlServer("sql")
23+
.RunAsContainer(container =>
24+
{
25+
// Configure SQL Server to run locally as a container
26+
container.WithLifetime(ContainerLifetime.Persistent);
27+
28+
// If desired, set SQL Server Port to a constant value
29+
//container.WithHostPort(1800);
30+
});
1031

1132
var db = sqlServer
12-
.AddDatabase("clean-architecture")
33+
.AddDatabase("CleanArchitecture", "clean-architecture")
1334
.WithDropDatabaseCommand();
1435

1536
var migrationService = builder.AddProject<MigrationService>("migrations")
37+
.PublishAsAzureAppServiceWebsite((_, site) =>
38+
{
39+
const string envNetCoreEnvironment = "ASPNETCORE_ENVIRONMENT";
40+
41+
// Needed for hosted service to run
42+
site.SiteConfig.IsAlwaysOn = true;
43+
44+
// Dynamically set environment, so we can enable seeding of data (only happens in 'development')
45+
var environment = Environment.GetEnvironmentVariable(envNetCoreEnvironment);
46+
if (string.IsNullOrWhiteSpace(environment))
47+
return;
48+
49+
var envSetting = new AppServiceNameValuePair { Name = envNetCoreEnvironment, Value = environment };
50+
site.SiteConfig.AppSettings.Add(new BicepValue<AppServiceNameValuePair>(envSetting));
51+
})
1652
.WithReference(db)
1753
.WaitFor(sqlServer);
1854

19-
builder
55+
var api = builder
2056
.AddProject<WebApi>("api")
2157
.WithExternalHttpEndpoints()
2258
.WithReference(db)
2359
.WaitForCompletion(migrationService);
2460

61+
// Configure Application Insights and Log Analytics only if in publish mode
62+
// When running locally, use Aspire Dashboard instead
63+
if (builder.ExecutionContext.IsPublishMode)
64+
{
65+
var logAnalytics = builder.AddAzureLogAnalyticsWorkspace("log-analytics");
66+
var insights = builder.AddAzureApplicationInsights("insights", logAnalytics);
67+
api.WithReference(insights);
68+
migrationService.WithReference(insights);
69+
}
70+
2571
builder.Build().Run();

tools/AppHost/AppHost.csproj

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,9 @@
1010

1111
<ItemGroup>
1212
<PackageReference Include="Aspire.Hosting.AppHost" />
13-
<PackageReference Include="Aspire.Hosting.SqlServer" />
13+
<PackageReference Include="Aspire.Hosting.Azure.ApplicationInsights"/>
14+
<PackageReference Include="Aspire.Hosting.Azure.AppService"/>
15+
<PackageReference Include="Aspire.Hosting.Azure.Sql"/>
1416
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" />
1517
</ItemGroup>
1618

tools/AppHost/Commands/SqlServerCommandExt.cs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,12 @@
1+
using Aspire.Hosting.Azure;
12
using Microsoft.EntityFrameworkCore;
2-
using Microsoft.Extensions.Diagnostics.HealthChecks;
3-
using SqlServerDatabaseResource = Aspire.Hosting.ApplicationModel.SqlServerDatabaseResource;
43

54
namespace AppHost.Commands;
65

76
public static class SqlServerDatabaseCommandExt
87
{
9-
public static IResourceBuilder<SqlServerDatabaseResource> WithDropDatabaseCommand(
10-
this IResourceBuilder<SqlServerDatabaseResource> builder)
8+
public static IResourceBuilder<AzureSqlDatabaseResource> WithDropDatabaseCommand(
9+
this IResourceBuilder<AzureSqlDatabaseResource> builder)
1110
{
1211
builder.WithCommand(
1312
"drop-database",
@@ -25,6 +24,7 @@ public static IResourceBuilder<SqlServerDatabaseResource> WithDropDatabaseComman
2524
return CommandResults.Success();
2625
},
2726
null); // Intentionally using 'null' for the command state resolver as this command does not require health status checks. Downstream code is expected to handle 'null' appropriately.
27+
2828
return builder;
2929
}
3030
}

tools/MigrationService/Program.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
builder.Services.AddScoped<ICurrentUserService, MigrationUserService>();
2020
builder.Services.AddSingleton(TimeProvider.System);
2121

22-
builder.AddSqlServerDbContext<ApplicationDbContext>("Clean-Architecture",
22+
builder.AddSqlServerDbContext<ApplicationDbContext>("CleanArchitecture",
2323
null,
2424
options =>
2525
{

0 commit comments

Comments
 (0)