From da8dc81fb0182332dc71200b46c6707456541593 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 20 Oct 2025 15:27:03 +0000 Subject: [PATCH 1/3] Initial plan From 210bc8aeb733d4fe7b4a481e3c7032e327801b49 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 20 Oct 2025 15:38:39 +0000 Subject: [PATCH 2/3] Add conceptual articles for new .NET Extensions packages Co-authored-by: IEvangelist <7679720+IEvangelist@users.noreply.github.com> --- .../ambient-metadata-application.md | 340 +++++++++++++++ docs/core/extensions/async-state.md | 290 +++++++++++++ docs/core/extensions/audit-reports.md | 327 ++++++++++++++ .../dependency-injection-auto-activation.md | 188 +++++++++ docs/core/extensions/timeprovider-testing.md | 399 ++++++++++++++++++ docs/fundamentals/toc.yml | 15 + 6 files changed, 1559 insertions(+) create mode 100644 docs/core/extensions/ambient-metadata-application.md create mode 100644 docs/core/extensions/async-state.md create mode 100644 docs/core/extensions/audit-reports.md create mode 100644 docs/core/extensions/dependency-injection-auto-activation.md create mode 100644 docs/core/extensions/timeprovider-testing.md diff --git a/docs/core/extensions/ambient-metadata-application.md b/docs/core/extensions/ambient-metadata-application.md new file mode 100644 index 0000000000000..5108dfe1aafae --- /dev/null +++ b/docs/core/extensions/ambient-metadata-application.md @@ -0,0 +1,340 @@ +--- +title: Application ambient metadata +description: Learn how to capture and flow application metadata for telemetry enrichment using Microsoft.Extensions.AmbientMetadata.Application in .NET. +author: IEvangelist +ms.author: dapine +ms.date: 10/20/2025 +ms.topic: concept-article +ai-usage: ai-assisted +--- + +# Application ambient metadata + +The [`Microsoft.Extensions.AmbientMetadata.Application`](https://www.nuget.org/packages/Microsoft.Extensions.AmbientMetadata.Application) NuGet package provides functionality to capture and flow application-level ambient metadata throughout your application. This metadata includes information such as the application name, version, deployment environment, and deployment ring, which is valuable for enriching telemetry, troubleshooting, and analysis. + +## Why use application metadata + +Application metadata provides essential context about your running application that can enhance observability: + +- **Telemetry enrichment**: Automatically add application details to logs, metrics, and traces. +- **Troubleshooting**: Quickly identify which version of your application is experiencing issues. +- **Environment identification**: Distinguish between Development, Staging, and Production environments in your telemetry. +- **Deployment tracking**: Track issues across different deployment rings or regions. +- **Consistent metadata**: Ensure all components in your application use the same metadata values. + +## Get started + +To get started with application metadata, install the [`Microsoft.Extensions.AmbientMetadata.Application`](https://www.nuget.org/packages/Microsoft.Extensions.AmbientMetadata.Application) NuGet package. + +### [.NET CLI](#tab/dotnet-cli) + +```dotnetcli +dotnet add package Microsoft.Extensions.AmbientMetadata.Application +``` + +### [PackageReference](#tab/package-reference) + +```xml + +``` + +--- + +For more information, see [dotnet add package](../tools/dotnet-add-package.md) or [Manage package dependencies in .NET applications](../tools/dependencies.md). + +## Configure application metadata + +Application metadata can be configured through your application's configuration system. The package looks for metadata under the `ambientmetadata:application` configuration section by default. + +### Configure with appsettings.json + +Add the application metadata to your `appsettings.json` file: + +```json +{ + "ambientmetadata": { + "application": { + "ApplicationName": "MyWebApi", + "BuildVersion": "1.0.0", + "DeploymentRing": "Production", + "EnvironmentName": "Production" + } + } +} +``` + +### Configure with IHostBuilder + +Use the extension method to register application metadata: + +```csharp +using Microsoft.Extensions.Hosting; + +var builder = Host.CreateApplicationBuilder(args); + +builder.UseApplicationMetadata(); + +var host = builder.Build(); + +await host.RunAsync(); +``` + +### Configure with IHostApplicationBuilder + +For applications using , such as ASP.NET Core applications: + +```csharp +using Microsoft.AspNetCore.Builder; +using Microsoft.Extensions.Hosting; + +var builder = WebApplication.CreateBuilder(args); + +builder.UseApplicationMetadata(); + +var app = builder.Build(); + +app.MapGet("/", () => "Hello World!"); + +app.Run(); +``` + +## Access application metadata + +Once configured, you can inject and use the type: + +```csharp +using Microsoft.Extensions.AmbientMetadata; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; + +var builder = Host.CreateApplicationBuilder(args); + +builder.UseApplicationMetadata(); +builder.Services.AddSingleton(); + +var host = builder.Build(); + +var metadataService = host.Services.GetRequiredService(); +metadataService.DisplayMetadata(); + +public class MetadataService +{ + private readonly ApplicationMetadata _metadata; + + public MetadataService(ApplicationMetadata metadata) + { + _metadata = metadata; + } + + public void DisplayMetadata() + { + Console.WriteLine($"Application: {_metadata.ApplicationName}"); + Console.WriteLine($"Version: {_metadata.BuildVersion}"); + Console.WriteLine($"Environment: {_metadata.EnvironmentName}"); + Console.WriteLine($"Deployment Ring: {_metadata.DeploymentRing}"); + } +} +``` + +## ApplicationMetadata properties + +The class includes the following properties: + +| Property | Description | +|----------|-------------| +| `ApplicationName` | The name of the application. | +| `BuildVersion` | The version of the application build. | +| `DeploymentRing` | The deployment ring or stage (for example, Canary, Production). | +| `EnvironmentName` | The environment where the application is running (for example, Development, Staging, Production). | + +## Use with logging + +Application metadata is particularly useful for enriching log messages: + +```csharp +using Microsoft.Extensions.AmbientMetadata; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Logging; + +var builder = Host.CreateApplicationBuilder(args); + +builder.UseApplicationMetadata(); +builder.Services.AddSingleton(); + +var host = builder.Build(); + +var loggingService = host.Services.GetRequiredService(); +loggingService.LogWithMetadata(); + +public class LoggingService +{ + private readonly ILogger _logger; + private readonly ApplicationMetadata _metadata; + + public LoggingService(ILogger logger, ApplicationMetadata metadata) + { + _logger = logger; + _metadata = metadata; + } + + public void LogWithMetadata() + { + _logger.LogInformation( + "Processing request in {ApplicationName} v{Version} ({Environment})", + _metadata.ApplicationName, + _metadata.BuildVersion, + _metadata.EnvironmentName); + } +} +``` + +## Custom configuration section + +If you prefer to use a different configuration section name, specify it when calling `UseApplicationMetadata`: + +```csharp +using Microsoft.Extensions.Hosting; + +var builder = Host.CreateApplicationBuilder(args); + +// Use custom configuration section +builder.UseApplicationMetadata("myapp:metadata"); + +var host = builder.Build(); + +await host.RunAsync(); +``` + +With this configuration, your settings would look like: + +```json +{ + "myapp": { + "metadata": { + "ApplicationName": "MyWebApi", + "BuildVersion": "1.0.0", + "DeploymentRing": "Production", + "EnvironmentName": "Production" + } + } +} +``` + +## Environment-specific configuration + +You can provide different metadata for different environments using environment-specific configuration files: + +**appsettings.Development.json**: + +```json +{ + "ambientmetadata": { + "application": { + "ApplicationName": "MyWebApi", + "BuildVersion": "1.0.0-dev", + "DeploymentRing": "Development", + "EnvironmentName": "Development" + } + } +} +``` + +**appsettings.Production.json**: + +```json +{ + "ambientmetadata": { + "application": { + "ApplicationName": "MyWebApi", + "BuildVersion": "1.0.0", + "DeploymentRing": "Production", + "EnvironmentName": "Production" + } + } +} +``` + +## Practical example: Telemetry enrichment + +Here's a complete example showing how to use application metadata to enrich telemetry in an ASP.NET Core application: + +```csharp +using Microsoft.AspNetCore.Builder; +using Microsoft.Extensions.AmbientMetadata; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Logging; + +var builder = WebApplication.CreateBuilder(args); + +builder.UseApplicationMetadata(); +builder.Services.AddSingleton(); + +var app = builder.Build(); + +app.Use(async (context, next) => +{ + var enricher = context.RequestServices.GetRequiredService(); + enricher.EnrichRequest(context); + await next(); +}); + +app.MapGet("/api/health", (ApplicationMetadata metadata) => +{ + return Results.Ok(new + { + Status = "Healthy", + Application = metadata.ApplicationName, + Version = metadata.BuildVersion, + Environment = metadata.EnvironmentName + }); +}); + +app.Run(); + +public class TelemetryEnricher +{ + private readonly ILogger _logger; + private readonly ApplicationMetadata _metadata; + + public TelemetryEnricher( + ILogger logger, + ApplicationMetadata metadata) + { + _logger = logger; + _metadata = metadata; + } + + public void EnrichRequest(HttpContext context) + { + using var scope = _logger.BeginScope(new Dictionary + { + ["ApplicationName"] = _metadata.ApplicationName ?? "Unknown", + ["BuildVersion"] = _metadata.BuildVersion ?? "Unknown", + ["Environment"] = _metadata.EnvironmentName ?? "Unknown", + ["DeploymentRing"] = _metadata.DeploymentRing ?? "Unknown", + ["RequestPath"] = context.Request.Path.Value ?? "Unknown" + }); + + _logger.LogInformation("Request received"); + } +} +``` + +## Best practices + +When using application metadata, consider the following best practices: + +- **Automate version setting**: Use your CI/CD pipeline to automatically set the `BuildVersion` from your version control system or build number. +- **Environment consistency**: Ensure environment names are consistent across all your applications for easier querying and filtering in telemetry systems. +- **Configuration management**: Use environment-specific configuration files or environment variables to manage different metadata values across environments. +- **Validate metadata**: Ensure required metadata fields are present during application startup to catch configuration issues early. +- **Telemetry integration**: Integrate application metadata with your telemetry system (such as Application Insights or OpenTelemetry) for comprehensive observability. + +## See also + +- [Configuration in .NET](configuration.md) +- [Configuration providers in .NET](configuration-providers.md) +- [Logging in .NET](logging.md) +- [Options pattern in .NET](options.md) diff --git a/docs/core/extensions/async-state.md b/docs/core/extensions/async-state.md new file mode 100644 index 0000000000000..b494844225673 --- /dev/null +++ b/docs/core/extensions/async-state.md @@ -0,0 +1,290 @@ +--- +title: Asynchronous state management +description: Learn how to efficiently manage ambient state in asynchronous contexts using Microsoft.Extensions.AsyncState in .NET. +author: IEvangelist +ms.author: dapine +ms.date: 10/20/2025 +ms.topic: concept-article +ai-usage: ai-assisted +--- + +# Asynchronous state management + +The [`Microsoft.Extensions.AsyncState`](https://www.nuget.org/packages/Microsoft.Extensions.AsyncState) NuGet package provides functionality to store and retrieve objects within the current asynchronous context. This package offers performance and usability improvements over using directly, particularly when multiple objects need to be shared across asynchronous operations. + +## Why use AsyncState + +While .NET provides `AsyncLocal` for managing ambient data in asynchronous contexts, using it directly can have drawbacks: + +- **Performance**: Each `AsyncLocal` instance adds overhead. When multiple objects need to flow through async contexts, managing many `AsyncLocal` instances can impact performance. +- **Abstraction**: Direct use of `AsyncLocal` couples your code to a specific implementation, making it harder to optimize or change in the future. +- **Lifetime management**: The AsyncState package provides better control over the lifetime of ambient data through explicit APIs. + +The `Microsoft.Extensions.AsyncState` package addresses these concerns by providing: + +- An optimized implementation that reduces the number of `AsyncLocal` instances needed. +- A clean abstraction for storing and retrieving ambient data. +- Integration with dependency injection for easier testing and configuration. + +## Get started + +To get started with asynchronous state management, install the [`Microsoft.Extensions.AsyncState`](https://www.nuget.org/packages/Microsoft.Extensions.AsyncState) NuGet package. + +### [.NET CLI](#tab/dotnet-cli) + +```dotnetcli +dotnet add package Microsoft.Extensions.AsyncState +``` + +### [PackageReference](#tab/package-reference) + +```xml + +``` + +--- + +For more information, see [dotnet add package](../tools/dotnet-add-package.md) or [Manage package dependencies in .NET applications](../tools/dependencies.md). + +## Register async state services + +Register the async state services with your dependency injection container using the extension method: + +```csharp +using Microsoft.Extensions.DependencyInjection; + +var services = new ServiceCollection(); + +services.AddAsyncState(); + +var provider = services.BuildServiceProvider(); +``` + +This registration makes the and interfaces available for dependency injection. + +## Use IAsyncContext + +The interface provides methods to get and set values in the current asynchronous context: + +```csharp +using Microsoft.Extensions.AsyncState; +using Microsoft.Extensions.DependencyInjection; + +var services = new ServiceCollection(); +services.AddAsyncState(); + +var provider = services.BuildServiceProvider(); +var context = provider.GetRequiredService>(); + +// Set a value in the async context +var userContext = new UserContext { UserId = "12345", UserName = "Alice" }; +context.Set(userContext); + +// Retrieve the value +if (context.TryGet(out var retrievedContext)) +{ + Console.WriteLine($"User: {retrievedContext.UserName}"); +} + +public class UserContext +{ + public string UserId { get; set; } = string.Empty; + public string UserName { get; set; } = string.Empty; +} +``` + +The value set in the context flows through asynchronous operations, making it available to all code executing within the same async context. + +## Use IAsyncState + +The interface provides a simpler property-based API for accessing async state: + +```csharp +using Microsoft.Extensions.AsyncState; +using Microsoft.Extensions.DependencyInjection; + +var services = new ServiceCollection(); +services.AddAsyncState(); + +var provider = services.BuildServiceProvider(); +var asyncState = provider.GetRequiredService>(); + +// Initialize the state +asyncState.Initialize(); + +// Set a value +asyncState.Value = new RequestInfo +{ + RequestId = Guid.NewGuid().ToString(), + Timestamp = DateTimeOffset.UtcNow +}; + +// Access the value +Console.WriteLine($"Request ID: {asyncState.Value.RequestId}"); + +// Reset the state +asyncState.Reset(); + +public class RequestInfo +{ + public string RequestId { get; set; } = string.Empty; + public DateTimeOffset Timestamp { get; set; } +} +``` + +## Practical example: Request correlation + +A common use case for async state is maintaining correlation information across an HTTP request: + +```csharp +using Microsoft.Extensions.AsyncState; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; + +var builder = Host.CreateApplicationBuilder(args); + +builder.Services.AddAsyncState(); +builder.Services.AddSingleton(); + +var host = builder.Build(); + +var processor = host.Services.GetRequiredService(); +await processor.ProcessRequestAsync("ABC-123"); + +public class RequestProcessor +{ + private readonly IAsyncContext _asyncContext; + + public RequestProcessor(IAsyncContext asyncContext) + { + _asyncContext = asyncContext; + } + + public async Task ProcessRequestAsync(string correlationId) + { + // Set correlation context at the beginning of request processing + _asyncContext.Set(new CorrelationContext { CorrelationId = correlationId }); + + // The correlation ID flows through all async operations + await Step1Async(); + await Step2Async(); + } + + private async Task Step1Async() + { + await Task.Delay(100); + + if (_asyncContext.TryGet(out var context)) + { + Console.WriteLine($"Step 1 - Correlation ID: {context.CorrelationId}"); + } + } + + private async Task Step2Async() + { + await Task.Delay(100); + + if (_asyncContext.TryGet(out var context)) + { + Console.WriteLine($"Step 2 - Correlation ID: {context.CorrelationId}"); + } + } +} + +public class CorrelationContext +{ + public string CorrelationId { get; set; } = string.Empty; +} +``` + +In this example, the correlation ID is set once at the beginning of request processing and is automatically available in all subsequent async operations without needing to pass it as a parameter. + +## ASP.NET Core integration + +In ASP.NET Core applications, you can use async state to flow request-specific information through your application: + +```csharp +using Microsoft.AspNetCore.Builder; +using Microsoft.Extensions.AsyncState; +using Microsoft.Extensions.DependencyInjection; + +var builder = WebApplication.CreateBuilder(args); + +builder.Services.AddAsyncState(); +builder.Services.AddScoped(); + +var app = builder.Build(); + +app.Use(async (context, next) => +{ + var asyncContext = context.RequestServices.GetRequiredService>(); + + // Set request metadata at the beginning of the pipeline + asyncContext.Set(new RequestMetadata + { + RequestId = context.TraceIdentifier, + RequestPath = context.Request.Path, + StartTime = DateTimeOffset.UtcNow + }); + + await next(); +}); + +app.MapGet("/api/data", async (RequestHandler handler) => +{ + return await handler.GetDataAsync(); +}); + +app.Run(); + +public class RequestHandler +{ + private readonly IAsyncContext _asyncContext; + + public RequestHandler(IAsyncContext asyncContext) + { + _asyncContext = asyncContext; + } + + public async Task GetDataAsync() + { + await Task.Delay(50); + + if (_asyncContext.TryGet(out var metadata)) + { + var duration = DateTimeOffset.UtcNow - metadata.StartTime; + return new + { + RequestId = metadata.RequestId, + RequestPath = metadata.RequestPath, + Duration = duration.TotalMilliseconds + }; + } + + return new { Error = "No request metadata available" }; + } +} + +public class RequestMetadata +{ + public string RequestId { get; set; } = string.Empty; + public string RequestPath { get; set; } = string.Empty; + public DateTimeOffset StartTime { get; set; } +} +``` + +## Best practices + +When using async state, consider the following best practices: + +- **Limit state size**: Keep async state objects small to minimize memory overhead and serialization costs in scenarios where async state might need to be serialized. +- **Initialize state early**: Set async state values as early as possible in your async operation to ensure they're available throughout the execution flow. +- **Clean up state**: Reset or clear async state when it's no longer needed to avoid memory leaks in long-running applications. +- **Use appropriate interfaces**: Use `IAsyncContext` when you need explicit control over state initialization and reset. Use `IAsyncState` for simpler property-based access. +- **Type safety**: Create specific context types rather than using generic dictionaries to maintain type safety and improve code clarity. + +## See also + +- [Dependency injection in .NET](dependency-injection.md) +- +- [ASP.NET Core middleware](/aspnet/core/fundamentals/middleware/) diff --git a/docs/core/extensions/audit-reports.md b/docs/core/extensions/audit-reports.md new file mode 100644 index 0000000000000..6c2464a9465f0 --- /dev/null +++ b/docs/core/extensions/audit-reports.md @@ -0,0 +1,327 @@ +--- +title: Audit reports for privacy and compliance +description: Learn how to generate audit reports for privacy-sensitive data and metrics using Microsoft.Extensions.AuditReports in .NET. +author: IEvangelist +ms.author: dapine +ms.date: 10/20/2025 +ms.topic: concept-article +ai-usage: ai-assisted +--- + +# Audit reports for privacy and compliance + +The [`Microsoft.Extensions.AuditReports`](https://www.nuget.org/packages/Microsoft.Extensions.AuditReports) NuGet package provides functionality to generate audit reports about the code being compiled. These reports are particularly useful for privacy audits, compliance reviews, and understanding what telemetry data your application collects and transmits. + +## Why use audit reports + +Audit reports help organizations maintain compliance and transparency: + +- **Privacy compliance**: Identify all places where privacy-sensitive data is accessed or logged. +- **Telemetry tracking**: Understand what metrics and telemetry your application generates. +- **Code review**: Review data classification usage across your codebase. +- **Compliance audits**: Provide documentation for compliance and security audits. +- **Data governance**: Ensure data handling practices align with organizational policies. + +## Get started + +The `Microsoft.Extensions.AuditReports` package is a build-time tool that generates reports during compilation. Install it as a development dependency: + +### [.NET CLI](#tab/dotnet-cli) + +```dotnetcli +dotnet add package Microsoft.Extensions.AuditReports +``` + +### [PackageReference](#tab/package-reference) + +```xml + +``` + +--- + +For more information, see [dotnet add package](../tools/dotnet-add-package.md) or [Manage package dependencies in .NET applications](../tools/dependencies.md). + +## Report types + +The package can generate three types of reports: + +### Metrics report + +Reports on the usage of source-generated metric definitions in your code. This helps you understand what metrics your application produces. + +### Compliance report + +Reports on the usage of privacy-sensitive data, including source-generated logging methods that handle personal or sensitive information. + +### Metadata report + +A comprehensive report that combines both metrics and compliance information. + +## Configure report generation + +Configure report generation by setting MSBuild properties in your project file: + +```xml + + + + net9.0 + + + true + + + $(OutputPath)compliance-report.json + + + + + + + +``` + +## Generate a compliance report + +To generate a compliance report, set the `GenerateComplianceReport` property to `true`: + +```xml + + true + +``` + +This report identifies code that handles privacy-sensitive data, particularly in logging operations. + +## Generate a metrics report + +To generate a metrics report, set the `GenerateMetricsReport` property to `true`: + +```xml + + true + $(OutputPath)metrics-report.json + +``` + +This report documents all metrics generated by your application. + +## Generate a metadata report + +For a comprehensive report that includes both compliance and metrics information: + +```xml + + true + $(OutputPath)metadata-report.json + +``` + +## Example: Compliance report output + +When you build a project with compliance reporting enabled, you get a JSON file that identifies privacy-sensitive data usage: + +```json +{ + "version": "1.0", + "reportType": "compliance", + "generatedAt": "2025-10-20T12:00:00Z", + "entries": [ + { + "filePath": "Services/UserService.cs", + "lineNumber": 42, + "memberName": "LogUserActivity", + "dataClassification": "PersonalData", + "message": "Logs user email address" + }, + { + "filePath": "Controllers/AccountController.cs", + "lineNumber": 88, + "memberName": "LogLoginAttempt", + "dataClassification": "AuthenticationData", + "message": "Logs authentication attempt with username" + } + ] +} +``` + +## Use with data classification + +The audit reports work in conjunction with the data classification attributes from `Microsoft.Extensions.Compliance.Abstractions`: + +```csharp +using Microsoft.Extensions.Compliance.Classification; +using Microsoft.Extensions.Logging; + +public class UserService +{ + private readonly ILogger _logger; + + public UserService(ILogger logger) + { + _logger = logger; + } + + [LoggerMessage(Level = LogLevel.Information, Message = "User {Email} logged in")] + public partial void LogUserLogin( + [PrivateData] string email); +} +``` + +When you build this code with compliance reporting enabled, the report will identify that `email` is classified as `PrivateData`. + +## Configure report output location + +Specify custom paths for your audit reports: + +```xml + + + true + true + + + $(OutputPath)audit\compliance.json + $(OutputPath)audit\metrics.json + +``` + +## Integrate with CI/CD pipelines + +Audit reports can be integrated into your CI/CD pipeline for automated compliance checks: + +```yaml +# Example GitHub Actions workflow +name: Compliance Check + +on: + pull_request: + branches: [ main ] + +jobs: + audit: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + + - name: Setup .NET + uses: actions/setup-dotnet@v3 + with: + dotnet-version: '9.0.x' + + - name: Build with compliance report + run: dotnet build -p:GenerateComplianceReport=true + + - name: Upload compliance report + uses: actions/upload-artifact@v3 + with: + name: compliance-report + path: '**/compliance-report.json' + + - name: Analyze compliance report + run: | + # Add script to analyze the compliance report + # and fail the build if violations are found + ./scripts/check-compliance.sh +``` + +## Example: Metrics report output + +A metrics report documents the metrics your application produces: + +```json +{ + "version": "1.0", + "reportType": "metrics", + "generatedAt": "2025-10-20T12:00:00Z", + "metrics": [ + { + "name": "http_request_duration", + "description": "HTTP request duration in milliseconds", + "unit": "milliseconds", + "type": "histogram", + "tags": ["endpoint", "method", "status_code"] + }, + { + "name": "active_connections", + "description": "Number of active connections", + "unit": "connections", + "type": "gauge", + "tags": ["connection_type"] + } + ] +} +``` + +## Practical example: Privacy audit workflow + +Here's a complete example showing how to set up privacy auditing: + +**Project file configuration**: + +```xml + + + + net9.0 + + + true + $(OutputPath)audit\compliance.json + + + + + + + + + +``` + +**Code with data classification**: + +```csharp +using Microsoft.Extensions.Compliance.Classification; +using Microsoft.Extensions.Logging; + +public partial class OrderService +{ + private readonly ILogger _logger; + + public OrderService(ILogger logger) + { + _logger = logger; + } + + [LoggerMessage(Level = LogLevel.Information, Message = "Order created for customer {CustomerId}")] + public partial void LogOrderCreated( + [PublicData] string customerId); + + [LoggerMessage(Level = LogLevel.Information, Message = "Payment processed for {CardNumber}")] + public partial void LogPaymentProcessed( + [PrivateData] string cardNumber); +} +``` + +When you build this project, the compliance report will identify the privacy-sensitive logging of `cardNumber`. + +## Best practices + +When using audit reports, consider the following best practices: + +- **Integrate early**: Add audit reporting to your projects early in development to catch privacy issues sooner. +- **Automate reviews**: Integrate audit report generation into your CI/CD pipeline for continuous compliance monitoring. +- **Review regularly**: Regularly review audit reports during code reviews and before releases. +- **Classify data**: Use data classification attributes consistently to ensure accurate audit reports. +- **Store reports**: Archive audit reports for compliance documentation and historical tracking. +- **Version control**: Track changes to audit reports over time to understand how your data handling evolves. +- **Security scanning**: Use audit reports as input for security and privacy scanning tools. + +## See also + +- [Data classification in .NET](data-classification.md) +- [Logging in .NET](logging.md) +- [High-performance logging in .NET](high-performance-logging.md) +- [Compile-time logging source generation](logger-message-generator.md) diff --git a/docs/core/extensions/dependency-injection-auto-activation.md b/docs/core/extensions/dependency-injection-auto-activation.md new file mode 100644 index 0000000000000..e58bb17e7f3a1 --- /dev/null +++ b/docs/core/extensions/dependency-injection-auto-activation.md @@ -0,0 +1,188 @@ +--- +title: Dependency injection auto-activation +description: Learn how to use auto-activation for singleton services in .NET to ensure services are instantiated at startup rather than on first use. +author: IEvangelist +ms.author: dapine +ms.date: 10/20/2025 +ms.topic: concept-article +ai-usage: ai-assisted +--- + +# Dependency injection auto-activation + +The [`Microsoft.Extensions.DependencyInjection.AutoActivation`](https://www.nuget.org/packages/Microsoft.Extensions.DependencyInjection.AutoActivation) NuGet package provides functionality to automatically activate singleton services at application startup, rather than waiting for their first use. This approach can help minimize latency for initial requests by ensuring services are ready to handle requests immediately when the application starts. + +## When to use auto-activation + +Auto-activation is beneficial in scenarios where: + +- You want to minimize the latency of the first request by avoiding the overhead of lazy initialization. +- Services need to perform initialization work at startup, such as warming up caches or establishing connections. +- You want consistent response times from the start of your application's lifetime. +- Services have expensive constructors that you want to execute during startup rather than during request processing. + +## Get started + +To get started with auto-activation, install the [`Microsoft.Extensions.DependencyInjection.AutoActivation`](https://www.nuget.org/packages/Microsoft.Extensions.DependencyInjection.AutoActivation) NuGet package. + +### [.NET CLI](#tab/dotnet-cli) + +```dotnetcli +dotnet add package Microsoft.Extensions.DependencyInjection.AutoActivation +``` + +### [PackageReference](#tab/package-reference) + +```xml + +``` + +--- + +For more information, see [dotnet add package](../tools/dotnet-add-package.md) or [Manage package dependencies in .NET applications](../tools/dependencies.md). + +## Register services for auto-activation + +The auto-activation package provides extension methods to register services that are automatically activated when the service provider is built. There are several ways to register services for auto-activation: + +### AddActivatedSingleton + +The method registers a service as a singleton and ensures it's activated at startup: + +```csharp +using Microsoft.Extensions.DependencyInjection; + +var services = new ServiceCollection(); + +services.AddActivatedSingleton(); + +var provider = services.BuildServiceProvider(); + +// MyService is already instantiated at this point +``` + +In this example, `MyService` is instantiated when `BuildServiceProvider()` is called, not when it's first requested from the service provider. + +### ActivateSingleton + +The method marks an already-registered singleton service for activation at startup: + +```csharp +using Microsoft.Extensions.DependencyInjection; + +var services = new ServiceCollection(); + +services.AddSingleton(); +services.ActivateSingleton(); + +var provider = services.BuildServiceProvider(); + +// MyService is already instantiated at this point +``` + +This approach is useful when you have existing service registrations that you want to activate at startup without changing the registration code. + +### TryAddActivatedSingleton + +The method conditionally registers a service for auto-activation only if it hasn't been registered already: + +```csharp +using Microsoft.Extensions.DependencyInjection; + +var services = new ServiceCollection(); + +services.TryAddActivatedSingleton(); +services.TryAddActivatedSingleton(); // This has no effect + +var provider = services.BuildServiceProvider(); +``` + +This method follows the same pattern as other `TryAdd*` methods in the dependency injection framework, ensuring services are only registered once. + +## Complete example + +Here's a complete example showing how to use auto-activation in a console application: + +```csharp +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; + +var builder = Host.CreateApplicationBuilder(args); + +// Register service with auto-activation +builder.Services.AddActivatedSingleton(); + +var host = builder.Build(); + +// CacheWarmer is already instantiated and initialized at this point +await host.RunAsync(); + +public class CacheWarmer +{ + public CacheWarmer() + { + Console.WriteLine("Warming up cache at startup..."); + // Perform expensive initialization + WarmUpCache(); + } + + private void WarmUpCache() + { + // Cache warming logic here + Console.WriteLine("Cache warmed up successfully!"); + } +} +``` + +When this application starts, you'll see the messages from the `CacheWarmer` constructor printed before the application begins processing requests, confirming that the service was activated at startup. + +## Auto-activation with dependencies + +Auto-activated services can depend on other services through dependency injection: + +```csharp +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; + +var services = new ServiceCollection(); + +services.AddLogging(); +services.AddActivatedSingleton(); + +var provider = services.BuildServiceProvider(); + +public class StartupTaskRunner +{ + private readonly ILogger _logger; + + public StartupTaskRunner(ILogger logger) + { + _logger = logger; + _logger.LogInformation("Running startup tasks..."); + RunTasks(); + } + + private void RunTasks() + { + // Execute startup tasks + _logger.LogInformation("Startup tasks completed."); + } +} +``` + +In this example, the `StartupTaskRunner` service depends on `ILogger`, which is automatically provided by the dependency injection container. + +## Best practices + +When using auto-activation, consider the following best practices: + +- **Use for singletons only**: Auto-activation is designed for singleton services. Services with shorter lifetimes (scoped or transient) should not be auto-activated. +- **Keep startup fast**: Auto-activated services should complete their initialization quickly to avoid delaying application startup. For longer-running initialization, consider using background services or hosted services. +- **Handle errors gracefully**: Exceptions thrown during the construction of auto-activated services will prevent the application from starting. Ensure robust error handling in service constructors. +- **Consider hosted services**: For services that need to perform ongoing work or asynchronous initialization, consider using instead of auto-activation. + +## See also + +- [Dependency injection in .NET](dependency-injection.md) +- [Dependency injection guidelines](dependency-injection-guidelines.md) +- [Worker Services in .NET](workers.md) diff --git a/docs/core/extensions/timeprovider-testing.md b/docs/core/extensions/timeprovider-testing.md new file mode 100644 index 0000000000000..d7b8c6c2d0995 --- /dev/null +++ b/docs/core/extensions/timeprovider-testing.md @@ -0,0 +1,399 @@ +--- +title: Testing with FakeTimeProvider +description: Learn how to test time-dependent code using FakeTimeProvider from Microsoft.Extensions.TimeProvider.Testing in .NET. +author: IEvangelist +ms.author: dapine +ms.date: 10/20/2025 +ms.topic: concept-article +ai-usage: ai-assisted +--- + +# Testing with FakeTimeProvider + +The [`Microsoft.Extensions.TimeProvider.Testing`](https://www.nuget.org/packages/Microsoft.Extensions.TimeProvider.Testing) NuGet package provides a `FakeTimeProvider` class that enables deterministic testing of code that depends on time. This fake implementation allows you to control the system time within your tests, ensuring predictable and repeatable results. + +## Why use FakeTimeProvider + +Testing code that depends on the current time or uses timers can be challenging: + +- **Non-deterministic tests**: Tests that depend on real time can produce inconsistent results. +- **Slow tests**: Tests that need to wait for actual time to pass can significantly slow down test execution. +- **Race conditions**: Time-dependent logic can introduce race conditions that are hard to reproduce. +- **Edge cases**: Testing time-based logic at specific times (such as midnight or month boundaries) is difficult with real time. + +The `FakeTimeProvider` addresses these challenges by: + +- Providing complete control over the current time. +- Allowing you to advance time instantly without waiting. +- Enabling deterministic testing of time-based behavior. +- Making it easy to test edge cases and boundary conditions. + +## Get started + +To get started with `FakeTimeProvider`, install the [`Microsoft.Extensions.TimeProvider.Testing`](https://www.nuget.org/packages/Microsoft.Extensions.TimeProvider.Testing) NuGet package. + +### [.NET CLI](#tab/dotnet-cli) + +```dotnetcli +dotnet add package Microsoft.Extensions.TimeProvider.Testing +``` + +### [PackageReference](#tab/package-reference) + +```xml + +``` + +--- + +For more information, see [dotnet add package](../tools/dotnet-add-package.md) or [Manage package dependencies in .NET applications](../tools/dependencies.md). + +## Basic usage + +The `FakeTimeProvider` extends to provide controllable time for testing: + +```csharp +using Microsoft.Extensions.Time.Testing; + +var fakeTimeProvider = new FakeTimeProvider(); + +// Get the current time +var now = fakeTimeProvider.GetUtcNow(); +Console.WriteLine($"Current time: {now}"); + +// Advance time by 1 hour +fakeTimeProvider.Advance(TimeSpan.FromHours(1)); + +var later = fakeTimeProvider.GetUtcNow(); +Console.WriteLine($"Time after advance: {later}"); +``` + +## Initialize with specific time + +You can initialize `FakeTimeProvider` with a specific starting time: + +```csharp +using Microsoft.Extensions.Time.Testing; + +var startTime = new DateTimeOffset(2025, 10, 20, 12, 0, 0, TimeSpan.Zero); +var fakeTimeProvider = new FakeTimeProvider(startTime); + +Console.WriteLine($"Started at: {fakeTimeProvider.GetUtcNow()}"); +``` + +## Set time explicitly + +Use the `SetUtcNow` method to set the time to a specific value: + +```csharp +using Microsoft.Extensions.Time.Testing; + +var fakeTimeProvider = new FakeTimeProvider(); + +// Set to a specific date and time +var specificTime = new DateTimeOffset(2025, 12, 31, 23, 59, 59, TimeSpan.Zero); +fakeTimeProvider.SetUtcNow(specificTime); + +Console.WriteLine($"Time set to: {fakeTimeProvider.GetUtcNow()}"); +``` + +## Advance time + +The `Advance` method moves time forward by a specified duration: + +```csharp +using Microsoft.Extensions.Time.Testing; + +var fakeTimeProvider = new FakeTimeProvider(); +var startTime = fakeTimeProvider.GetUtcNow(); + +// Advance by 30 minutes +fakeTimeProvider.Advance(TimeSpan.FromMinutes(30)); + +var elapsed = fakeTimeProvider.GetUtcNow() - startTime; +Console.WriteLine($"Time advanced by: {elapsed.TotalMinutes} minutes"); +``` + +## Configure time zone + +Set the local time zone for the fake time provider: + +```csharp +using Microsoft.Extensions.Time.Testing; + +var fakeTimeProvider = new FakeTimeProvider(); + +// Set to Pacific Standard Time +var pacificTimeZone = TimeZoneInfo.FindSystemTimeZoneById("Pacific Standard Time"); +fakeTimeProvider.SetLocalTimeZone(pacificTimeZone); + +var localTime = fakeTimeProvider.GetLocalNow(); +Console.WriteLine($"Local time: {localTime}"); +``` + +## Test delayed operations + +`FakeTimeProvider` is particularly useful for testing operations that involve delays: + +```csharp +using Microsoft.Extensions.Time.Testing; +using Xunit; + +public class DelayedOperationTests +{ + [Fact] + public async Task DelayedOperation_CompletesAfterDelay() + { + // Arrange + var fakeTimeProvider = new FakeTimeProvider(); + var operation = new DelayedOperation(fakeTimeProvider); + + // Act + var task = operation.ExecuteAsync(TimeSpan.FromMinutes(5)); + + // Assert - operation should not be complete yet + Assert.False(task.IsCompleted); + + // Advance time by 5 minutes + fakeTimeProvider.Advance(TimeSpan.FromMinutes(5)); + + // Wait for the task to complete + await task; + + // Operation should now be complete + Assert.True(task.IsCompleted); + } +} + +public class DelayedOperation +{ + private readonly TimeProvider _timeProvider; + + public DelayedOperation(TimeProvider timeProvider) + { + _timeProvider = timeProvider; + } + + public async Task ExecuteAsync(TimeSpan delay) + { + await Task.Delay(delay, _timeProvider); + } +} +``` + +## Test periodic operations + +Test operations that execute periodically using timers: + +```csharp +using Microsoft.Extensions.Time.Testing; +using Xunit; + +public class PeriodicOperationTests +{ + [Fact] + public void PeriodicOperation_ExecutesAtIntervals() + { + // Arrange + var fakeTimeProvider = new FakeTimeProvider(); + var counter = new PeriodicCounter(fakeTimeProvider); + counter.Start(TimeSpan.FromSeconds(10)); + + // Act & Assert + Assert.Equal(0, counter.Count); + + // Advance by 10 seconds + fakeTimeProvider.Advance(TimeSpan.FromSeconds(10)); + Assert.Equal(1, counter.Count); + + // Advance by 20 more seconds + fakeTimeProvider.Advance(TimeSpan.FromSeconds(20)); + Assert.Equal(3, counter.Count); + + // Clean up + counter.Stop(); + } +} + +public class PeriodicCounter +{ + private readonly TimeProvider _timeProvider; + private ITimer? _timer; + + public int Count { get; private set; } + + public PeriodicCounter(TimeProvider timeProvider) + { + _timeProvider = timeProvider; + } + + public void Start(TimeSpan interval) + { + _timer = _timeProvider.CreateTimer( + callback: _ => Count++, + state: null, + dueTime: interval, + period: interval); + } + + public void Stop() + { + _timer?.Dispose(); + } +} +``` + +## Test time-based business logic + +Test business logic that depends on specific times or dates: + +```csharp +using Microsoft.Extensions.Time.Testing; +using Xunit; + +public class SubscriptionTests +{ + [Fact] + public void Subscription_ExpiresAfterOneYear() + { + // Arrange + var fakeTimeProvider = new FakeTimeProvider(); + var startDate = new DateTimeOffset(2025, 1, 1, 0, 0, 0, TimeSpan.Zero); + fakeTimeProvider.SetUtcNow(startDate); + + var subscription = new Subscription(fakeTimeProvider); + subscription.Activate(); + + // Assert - subscription is active + Assert.True(subscription.IsActive); + + // Act - advance time by 11 months + fakeTimeProvider.Advance(TimeSpan.FromDays(30 * 11)); + Assert.True(subscription.IsActive); + + // Advance time by 2 more months + fakeTimeProvider.Advance(TimeSpan.FromDays(60)); + Assert.False(subscription.IsActive); + } +} + +public class Subscription +{ + private readonly TimeProvider _timeProvider; + private DateTimeOffset _activationDate; + + public Subscription(TimeProvider timeProvider) + { + _timeProvider = timeProvider; + } + + public void Activate() + { + _activationDate = _timeProvider.GetUtcNow(); + } + + public bool IsActive + { + get + { + var currentTime = _timeProvider.GetUtcNow(); + var expirationDate = _activationDate.AddYears(1); + return currentTime < expirationDate; + } + } +} +``` + +## Integration with dependency injection + +Use `FakeTimeProvider` in tests for services registered with dependency injection: + +```csharp +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Time.Testing; +using Xunit; + +public class CacheServiceTests +{ + [Fact] + public void Cache_ExpiresAfterTimeout() + { + // Arrange + var fakeTimeProvider = new FakeTimeProvider(); + + var services = new ServiceCollection(); + services.AddSingleton(fakeTimeProvider); + services.AddSingleton(); + + var provider = services.BuildServiceProvider(); + var cache = provider.GetRequiredService(); + + // Act + cache.Set("key", "value", TimeSpan.FromMinutes(10)); + + // Assert - value is present + Assert.True(cache.TryGet("key", out var value)); + Assert.Equal("value", value); + + // Advance time beyond expiration + fakeTimeProvider.Advance(TimeSpan.FromMinutes(11)); + + // Value should be expired + Assert.False(cache.TryGet("key", out _)); + } +} + +public class CacheService +{ + private readonly TimeProvider _timeProvider; + private readonly Dictionary _cache = new(); + + public CacheService(TimeProvider timeProvider) + { + _timeProvider = timeProvider; + } + + public void Set(string key, string value, TimeSpan expiration) + { + var expiresAt = _timeProvider.GetUtcNow() + expiration; + _cache[key] = new CacheEntry(value, expiresAt); + } + + public bool TryGet(string key, out string? value) + { + if (_cache.TryGetValue(key, out var entry)) + { + if (_timeProvider.GetUtcNow() < entry.ExpiresAt) + { + value = entry.Value; + return true; + } + + // Entry expired, remove it + _cache.Remove(key); + } + + value = null; + return false; + } + + private record CacheEntry(string Value, DateTimeOffset ExpiresAt); +} +``` + +## Best practices + +When using `FakeTimeProvider`, consider the following best practices: + +- **Inject TimeProvider**: Always inject `TimeProvider` as a dependency rather than using or directly. This makes your code testable. +- **Use UTC time**: Work with UTC time in your business logic and convert to local time only when needed for display. +- **Test edge cases**: Use `FakeTimeProvider` to test edge cases like midnight, month boundaries, daylight saving time transitions, and leap years. +- **Clean up timers**: Dispose of timers created with `CreateTimer` to avoid resource leaks in your tests. +- **Advance time deliberately**: Advance time explicitly in your tests to make the test behavior clear and predictable. +- **Avoid mixing real and fake time**: Don't mix real `TimeProvider.System` with `FakeTimeProvider` in the same test, as this can lead to unpredictable behavior. + +## See also + +- +- [Unit testing in .NET](../testing/index.md) +- [Dependency injection in .NET](dependency-injection.md) diff --git a/docs/fundamentals/toc.yml b/docs/fundamentals/toc.yml index 7b74f1965d772..d12946af75576 100644 --- a/docs/fundamentals/toc.yml +++ b/docs/fundamentals/toc.yml @@ -1055,6 +1055,9 @@ items: - name: Dependency injection guidelines href: ../core/extensions/dependency-injection-guidelines.md displayName: dependency injection best practices,guidelines,di,ioc,ioc container,dependency container,inversion of control + - name: Dependency injection auto-activation + href: ../core/extensions/dependency-injection-auto-activation.md + displayName: auto-activation,singleton activation,startup activation,di auto-activation - name: Configuration items: - name: Overview @@ -1106,6 +1109,18 @@ items: displayName: console log formatting,console log formatter,console log formatting provider,console log formatter provider - name: HostBuilder (generic host) href: ../core/extensions/generic-host.md + - name: Asynchronous state management + href: ../core/extensions/async-state.md + displayName: async state,asynclocal,ambient data,async context,asynchronous context + - name: Application ambient metadata + href: ../core/extensions/ambient-metadata-application.md + displayName: application metadata,ambient metadata,telemetry enrichment,application version + - name: Testing with FakeTimeProvider + href: ../core/extensions/timeprovider-testing.md + displayName: fake time provider,time testing,testing time,test timers,mock time + - name: Audit reports for privacy and compliance + href: ../core/extensions/audit-reports.md + displayName: audit reports,compliance reports,privacy audit,data classification,telemetry audit - name: Resilience href: ../core/resilience/index.md displayName: resilience,transient fault handling,recovery,polly From 403e5ead9ce0da96a1efd8ec06cc808e861baf72 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 20 Oct 2025 18:25:59 +0000 Subject: [PATCH 3/3] Fix IAsyncState references to use non-generic interface Co-authored-by: IEvangelist <7679720+IEvangelist@users.noreply.github.com> --- docs/core/extensions/async-state.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/core/extensions/async-state.md b/docs/core/extensions/async-state.md index b494844225673..6856b97d5fc92 100644 --- a/docs/core/extensions/async-state.md +++ b/docs/core/extensions/async-state.md @@ -60,7 +60,7 @@ services.AddAsyncState(); var provider = services.BuildServiceProvider(); ``` -This registration makes the and interfaces available for dependency injection. +This registration makes the and interfaces available for dependency injection. ## Use IAsyncContext @@ -97,7 +97,7 @@ The value set in the context flows through asynchronous operations, making it av ## Use IAsyncState -The interface provides a simpler property-based API for accessing async state: +The interface provides a simpler property-based API for accessing async state: ```csharp using Microsoft.Extensions.AsyncState; @@ -107,7 +107,7 @@ var services = new ServiceCollection(); services.AddAsyncState(); var provider = services.BuildServiceProvider(); -var asyncState = provider.GetRequiredService>(); +var asyncState = provider.GetRequiredService(); // Initialize the state asyncState.Initialize(); @@ -280,7 +280,7 @@ When using async state, consider the following best practices: - **Limit state size**: Keep async state objects small to minimize memory overhead and serialization costs in scenarios where async state might need to be serialized. - **Initialize state early**: Set async state values as early as possible in your async operation to ensure they're available throughout the execution flow. - **Clean up state**: Reset or clear async state when it's no longer needed to avoid memory leaks in long-running applications. -- **Use appropriate interfaces**: Use `IAsyncContext` when you need explicit control over state initialization and reset. Use `IAsyncState` for simpler property-based access. +- **Use appropriate interfaces**: Use `IAsyncContext` when you need explicit control over state initialization and reset. Use `IAsyncState` for simpler property-based access. - **Type safety**: Create specific context types rather than using generic dictionaries to maintain type safety and improve code clarity. ## See also