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
14 changes: 7 additions & 7 deletions .azure/modules/api-appservice.bicep
Original file line number Diff line number Diff line change
Expand Up @@ -41,16 +41,16 @@ param planId string
])
param kind string = 'app'

@description('The .NET version for the Web App. Allowed values: v4.8 (for .NET Framework), 6.0, 7.0, 8.0, 9.0, 10.0 (for .NET). Default is 10.0.')
@description('The .NET stack version for the Windows API App. Use v4.8 for .NET Framework, or v6.0/v7.0/v8.0/v9.0/v10.0 for modern .NET. Default is v10.0.')
@allowed([
'v4.8'
'6.0'
'7.0'
'8.0'
'9.0'
'10.0'
'v6.0'
'v7.0'
'v8.0'
'v9.0'
'v10.0'
])
param dotnetVersion string = '10.0'
param dotnetVersion string = 'v10.0'

@description('Enable Always On for the App Service')
param alwaysOn bool = false
Expand Down
36 changes: 0 additions & 36 deletions .azure/modules/azoai-azureopenai.bicep

This file was deleted.

14 changes: 7 additions & 7 deletions .azure/modules/web-appservice.bicep
Original file line number Diff line number Diff line change
Expand Up @@ -40,16 +40,16 @@ param planId string
])
param kind string = 'app'

@description('The .NET version for the Web App. Allowed values: v4.8 (for .NET Framework), 6.0, 7.0, 8.0, 9.0, 10.0 (for .NET). Default is 10.0.')
@description('The .NET stack version for the Windows Web App. Use v4.8 for .NET Framework, or v6.0/v7.0/v8.0/v9.0/v10.0 for modern .NET. Default is v10.0.')
@allowed([
'v4.8'
'6.0'
'7.0'
'8.0'
'9.0'
'10.0'
'v6.0'
'v7.0'
'v8.0'
'v9.0'
'v10.0'
])
param dotnetVersion string = '10.0'
param dotnetVersion string = 'v10.0'

@description('Enable Always On for the App Service')
param alwaysOn bool = false
Expand Down
5 changes: 5 additions & 0 deletions .github/copilot-instructions.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,11 @@
- **RBAC & Security:** Enforced in API layer, see `ConfigureServicesAuth.cs`.
- **Auth-Triggered User Provisioning:** Keep the flow UI-tied (not middleware/pipeline) because OBO token acquisition requires user context on the main UI thread/circuit.

## UI Design Guidelines
- **ChatPage Layout:** Implement distinct desktop and mobile layouts with a strict row/column structure on mobile.
- **Input Visibility:** Ensure persistent visible input under a capped message list height.
- **Component Usage:** Use only components in `.razor` files (no raw HTML elements), except in `App.razor` where raw HTML is allowed.

## References
- [README.md](../README.md): Project overview and getting started

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,13 @@ on:
branches:
- main
paths:
- .github/workflows/gtc-agent-standalone-web-api-sql.yml
- .github/workflows/gtc-agent-standalone-web-api-sql-aoai.yml
- src/**
push:
branches:
- main
paths:
- .github/workflows/gtc-agent-standalone-web-api-sql.yml
- .github/workflows/gtc-agent-standalone-web-api-sql-aoai.yml
- src/**
workflow_dispatch:
inputs:
Expand All @@ -29,6 +29,14 @@ permissions:
security-events: write

env:
# Manual environment-specific values (update these first)
APPI_NAME: 'agent-dev-wus2-001-appi'
AZURE_APIAPP_NAME: 'agent-dev-wus2-001-api'
AZURE_WEBAPP_NAME: 'agent-dev-wus2-001-web'
AZURE_RG_NAME: 'gtc-agent-dev-wus2-001-rg'
SQL_NAME: 'agent-dev-wus2-001-sql'
SQLDB_NAME: 'agent-dev-wus2-001-sqldb'

API_ARTIFACT_OUTPUT: 'publish_output'
WEB_ARTIFACT_OUTPUT: 'publish_web_output'
API_ARTIFACT_NAME: 'api-artifact'
Expand Down Expand Up @@ -94,15 +102,17 @@ jobs:
- name: pipeline environment configuration
run: |
echo "ASPNETCORE_ENVIRONMENT=${{ env.RUNTIME_ENV }}" >> $GITHUB_ENV
echo "OpenAI:ApiKey=${{ secrets.OPENAI_APIKEY }}" >> $GITHUB_ENV
echo "AzureOpenAI:Endpoint=${{ secrets.AZURE_OPENAI_ENDPOINT }}" >> $GITHUB_ENV
echo "AzureOpenAI:ApiKey=${{ secrets.AZURE_OPENAI_APIKEY }}" >> $GITHUB_ENV
shell: pwsh

- name: App Settings Variable Substitution
uses: microsoft/variable-substitution@v1
with:
files: '${{ env.SRC_PATH }}/${{ env.API_PATH }}/appsettings.json, ${{ env.SRC_PATH }}/${{ env.API_PATH }}/appsettings.${{ env.RUNTIME_ENV }}.json, ${{ env.SRC_PATH }}/${{ env.TEST_PATH }}/appsettings.test.json'
env:
OpenAI.ApiKey: ${{ secrets.OPENAI_APIKEY }}
AzureOpenAI.Endpoint: ${{ secrets.AZURE_OPENAI_ENDPOINT }}
AzureOpenAI.ApiKey: ${{ secrets.AZURE_OPENAI_APIKEY }}

- name: Initialize CodeQL
uses: github/codeql-action/init@v4
Expand Down Expand Up @@ -200,13 +210,7 @@ jobs:
matrix:
DOTNET_VERSION: ["10.x"]
env:
APPI_NAME: 'agent-dev-wus2-001-appi'
AZURE_APIAPP_NAME: 'agent-dev-wus2-001-api'
AZURE_WEBAPP_PACKAGE_PATH: '.'
AZURE_WEBAPP_NAME: 'agent-dev-wus2-001-web'
AZURE_RG_NAME: 'gtc-agent-dev-wus2-001-rg'
SQL_NAME: 'agent-dev-wus2-001-sql'
SQLDB_NAME: 'agent-dev-wus2-001-sqldb'
RUNTIME_ENV: "Development"

steps:
Expand Down Expand Up @@ -268,7 +272,8 @@ jobs:
az webapp config appsettings set -g ${{ env.AZURE_RG_NAME }} -n ${{ env.AZURE_APIAPP_NAME }} --settings ASPNETCORE_ENVIRONMENT=${{ env.RUNTIME_ENV }}
az webapp config appsettings set -g ${{ env.AZURE_RG_NAME }} -n ${{ env.AZURE_APIAPP_NAME }} --settings EntraExternalId:TenantId=${{ secrets.EEID_TENANT_ID }}
az webapp config appsettings set -g ${{ env.AZURE_RG_NAME }} -n ${{ env.AZURE_APIAPP_NAME }} --settings EntraExternalId:ClientId=${{ secrets.API_CLIENT_ID }}
az webapp config appsettings set -g ${{ env.AZURE_RG_NAME }} -n ${{ env.AZURE_APIAPP_NAME }} --settings OpenAI:ApiKey=${{ secrets.OPENAI_APIKEY }}
az webapp config appsettings set -g ${{ env.AZURE_RG_NAME }} -n ${{ env.AZURE_APIAPP_NAME }} --settings AzureOpenAI:Endpoint=${{ secrets.AZURE_OPENAI_ENDPOINT }}
az webapp config appsettings set -g ${{ env.AZURE_RG_NAME }} -n ${{ env.AZURE_APIAPP_NAME }} --settings AzureOpenAI:ApiKey=${{ secrets.AZURE_OPENAI_APIKEY }}
shell: pwsh

- name: ${{ env.AZURE_APIAPP_NAME }} connection strings
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

**Clean Architecture C# Blazor Microapp and Web API Microservice**

[![Web & API & SQL CI/CD](https://github.com/goodtocode/agent-framework-quick-start/actions/workflows/gtc-agent-standalone-web-api-sql.yml/badge.svg)](https://github.com/goodtocode/agent-framework-quick-start/actions/workflows/gtc-agent-standalone-web-api-sql.yml)
[![Web & API & SQL CI/CD](https://github.com/goodtocode/agent-framework-quick-start/actions/workflows/gtc-agent-standalone-web-api-sql-aoai.yml/badge.svg)](https://github.com/goodtocode/agent-framework-quick-start/actions/workflows/gtc-agent-standalone-web-api-sql-aoai.yml)

Microsoft Agent Framework Quick-start is a enterprise-ready starter kit for building modern, agentic applications with C#, Blazor (Fluent UI), and ASP.NET Core Web API. This solution demonstrates how to use the Microsoft Agent Framework to create a Copilot-style chat client, fully integrated with SQL Server for persistent storage of authors, chat sessions, and messages—all orchestrated through a clean architecture pattern.

Expand Down
182 changes: 182 additions & 0 deletions Reset-EfDatabase.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,182 @@
<#
=====================================================================
EF database reset script (drop + re-migrate)
Example usage:
1. Open PowerShell in solution root
2. Run: ./Reset-EfDatabase.ps1
or: ./Reset-EfDatabase.ps1 -Products $customProducts
For each product:
3. Script will drop the database
4. Script will apply existing EF migrations to recreate schema

Notes:
- This script does NOT create new migrations.
- This script does NOT run NSwag client generation.
=====================================================================
#>

param (
[Parameter(Mandatory = $false)]
[array]$Products = @(
@{ Name = "AgentFramework"; Root = ".\src"; Database = "AgentFramework"; ApiProject = "Presentation.Api" }
),
[switch]$DropDatabase,
[switch]$DropTables,
[string]$DropTablesPath = ".\data\Admin\Drop Tables.sql"
)

Push-Location
try {
function Invoke-DiagnosticCommand($cmd) {
Write-Host "[DIAG] Running: $cmd" -ForegroundColor Yellow
try {
Invoke-Expression $cmd
}
catch {
Write-Error "[ERROR] Command failed: $cmd"
Write-Error $_
throw
}
}

function Install-SqlCmd {
$sqlcmd = Get-Command sqlcmd -ErrorAction SilentlyContinue
if (-not $sqlcmd) {
Write-Host "sqlcmd not found. Installing via winget..." -ForegroundColor Yellow
winget install --id Microsoft.SQLServerCommandLineTools -e --silent
$sqlcmd = Get-Command sqlcmd -ErrorAction SilentlyContinue
if (-not $sqlcmd) {
throw "sqlcmd installation failed. Please install manually."
}
Write-Host "sqlcmd installed." -ForegroundColor Green
}
else {
Write-Host "sqlcmd is already installed." -ForegroundColor Green
}
}

function Test-DatabaseExists {
param (
[string]$DatabaseName
)

$query = "IF DB_ID('$DatabaseName') IS NOT NULL SELECT 1 ELSE SELECT 0"
$result = sqlcmd -S "(localdb)\MSSQLLocalDB" -Q $query -h -1 -W 2>$null
return ($result -eq '1')
}

function Reset-DatabaseState {
param (
[string]$Name,
[string]$Database,
[string]$Context,
[string]$Connection,
[string]$InfraProjAbs,
[string]$WebApiProjAbs
)

if ($DropDatabase -and $DropTables) {
throw "Choose either -DropDatabase or -DropTables, not both."
}

$useDropDatabase = $DropDatabase
$useDropTables = $DropTables
if (-not $DropDatabase -and -not $DropTables) {
# Preserve existing behavior for this script: drop and recreate database.
$useDropDatabase = $true
}

if ($useDropTables) {
Install-SqlCmd
if (-not (Test-Path $DropTablesPath)) {
throw "Drop tables script not found at $DropTablesPath"
}
if (Test-DatabaseExists -DatabaseName $Database) {
Write-Host "[STEP] Dropping all tables for $Name via $DropTablesPath..." -ForegroundColor Magenta
$dropResult = sqlcmd -S "(localdb)\MSSQLLocalDB" -d $Database -b -i $DropTablesPath 2>&1
if ($LASTEXITCODE -ne 0) {
throw "Drop tables script failed for ${Name}: $dropResult"
}
Write-Host "[SUCCESS] All tables dropped for $Name." -ForegroundColor Green
}
else {
Write-Host "[WARN] Database '$Database' does not exist. Skipping drop tables step for $Name." -ForegroundColor Yellow
}
}

if ($useDropDatabase) {
Write-Host "[STEP] Dropping database for $Name..." -ForegroundColor Magenta
Invoke-DiagnosticCommand "dotnet ef database drop --project `"$InfraProjAbs`" --startup-project `"$WebApiProjAbs`" --context $Context --connection '$Connection' --force --verbose"
}
}

# Ensure dotnet-ef tool is available (idempotent).
$srcPath = Join-Path $PSScriptRoot 'src'
$toolsManifest = Join-Path $srcPath 'dotnet-tools.json'
Push-Location $srcPath
try {
if (!(Test-Path -Path $toolsManifest)) {
Write-Host "[STEP] Creating dotnet tool manifest in src/..." -ForegroundColor Magenta
Invoke-DiagnosticCommand "dotnet new tool-manifest --force"
}

$toolList = & dotnet tool list --local | Out-String
if ($toolList -notmatch 'dotnet-ef') {
Write-Host "[STEP] Installing dotnet-ef as a local tool in src/..." -ForegroundColor Magenta
Invoke-DiagnosticCommand "dotnet tool install dotnet-ef --local"
}
else {
Write-Host "[STEP] dotnet-ef already installed as a local tool." -ForegroundColor Green
}

Write-Host "[STEP] Restoring local dotnet tools..." -ForegroundColor Magenta
Invoke-DiagnosticCommand "dotnet tool restore"
}
finally {
Pop-Location
}

foreach ($product in $Products) {
$name = $product.Name
$root = $product.Root
$database = $product.Database
$apiProject = $product.ApiProject

$infraProj = "$root\Infrastructure.SqlServer\Infrastructure.SqlServer.csproj"
$webApiProj = "$root\$apiProject\$apiProject.csproj"
$context = "${name}Context"
$connection = "Data Source=(localdb)\MSSQLLocalDB;Initial Catalog=$database;Min Pool Size=3;MultipleActiveResultSets=True;Trusted_Connection=Yes;TrustServerCertificate=True;Encrypt=True;Connection Timeout=30;"

Write-Host "[STEP] Product - $name" -ForegroundColor Cyan
Write-Host "[STEP] Context - $context" -ForegroundColor Cyan
Write-Host "[STEP] Infra project - $infraProj" -ForegroundColor Cyan
Write-Host "[STEP] WebApi project - $webApiProj" -ForegroundColor Cyan

Invoke-DiagnosticCommand "dotnet clean $infraProj"
Invoke-DiagnosticCommand "dotnet restore $infraProj"
Invoke-DiagnosticCommand "dotnet build $infraProj --no-restore"
Invoke-DiagnosticCommand "dotnet clean $webApiProj"
Invoke-DiagnosticCommand "dotnet restore $webApiProj"
Invoke-DiagnosticCommand "dotnet build $webApiProj --no-restore"

# Build absolute paths from $PSScriptRoot to avoid CWD-related path resolution issues.
$infraProjAbs = Join-Path $PSScriptRoot ($infraProj -replace '^\.[\\/]', '')
$webApiProjAbs = Join-Path $PSScriptRoot ($webApiProj -replace '^\.[\\/]', '')

Push-Location $srcPath
try {
Reset-DatabaseState -Name $name -Database $database -Context $context -Connection $connection -InfraProjAbs $infraProjAbs -WebApiProjAbs $webApiProjAbs

Write-Host "[STEP] Applying existing migrations for $name..." -ForegroundColor Magenta
Invoke-DiagnosticCommand "dotnet ef database update --project `"$infraProjAbs`" --startup-project `"$webApiProjAbs`" --context $context --connection '$connection' --verbose"
}
finally {
Pop-Location
}
}

Write-Host "[DONE] Database reset and migration update complete." -ForegroundColor Green
}
finally {
Pop-Location
}
Loading
Loading