Flowsy.Web.Api
10.1.2
See the version list below for details.
dotnet add package Flowsy.Web.Api --version 10.1.2
NuGet\Install-Package Flowsy.Web.Api -Version 10.1.2
<PackageReference Include="Flowsy.Web.Api" Version="10.1.2" />
paket add Flowsy.Web.Api --version 10.1.2
#r "nuget: Flowsy.Web.Api, 10.1.2"
// Install Flowsy.Web.Api as a Cake Addin #addin nuget:?package=Flowsy.Web.Api&version=10.1.2 // Install Flowsy.Web.Api as a Cake Tool #tool nuget:?package=Flowsy.Web.Api&version=10.1.2
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
- Request Validation
- Mediator Pattern for Controllers
- Data Streaming
- 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
// You must call AddRequestValidation on MediationBuilder when adding mediation to the application services.
[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 Mediator.Send(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 MediateAsync<DeleteCustomerCommand, DeleteCustomerCommandResult>(command, cancellationToken);
}
}
Data Streaming
1. Configure File Buffering Options and Register a Buffering Provider
// Program.cs
// using ...
using Flowsy.Web.Api.Streaming.Buffering;
// using ...
var builder = WebApplication.CreateBuilder(args);
// Add services
builder.Services.Configure<FileBufferingOptions>(options =>
{
// Configure options:
// MemoryThreshold
// BufferLimit
// TempFileDirectory
// TempFileDirectoryAccessor
// BytePool
});
builder.Services.AddSingleton<IBufferingProvider, BufferingProvider>()
var app = builder.Build();
// Use services
app.Run();
2. Read or Write Streams Using a Buffering Provider
// FileUploader.cs
// using ...
using Flowsy.Web.Api.Streaming.Buffering;
// using ...
public class FileUploader
{
private readonly IBufferingProvider _bufferingProvider;
public FileUploader(IBufferingProvider bufferingProvider)
{
_bufferingProvider = bufferingProvider;
}
public void UploadLargeFile(Stream inputStream)
{
using var bufferingStream = _bufferingProvider.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
}
}
Multipart Content
The following example shows how to read data from a multipart request. If an instance of IBufferingProvider is registered, the MultipartHandler service will use it to buffer content while reading request body sections.
using System.Threading;
using Flowsy.Web.Api.Streaming.Multipart;
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 | 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. |
-
net6.0
- Flowsy.Content (>= 3.2.1)
- Flowsy.Core (>= 1.1.7)
- Flowsy.Localization (>= 2.0.1)
- Flowsy.Mediation (>= 6.0.3)
- FluentValidation.AspNetCore (>= 11.2.2)
- Hashids.net (>= 1.6.1)
- Hellang.Middleware.ProblemDetails (>= 6.5.1)
- Microsoft.AspNetCore.Mvc.Versioning (>= 5.0.0)
- MimeTypesMap (>= 1.0.8)
- Serilog.AspNetCore (>= 6.1.0)
- Swashbuckle.AspNetCore (>= 6.5.0)
- Swashbuckle.AspNetCore.Filters (>= 7.0.6)
- Swashbuckle.AspNetCore.Swagger (>= 6.5.0)
- Swashbuckle.AspNetCore.SwaggerGen (>= 6.5.0)
- Swashbuckle.AspNetCore.SwaggerUI (>= 6.5.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 | 242 | 11/28/2023 |
12.2.0 | 104 | 11/27/2023 |
12.1.7 | 172 | 10/5/2023 |
12.1.6 | 113 | 10/5/2023 |
12.1.5 | 123 | 10/4/2023 |
12.1.4 | 130 | 10/4/2023 |
12.1.3 | 114 | 10/4/2023 |
12.1.2 | 136 | 9/27/2023 |
12.1.1 | 144 | 9/15/2023 |
12.1.0 | 130 | 9/15/2023 |
12.0.0 | 170 | 8/29/2023 |
11.1.0 | 159 | 8/29/2023 |
11.0.1 | 138 | 8/28/2023 |
11.0.0 | 126 | 8/28/2023 |
10.1.9 | 272 | 6/2/2023 |
10.1.8 | 152 | 5/24/2023 |
10.1.7 | 174 | 5/8/2023 |
10.1.6 | 229 | 3/25/2023 |
10.1.5 | 305 | 3/10/2023 |
10.1.4 | 215 | 3/10/2023 |
10.1.3 | 208 | 3/10/2023 |
10.1.2 | 194 | 3/9/2023 |
10.1.1 | 224 | 3/9/2023 |
10.1.0 | 214 | 3/9/2023 |
10.0.1 | 227 | 3/9/2023 |
10.0.0 | 221 | 3/9/2023 |
9.1.1 | 268 | 2/27/2023 |
9.1.0 | 254 | 2/24/2023 |
9.0.0 | 251 | 2/24/2023 |
8.0.1 | 264 | 2/22/2023 |
8.0.0 | 251 | 2/21/2023 |
7.1.2 | 269 | 2/21/2023 |
7.1.1 | 235 | 2/21/2023 |
7.1.0 | 239 | 2/21/2023 |
7.0.10 | 244 | 2/21/2023 |
7.0.9 | 232 | 2/21/2023 |
7.0.8 | 260 | 2/8/2023 |
7.0.7 | 270 | 2/8/2023 |
7.0.6 | 302 | 1/15/2023 |
7.0.5 | 392 | 12/8/2022 |
7.0.4 | 310 | 12/4/2022 |
7.0.3 | 310 | 12/4/2022 |
7.0.2 | 324 | 12/4/2022 |
7.0.1 | 324 | 11/20/2022 |
7.0.0 | 335 | 11/17/2022 |
6.0.0 | 359 | 11/10/2022 |
5.0.1 | 342 | 11/8/2022 |
5.0.0 | 332 | 11/7/2022 |
4.0.0 | 347 | 11/7/2022 |
3.1.1 | 367 | 11/6/2022 |
3.1.0 | 362 | 11/6/2022 |
3.0.2 | 367 | 11/6/2022 |
3.0.1 | 356 | 11/6/2022 |
3.0.0 | 344 | 11/6/2022 |
2.0.2 | 378 | 11/4/2022 |
2.0.1 | 351 | 11/3/2022 |
2.0.0 | 344 | 11/3/2022 |
1.0.1 | 367 | 11/3/2022 |
1.0.0 | 354 | 11/3/2022 |