> ## Documentation Index
>
> Fetch the complete documentation index at: https://superpowers.frontis.nl/llms.txt
> Use this file to discover all available pages before exploring further.

---

title: .NET development
summary: Frontis .NET development guidance for coding agents working in repositories that use .NET 10, Visual Studio, Azure DevOps and local .frontis conventions.
order: 50

---

# .NET development

Use this guide when working on .NET code in Frontis repositories.

This document is written for coding agents. It explains how to make safe, consistent .NET changes while respecting local Frontis repository conventions.

Frontis repositories may use:

```text
Visual Studio
VS Code
.NET 10
Azure DevOps
YAML multi-stage pipelines
```

Local repository files remain the source of truth.

Before changing .NET code, read:

```text
AGENTS.md
.frontis/project.json
.frontis/project-context.yaml
.frontis/naming-convention.yaml
```

Also read:

```text
.frontis/azure-conventions.yaml
```

when the change touches:

- Azure hosting;
- deployment;
- configuration;
- App Services;
- Functions;
- Storage;
- SQL;
- Key Vault;
- Application Insights;
- Log Analytics;
- Azure DevOps pipelines.

## Purpose

This guide helps agents:

- create and modify .NET projects consistently;
- keep namespaces aligned with project identity;
- apply Frontis naming conventions;
- write tests for behavior changes;
- avoid vague project names;
- avoid unsafe infrastructure or configuration changes;
- verify changes before claiming completion.

## Source-of-truth priority

Use this priority order:

1. explicit user instruction;
2. local repository files;
3. Frontis online agent documentation;
4. existing code style;
5. general .NET knowledge.

Local files override generic examples.

Never invent Frontis .NET conventions from memory.

## Required startup procedure

Before making a non-trivial .NET change:

1. Read `AGENTS.md`.
2. Read `.frontis/project.json`.
3. Read `.frontis/project-context.yaml`.
4. Read `.frontis/naming-convention.yaml`.
5. Inspect the existing solution and project structure.
6. Identify the target framework used by the repository.
7. Identify test projects.
8. Identify relevant build, test and pipeline commands.
9. Summarize the effective project context.
10. Produce a short plan before editing many files.

The summary should include:

```text
company
product
component
canonical project name
target framework
solution file
main projects
test projects
affected namespaces
relevant conventions
```

## Target framework

When editing an existing project:

- keep the existing target framework unless the user explicitly asks to upgrade;
- do not upgrade framework versions as part of unrelated work;
- do not edit `global.json` without approval;
- do not change SDK versions without approval;
- do not change package baselines without a clear reason.

When creating a new Frontis .NET project:

- use the current Frontis default target framework;
- if the repository or user states `.NET 10`, target `.NET 10`;
- align project names and namespaces with `.frontis/project.json` and `.frontis/naming-convention.yaml`.

Do not assume every repository is already on the same framework version.

## Naming

Project names, namespaces, solution names and test project names must be derived from local conventions.

Read:

```text
.frontis/project.json
.frontis/naming-convention.yaml
```

before creating or renaming:

- solutions;
- projects;
- namespaces;
- folders;
- test projects;
- NuGet package names;
- pipeline files;
- generated clients.

Typical canonical shape:

```text
{Company}.{Product}.{Component}
```

Example:

```text
AALP.CompositePIM.Api
```

Do not create vague names such as:

```text
Common
Shared
Helpers
Utilities
Misc
General
Core
Base
```

unless the local convention explicitly allows them.

Prefer names that describe bounded context or purpose.

## Solution structure

Follow the existing repository structure.

Do not force a new architecture into an existing codebase.

When creating a new .NET solution, prefer a clear structure such as:

```text
src/
  Company.Product.Component/
tests/
  Company.Product.Component.Tests/
  Company.Product.Component.Tests.Integration/
```

Only create extra projects when they have a clear responsibility.

Avoid premature splitting.

Good reasons for a separate project:

- independent deployable unit;
- clear infrastructure adapter;
- explicit contract package;
- test project;
- shared domain model intentionally reused by multiple apps.

Weak reasons:

- "helpers";
- "common stuff";
- "misc utilities";
- "future reuse";
- "clean architecture because generic template says so".

## Project types

Choose project types based on the repository and task.

Typical project types:

```text
webapi
classlib
worker
xunit
nunit
mstest
```

Do not introduce a new test framework if the repository already has one.

Do not introduce a new web framework or architecture style without approval.

## Namespaces

Namespaces should align with project names.

Example:

```csharp
namespace AALP.CompositePIM.Api.Products;
```

Avoid namespaces that do not reflect project identity.

Do not create namespaces such as:

```csharp
namespace Common;
namespace Helpers;
namespace Utilities;
```

For new files, follow the namespace style already used by the repository:

- file-scoped namespace;
- block-scoped namespace;
- implicit usings;
- nullable reference types.

Do not change namespace style across the repository as part of unrelated work.

## Nullable reference types

Respect the existing nullable setting.

When nullable reference types are enabled:

- avoid `!` unless there is a clear reason;
- prefer explicit null handling;
- use guard clauses where appropriate;
- keep DTO nullability aligned with API behavior;
- avoid returning `null` where an empty collection is expected.

Do not disable nullable reference types to make code compile.

## Dependency injection

Follow the existing dependency injection style.

Prefer constructor injection.

Avoid service locator patterns.

Do not inject `IServiceProvider` unless there is a clear framework-level reason.

Keep registration close to the owning feature or infrastructure area when that is the existing style.

Avoid large unstructured registration files.

Good:

```csharp
services.AddProductSearch();
services.AddCompositePimPersistence(configuration);
```

Avoid:

```csharp
services.AddHelpers();
services.AddCommon();
```

## Configuration

Use typed options for non-trivial configuration.

Prefer:

```csharp
services.Configure<MyFeatureOptions>(
    configuration.GetSection(MyFeatureOptions.SectionName));
```

Avoid scattering raw configuration keys throughout the codebase.

Do not commit secrets.

Do not add production secrets to:

```text
appsettings.json
appsettings.Production.json
local.settings.json
.env
pipeline YAML
```

Use appropriate secure mechanisms such as:

- Azure Key Vault;
- Azure App Configuration;
- App Service settings;
- pipeline secret variables;
- variable groups;
- managed identity.

## Logging

Use structured logging.

Good:

```csharp
logger.LogInformation(
    "Import completed for product group {ProductGroupId} with {ItemCount} items",
    productGroupId,
    itemCount);
```

Avoid string interpolation in log messages:

```csharp
logger.LogInformation($"Import completed for {productGroupId}");
```

Do not log:

- passwords;
- tokens;
- connection strings;
- personal data unless explicitly allowed and reviewed;
- full request bodies containing sensitive data;
- authorization headers.

## Error handling

Handle expected errors close to the boundary where they occur.

Do not swallow exceptions silently.

Do not catch `Exception` without a clear reason.

When catching exceptions:

- log useful context;
- avoid logging sensitive data;
- rethrow or return a meaningful result;
- preserve stack traces.

Good:

```csharp
catch (ExternalServiceException ex)
{
    logger.LogWarning(ex, "Product sync failed for product group {ProductGroupId}", productGroupId);
    return SyncResult.Failed(productGroupId);
}
```

Avoid:

```csharp
catch
{
    return null;
}
```

## API design

When changing Web APIs:

- inspect existing controller or endpoint style;
- preserve route conventions;
- preserve response shape unless the user asks to change it;
- avoid breaking clients without approval;
- update tests;
- update OpenAPI or client generation if the repository uses it.

Prefer explicit request and response DTOs.

Do not expose domain entities directly unless the existing codebase intentionally does that.

Keep validation close to the boundary.

Return appropriate status codes.

Do not hide failures behind `200 OK`.

## Minimal APIs and controllers

Follow the style already used by the repository.

If the project uses controllers, use controllers.

If the project uses minimal APIs, use minimal APIs.

Do not mix styles without a clear reason.

For new endpoints, define:

- route;
- request model;
- response model;
- validation;
- authorization behavior;
- tests;
- documentation or OpenAPI impact.

## Authorization

Treat authorization changes as security-sensitive.

Before changing authorization:

1. identify current policy;
2. identify required roles, scopes or claims;
3. describe impact;
4. add or update tests;
5. ask for approval when behavior changes.

Do not remove authorization attributes to make tests pass.

Do not weaken policies without explicit approval.

## Authentication

Authentication changes are high-risk.

Do not change:

- token validation;
- authority;
- audience;
- issuer validation;
- cookie settings;
- OpenID Connect settings;
- OAuth scopes;
- certificate validation;

without a clear task and approval.

Always document risks and validation.

## Data access

Follow the existing data access approach.

Typical approaches may include:

- Entity Framework Core;
- Dapper;
- raw ADO.NET;
- external APIs;
- Azure SDK clients.

Do not introduce a new ORM or data access pattern without approval.

When using EF Core:

- keep migrations reviewable;
- do not generate broad migrations without inspecting them;
- avoid destructive migrations without explicit approval;
- seed data carefully;
- verify generated SQL where needed;
- update integration tests.

Destructive migrations require normal precise language, not Caveman style.

## Entity Framework migrations

Before adding or changing a migration:

1. inspect the model change;
2. generate migration;
3. inspect migration code;
4. identify destructive operations;
5. run tests;
6. document rollback or mitigation for risky changes.

Do not commit migrations that contain unexpected:

```text
DropTable
DropColumn
AlterColumn with data loss risk
Sql raw destructive statements
```

without explicit approval.

## External services

When integrating external services:

- use typed clients or existing client abstractions;
- keep configuration out of code;
- add timeouts;
- handle retries intentionally;
- avoid retrying non-idempotent operations blindly;
- log failures with context;
- avoid logging secrets or sensitive payloads;
- test error scenarios.

Do not introduce a new HTTP client library when the existing stack already has one.

## HTTP clients

Prefer `IHttpClientFactory` or existing abstractions.

Do not create raw `new HttpClient()` repeatedly.

Set:

- base address through configuration;
- timeout where appropriate;
- headers intentionally;
- resilience policies if the repository already uses them.

Keep API contracts typed.

## Caching

When adding caching:

- define cache key clearly;
- define expiration;
- define invalidation strategy;
- document consistency trade-offs;
- test cache hits and misses where practical.

Do not cache user-specific or sensitive data unless explicitly reviewed.

Do not add long-lived cache for data that must be strongly consistent.

## Background jobs

When changing background processing:

- identify trigger mechanism;
- identify retry behavior;
- identify concurrency behavior;
- identify idempotency;
- log start, success and failure;
- protect against duplicate processing;
- test job logic separately from scheduling where possible.

Do not make background jobs destructive without approval.

## Validation

Use the validation approach already present in the codebase.

Possibilities:

- data annotations;
- FluentValidation;
- custom validators;
- endpoint filters;
- manual guard clauses.

Do not introduce a new validation framework without approval.

For APIs, return useful validation errors while avoiding internal implementation details.

## Testing policy

For behavior changes, add or update tests.

Use TDD where possible:

```text
red -> green -> refactor
```

Do not claim TDD was used unless the failing test was observed first.

Test categories:

```text
unit tests
integration tests
contract tests
end-to-end tests
pipeline validation
```

Prefer the smallest test that proves the behavior.

Add broader tests when the change crosses boundaries.

## Unit tests

Unit tests should be fast and deterministic.

Good unit tests:

- test behavior;
- avoid real network calls;
- avoid real databases;
- use clear arrange-act-assert structure;
- have meaningful names;
- avoid excessive mocking;
- verify observable behavior.

Avoid tests that only assert implementation details.

## Integration tests

Use integration tests for:

- API behavior;
- database interaction;
- authentication/authorization behavior;
- external infrastructure boundaries with test doubles;
- serialization and configuration behavior.

Do not connect integration tests to production resources.

Use local or test containers only when that is already part of the project setup.

## Test naming

Follow local naming conventions.

Common useful pattern:

```text
MethodOrScenario_ShouldExpectedBehavior_WhenCondition
```

or the style already used by the repository.

Do not rename existing tests just to match a different style.

## Test projects

Test project names must follow `.frontis/naming-convention.yaml`.

Common shape:

```text
{Company}.{Product}.{Component}.Tests
{Company}.{Product}.{Component}.Tests.Integration
```

Example:

```text
AALP.CompositePIM.Api.Tests
AALP.CompositePIM.Api.Tests.Integration
```

Do not create:

```text
Tests
UnitTests
IntegrationTests
Common.Tests
```

unless local conventions allow it.

## Build and test commands

Before claiming completion, run relevant commands.

Typical commands:

```bash
dotnet restore
dotnet build
dotnet test
```

For a specific solution:

```bash
dotnet restore ./Company.Product.Component.sln
dotnet build ./Company.Product.Component.sln --configuration Release --no-restore
dotnet test ./Company.Product.Component.sln --configuration Release --no-build
```

For a specific test project:

```bash
dotnet test ./tests/Company.Product.Component.Tests/Company.Product.Component.Tests.csproj
```

Only run commands that apply to the repository.

If commands cannot be run, state exactly why.

Never claim tests passed unless they actually passed.

## Formatting

Use the repository's existing formatting approach.

Possible command:

```bash
dotnet format
```

Do not reformat the entire repository as part of an unrelated change.

Avoid formatting churn.

## Package management

When adding NuGet packages:

- check whether an existing package or abstraction already exists;
- choose widely used packages only when needed;
- avoid adding large dependencies for small helpers;
- keep package versions consistent with the repository;
- do not upgrade unrelated packages;
- explain why the package is needed.

Do not add prerelease packages unless requested.

Do not change package management style without approval.

## Generated code

Generated code should not be edited manually unless the repository expects it.

If changing generated outputs:

1. identify the generator;
2. update the source definition;
3. regenerate;
4. inspect the diff;
5. run tests.

Examples:

```text
OpenAPI clients
gRPC clients
EF migrations
source generator output
NSwag output
```

## Performance

For performance-sensitive changes:

- establish baseline;
- identify measurement method;
- avoid guessing;
- make one change at a time;
- measure again;
- document trade-offs.

Avoid micro-optimizing unclear code paths.

Use appropriate async patterns.

Do not block on async code:

```csharp
.Result
.Wait()
.GetAwaiter().GetResult()
```

unless there is a clear and safe reason.

## Async

Use async all the way when working with I/O.

Prefer:

```csharp
await repository.GetByIdAsync(id, cancellationToken);
```

Pass `CancellationToken` through call chains where appropriate.

Do not ignore cancellation tokens in long-running operations.

## Cancellation tokens

For APIs and background work, pass cancellation tokens into:

- EF Core calls;
- HTTP calls;
- Azure SDK calls;
- long-running async operations.

Follow existing project style.

## Security

Security-sensitive changes require normal precise language and human approval.

Security-sensitive areas:

- authentication;
- authorization;
- token handling;
- password handling;
- secrets;
- cryptography;
- input validation;
- file upload;
- deserialization;
- CORS;
- SSRF risk;
- SQL injection risk;
- logging of sensitive data.

Do not use Caveman style for security warnings.

## Secrets

Never commit secrets.

Do not include:

```text
connection strings
storage account keys
client secrets
API keys
JWT signing keys
passwords
certificates
private keys
personal access tokens
```

Use secure configuration mechanisms.

If a secret is found in source control, stop and report it.

Do not copy it into summaries.

## Azure integration

When .NET code uses Azure services:

- prefer managed identity where applicable;
- avoid hardcoded connection strings;
- keep resource names aligned with `.frontis/azure-conventions.yaml`;
- use configuration for endpoints and resource names;
- handle transient failures;
- log with correlation where possible.

Read Azure conventions before changing:

- App Service settings;
- Storage account names;
- queues;
- tables;
- blobs;
- Key Vault references;
- Application Insights;
- managed identities;
- deployment YAML.

## Application Insights

When changing telemetry:

- keep structured logging;
- avoid sensitive data;
- preserve correlation;
- ensure important failures are observable;
- do not flood telemetry with high-cardinality values unnecessarily.

## Docker

If the repository has Docker files:

- follow existing base image choices;
- do not change runtime image versions casually;
- keep build context small;
- avoid copying secrets into images;
- verify container build when changed.

Typical validation:

```bash
docker build .
```

Only run if applicable.

## Azure DevOps pipeline impact

When .NET project structure changes, check pipeline impact.

Common affected areas:

- solution path;
- project path;
- test project path;
- artifact publish path;
- build configuration;
- SDK version;
- test result publishing;
- code coverage publishing;
- deployment package path.

If the pipeline is affected, read:

```text
.frontis/naming-convention.yaml
.frontis/azure-conventions.yaml
```

and the Azure DevOps pipeline documentation.

## Pull request checklist

Before opening or finishing a PR:

```text
[ ] AGENTS.md and .frontis files were followed
[ ] project and namespace names match conventions
[ ] behavior changes have tests
[ ] relevant build and test commands were run
[ ] no unrelated refactors included
[ ] no secrets added
[ ] API changes are documented or tested
[ ] authorization changes reviewed
[ ] migrations inspected
[ ] pipeline impact checked
[ ] risks documented
```

## Recommended prompts

### Start .NET feature

```text
Use Superpowers workflow for this .NET change.

Read:
- AGENTS.md
- .frontis/project.json
- .frontis/project-context.yaml
- .frontis/naming-convention.yaml

Inspect the solution and test projects.
Summarize the effective .NET project context.
Create a short spec, implementation plan and validation plan.
Use TDD for behavior changes.
Do not edit files until the plan is clear.
```

### Start .NET bugfix

```text
Use Superpowers systematic debugging for this .NET bug.

Read AGENTS.md and .frontis/*.yaml.
Do not guess.

First:
1. describe the symptom;
2. identify expected behavior;
3. inspect relevant code and tests;
4. gather evidence;
5. propose likely root causes;
6. choose the smallest verification step.

Implement only after the root cause is supported.
```

### Add a new .NET project

```text
Add a new .NET project using Frontis conventions.

Read:
- .frontis/project.json
- .frontis/naming-convention.yaml

Derive the project name and namespace.
Show the derived name before creating files.
Create matching test project if behavior will be added.
Update the solution.
Update pipeline references if needed.
```

### Add or change an API endpoint

```text
Use Superpowers workflow and TDD.

Read AGENTS.md and .frontis/*.yaml.
Inspect existing API style.
Define route, request, response, validation, authorization and tests.
Do not break existing clients unless explicitly requested.
Run relevant tests.
```

### Change Entity Framework model

```text
Use normal precise language for this migration-related task.

Inspect the model change.
Generate migration if needed.
Review migration for destructive operations.
Ask for approval before data loss risk.
Run tests.
Document rollback or mitigation if risky.
```

## Anti-patterns

Avoid:

- coding before reading `.frontis` files;
- inventing namespaces;
- creating `Common` or `Helpers` projects;
- changing target framework casually;
- changing package versions as drive-by cleanup;
- broad formatting changes;
- catching and swallowing exceptions;
- logging secrets;
- removing authorization to make tests pass;
- editing generated code manually;
- claiming tests passed without running them;
- adding Azure resource names from memory;
- making migrations without inspection;
- mixing unrelated refactors into a feature.

## Completion summary format

At the end of a .NET task, use:

```text
Summary
- ...

Changed
- ...

Verified
- ...

Not verified
- ...

Risks
- ...

Next steps
- ...
```

For small changes, keep it short.

For risky changes, include precise caveats.
