Flowsy.Web.Api
                              
                            
                                4.0.0
                            
                        
                    See the version list below for details.
dotnet add package Flowsy.Web.Api --version 4.0.0
NuGet\Install-Package Flowsy.Web.Api -Version 4.0.0
<PackageReference Include="Flowsy.Web.Api" Version="4.0.0" />
<PackageVersion Include="Flowsy.Web.Api" Version="4.0.0" />
<PackageReference Include="Flowsy.Web.Api" />
paket add Flowsy.Web.Api --version 4.0.0
#r "nuget: Flowsy.Web.Api, 4.0.0"
#:package Flowsy.Web.Api@4.0.0
#addin nuget:?package=Flowsy.Web.Api&version=4.0.0
#tool nuget:?package=Flowsy.Web.Api&version=4.0.0
Flowsy Web API
Foundation components for Web APIs.
Features
This package gathers and extends the tools needed to create solid Web APIs by covering the following aspects:
- API Versioning
- API Key Security
- Routing Naming Convenion
- Data Validation
- Mediator Pattern for Controllers
- Form Content Management
- Logging
- Problem Details
- Swagger Documentation with Schema & Operation Filters
Dependencies
Flowsy Web API relies on other Flowsy packages as well as other excellent libraries well known by the community.
- Flowsy.Core
- Flowsy.Localization
- Flowsy.Mediation
- FluentValidation.AspNetCore
- Hellang.Middleware.ProblemDetails
- Serilog.AspNetCore
- Swashbuckle.AspNetCore
- Swashbuckle.AspNetCore.Filters
- Swashbuckle.AspNetCore.Swagger
- Swashbuckle.AspNetCore.SwaggerGen
- Swashbuckle.AspNetCore.SwaggerUI
Startup
Add the following code to the Program.cs file and customize as needed:
using System.Globalization;
using System.Reflection;
using System.Security.Claims;
using System.Text.Json;
using System.Text.Json.Serialization;
using Flowsy.Mediation;
using Flowsy.Web.Api.Documentation;
using Flowsy.Web.Api.Exceptions;
using Flowsy.Web.Api.Routing;
using Flowsy.Web.Api.Security;
using Flowsy.Web.Api.Versioning;
// using Flowsy.Web.Localization; // Add a reference to Flowsy.Web.Localization to add localization support
using FluentValidation;
using Hellang.Middleware.ProblemDetails;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Http.Features;
using Microsoft.Extensions.DependencyInjection;
using Serilog;
//////////////////////////////////////////////////////////////////////
// Build the application
//////////////////////////////////////////////////////////////////////
var builder = WebApplication.CreateBuilder(args);
var myApiAssembly = Assembly.GetExecutingAssembly();
// Load default culture from configuration
CultureInfo.CurrentUICulture = new CultureInfo("en-US");
// Add logging
builder.Host.UseSerilog((_, loggerConfiguration) =>
    {
        loggerConfiguration
            .ReadFrom
            .Configuration(builder.Configuration, "Some:Configuration:Section")
            .Destructure.ByTransforming<ClaimsPrincipal>(p => p.Identity?.Name ?? p.ToString() ?? "Unknown user")
            .Destructure.ByTransforming<ApiClient>(c => c.ClientId)
            .Destructure.ByTransforming<CultureInfo>(c => c.Name);
            // Add other transformations as needed
    
        // Customize loggerConfiguration
    });
// Add API Versioning
builder.Services.AddApiVersioning("1.0");
    
// Add a reference to Flowsy.Web.Localization to add localization support
// builder.AddLocalization(options => {
//     // Load supported culture names from configuration
//     options.SupportedCultureNames = new [] { "en-US", "es-MX" };
// });
// Add CORS policy
builder.Services.AddCors(options =>
    {
        options.AddDefaultPolicy(policy =>
        {
            // Load settings from configuration
            policy.WithOrigins("https://www.example1.com", "https://www.example2.com");
            policy.WithMethods("OPTIONS", "GET", "POST", "PUT", "PATCH", "DELETE");
            policy.WithHeaders("Accept-Content", "Content-Type");
        });
    });
// Add controllers and customize as needed
builder.Services
    .AddControllers(options =>
    {
        options.Conventions.Add(new RouteTokenTransformerConvention(new KebabCaseRouteParameterTransformer()));
    })
    .AddJsonOptions(options =>
    {
        options.JsonSerializerOptions.Converters.Add(new JsonStringEnumConverter());
        options.JsonSerializerOptions.DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull;
        options.JsonSerializerOptions.PropertyNamingPolicy = JsonNamingPolicy.CamelCase;
    });
// Configure form options
builder.Services.Configure<FormOptions>(options =>
    {
        // Customize options
    });
// Add API client management
builder.Services.AddSingleton<IApiClientManager>(serviceProvider => {
    var clients = new List<ApiClient>();
    // Load clients from some data store or provide a custom IApiClientManager implementation
    return new InMemoryApiClientManager(clients);
});
// Add FluentValidation and customize as needed
ValidatorOptions.Global.LanguageManager.Enabled = languageManagerEnabled;
ValidatorOptions.Global.PropertyNameResolver = (_, member, _) => member?.Name.ApplyNamingConvention(propertyNamingConvention);
builder.Services.AddValidatorsFromAssembly(myApiAssembly);
// Add mediation
builder.Services.AddMediation(
    true, // Register InitializationBehavior to set the current user and culture for every request
    true, // Register LoggingBehavior to log information for every request and its result
    myApiAssembly // Register queries and commands from this assembly
    // Register queries and commands from others assemblies
    );
// Add other services
// Add Problem Details and customize as needed
builder.Services.AddProblemDetails(options =>
    {
        var isProduction = builder.Environment.IsProduction();
        options.IncludeExceptionDetails = (context, exception) => !isProduction;
        options.ShouldLogUnhandledException = (context, exception, problemDetails) => true;
        
        // Map different exception classes to specific HTTP status codes
        options.Map<SomeException>(exception => exception.Map(StatusCodes.Status500InternalServerError, !isProduction));
    });
// If not in production environment, add Swagger documentation with request examples from the executing assembly
if (!builder.Environment.IsProduction())
    builder.AddDocumentation(myApiAssembly);
//////////////////////////////////////////////////////////////////////
// Configure the HTTP request pipeline and run the application
//////////////////////////////////////////////////////////////////////
var app = builder.Build();
app.UseProblemDetails();
if (!app.Environment.IsDevelopment())
    app.UseHttpsRedirection();
if (!app.Environment.IsProduction())
{
    app.UseSwagger();
    app.UseSwaggerUI();
}
app
    .UseApiVersioning()
    .UseSerilogRequestLogging(options =>
    {
        // Customize options if needed
    });
    
app.UseCors();
// Add a reference to Flowsy.Web.Localization to add localization support
// app.UseLocalization();
app.UseAuthentication();
app.UseRouting();
app.MapControllers();
// Add custom middleware
// app
    // .UseMiddleware<SomeMiddleware>()
    // .Use((context, next) =>
    // {
    //     // Perform some task
    //     return next(context);
    // });
app.UseAuthorization();   
app.Run();
Controllers
This package provides the MediationController class to offer built-in validation and mediation functionallity based on FluentValidation.AspNetCore and Flowsy.Mediation.
Our goal is to put the application logic out of controllers, even in separate assemblies.
using System.Threading;
using Flowsy.Web.Api.Mediation;
using Flowsy.Web.Api.Security;
using Microsoft.AspNetCore.Mvc;
using My.Application.Commands;
[ApiController]
[Route("/api/[controller]")]
[AuthorizeApiClient("ClientId1", "ClientId2")] // Only for specific API clients with a valid API Key 
// [AuthorizeApiClient] // For any API client with a valid API Key 
public class CustomerController : MediationController
{
    // With manual validation and using the IMediator instance directly
    // The mediation result is and instance of the expected CreateCustomerCommandResult class 
    [HttpPost]
    public async Task<IActionResult> CreateAsync([FromBody] CreateCustomerCommand command, CancellationToken cancellationToken)
    {
        var validationResult = await ValidateAsync(command, cancellationToken);
        if (!validationResult.IsValid)
            return ValidationProblem(validationResult);
            
        var commandResult = await Mediator.Send(command, cancellationToken);
        return Ok(commandResult);
    }
    
    // With automatic validation if an instance of IValidator<UpdateCustomerCommand> is registered
    // The mediation result is and instance of the expected UpdateCustomerCommandResult class 
    [HttpPut("{customerId:int}")]
    public async Task<IActionResult> UpdateAsync(int customerId, [FromBody] UpdateCustomerCommand command, CancellationToken cancellationToken)
    {
        command.CustomerId = customerId; // Ensure the command is using the right customer ID
        var commandResult = await MediateAsync<UpdateCustomerCommand, UpdateCustomerCommandResult>(command, cancellationToken);
        return Ok(commandResult);
    }
    
    // With automatic validation if an instance of IValidator<DeleteCustomerCommand>
    // The mediation result is an instance of IActionResult 
    [HttpDelete("{customerId:int}")]
    public Task<IActionResult> DeleteAsync(int customerId, [FromBody] DeleteCustomerCommand command, CancellationToken cancellationToken)
    {
        command.CustomerId = customerId; // Ensure the command is using the right customer ID
        return MediateActionResultAsync<DeleteCustomerCommand, DeleteCustomerCommandResult>(command, cancellationToken);
    }
}
Forms
using System.Threading;
using Flowsy.Web.Api.Forms;
using Microsoft.AspNetCore.Mvc;
[ApiController]
[Route("/api/[controller]")]
public class ExampleController : ControllerBase // Or MediationController
{
    private readonly IMultipartHandler _multipartHandler;
    
    public ExampleController(IMultipartHandler multipartHandler)
    {
        _multipartHandler = multipartHandler;
    }
    [HttpPost]
    public async Task<IActionResult> Post(CancellationToken cancellationToken)
    {
        // The MultipartContent instance will be disposed after return
        await using var multpartContent = await _multipartHandler.GetContentAsync(Request, cancellationToken);
        
        // Loop through request fields
        foreach (var (key, values) in multpartContent.Data)
        {
            // Process each field
            foreach (var value in values)
            {
                // Process each field value
            }
        }
        // Loop through request files
        foreach (var (key, multipartFile) in multpartContent.Files)
        {
            // Process each multipart file
        }
        
        // Deserialize fields expected to be in JSON format
        var myObject = multpartContent.DeserializeJsonField<MyClass>("fieldName");
        
        return Ok(/* Some result */);
    }
}
Data Streaming
1. Prepare File Buffering Options
// Program.cs
// using ...
using Flowsy.Web.Api.Streaming;
// using ...
var builder = WebApplication.CreateBuilder(args);
// Add services
builder.Services.Configure<FileBufferingOptions>(options =>
    {
        // Configure options:
        // MemoryThreshold
        // BufferLimit
        // TempFileDirectory
        // TempFileDirectoryAccessor
        // BytePool
    });
var app = builder.Build();
// Use services
app.Run();
2. Read or Write Streams Using a Streaming Provider
// FileUploader.cs
// using ...
using Flowsy.Web.Api.Streaming;
// using ...
public class FileUploader
{
    private readonly IStreamingProvider _streamingProvider;
    
    public FileUploader(IStreamingProvider streamingProvider)
    {
        _streamingProvider = streamingProvider;
    }
    
    public void UploadLargeFile(Stream inputStream)
    {
        using var bufferingStream = _streamingProvider.CreateFileBufferingReadStream(inputStream);
        // Read content using bufferingStream 
        // Make decisions based on the content
        bufferingStream.Seek(0, SeekOrigin.Begin); // Rewind
        // Read content again and store it somewhere
    }
}
Security Extensions
using System.Threading;
using Flowsy.Web.Api.Forms;
using Microsoft.AspNetCore.Mvc;
[ApiController]
[Route("/api/[controller]")]
public class ExampleController : ControllerBase // Or MediationController
{
    [HttpPost]
    public IActionResult Post()
    {
        // Obtain the client identifier and API Key from a header named X-{ClientId}-ApiKey
        HttpContext.GetApiKey(out var clientId, out var apiKey);
        // Do something with clientId and apiKey
        
        // Obtain value from a header named Athorization without the 'Bearer ' prefix
        var authorization = HttpContext.GetHeaderValue("Authorization", "Bearer ");
        // Do something with authorization
        
        return Ok(/* Some result */);
    }
}
| Product | Versions Compatible and additional computed target framework versions. | 
|---|---|
| .NET | net6.0 is compatible. net6.0-android was computed. net6.0-ios was computed. net6.0-maccatalyst was computed. net6.0-macos was computed. net6.0-tvos was computed. net6.0-windows was computed. net7.0 was computed. net7.0-android was computed. net7.0-ios was computed. net7.0-maccatalyst was computed. net7.0-macos was computed. net7.0-tvos was computed. net7.0-windows was computed. net8.0 was computed. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. net9.0 was computed. net9.0-android was computed. net9.0-browser was computed. net9.0-ios was computed. net9.0-maccatalyst was computed. net9.0-macos was computed. net9.0-tvos was computed. net9.0-windows was computed. net10.0 was computed. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. | 
- 
                                                    net6.0- Flowsy.Content (>= 2.1.1)
- Flowsy.Core (>= 1.1.3)
- Flowsy.Localization (>= 2.0.1)
- Flowsy.Mediation (>= 3.0.1)
- FluentValidation.AspNetCore (>= 11.2.2)
- Hellang.Middleware.ProblemDetails (>= 6.5.1)
- Microsoft.AspNetCore.Mvc.Versioning (>= 5.0.0)
- Serilog.AspNetCore (>= 6.0.1)
- Swashbuckle.AspNetCore (>= 6.4.0)
- Swashbuckle.AspNetCore.Filters (>= 7.0.5)
- Swashbuckle.AspNetCore.Swagger (>= 6.4.0)
- Swashbuckle.AspNetCore.SwaggerGen (>= 6.4.0)
- Swashbuckle.AspNetCore.SwaggerUI (>= 6.4.0)
 
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated | 
|---|---|---|
| 12.2.1 | 302 | 11/28/2023 | 
| 12.2.0 | 147 | 11/27/2023 | 
| 12.1.7 | 239 | 10/5/2023 | 
| 12.1.6 | 170 | 10/5/2023 | 
| 12.1.5 | 178 | 10/4/2023 | 
| 12.1.4 | 181 | 10/4/2023 | 
| 12.1.3 | 165 | 10/4/2023 | 
| 12.1.2 | 189 | 9/27/2023 | 
| 12.1.1 | 194 | 9/15/2023 | 
| 12.1.0 | 188 | 9/15/2023 | 
| 12.0.0 | 238 | 8/29/2023 | 
| 11.1.0 | 227 | 8/29/2023 | 
| 11.0.1 | 209 | 8/28/2023 | 
| 11.0.0 | 198 | 8/28/2023 | 
| 10.1.9 | 364 | 6/2/2023 | 
| 10.1.8 | 234 | 5/24/2023 | 
| 10.1.7 | 261 | 5/8/2023 | 
| 10.1.6 | 315 | 3/25/2023 | 
| 10.1.5 | 401 | 3/10/2023 | 
| 10.1.4 | 296 | 3/10/2023 | 
| 10.1.3 | 300 | 3/10/2023 | 
| 10.1.2 | 287 | 3/9/2023 | 
| 10.1.1 | 318 | 3/9/2023 | 
| 10.1.0 | 310 | 3/9/2023 | 
| 10.0.1 | 312 | 3/9/2023 | 
| 10.0.0 | 312 | 3/9/2023 | 
| 9.1.1 | 355 | 2/27/2023 | 
| 9.1.0 | 352 | 2/24/2023 | 
| 9.0.0 | 346 | 2/24/2023 | 
| 8.0.1 | 360 | 2/22/2023 | 
| 8.0.0 | 351 | 2/21/2023 | 
| 7.1.2 | 370 | 2/21/2023 | 
| 7.1.1 | 334 | 2/21/2023 | 
| 7.1.0 | 335 | 2/21/2023 | 
| 7.0.10 | 334 | 2/21/2023 | 
| 7.0.9 | 325 | 2/21/2023 | 
| 7.0.8 | 345 | 2/8/2023 | 
| 7.0.7 | 374 | 2/8/2023 | 
| 7.0.6 | 397 | 1/15/2023 | 
| 7.0.5 | 492 | 12/8/2022 | 
| 7.0.4 | 405 | 12/4/2022 | 
| 7.0.3 | 410 | 12/4/2022 | 
| 7.0.2 | 419 | 12/4/2022 | 
| 7.0.1 | 412 | 11/20/2022 | 
| 7.0.0 | 437 | 11/17/2022 | 
| 6.0.0 | 458 | 11/10/2022 | 
| 5.0.1 | 447 | 11/8/2022 | 
| 5.0.0 | 430 | 11/7/2022 | 
| 4.0.0 | 444 | 11/7/2022 | 
| 3.1.1 | 473 | 11/6/2022 | 
| 3.1.0 | 459 | 11/6/2022 | 
| 3.0.2 | 471 | 11/6/2022 | 
| 3.0.1 | 455 | 11/6/2022 | 
| 3.0.0 | 446 | 11/6/2022 | 
| 2.0.2 | 481 | 11/4/2022 | 
| 2.0.1 | 459 | 11/3/2022 | 
| 2.0.0 | 441 | 11/3/2022 | 
| 1.0.1 | 463 | 11/3/2022 | 
| 1.0.0 | 453 | 11/3/2022 |