diff --git a/.gitignore b/.gitignore index 5b59a74e..894a117e 100644 --- a/.gitignore +++ b/.gitignore @@ -13,6 +13,7 @@ bld/ # vs **/.vs/**/* +**/**/*.csproj.user # visual studio code .vscode/ diff --git a/Directory.Packages.props b/Directory.Packages.props index 796ef925..9fc49203 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -5,9 +5,17 @@ + + + + + + + + - + @@ -28,4 +36,4 @@ - + \ No newline at end of file diff --git a/README.md b/README.md index 31a18717..32a747db 100644 --- a/README.md +++ b/README.md @@ -53,6 +53,7 @@ You can see them in the links below. keep using `Dockerfile`. - **[Reflection:](/reflection/README.md)** Researching and learning what `.Net` offers under reflection. +- **[Aspire:](/aspire/README.md)** Research and learn `.NET Aspire` ### ASP.NET Core diff --git a/aspire/Aspire.AppHost/Aspire.AppHost.csproj b/aspire/Aspire.AppHost/Aspire.AppHost.csproj new file mode 100644 index 00000000..994e0737 --- /dev/null +++ b/aspire/Aspire.AppHost/Aspire.AppHost.csproj @@ -0,0 +1,20 @@ + + + + + + Exe + true + 2f4ae309-5273-4857-80b7-4968ec263b33 + + + + + + + + + + + + \ No newline at end of file diff --git a/aspire/Aspire.AppHost/Program.cs b/aspire/Aspire.AppHost/Program.cs new file mode 100644 index 00000000..4cc6897f --- /dev/null +++ b/aspire/Aspire.AppHost/Program.cs @@ -0,0 +1,10 @@ +var builder = DistributedApplication.CreateBuilder(args); + +var projectA = builder.AddProject("serviceA").WithExternalHttpEndpoints(); + +builder.AddProject("serviceB") + .WithExternalHttpEndpoints() + .WithReference(projectA) + .WaitFor(projectA); + +builder.Build().Run(); \ No newline at end of file diff --git a/aspire/Aspire.AppHost/Properties/launchSettings.json b/aspire/Aspire.AppHost/Properties/launchSettings.json new file mode 100644 index 00000000..1fd5a945 --- /dev/null +++ b/aspire/Aspire.AppHost/Properties/launchSettings.json @@ -0,0 +1,30 @@ +{ + "$schema": "https://json.schemastore.org/launchsettings.json", + "profiles": { + "http": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": true, + "applicationUrl": "http://localhost:15046", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development", + "DOTNET_ENVIRONMENT": "Development", + "DOTNET_DASHBOARD_OTLP_ENDPOINT_URL": "http://localhost:19115", + "DOTNET_RESOURCE_SERVICE_ENDPOINT_URL": "http://localhost:20062", + "ASPIRE_ALLOW_UNSECURED_TRANSPORT": "true" + } + }, + "https": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": true, + "applicationUrl": "https://localhost:17286;http://localhost:15046", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development", + "DOTNET_ENVIRONMENT": "Development", + "DOTNET_DASHBOARD_OTLP_ENDPOINT_URL": "https://localhost:21115", + "DOTNET_RESOURCE_SERVICE_ENDPOINT_URL": "https://localhost:22171" + } + }, + } +} diff --git a/aspire/Aspire.AppHost/appsettings.Development.json b/aspire/Aspire.AppHost/appsettings.Development.json new file mode 100644 index 00000000..0c208ae9 --- /dev/null +++ b/aspire/Aspire.AppHost/appsettings.Development.json @@ -0,0 +1,8 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + } +} diff --git a/aspire/Aspire.AppHost/appsettings.json b/aspire/Aspire.AppHost/appsettings.json new file mode 100644 index 00000000..31c092aa --- /dev/null +++ b/aspire/Aspire.AppHost/appsettings.json @@ -0,0 +1,9 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning", + "Aspire.Hosting.Dcp": "Warning" + } + } +} diff --git a/aspire/Aspire.ServiceDefaults/Aspire.ServiceDefaults.csproj b/aspire/Aspire.ServiceDefaults/Aspire.ServiceDefaults.csproj new file mode 100644 index 00000000..3b5a7358 --- /dev/null +++ b/aspire/Aspire.ServiceDefaults/Aspire.ServiceDefaults.csproj @@ -0,0 +1,19 @@ + + + + true + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/aspire/Aspire.ServiceDefaults/Extensions.cs b/aspire/Aspire.ServiceDefaults/Extensions.cs new file mode 100644 index 00000000..0a2ee48c --- /dev/null +++ b/aspire/Aspire.ServiceDefaults/Extensions.cs @@ -0,0 +1,111 @@ +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Diagnostics.HealthChecks; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Diagnostics.HealthChecks; +using Microsoft.Extensions.Logging; +using OpenTelemetry; +using OpenTelemetry.Metrics; +using OpenTelemetry.Trace; + +namespace Microsoft.Extensions.Hosting; + +// Adds common .NET Aspire services: service discovery, resilience, health checks, and OpenTelemetry. +// This project should be referenced by each service project in your solution. +// To learn more about using this project, see https://aka.ms/dotnet/aspire/service-defaults +public static class Extensions +{ + public static IHostApplicationBuilder AddServiceDefaults(this IHostApplicationBuilder builder) + { + builder.ConfigureOpenTelemetry(); + + builder.AddDefaultHealthChecks(); + + builder.Services.AddServiceDiscovery(); + + builder.Services.ConfigureHttpClientDefaults(http => + { + // Turn on resilience by default + http.AddStandardResilienceHandler(); + + // Turn on service discovery by default + http.AddServiceDiscovery(); + }); + + return builder; + } + + public static IHostApplicationBuilder ConfigureOpenTelemetry(this IHostApplicationBuilder builder) + { + builder.Logging.AddOpenTelemetry(logging => + { + logging.IncludeFormattedMessage = true; + logging.IncludeScopes = true; + }); + + builder.Services.AddOpenTelemetry() + .WithMetrics(metrics => + { + metrics.AddAspNetCoreInstrumentation() + .AddHttpClientInstrumentation() + .AddRuntimeInstrumentation(); + }) + .WithTracing(tracing => + { + tracing.AddAspNetCoreInstrumentation() + // Uncomment the following line to enable gRPC instrumentation (requires the OpenTelemetry.Instrumentation.GrpcNetClient package) + //.AddGrpcClientInstrumentation() + .AddHttpClientInstrumentation(); + }); + + builder.AddOpenTelemetryExporters(); + + return builder; + } + + private static IHostApplicationBuilder AddOpenTelemetryExporters(this IHostApplicationBuilder builder) + { + var useOtlpExporter = !string.IsNullOrWhiteSpace(builder.Configuration["OTEL_EXPORTER_OTLP_ENDPOINT"]); + + if (useOtlpExporter) + { + builder.Services.AddOpenTelemetry().UseOtlpExporter(); + } + + // Uncomment the following lines to enable the Azure Monitor exporter (requires the Azure.Monitor.OpenTelemetry.AspNetCore package) + //if (!string.IsNullOrEmpty(builder.Configuration["APPLICATIONINSIGHTS_CONNECTION_STRING"])) + //{ + // builder.Services.AddOpenTelemetry() + // .UseAzureMonitor(); + //} + + return builder; + } + + public static IHostApplicationBuilder AddDefaultHealthChecks(this IHostApplicationBuilder builder) + { + builder.Services.AddHealthChecks() + // Add a default liveness check to ensure app is responsive + .AddCheck("self", () => HealthCheckResult.Healthy(), ["live"]); + + return builder; + } + + public static WebApplication MapDefaultEndpoints(this WebApplication app) + { + // Adding health checks endpoints to applications in non-development environments has security implications. + // See https://aka.ms/dotnet/aspire/healthchecks for details before enabling these endpoints in non-development environments. + if (app.Environment.IsDevelopment()) + { + // All health checks must pass for app to be considered ready to accept traffic after starting + app.MapHealthChecks("/health"); + + // Only health checks tagged with the "live" tag must pass for app to be considered alive + app.MapHealthChecks("/alive", new HealthCheckOptions + { + Predicate = r => r.Tags.Contains("live") + }); + } + + return app; + } +} \ No newline at end of file diff --git a/aspire/ProjectA/Program.cs b/aspire/ProjectA/Program.cs new file mode 100644 index 00000000..a5f9bfa3 --- /dev/null +++ b/aspire/ProjectA/Program.cs @@ -0,0 +1,20 @@ +var builder = WebApplication.CreateBuilder(args); + +builder.AddServiceDefaults(); + +builder.Services.AddOpenApi(); + +var app = builder.Build(); + +if (app.Environment.IsDevelopment()) +{ + app.MapOpenApi(); +} + +app.UseHttpsRedirection(); + +app.MapGet("/", () => "Hello World! I'm ProjectA"); + +app.MapDefaultEndpoints(); + +app.Run(); \ No newline at end of file diff --git a/aspire/ProjectA/ProjectA.csproj b/aspire/ProjectA/ProjectA.csproj new file mode 100644 index 00000000..f7e22b44 --- /dev/null +++ b/aspire/ProjectA/ProjectA.csproj @@ -0,0 +1,11 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/aspire/ProjectA/Properties/launchSettings.json b/aspire/ProjectA/Properties/launchSettings.json new file mode 100644 index 00000000..9d120b90 --- /dev/null +++ b/aspire/ProjectA/Properties/launchSettings.json @@ -0,0 +1,23 @@ +{ + "$schema": "https://json.schemastore.org/launchsettings.json", + "profiles": { + "http": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": false, + "applicationUrl": "http://localhost:5143", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "https": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": false, + "applicationUrl": "http://localhost:5143", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + } + } +} diff --git a/aspire/ProjectA/appsettings.Development.json b/aspire/ProjectA/appsettings.Development.json new file mode 100644 index 00000000..0c208ae9 --- /dev/null +++ b/aspire/ProjectA/appsettings.Development.json @@ -0,0 +1,8 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + } +} diff --git a/aspire/ProjectA/appsettings.json b/aspire/ProjectA/appsettings.json new file mode 100644 index 00000000..10f68b8c --- /dev/null +++ b/aspire/ProjectA/appsettings.json @@ -0,0 +1,9 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + }, + "AllowedHosts": "*" +} diff --git a/aspire/ProjectB/Program.cs b/aspire/ProjectB/Program.cs new file mode 100644 index 00000000..abc846a0 --- /dev/null +++ b/aspire/ProjectB/Program.cs @@ -0,0 +1,20 @@ +var builder = WebApplication.CreateBuilder(args); + +builder.AddServiceDefaults(); + +builder.Services.AddOpenApi(); + +var app = builder.Build(); + +if (app.Environment.IsDevelopment()) +{ + app.MapOpenApi(); +} + +app.UseHttpsRedirection(); + +app.MapGet("/", () => "Hello World! I'm ProjectB"); + +app.MapDefaultEndpoints(); + +app.Run(); \ No newline at end of file diff --git a/aspire/ProjectB/ProjectB.csproj b/aspire/ProjectB/ProjectB.csproj new file mode 100644 index 00000000..f6ab5fd1 --- /dev/null +++ b/aspire/ProjectB/ProjectB.csproj @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/aspire/ProjectB/Properties/launchSettings.json b/aspire/ProjectB/Properties/launchSettings.json new file mode 100644 index 00000000..ab7a21c9 --- /dev/null +++ b/aspire/ProjectB/Properties/launchSettings.json @@ -0,0 +1,23 @@ +{ + "$schema": "https://json.schemastore.org/launchsettings.json", + "profiles": { + "http": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": false, + "applicationUrl": "http://localhost:5200", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "https": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": false, + "applicationUrl": "http://localhost:5200", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + } + } +} diff --git a/aspire/ProjectB/appsettings.Development.json b/aspire/ProjectB/appsettings.Development.json new file mode 100644 index 00000000..0c208ae9 --- /dev/null +++ b/aspire/ProjectB/appsettings.Development.json @@ -0,0 +1,8 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + } +} diff --git a/aspire/ProjectB/appsettings.json b/aspire/ProjectB/appsettings.json new file mode 100644 index 00000000..10f68b8c --- /dev/null +++ b/aspire/ProjectB/appsettings.json @@ -0,0 +1,9 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + }, + "AllowedHosts": "*" +} diff --git a/aspire/README.md b/aspire/README.md new file mode 100644 index 00000000..6d43c91a --- /dev/null +++ b/aspire/README.md @@ -0,0 +1,132 @@ +# Aspire + +.NET Aspire helps manage applications, services (such as Redis), and other +project dependencies. It also provides a dashboard for monitoring. Aspire +consists of two main components: `AppHost` and `ServiceDefaults`. + +> [!WARNING] +> +> Only Azure supports using Aspire in cloud. + +> [!NOTE] +> +> We looked into it and found it more suitable for local testing than `docker +> compose`, but not for production. + +## AppHost + +AppHost is the orchestration project where we manage the referenced services. +Projects can be added as follows: + +[program.cs] +```csharp +var builder = DistributedApplication.CreateBuilder(args); + +var projectA = builder.AddProject("serviceA").WithExternalHttpEndpoints(); + +builder.AddProject("serviceB") + .WithExternalHttpEndpoints() + .WithReference(projectA) + .WaitFor(projectA); + +builder.Build().Run(); +``` + +In the example above, two service projects are added. Using extensions, we can +configure various settings and behaviors for these projects. + +Once the AppHost project starts, all referenced projects will automatically +launch, eliminating the need to start them manually. + +After AppHost is running, the [dashboard](#dashboard) becomes available. The +dashboard allows us to view the added projects and monitor their details +(telemetry, etc.), which are provided through the +[ServiceDefaults](#servicedefault) module. + +## ServiceDefaults + +ServiceDefaults provides extensions that can be used in the added projects. +These extensions collect project-related data, which AppHost utilizes to display +information on the dashboard. Extension can be used as follows: + +[program.cs] +```csharp +var builder = WebApplication.CreateBuilder(args); + +builder.AddServiceDefaults(); // #1 + +... + +var app = builder.Build(); + +... + +app.MapDefaultEndpoints(); // #2 + +app.Run(); +``` + +## Adding or Creating Aspire + +To create a new Aspire project, you can use the following command: + +```sh +dotnet new aspire +``` + +If you want to add Aspire to an existing project, modify the `.csproj` file of +a Web API project as follows to designate it as an AppHost project: + +```xml + + + + true + ... + + + + + ... + + + +``` + +The AppHost project also requires a class library project(`ServiceDefaults` +projects). Create a new class library and update its `.csproj` file as follows: + +```xml + + + + true + + + + + + + + + + + + + + + + +``` + +If you encounter the `error ASPIRE006` SDK error while adding Aspire to an +existing project, update the `.csproj` file to specify the correct SDK version: + +```xml + +``` + +## Dashboard + +The Aspire dashboard provides a live interface for monitoring telemetry data, +viewing logs, and starting or stopping projects in real time. \ No newline at end of file diff --git a/authentication/MultiAuthentication/MultiAuthentication.csproj.user b/authentication/MultiAuthentication/MultiAuthentication.csproj.user deleted file mode 100644 index ccfffb1f..00000000 --- a/authentication/MultiAuthentication/MultiAuthentication.csproj.user +++ /dev/null @@ -1,6 +0,0 @@ - - - - https - - \ No newline at end of file diff --git a/benchmarking-in-dotnet/README.md b/benchmarking-in-dotnet/README.md index e0d8b959..f4787fb7 100644 --- a/benchmarking-in-dotnet/README.md +++ b/benchmarking-in-dotnet/README.md @@ -1,6 +1,6 @@ # Benchmarking in DotNet -This is a simple project to demonstrate how we run benchmarking dotnet using +This is a simple project to demonstrate how we run benchmarking dotnet using [BenchmarkDotNet][] library. ## BenchmarkDotNet @@ -12,7 +12,7 @@ This is a simple project to demonstrate how we run benchmarking dotnet using - Add `[SimplJob]` attribute to the class - Add `[Benchmark]` attribute to methods -> :warning: +> [!WARNING] > >`BenchmarkDotNet` library only works with console applications @@ -28,14 +28,14 @@ See [How To Run][] for more details. ### Config -Configs provide setup for building a benchmark by setting up various +Configs provide setup for building a benchmark by setting up various configuration. See [Config][] for more details. ### Jobs Jobs define how a run is performed based. Parameters for a benchmark run -such as _RunStrategy_,_RuntimeMoniker_,_LaunchCount_,_IterationCount_. See -[Jobs][] for more details. +such as _RunStrategy_,_RuntimeMoniker_,_LaunchCount_,_IterationCount_. See +[Jobs][] for more details. ### Setup and Cleanup @@ -44,20 +44,22 @@ invocation. See [Setup and Cleanup][] for more details ### Important Notes -- Run benchmark tests in `RELEASE` configuration, `DEBUG` mode is not +- Run benchmark tests in `RELEASE` configuration, `DEBUG` mode is not recommended. -> :bulb: + +> [!TIP] > > Enable optimize in your _.csproj_ file to run benchmarks in `DEBUG` > configuration > ```xml > true > ``` + - Static methods are not supported, instance methods can be tested - Benchmarked classes should have `public` - Benchmarked methods should be `public` - Setup and Cleanup methods does not support `Task` return type -- We use `./.benchmark/` path for output results, add this directory to +- We use `./.benchmark/` path for output results, add this directory to `.gitignore` file [BenchmarkDotNet]: https://benchmarkdotnet.org/ diff --git a/central-package-management/README.md b/central-package-management/README.md index 01708052..91d320f0 100644 --- a/central-package-management/README.md +++ b/central-package-management/README.md @@ -11,7 +11,7 @@ to true so that projects can see the packages introduced here. With Look [Directory.Packages.props](Directory.Packages.props) for example -> :information_source: +> [!NOTE] > > If you had multiple `Directory.Packages.props` files in your repository, the > file that is closest to your project's directory will be evaluated for it and diff --git a/dependency-injection/README.md b/dependency-injection/README.md index 3d654167..a950937d 100644 --- a/dependency-injection/README.md +++ b/dependency-injection/README.md @@ -44,7 +44,7 @@ public class ServiceA(Func _newServiceB) In this example, `ServiceA` depends on `ServiceB` explicitly, thus having a better readability. -> :warning: +> [!WARNING] > > When registering a generic factory function, make sure you use > `HttpContext.RequestServices` instead of root service provider. Otherwise @@ -108,7 +108,7 @@ public void Action([FromKeyedServices("key")] ServiceType service) { } public void Action([FromServices] ServiceType service) { } ``` -> :information_source: +> [!NOTE] > > If you intentionally register more than one service with the same key, you can > call them all using `IEnumerable` when calling the service, otherwise the last diff --git a/dotnet-9-research-notes.md b/dotnet-9-research-notes.md deleted file mode 100644 index 9dbe7b37..00000000 --- a/dotnet-9-research-notes.md +++ /dev/null @@ -1,226 +0,0 @@ -# .NET 9 Research Notes - -## Topics to be learn - -### OpenAPI - -#### Built-in support for OpenAPI document generation - -In .NET 9, ASP.NET Core provides built-in support for generating OpenAPI -documents representing controller-based or minimal APIs via the -`Microsoft.AspNetCore.OpenApi` package. - -```csharp -var builder = WebApplication.CreateBuilder(); - -builder.Services.AddOpenApi(); - -var app = builder.Build(); - -app.MapOpenApi(); - -app.MapGet("/hello/{name}", (string name) => $"Hello {name}"!); - -app.Run(); -``` - -OpenAPI documents can also be generated at build-time by adding the -`Microsoft.Extensions.ApiDescription.Server` package. - -To modify the location of the emitted OpenAPI documents, set the target path in -the `OpenApiDocumentsDirectory` property in the app's project file: - -```xml - - $(MSBuildProjectDirectory) - -``` - -### Persisted assemblies - -`.NET 9` introduces the `PersistedAssemblyBuilder` class, which supports -creating and saving dynamic assemblies. This brings the assembly-saving -capabilities from `.NET Framework` to `.NET`. - -- **Assembly Saving**: Dynamically create types and methods, then save them as - executable (.exe) or library (.dll) files. -- **PDB Support**: Add symbol information for debugging. -- **Advantage**: Simplifies migration from `.NET Framework` to `.NET 9` and - modernizes dynamic code workflows. -This enables assemblies to be both created and stored for later use. - -### Type-name parsing - -`TypeName` class for parsing and handling `ECMA-335` type names, similar to -`System.Type` but independent of the runtime. It's designed for tools like -serializers. - -```csharp -using System.Reflection.Metadata; - -internal class RestrictedSerializationBinder -{ - Dictionary AllowList { get; set; } - - RestrictedSerializationBinder(Type[] allowedTypes) - => AllowList = allowedTypes.ToDictionary(type => type.FullName!); - - Type? GetType(ReadOnlySpan untrustedInput) - { - if (!TypeName.TryParse(untrustedInput, out TypeName? parsed)) - { - throw new InvalidOperationException($"Invalid type name: '{untrustedInput.ToString()}'"); - } - - if (AllowList.TryGetValue(parsed.FullName, out Type? type)) - { - return type; - } - else if (parsed.IsSimple // It's not generic, pointer, reference, or an array. - && parsed.AssemblyName is not null - && parsed.AssemblyName.Name == "MyTrustedAssembly" - ) - { - return Type.GetType(parsed.AssemblyQualifiedName, throwOnError: true); - } - - throw new InvalidOperationException($"Not allowed: '{untrustedInput.ToString()}'"); - } -} -``` - -## Topics to be checked for existence in the projects and updated if any - -### Middleware supports Keyed DI - -```csharp -var builder = WebApplication.CreateBuilder(args); -builder.Services.AddKeyedSingleton("test"); -builder.Services.AddKeyedScoped("test2"); - -var app = builder.Build(); -app.UseMiddleware(); -app.Run(); - -internal class MyMiddleware -{ - private readonly RequestDelegate _next; - - public MyMiddleware(RequestDelegate next, - [FromKeyedServices("test")] MySingletonClass service) - { - _next = next; - } - - public Task Invoke(HttpContext context, - [FromKeyedServices("test2")] - MyScopedClass scopedService) => _next(context); -} -``` - -### Linq - -In `Linq`, the new methods `CountBy` and `AggregateBy`, `Index` added. - -#### `CountBy` - -[Before] -```csharp -class User -{ - public string Name { get; set; } - public string Role { get; set; } -} - -var users = new List -{ - new User { Name = "Alice", Role = "Admin" }, - new User { Name = "Bob", Role = "Member" }, - new User { Name = "Charlie", Role = "Admin" }, - new User { Name = "David", Role = "Member" }, - new User { Name = "Eve", Role = "Guest" }, - new User { Name = "Frank", Role = "Admin" } -}; - -var roleCounts = users - .GroupBy(user => user.Role) - .Select(group => new { Role = group.Key, Count = group.Count() }); -``` - -[After] -```csharp -var roleCounts = users.CountBy(user => user.Role); -// [(Role, Count), (Role, Count), ....] -``` - -#### `AggregateBy` - -[Before] -```csharp -class User -{ - public string Name { get; set; } - public string Role { get; set; } - public int AccessLevel { get; set; } -} - -var users = new List -{ - new User { Name = "Alice", Role = "Admin", AccessLevel = 10 }, - new User { Name = "Bob", Role = "Member", AccessLevel = 5 }, - new User { Name = "Charlie", Role = "Admin", AccessLevel = 20 }, - new User { Name = "David", Role = "Member", AccessLevel = 5 }, - new User { Name = "Eve", Role = "Guest", AccessLevel = 1 }, - new User { Name = "Frank", Role = "Admin", AccessLevel = 10 } -}; - -var accessLevelSumByRole = users - .GroupBy(user => user.Role) - .Select(group => new { Role = group.Key, TotalAccessLevel = group.Sum(user => user.AccessLevel) }); -``` - -[After] -```csharp -var accessLevelSumByRole = users.AggregateBy( - user => user.Role, - seed: 0, - (currentTotal, user) => currentTotal + user.AccessLevel); -// [(Key, Value), (Key, Value), ...] -``` - -#### `Index(IEnumerable)` - -Makes it possible to quickly extract the implicit index of an enumerable. You -can now write code such as the following snippet to automatically index items in -a collection. - -```csharp -IEnumerable lines2 = File.ReadAllLines("output.txt"); -foreach ((int index, string line) in lines2.Index()) -{ - Console.WriteLine($"Line number: {index + 1}, Line: {line}"); -} -``` - -### `BinaryFormatter` Removed - -It is throwing exceptions, although there's still access. - -### New `TimeSpan.From*` Overloads - -[Example] -```csharp -TimeSpan timeSpan1 = TimeSpan.FromSeconds(value: 101.832); -Console.WriteLine($"timeSpan1 = {timeSpan1}"); -// timeSpan1 = 00:01:41.8319999 - -TimeSpan timeSpan2 = TimeSpan.FromSeconds(seconds: 101, milliseconds: 832); -Console.WriteLine($"timeSpan2 = {timeSpan2}"); -``` - -- `FromDays` -- `FromHours` -- `FromMinutes` -- `FromSeconds` -- `FromMilliseconds` -- `FromMicroseconds` diff --git a/exception-handling/README.md b/exception-handling/README.md index 0a7d4bb0..ea4f607b 100644 --- a/exception-handling/README.md +++ b/exception-handling/README.md @@ -21,7 +21,7 @@ To use it, we register it with `AddExceptionHandler` builder.Services.AddExceptionHandler(); ``` -> :warning: +> [!WARNING] > > When registering multiple exception handlers you should pay attention to the > order. It works according to the order of insertion. diff --git a/learn-dotnet.sln b/learn-dotnet.sln index f45bbb07..e1cbe029 100644 --- a/learn-dotnet.sln +++ b/learn-dotnet.sln @@ -138,6 +138,19 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "reflection", "reflection", EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Reflection", "reflection\Reflection\Reflection.csproj", "{E0B6EFF9-41D8-4EDF-8303-5977D6637FF6}" EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "aspire", "aspire", "{6EAC4719-E994-426D-87BD-6BB12FF17232}" + ProjectSection(SolutionItems) = preProject + aspire\README.md = aspire\README.md + EndProjectSection +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Aspire.AppHost", "aspire\Aspire.AppHost\Aspire.AppHost.csproj", "{FF280BA1-B772-48AC-8110-065520F3B852}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Aspire.ServiceDefaults", "aspire\Aspire.ServiceDefaults\Aspire.ServiceDefaults.csproj", "{EA1FCF65-B230-4932-9164-85C2E39449BC}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ProjectA", "aspire\ProjectA\ProjectA.csproj", "{1F34FA6D-124F-42AD-BE5F-F31FF07B9E28}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ProjectB", "aspire\ProjectB\ProjectB.csproj", "{0DCC0BF3-FDFF-4AF7-A080-6A5D9213386F}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -232,6 +245,22 @@ Global {E0B6EFF9-41D8-4EDF-8303-5977D6637FF6}.Debug|Any CPU.Build.0 = Debug|Any CPU {E0B6EFF9-41D8-4EDF-8303-5977D6637FF6}.Release|Any CPU.ActiveCfg = Release|Any CPU {E0B6EFF9-41D8-4EDF-8303-5977D6637FF6}.Release|Any CPU.Build.0 = Release|Any CPU + {FF280BA1-B772-48AC-8110-065520F3B852}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {FF280BA1-B772-48AC-8110-065520F3B852}.Debug|Any CPU.Build.0 = Debug|Any CPU + {FF280BA1-B772-48AC-8110-065520F3B852}.Release|Any CPU.ActiveCfg = Release|Any CPU + {FF280BA1-B772-48AC-8110-065520F3B852}.Release|Any CPU.Build.0 = Release|Any CPU + {EA1FCF65-B230-4932-9164-85C2E39449BC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {EA1FCF65-B230-4932-9164-85C2E39449BC}.Debug|Any CPU.Build.0 = Debug|Any CPU + {EA1FCF65-B230-4932-9164-85C2E39449BC}.Release|Any CPU.ActiveCfg = Release|Any CPU + {EA1FCF65-B230-4932-9164-85C2E39449BC}.Release|Any CPU.Build.0 = Release|Any CPU + {1F34FA6D-124F-42AD-BE5F-F31FF07B9E28}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {1F34FA6D-124F-42AD-BE5F-F31FF07B9E28}.Debug|Any CPU.Build.0 = Debug|Any CPU + {1F34FA6D-124F-42AD-BE5F-F31FF07B9E28}.Release|Any CPU.ActiveCfg = Release|Any CPU + {1F34FA6D-124F-42AD-BE5F-F31FF07B9E28}.Release|Any CPU.Build.0 = Release|Any CPU + {0DCC0BF3-FDFF-4AF7-A080-6A5D9213386F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {0DCC0BF3-FDFF-4AF7-A080-6A5D9213386F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {0DCC0BF3-FDFF-4AF7-A080-6A5D9213386F}.Release|Any CPU.ActiveCfg = Release|Any CPU + {0DCC0BF3-FDFF-4AF7-A080-6A5D9213386F}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -281,6 +310,11 @@ Global {AD45E9C7-D739-4513-8F01-3A6826E0F4FA} = {5EFD2336-9236-4C29-9CFD-077E702D6ACD} {5771998F-DD44-4CEA-8B37-C2E90E5014AC} = {E148C898-1BFD-4F28-8940-583CC50F90DB} {E0B6EFF9-41D8-4EDF-8303-5977D6637FF6} = {5771998F-DD44-4CEA-8B37-C2E90E5014AC} + {6EAC4719-E994-426D-87BD-6BB12FF17232} = {E148C898-1BFD-4F28-8940-583CC50F90DB} + {FF280BA1-B772-48AC-8110-065520F3B852} = {6EAC4719-E994-426D-87BD-6BB12FF17232} + {EA1FCF65-B230-4932-9164-85C2E39449BC} = {6EAC4719-E994-426D-87BD-6BB12FF17232} + {1F34FA6D-124F-42AD-BE5F-F31FF07B9E28} = {6EAC4719-E994-426D-87BD-6BB12FF17232} + {0DCC0BF3-FDFF-4AF7-A080-6A5D9213386F} = {6EAC4719-E994-426D-87BD-6BB12FF17232} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {ECEAECE7-49E5-4CBC-B84D-5FF82F2399FC} diff --git a/migration-notes.md b/migration-notes.md deleted file mode 100644 index 45d414d1..00000000 --- a/migration-notes.md +++ /dev/null @@ -1,20 +0,0 @@ -# Migration Notes - -Here we list the operations performed when upgrading projects to .Net 9 - -> :info: -> -> The order is not important for now. - -- [ ] Upgrade `dotnet` and `language` version in `csproj` or `build.props` -- [ ] Upgrade `dotnet` version to 9 and `checkout`, `setup-dotnet` version to - `4` in workflows -- [ ] Upgrade libraries to new versions -- [ ] (Optional) If `Base64` encoded information is carried in the url, use - `Base64Url`. -- [ ] (Optional) If you need to take parameters with an `Array` using `params` - and then convert to `IEnumerable` type, use `IEnumerable` instead of `Array`. -- [ ] (Optional) `GeneratedRegex` can work with properties. It can be switched - for a better usage. -- [ ] (Optional) See if `UseExceptionHandler(ExceptionHandlerOptions options)` - override is available. Especially for the `StatusCodeSelector` property. diff --git a/model-binders/ModelBinders/ModelBinders.csproj.user b/model-binders/ModelBinders/ModelBinders.csproj.user deleted file mode 100644 index ccfffb1f..00000000 --- a/model-binders/ModelBinders/ModelBinders.csproj.user +++ /dev/null @@ -1,6 +0,0 @@ - - - - https - - \ No newline at end of file diff --git a/nullable-usage/README.md b/nullable-usage/README.md index 49c3e30f..a3da5275 100644 --- a/nullable-usage/README.md +++ b/nullable-usage/README.md @@ -28,7 +28,7 @@ public class PersonService(IQueryContext? queryContext) } ``` -> :information_source: +> [!NOTE] > > The DI container will resolve every dependency before initializing the object > and an exception will be thrown for a if a component is not registered. @@ -54,7 +54,7 @@ public class Person(IEntityContext _context) } ``` -> :information_source: +> [!NOTE] > > Because of NHibernate, entities need a _protected_ parameterless constructor > and compiler will highlight an error stating that the value of __context_ is diff --git a/openapi/README.md b/openapi/README.md index 23deb35a..09a2d74c 100644 --- a/openapi/README.md +++ b/openapi/README.md @@ -38,7 +38,7 @@ expose the schema, we need to map the endpoints as shown below: app.MapOpenApi(); ``` -> :warning: +> [!WARNING] > > It’s a good practice to do this only in developer mode. If you don’t want to > expose the entire schema to everyone, make sure to restrict access in @@ -120,7 +120,7 @@ app.UseSwaggerUI(options => }); ``` -> :warning: +> [!WARNING] > One important thing to note here is that `UseSwaggerUI` must be called after > `MapOpenApi`. Otherwise, Swagger UI will not work correctly. diff --git a/primary-constructor/README.md b/primary-constructor/README.md index f2212259..c98d5271 100644 --- a/primary-constructor/README.md +++ b/primary-constructor/README.md @@ -4,7 +4,7 @@ We use Primary Constructors to achieve a better representation of required dependencies and initialization parameters and get rid of constructors with only assignments and no logic to have a simpler code. -> :warning: +> [!WARNING] > > .NET 8 and C# 12 are required. If you are using Visual Studio and you get > unsupported warning, make sure you get update. @@ -28,7 +28,7 @@ constructor, field and parameter. If the parameter is directly used as a field, place an underscore (`_`) as a prefix, just like a field name. -> :bulb: This is mostly the case for dependency injection. +> [!TIP] This is mostly the case for dependency injection. ```csharp public class SalaryBase(SalaryCalculator _calculator) diff --git a/publish-over-dockerfile/README.md b/publish-over-dockerfile/README.md index 6ce83f5e..78cea264 100644 --- a/publish-over-dockerfile/README.md +++ b/publish-over-dockerfile/README.md @@ -1,6 +1,6 @@ # Publish Over Dockerfile -> :warning: +> [!WARNING] > > Until there is a support for credentials in remote image repositories we will > continue to use `Dockerfile`. @@ -14,11 +14,11 @@ For example dotnet publish --os linux --arch x64 /t:PublishContainer -c Release ``` -> :warning: +> [!WARNING] > > Before you run docker compose, you need to publish image file with publish. -> :information_source: +> [!NOTE] > > `Microsoft.NET.Build.Containers` package is needed for containerize. diff --git a/source-generator/README.md b/source-generator/README.md index b53d9e7e..52bf147f 100644 --- a/source-generator/README.md +++ b/source-generator/README.md @@ -8,7 +8,7 @@ We make the code to be generated either with the service model (schema json) presented to us or by looking under certain namespaces according to the content of the classes there. -> :information_source: +> [!NOTE] > > The purpose of this source generator learning is to automatically create > controllers by inspecting the classes within the target namespace in the @@ -32,7 +32,7 @@ content of the classes there. > WebApp Pre Build->>+WebApp Build: Go Next Stage > ``` -> :information_source: +> [!NOTE] > > It's work with target framework `netstandard2.0` and > `Microsoft.CodeAnalysis.CSharp 4.x` library. @@ -69,7 +69,7 @@ schema accordingly. We use the `Newtonsoft.Json` library to serialize and deserialize the service model schema json provided to us. -> :information_source: +> [!NOTE] > > In order for the `Newtonsoft.Json` library to work during analyze, it must be > added as `GeneratePathProperty="true" PrivateAssets="all"/>` and dll path diff --git a/source-generator/WebApp/WebApp.csproj.user b/source-generator/WebApp/WebApp.csproj.user deleted file mode 100644 index ccfffb1f..00000000 --- a/source-generator/WebApp/WebApp.csproj.user +++ /dev/null @@ -1,6 +0,0 @@ - - - - https - - \ No newline at end of file