Flowsy.Web.Api 3.1.0

There is a newer version of this package available.
See the version list below for details.
dotnet add package Flowsy.Web.Api --version 3.1.0                
NuGet\Install-Package Flowsy.Web.Api -Version 3.1.0                
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="Flowsy.Web.Api" Version="3.1.0" />                
For projects that support PackageReference, copy this XML node into the project file to reference the package.
paket add Flowsy.Web.Api --version 3.1.0                
#r "nuget: Flowsy.Web.Api, 3.1.0"                
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
// Install Flowsy.Web.Api as a Cake Addin
#addin nuget:?package=Flowsy.Web.Api&version=3.1.0

// Install Flowsy.Web.Api as a Cake Tool
#tool nuget:?package=Flowsy.Web.Api&version=3.1.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.

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 */);
    }
}

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 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. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

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 243 11/28/2023
12.2.0 105 11/27/2023
12.1.7 174 10/5/2023
12.1.6 114 10/5/2023
12.1.5 124 10/4/2023
12.1.4 131 10/4/2023
12.1.3 115 10/4/2023
12.1.2 137 9/27/2023
12.1.1 145 9/15/2023
12.1.0 131 9/15/2023
12.0.0 171 8/29/2023
11.1.0 162 8/29/2023
11.0.1 139 8/28/2023
11.0.0 127 8/28/2023
10.1.9 273 6/2/2023
10.1.8 153 5/24/2023
10.1.7 175 5/8/2023
10.1.6 230 3/25/2023
10.1.5 306 3/10/2023
10.1.4 216 3/10/2023
10.1.3 209 3/10/2023
10.1.2 195 3/9/2023
10.1.1 225 3/9/2023
10.1.0 215 3/9/2023
10.0.1 228 3/9/2023
10.0.0 222 3/9/2023
9.1.1 269 2/27/2023
9.1.0 255 2/24/2023
9.0.0 252 2/24/2023
8.0.1 265 2/22/2023
8.0.0 252 2/21/2023
7.1.2 270 2/21/2023
7.1.1 236 2/21/2023
7.1.0 240 2/21/2023
7.0.10 245 2/21/2023
7.0.9 233 2/21/2023
7.0.8 261 2/8/2023
7.0.7 271 2/8/2023
7.0.6 303 1/15/2023
7.0.5 393 12/8/2022
7.0.4 311 12/4/2022
7.0.3 311 12/4/2022
7.0.2 325 12/4/2022
7.0.1 325 11/20/2022
7.0.0 338 11/17/2022
6.0.0 360 11/10/2022
5.0.1 343 11/8/2022
5.0.0 333 11/7/2022
4.0.0 348 11/7/2022
3.1.1 368 11/6/2022
3.1.0 363 11/6/2022
3.0.2 368 11/6/2022
3.0.1 357 11/6/2022
3.0.0 345 11/6/2022
2.0.2 381 11/4/2022
2.0.1 352 11/3/2022
2.0.0 345 11/3/2022
1.0.1 368 11/3/2022
1.0.0 355 11/3/2022