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.
A ready-to-run container image is published on Docker Hub:
congiuluc/foundry-test-chatbot.
The full source code lives at
github.com/congiuluc/foundry-test-chatbot.
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" `
-e AZURE_OPENAI_API_KEY="<your-api-key>" `
congiuluc/foundry-test-chatbot:latestRunning outside Azure (local Docker, another cloud, on-prem): managed identity is not available, so you must provide
AZURE_OPENAI_API_KEY. When the image runs on Azure with a managed identity, you can omit the key.
Deploy to Azure: Azure App Service · Azure Container Apps · Azure Kubernetes Service (AKS)
- Run the published image
- 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. |
Authentication: the API key is optional when running on Azure. By default the app authenticates with managed identity (
DefaultAzureCredential, also honoringaz loginlocally andAZURE_CLIENT_IDfor a user-assigned identity). If you prefer, or when the image runs outside Azure (local Docker, another cloud, on-prem) where managed identity is unavailable, pass an API key through theAZURE_OPENAI_API_KEYenvironment variable — when it is set, it takes precedence over managed identity.
$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.
The quickest path: run the published Docker Hub image directly as a Linux Web App for Containers — no build required.
az group create --name rg-chat --location westeurope
az appservice plan create `
--name plan-chat --resource-group rg-chat `
--is-linux --sku B1
az webapp create `
--name <app-name> --resource-group rg-chat --plan plan-chat `
--container-image-name docker.io/congiuluc/foundry-test-chatbot:latest
# The container listens on 8080; route to it and set the app configuration.
az webapp config appsettings set `
--name <app-name> --resource-group rg-chat `
--settings `
WEBSITES_PORT=8080 `
AI_MODE=model `
AZURE_OPENAI_ENDPOINT="https://<your-resource>.openai.azure.com/" `
AZURE_OPENAI_DEPLOYMENT_NAME="gpt-4o-mini"Image name: use
congiuluc/foundry-test-chatbot:latest(Docker Hub is the default registry). Do not prefix it withhub.docker.com/— that is the website, not the registry endpoint, and causes an "image not found" pull error.
To authenticate with a managed identity instead of an API key, enable the Web App's
system-assigned identity and grant it the Cognitive Services OpenAI User role on your
Azure OpenAI / Foundry resource, then leave AZURE_OPENAI_API_KEY unset.
./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).
Or deploy the published Docker Hub image directly, without building or pushing to ACR:
az containerapp create `
--name chatapp --resource-group rg-chat `
--environment cae-chat `
--image docker.io/congiuluc/foundry-test-chatbot:latest `
--target-port 8080 --ingress external `
--env-vars `
AI_MODE=model `
AZURE_OPENAI_ENDPOINT="https://<your-resource>.openai.azure.com/" `
AZURE_OPENAI_DEPLOYMENT_NAME="gpt-4o-mini"Apply a Deployment and a Service that pull the public image. Save this as chatapp.yaml:
apiVersion: apps/v1
kind: Deployment
metadata:
name: chatapp
spec:
replicas: 2
selector:
matchLabels:
app: chatapp
template:
metadata:
labels:
app: chatapp
spec:
containers:
- name: chatapp
image: congiuluc/foundry-test-chatbot:latest
ports:
- containerPort: 8080
env:
- name: AI_MODE
value: "model"
- name: AZURE_OPENAI_ENDPOINT
value: "https://<your-resource>.openai.azure.com/"
- name: AZURE_OPENAI_DEPLOYMENT_NAME
value: "gpt-4o-mini"
readinessProbe:
httpGet:
path: /healthz
port: 8080
---
apiVersion: v1
kind: Service
metadata:
name: chatapp
spec:
type: LoadBalancer
selector:
app: chatapp
ports:
- port: 80
targetPort: 8080az aks get-credentials --resource-group rg-chat --name <aks-cluster>
kubectl apply -f chatapp.yaml
kubectl get service chatapp # note the EXTERNAL-IP once provisionedFor production, keep secrets such as AZURE_OPENAI_API_KEY in a Kubernetes Secret and
reference them with valueFrom.secretKeyRef, or use workload identity for managed-identity
authentication to Azure OpenAI.
Tagging a release (v*.*.*) triggers the release workflow,
which builds the image and pushes it to Docker Hub as
congiuluc/foundry-test-chatbot. 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 congiuluc/foundry-test-chatbot: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" `
congiuluc/foundry-test-chatbot: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.
