ワンクリックで
writing-csharp-code
// Provides C# and ASP.NET Core coding standards for this repository. Use when writing or modifying C# code, implementing API endpoints, configuring middleware, or working with authentication in the backend.
// Provides C# and ASP.NET Core coding standards for this repository. Use when writing or modifying C# code, implementing API endpoints, configuring middleware, or working with authentication in the backend.
Provides deployment commands and troubleshooting for Azure Container Apps. Use when running azd commands, deploying containers, debugging deployment failures, or updating infrastructure in this repository.
Provides SSE streaming patterns for the chat API and frontend. Use when implementing or modifying chat streaming, handling SSE events, or troubleshooting message flow between frontend and backend.
Provides research patterns for Foundry Agent Service SDK. Use when implementing agent features, looking up SDK methods, finding code samples, or troubleshooting Azure.AI.Projects API usage.
Provides architecture overview with state machines, SSE event flow, and file mappings. Use when understanding system design, debugging state issues, or maintaining ARCHITECTURE-FLOW.md.
Diagnose and fix incomplete local development setup. Use when dev servers fail to start, env vars are missing, authentication errors occur, or before running any dev commands for the first time.
Provides TypeScript and React coding standards for this repository. Use when writing or modifying TypeScript code, creating React components, implementing MSAL authentication, or working with the frontend.
| name | writing-csharp-code |
| description | Provides C# and ASP.NET Core coding standards for this repository. Use when writing or modifying C# code, implementing API endpoints, configuring middleware, or working with authentication in the backend. |
Goal: Write clean, secure ASP.NET Core code with proper authentication
The backend runs in watch mode (dotnet watch run). When you edit C# code:
VS Code Tasks (use Run Task command or check terminal panel):
Backend: ASP.NET Core API - Runs dotnet watch run with live recompilationNo restart needed - Just edit, save, and test. Watch for compilation errors in the terminal.
Testing changes: Use Playwright browser tools to make requests and check browser console logs, or call endpoints directly.
Use typed request models, CancellationToken, and IHostEnvironment:
app.MapPost("/api/endpoint", async (
RequestModel request,
MyService service,
IHostEnvironment env,
CancellationToken cancellationToken) =>
{
try
{
var result = await service.ProcessAsync(request, cancellationToken);
return Results.Ok(result);
}
catch (Exception ex)
{
return ErrorResponseFactory.CreateFromException(ex, env);
}
})
.RequireAuthorization("RequireChatScope")
.WithName("EndpointName");
JWT Bearer with Entra ID:
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddMicrosoftIdentityWebApi(options =>
{
builder.Configuration.Bind("AzureAd", options);
options.TokenValidationParameters.ValidAudiences = new[]
{
builder.Configuration["AzureAd:ClientId"],
$"api://{builder.Configuration["AzureAd:ClientId"]}"
};
}, options => builder.Configuration.Bind("AzureAd", options));
// ✅ Use async/await with CancellationToken
public async Task<Result> ProcessAsync(Request req, CancellationToken ct)
{
return await _service.ExecuteAsync(req, ct);
}
// ❌ Never block on async
var result = _service.ExecuteAsync(req).Result; // WRONG
public async IAsyncEnumerable<string> StreamAsync(
string input,
[EnumeratorCancellation] CancellationToken cancellationToken = default)
{
await foreach (var chunk in source.WithCancellation(cancellationToken))
{
yield return chunk;
}
}
TokenCredential credential = env.IsDevelopment()
? new ChainedTokenCredential(
new AzureCliCredential(),
new AzureDeveloperCliCredential()) // Supports 'azd auth login'
: new ManagedIdentityCredential(miClientId); // User-assigned MI in production
Why ChainedTokenCredential: Avoids DefaultAzureCredential's "fail fast" mode issues. Explicit, predictable credential chain.
public class MyService : IDisposable
{
private readonly SemaphoreSlim _lock = new(1, 1);
private readonly CancellationTokenSource _disposeCts = new();
private bool _disposed;
public void DoWork()
{
ObjectDisposedException.ThrowIf(_disposed, this);
// ...
}
public void Dispose()
{
if (_disposed) return;
_disposed = true;
// Cancel pending operations first
try { _disposeCts.Cancel(); }
catch (ObjectDisposedException) { }
_disposeCts.Dispose();
_lock.Dispose();
}
}
Use ErrorResponseFactory.CreateFromException() for consistent error responses.
See: backend/WebApp.Api/Models/ErrorResponse.cs
.Result or .Wait() on async methodsCancellationToken parameter.RequireAuthorization() on endpointsIDisposableGoal: Serve static files → validate auth → route APIs → SPA fallback
app.UseDefaultFiles(); // index.html for /
app.UseStaticFiles(); // wwwroot/* assets
app.UseCors(); // Dev only
app.UseAuthentication(); // Validate JWT
app.UseAuthorization(); // Enforce scope
// Map endpoints here
app.MapFallbackToFile("index.html"); // MUST BE LAST
See: backend/WebApp.Api/Services/AgentFrameworkService.cs
SDK Packages:
Azure.AI.Projects — Main entry point, v2 Agents API (see *.csproj for version)Azure.AI.Projects.Agents — ProjectsAgentVersion, DeclarativeAgentDefinition, AgentAdministrationClientAzure.AI.Extensions.OpenAI — ProjectOpenAIClient, ProjectConversationsClient, ProjectResponsesClientSub-namespaces: Azure.AI.Projects.Agents, Azure.AI.Extensions.OpenAI, OpenAI.Responses
Key patterns:
IDisposable implementationObjectDisposedException.ThrowIf) in all public methodsProjectsAgentVersion resolved once per process via SemaphoreSlimAI_AGENT_ENDPOINT, AI_AGENT_ID, optional AI_AGENT_VERSION)Agent Loading (direct SDK):
// Load agent metadata directly from v2 Agents API.
// NOTE: the REST spec has no "latest" keyword — the agent_version path parameter is a
// plain string. To resolve the newest version, enumerate versions in descending order
// and take the first. Pin a specific version by passing its id to GetAgentVersionAsync.
ProjectsAgentVersion? agentVersion = null;
await foreach (var v in projectClient.AgentAdministrationClient.GetAgentVersionsAsync(
agentName: agentId,
limit: 1,
order: AgentListOrder.Descending,
after: null,
before: null,
cancellationToken: ct))
{
agentVersion = v;
break;
}
// Access definition for model/instructions/structured inputs
var definition = agentVersion?.Definition as DeclarativeAgentDefinition;
Streaming (direct ProjectResponsesClient — required for specialized types):
// Direct SDK for streaming — IChatClient doesn't expose MCP/annotations.
// Pin to the resolved agentVersion.Version so streaming and metadata stay in sync.
ProjectResponsesClient responsesClient = projectClient.ProjectOpenAIClient.GetProjectResponsesClientForAgent(
new AgentReference(agentId, agentVersion.Version), conversationId);
Why direct streaming? The IChatClient abstraction doesn't expose:
McpToolCallApprovalRequestItem for MCP approval flowsFileSearchCallResponseItem for file search quotesMessageResponseItem.OutputTextAnnotations for citationsStreaming Pattern: Returns IAsyncEnumerable<StreamChunk> where StreamChunk contains either:
chunk.IsText, chunk.TextDelta)chunk.HasAnnotations, chunk.Annotations)Streaming Response Types (from OpenAI.Responses):
StreamingResponseOutputTextDeltaUpdate - Text content deltaStreamingResponseOutputItemDoneUpdate - Item completion (has annotations)StreamingResponseCompletedUpdate - Response completion with usage statsImage Validation (in BuildUserMessage()):
image/png, image/jpeg, image/gif, image/webpAnnotation Types (from OpenAI.Responses):
UriCitationMessageAnnotation - Bing, Azure AI Search, SharePointFileCitationMessageAnnotation - File search (vector stores)FilePathMessageAnnotation - Code interpreter outputContainerFileCitationMessageAnnotation - Container file citationsStarter Prompts: Parsed from agent metadata (starterPrompts key, newline-separated).
Auto-load .env file before building configuration:
var envFile = Path.Combine(Directory.GetCurrentDirectory(), ".env");
if (File.Exists(envFile))
{
foreach (var line in File.ReadAllLines(envFile)
.Where(l => !string.IsNullOrWhiteSpace(l) && !l.StartsWith("#")))
{
var parts = line.Split('=', 2);
if (parts.Length == 2)
Environment.SetEnvironmentVariable(parts[0].Trim(), parts[1].Trim());
}
}
When things break: SDK type mismatches and missing methods almost always happen after a package upgrade. Check backend/WebApp.Api/WebApp.Api.csproj for current versions:
Azure.AI.Projects - check WebApp.Api.csproj for current versionAzure.Identity - check WebApp.Api.csproj for current versionIf types don't match documentation or samples, verify you're looking at docs for the same version installed in the project.
When you need to understand SDK internals, fetch the actual source:
For project-level overviews only—use sparingly:
# List public types in backend (overview, not deep exploration)
Get-ChildItem -Path backend -Recurse -Include *.cs |
Select-String -Pattern "^\s*(public|internal)\s+(class|record|interface)\s+(\w+)" |
ForEach-Object { $_.Matches.Groups[3].Value } | Sort-Object -Unique
# Find IDisposable implementations
Get-ChildItem -Path backend -Recurse -Include *.cs |
Select-String -Pattern ":\s*.*IDisposable"
Use for: Quick inventory of what exists. Follow up with pattern search or IDE navigation for understanding.
Use when you need to discover exact type members on any .NET assembly (especially beta SDKs):
# 1. Build first to ensure DLLs are current
cd backend/WebApp.Api; dotnet build --no-restore
# 2. Find and load any assembly by name
$dll = Get-ChildItem -Path "bin/Debug" -Recurse -Filter "SomePackage.dll" | Select-Object -First 1
$asm = [System.Reflection.Assembly]::LoadFrom($dll.FullName)
# 3. Inspect a specific type's properties
$type = $asm.GetType("SomeNamespace.SomeClass")
Write-Host "Type: $($type.FullName)"
Write-Host "Assembly: $($asm.GetName().Name) v$($asm.GetName().Version)"
$type.GetProperties() | ForEach-Object { Write-Host " $($_.PropertyType.Name) $($_.Name)" }
# 4. Check base type for inherited members
Write-Host "Base: $($type.BaseType.Name)"
$type.BaseType.GetProperties() | ForEach-Object { Write-Host " $($_.PropertyType.Name) $($_.Name)" }
Finding types by pattern (when you don't know exact namespace):
# Search for types matching a pattern
$asm.GetTypes() | Where-Object { $_.Name -like "*Response*" } | ForEach-Object { Write-Host $_.FullName }
# Find methods on a type
$type.GetMethods() | Where-Object { $_.Name -like "*Async*" } | Select-Object Name, ReturnType
Common assemblies to inspect (after dotnet build):
| Assembly | Path | Contains |
|---|---|---|
Azure.AI.Projects.dll | bin/Debug/net10.0/ | AIProjectClient, AgentReference |
Azure.AI.Projects.Agents.dll | bin/Debug/net10.0/ | AgentAdministrationClient, ProjectsAgentVersion, DeclarativeAgentDefinition |
Azure.AI.Extensions.OpenAI.dll | bin/Debug/net10.0/ | ProjectOpenAIClient, ProjectConversationsClient, ProjectResponsesClient |
OpenAI.dll | bin/Debug/net10.0/ | ResponseItem, StreamingResponse*, annotations |
Azure.Identity.dll | bin/Debug/net10.0/ | Credential types |
When to use: Beta SDK properties aren't in docs, IDE tooltips are incomplete, or you need to verify a type's actual API surface.
Limitation: Returns raw API surface without intent or usage guidance. Combine with GitHub source for context.