# Net.Sdk.Web.Extensions .NET library extending functionality for web applications # Features - [MVC Source Generators](#mvc-source-generators) - [Improved Websocket Support](#better-websocket-support) - [Improved request tracing](#request-tracing-using-correlationvectors) - [Improved client IP handling](#client-ip-extraction) - [Improved configuration binding](#better-configuration-binding-support) - [Mockable IHttpClient builder](#mockable-httpclient) - [Support for base64 encoded certificates in configuration](#base64-to-x509certificate2-converter-for-options) ## MVC Source Generators Net.Sdk.Web.Extensions provides a set of source generators to replicate the MVC functionality of Asp while being compatible with NativeAOT compilation. ### Usage 1) Add the `Net.Sdk.Web.Extensions.SourceGenerators` package to your project 2) Create a controller class ```csharp [GenerateController("api/v1/accounts")] public sealed class AccountsController( IAccountService accountService) { private readonly IAccountService accountService = accountService; [GenerateGet("{accountId}")] [RouteFilter] public async Task GetAccount(HttpContext context, [FromRoute] string accountId, CancellationToken cancellationToken) { var account = await this.accountService.GetAccountById(accountId, cancellationToken); if (account is null) { return Results.NotFound(); } return Results.Ok(account); } [GeneratePost] public async Task PostAccount([FromBody] PostAccountRequest request, CancellationToken cancellationToken) { if (await this.accountService.CreateAccount(request.Email, request.Email, request.Password, cancellationToken)) { return Results.Created(); } return Results.BadRequest(); } } ``` 3) Register the routes in the `WebApplicationBuilder` using `WithRoutes()` extension. This extension is generated by the source generator and is available under the `Net.Sdk.Web` namespace in `WebApplicationBuilderExtensions` class. ```csharp builder.WithRoutes(); ``` 4) Use the routes in your `WebApplication` class using `UseRoutes()` extension. This extension is generated by the source generator and is available under the `Net.Sdk.Web` namespace in `WebApplicationExtensions` class. ```csharp app.UseRoutes(); ``` ### Functionality - `GenerateController` attribute allows you to define a controller class and its route. - `GenerateGet`, `GeneratePost`, `GeneratePut`, `GenerateDelete` attributes allow you to define the HTTP methods and their routes. - `FromRoute`, `FromBody`, `FromQuery` attributes allow you to define the parameters of the methods. - `RouteFilter` attribute allows you to define the filters for the methods. Filters must be of type `Microsoft.AspNetCore.Http.IEndpointFilter`. - `HttpContext` can be accessed by requesting it as a parameter of your route method. The generator will automatically fetch it from the context and pass it to your method. - Controllers are registered as services in the DI container, so you can inject any service into them. - There is no restriction on the modifiers of your controllers. You can freely use inheritance for shared functionality between controllers. ## Better WebSocket support Net.Sdk.Web.Extensions provides a streamlined implementation of WebSockets to be used in a web app, in a syntax similar to Asp Mvc ### Integration into `WebApplication` ```C# var builder = WebApplication.CreateSlimBuilder(args); builder.WithWebSocketRoute(); var app = builder.Build(); app.UseWebSocketRoute("custom-route"); ``` ### Message mapping #### Define your request ```C# [WebSocketConverter, CustomRequest>] public class CustomRequest{ } ``` #### Define your response ```C# [WebSocketConverter, CustomResponse>] public class CustomResponse{ } ``` #### Implement your WebSocketRoute ```C# public class CustomRoute : WebSocketRouteBase { public override Task SocketAccepted(CancellationToken cancellationToken) { return base.SocketAccepted(cancellationToken); } public override Task SocketClosed() { return base.SocketClosed(); } public override async Task ExecuteAsync(CustomRequest? request, CancellationToken cancellationToken) { await this.SendMessage(new BotResponse(), cancellationToken); } } ``` ### Support for `IActionFilter` Decorate your `CustomRoute` with filters that get executed the same way as in a AspNetCore web app Both `IActionFilter` and `IAsyncActionFilter` are supported ```C# [ServiceFilter] public class CustomRoute : WebSocketRouteBase ``` ### Custom message converters If you need specialized converters, implement your own `WebSocketMessageConverter` ## Request tracing using CorrelationVectors ### Integration into `WebApplication` ```C# var builder = WebApplication.CreateSlimBuilder(args); builder.WithCorrelationVector(); var app = builder.Build(); app.UseCorrelationVector(); ``` ### Accessing the correlation vector Take a dependency on `IHttpContextAccessor` and get the CV using the `HttpContextExtensions.GetCorrelationVector` method ```C# var cv = accessor.HttpContext.GetCorrelationVector(); ``` ### Configuration Configure an instance of `CorrelationVectorOptions` to adjust the CV header ### Requests and responses - On a request, the `CorrelationVectorMiddleware` retrieves the CV from the request header if present, otherwise it creates a new one - The CV is stored in `HttpContext.Items` under `CorrelationVector` key - Log the CV to follow traces of one application flow under multiple operations and across http requests ### Integration with `HttpClient` - Pass the `CorrelationVectorHandler` to the `IHttpClientBuilder` to manage CVs across requests - On each `HttpClient` request, the CV is added to the configured header - After receiving the response, the `CorrelationVectorHandler` will receive and parse any existing CV from the headers and reapply it to the `HttpContext.Items` ## Client IP extraction `IPExtractingMiddleware` figures out the IP of the client, being able to handle reverse proxying through `X-Forwarded-For` header as well as CloudFlare specific `CF-Connecting-IP` header ### Integration into `WebApplication` ```C# var builder = WebApplication.CreateSlimBuilder(args); builder.WithIPExtraction(); var app = builder.Build(); app.UseIPExtraction(); ``` ### Accessing the client IP Take a dependency on `IHttpContextAccessor` and get the CV using the `HttpContextExtensions.GetCorrelationVector` method ```C# var cv = accessor.HttpContext.GetClientIP(); ``` ## Better configuration binding support ### Integration into `WebApplication` ```C# var builder = WebApplication.CreateSlimBuilder(args); builder.ConfigureExtended(); public class CustomService { public CustomService (IOptions options) { } } ``` ### Customizing configuration key ```C# [OptionsName(Name = "CustomKey")] public class CustomOptions { } ``` ## Mockable HttpClient ### Integration into `WebApplication` ```C# var builder = WebApplication.CreateSlimBuilder(args); builder.RegisterHttpClient() .WithTimeout(TimeSpan.FromSeconds(5)) .WithCorrelationVector() .CreateBuilder(); public class CustomService { public CustomService (IHttpClient client) { } } ``` ## Base64 to X509Certificate2 converter for options Use the `Base64ToCertificateConverter` to retrieve your SSL certificate from configuration and bind it to options, to allow for dynamic loading of certificates ### Use `Base64ToCertificateConverter` on your `X509Certificate2` property ```C# public class ServerOptions { [JsonConverter(typeof(Base64ToCertificateConverter))] public X509Certificate2 Certificate { get; set; } = default!; } ```