A minimal .NET 10 web application that lets users chat with an AI assistant powered by Microsoft Foundry through the Microsoft Agent Framework. The backend talks either directly to a deployed model or to an existing Foundry agent, streams responses to a lightweight HTML/JS UI, and is fully configured through environment variables.
The chat experience is also packaged as a zero-dependency embeddable widget that can be
dropped onto any website with a single <script> tag.
- Features
- Architecture
- Quick start
- Configuration
- Running locally
- Deployment
- Embeddable widget
- Documentation site
- Contributing
- License
- ASP.NET Core minimal API (no Blazor, no MVC) serving a static chat page from
wwwroot. - Two backend modes, selected by the
AI_MODEenvironment variable:model— chat completions against an Azure OpenAI / Foundry model deployment.agent— an existing agent hosted on Microsoft Foundry.
- Flexible authentication: uses an API key when
AZURE_OPENAI_API_KEYis set; otherwise falls back to managed identity (DefaultAzureCredential, honoringAZURE_CLIENT_IDfor a user-assigned identity). - Streamed responses (token-by-token) over Server-Sent Events.
- Light / dark / system theme toggle in the chat widget and the demo page.
- Production-ready plumbing: Serilog logging (console + rolling file), Swagger UI, health checks, response compression, and consistent JSON error handling.
- Containerized and deployable to Azure Container Apps via the included scripts, or pulled from Docker Hub through the automated release workflow.
- Embeddable widget: a single self-contained
chatbot-widget.jswith no runtime dependencies, fully themeable throughdata-*attributes.
src/ChatApp/
├── Program.cs # App startup, DI, middleware, endpoint mapping
├── Endpoints/ # Minimal API endpoints (chat, health)
├── Services/ # Chat agent providers and credential factory
├── Middleware/ # Centralized error handling
├── Models/ # Request/response DTOs
├── Configuration/ # Strongly-typed options bound from env vars
├── wwwroot/ # Static chat page + built widget bundle
└── widget/ # TypeScript sources for the embeddable widget
docs/ # GitHub Pages site (widget reference & examples)
scripts/ # PowerShell build/deploy helpers
At runtime the app selects a chat provider based on AI_MODE, resolves credentials
(API key or managed identity), and exposes a streaming chat endpoint consumed by both the
bundled chat page and the embeddable widget.
Prerequisites: .NET 10 SDK, and (for deployment) the
Azure CLI with the containerapp extension.
git clone https://github.com/congiuluc/foundry-test-chatbot.git
cd foundry-test-chatbot
dotnet restore src/ChatApp/ChatApp.csproj
dotnet build src/ChatApp/ChatApp.csproj -c ReleaseAll configuration is supplied through environment variables:
| Variable | Mode | Description |
|---|---|---|
AI_MODE |
both | model (default) or agent. |
AZURE_OPENAI_ENDPOINT |
model | Azure OpenAI / Foundry models endpoint. |
AZURE_OPENAI_DEPLOYMENT_NAME |
model | Model deployment name. |
AZURE_OPENAI_API_KEY |
model | Optional. When omitted, managed identity is used. |
AZURE_FOUNDRY_PROJECT_ENDPOINT |
agent | Foundry project endpoint. |
AZURE_FOUNDRY_AGENT_ID |
agent | Name of the existing Foundry agent. |
AZURE_FOUNDRY_AGENT_VERSION |
agent | Optional agent version (latest when omitted). |
AZURE_CLIENT_ID |
both | Optional user-assigned managed identity client id. |
CHAT_SYSTEM_PROMPT |
model | Optional system instructions. |
CHAT_AGENT_NAME |
model | Optional agent display name. |
$env:AI_MODE = "model"
$env:AZURE_OPENAI_ENDPOINT = "https://<your-resource>.openai.azure.com/"
$env:AZURE_OPENAI_DEPLOYMENT_NAME = "gpt-4o-mini"
# Leave AZURE_OPENAI_API_KEY unset to use your `az login` / managed identity.
dotnet run --project src/ChatApp/ChatApp.csprojThen open the printed URL. Swagger is available at /swagger and the health check at
/healthz.
./scripts/build-and-push.ps1 -RegistryName <acrName> -Tag v1./scripts/deploy-containerapp.ps1 `
-ResourceGroup rg-chat `
-EnvironmentName cae-chat `
-AppName chatapp `
-RegistryName <acrName> `
-Tag v1 `
-EnvVars @{
AI_MODE = "model"
AZURE_OPENAI_ENDPOINT = "https://<your-resource>.openai.azure.com/"
AZURE_OPENAI_DEPLOYMENT_NAME = "gpt-4o-mini"
}The script enables a system-assigned managed identity and grants it AcrPull. Remember to
also grant that identity access to your Foundry resource (e.g. Azure AI User or
Cognitive Services OpenAI User).
Tagging a release (v*.*.*) triggers the release workflow,
which builds the image and pushes it to Docker Hub as <docker-hub-user>/chatapp. Each
release is tagged with its version plus a channel tag derived from the Git tag:
| Git tag example | Channel tag | GitHub release |
|---|---|---|
v1.2.3 |
latest |
stable |
v1.2.3-prerelease |
prerelease |
pre-release |
v1.2.3-dev |
dev |
pre-release |
The workflow requires the DOCKERHUB_USERNAME and DOCKERHUB_TOKEN repository secrets.
Pull and run the published image with:
docker pull <docker-hub-user>/chatapp:latest
docker run -p 8080:8080 `
-e AI_MODE=model `
-e AZURE_OPENAI_ENDPOINT="https://<your-resource>.openai.azure.com/" `
-e AZURE_OPENAI_DEPLOYMENT_NAME="gpt-4o-mini" `
<docker-hub-user>/chatapp:latestThe chat UI is also shipped as a single, self-contained script (chatbot-widget.js, built
from the TypeScript sources in src/ChatApp/widget). Drop it on any website to render a
floating launcher and chat panel:
<script
src="https://your-app.example.com/chatbot-widget.js"
data-title="Support"
data-accent="#2563eb"
defer></script>All behaviour and theming is controlled through data-* attributes on the script tag:
| Attribute | Default | Description |
|---|---|---|
data-api-base |
script origin | Backend base URL the widget calls. |
data-title |
Chatbot |
Header title text. |
data-accent |
#2563eb |
Accent colour (launcher and primary actions). |
data-icon |
— | URL of a custom launcher icon image (https:, data:image/, or /path). |
data-panel |
#1e293b |
Chat panel background colour. |
data-user-bubble |
accent | Background colour of user (outgoing) bubbles. |
data-bot-bubble |
#334155 |
Background colour of bot (incoming) bubbles. |
data-text |
#e2e8f0 |
Primary text colour inside the panel. |
data-position |
bottom-right |
Anchor corner: bottom-right or bottom-left. |
data-allow-settings |
true |
Set to false to hide the in-panel settings UI. |
data-greeting |
Ask me anything to get started. |
Opening assistant message. |
data-storage-key |
derived | LocalStorage key for persisting the conversation. |
data-debug |
false |
Set to true to enable verbose console logging. |
Colour values accept hex (#0f172a), rgb()/rgba(), hsl()/hsla() or CSS named colours;
invalid values are ignored and fall back to the defaults.
The widget also includes an interactive light / dark / system theme toggle (a header icon button plus a selector in the settings panel); the chosen theme is remembered per session.
A full reference with live, copyable examples is published as a
GitHub Pages site (sources in docs/).
cd src/ChatApp/widget
npm install
npm run build # outputs ../wwwroot/chatbot-widget.jsA full widget reference with live, copyable examples is published as a
GitHub Pages site (sources in docs/).
It is deployed automatically by the Pages workflow whenever
the docs/ directory changes on main.
Contributions are welcome! Please read CONTRIBUTING.md for the development setup and conventions, and note our Code of Conduct. Changes are tracked in CHANGELOG.md. To report a security issue, follow SECURITY.md instead of opening a public issue.
Licensed under the MIT License. See LICENSE for details.
