Tooark.AspNetCore 4.7.0

dotnet add package Tooark.AspNetCore --version 4.7.0
                    
NuGet\Install-Package Tooark.AspNetCore -Version 4.7.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="Tooark.AspNetCore" Version="4.7.0" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="Tooark.AspNetCore" Version="4.7.0" />
                    
Directory.Packages.props
<PackageReference Include="Tooark.AspNetCore" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add Tooark.AspNetCore --version 4.7.0
                    
#r "nuget: Tooark.AspNetCore, 4.7.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.
#:package Tooark.AspNetCore@4.7.0
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=Tooark.AspNetCore&version=4.7.0
                    
Install as a Cake Addin
#tool nuget:?package=Tooark.AspNetCore&version=4.7.0
                    
Install as a Cake Tool

Tooark.AspNetCore

Library that concentrates the ASP.NET Core specific pieces of Tooark, keeping the general-purpose packages free of the runtime requirement. Packages whose purpose is to integrate with ASP.NET Core (Tooark.Dtos, Tooark.Observability, Tooark.Securities.OpenId) declare the shared framework themselves.

🌍 Languages: πŸ‡ΊπŸ‡Έ English (this file) Β· πŸ‡§πŸ‡· PortuguΓͺs


πŸ“‘ Contents


πŸ“– Overview

The Tooark.AspNetCore package provides:

  • extensions for ASP.NET Core types, today the ModelStateDictionary;
  • the ResponseDto body for validation failures in [ApiController] controllers, with AddTooarkModelStateEnvelope;
  • MVC validation of the Tooark.Attributes attributes without the duplicated missing-field message, with AddTooarkValidationAttributes;
  • the place where the ASP.NET Core requirement was moved to, taking it out of the general-purpose packages;
  • integration with the Tooark.Extensions localizer, translating the validation error keys;
  • the intended home for the family's filters and middlewares.

Why the package exists. Microsoft.AspNetCore.App is not an ordinary NuGet dependency: declaring it makes the application require the ASP.NET Core runtime to be installed, and that requirement is contagious β€” it propagates to every package that references it, and to whoever references those.

Until v3 the requirement came from Tooark.Extensions, because of a single file. In practice, a worker service or a command-line tool that used Tooark.ValueObjects got Microsoft.AspNetCore.App in its own runtimeconfig.json and would not start on a mcr.microsoft.com/dotnet/runtime image.

From v4 on the requirement lives here. Web projects reference this package; the others do not pay for it.

Package Requires the ASP.NET Core runtime
Tooark.AspNetCore yes β€” it is its purpose
Tooark.Dtos yes β€” SearchDto, PaginationDto and ResponseDto use MVC and Http types
Tooark (aggregator) yes β€” by referencing the two above
Tooark.Extensions, Tooark.Utils, Tooark.ValueObjects, Tooark.Entities, Tooark.Attributes and the rest no

πŸ”§ Installation

dotnet add package Tooark.AspNetCore

GetErrors is an extension method and works without configuration. The validation response and the attribute validation have a registration, and both are opt-in: neither the package nor the AddTooarkService of the Tooark aggregator turns them on by itself. The response changes the body of every validation failure in the API, and the attribute validation changes the messages of a missing field.

using Tooark.AspNetCore.Injections;

builder.Services.AddControllers();
builder.Services.AddTooarkModelStateEnvelope();
builder.Services.AddTooarkValidationAttributes();

Runtime requirement: because it declares the shared framework, whoever consumes this package needs the ASP.NET Core runtime installed. In a Docker image, use mcr.microsoft.com/dotnet/aspnet instead of mcr.microsoft.com/dotnet/runtime. A web application already uses that image, so in practice nothing changes.


πŸ“¦ Components

ModelState extensions

  • ModelStateExtension.GetErrors(ModelStateDictionary): returns the ModelState error messages.

Walks every entry of the ModelStateDictionary and gathers the ErrorMessage of each error into a single list. A field with more than one error contributes all of its messages, together and in the order they were recorded. Between fields, the order is the ModelStateDictionary's: it comes from the keys, not from the order in which the fields were validated, but it is not alphabetical either β€” today, top-level fields come before nested ones and shorter keys before longer ones (Cpf before Email). Do not rely on the position of an error in the list.

An error without text β€” recorded with just an exception, or with a blank message β€” becomes the key Field.Invalid;{key}, or BadRequest when it is not tied to a field. ASP.NET Core records errors with just an exception when the exception message is not safe for the client β€” a malformed JSON body with AllowInputFormatterExceptionMessages = false, for instance β€” and when MaxModelValidationErrors is reached. The exception message is never used. Up to v4.5.0 these errors came back with their empty or blank text.

Validation response

  • TooarkDependencyInjection.AddTooarkModelStateEnvelope(IServiceCollection): makes a validation failure in an [ApiController] controller answer with a ResponseDto<object>, instead of ASP.NET Core's ValidationProblemDetails.

In a controller with [ApiController], ASP.NET Core checks the ModelState before the action runs and, when it is invalid, answers 400 with the body built by ApiBehaviorOptions.InvalidModelStateResponseFactory. The method replaces that factory with one that answers 400 with a ResponseDto<object>: Data null and Errors with the messages of GetErrors, already translated by the ResponseDto itself. Keys become text, and the model binding messages arrive unchanged.

What changes for the client:

ValidationProblemDetails (ASP.NET Core default) ResponseDto<object> (with the envelope)
Content type application/problem+json application/json
Errors per field: {"Email": ["..."]}, not translated a single list: ["..."], translated
Other fields type, title, status, traceId data, pagination, metadata

Registration order does not matter. AddControllers sets the default factory while the options are being built, and would win if it came after a plain Configure. The envelope is applied after every configuration (PostConfigure), so it wins whether it is registered before or after AddControllers. For the same reason, a factory of the application's own set with ConfigureApiBehaviorOptions is replaced: an application that needs its own factory does not call this method. Calling it twice is harmless.

Where it does not apply. Only where ASP.NET Core calls the factory:

  • controllers without [ApiController], which have no automatic check: use GetErrors in the action or a filter;
  • SuppressModelStateInvalidFilter = true, which turns the automatic check off;
  • minimal APIs, which have no ModelState.

OpenAPI. The method changes the response, not the API description. A [ProducesResponseType(400)] without a type is still documented as ProblemDetails, the error type ASP.NET Core assumes for [ApiController]. To make the documentation match the response, declare the error type once in the project with the controllers:

using Microsoft.AspNetCore.Mvc;
using Tooark.Dtos;

[assembly: ProducesErrorResponseType(typeof(ResponseDto<object>))]

Tooark attributes in MVC validation

  • TooarkDependencyInjection.AddTooarkValidationAttributes(IServiceCollection): makes MVC stop inferring [Required] on members validated by a Tooark.Attributes attribute.

With <Nullable>enable</Nullable>, MVC treats every non-nullable reference type as if it had [Required], with the framework's message (The Email field is required.), which is not a translation key. The Tooark.Attributes attributes already report the missing value with the Field.Required key, so, without the registration, a missing field gets both messages. With the registration, only the attribute's key remains:

Missing member Without the registration With the registration
[EmailValidation] string Email The Email field is required. and Field.Required;Email Field.Required;Email
string Name, without a Tooark attribute The Name field is required. The Name field is required.
[Required][EmailValidation] string Email both messages both messages

The reach is that of the Tooark attributes. The inferred [Required] only leaves a member that has a Tooark attribute. A [Required] declared on the member stays, because it is the choice of whoever wrote the DTO, and members without a Tooark attribute keep the inference. That is the difference from SuppressImplicitRequiredAttributeForNonNullableReferenceTypes, which turns the inference off for every DTO in the application. The member is still flagged as required in the MVC metadata, which is true: the attribute rejects the missing value.

Registration order does not matter. The provider that infers [Required] enters the MVC options with AddControllers. The registration is applied after every configuration (PostConfigure), so it runs after that provider, whether called before or after AddControllers. Calling it twice does not repeat the registration.

Where it applies. In MVC validation: positional record, class and action parameter, in controllers with or without [ApiController] and with or without the envelope. Minimal APIs do not go through the MVC options.

Integration with the validations

The returned messages are what the Tooark.Attributes attributes produce: translation keys in the Key;Field format, such as Field.Required;Email. They become text through the IStringLocalizer of Tooark.Extensions, which resolves the language from the request's execution flow.

Errors generated by model binding itself β€” an integer receiving text, for instance β€” come with the framework's message, not with a key. The localizer returns that text unchanged and flags ResourceNotFound, which lets you tell the two cases apart when it matters.

A missing non-nullable field may carry both messages: the attribute's Field.Required key and the [Required] that MVC infers, with the framework's text. AddTooarkValidationAttributes keeps only the key: see Tooark attributes in MVC validation.

Filters and middlewares

Not shipped yet. The validation response is not a filter: ASP.NET Core already calls a factory at the point where it decides the body of a validation failure, and replacing that factory was enough. When filters and middlewares exist, they go in this package, under the Filters/ and Middlewares/ folders. The family only splits a package when the dependency profile differs β€” that is what motivated separating Tooark.Mediator.EntityFrameworkCore, which pulls Entity Framework Core, from Tooark.Mediator, which does not use it. Filters, middlewares and the extensions here share exactly the same profile, so splitting them into separate packages would reduce nothing.

Namespaces

The extensions live in Tooark.AspNetCore.Extensions, and the registrations in Tooark.AspNetCore.Injections.


πŸ“ Usage Examples

Standard response for validation failures

using Tooark.AspNetCore.Injections;

builder.Services.AddControllers();
builder.Services.AddTooarkModelStateEnvelope();

The DTO validated by the Tooark.Attributes attributes:

using Tooark.Attributes;

public sealed class CreatePersonDto
{
  [EmailValidation]
  public string Email { get; set; } = null!;

  [DocumentValidation("CPF")]
  public string Cpf { get; set; } = null!;
}
using Microsoft.AspNetCore.Mvc;

[ApiController]
[Route("people")]
public sealed class PersonController : ControllerBase
{
  [HttpPost]
  public IActionResult Create([FromBody] CreatePersonDto dto)
  {
    // Only reached with a valid ModelState: [ApiController] answers 400 before the action runs
    return Ok();
  }
}

POST /people with {"email": "x", "cpf": "123"}, in en-US:

{
  "data": null,
  "errors": ["Field Document is invalid", "Field Email is invalid"],
  "pagination": null,
  "metadata": []
}

A model binding failure β€” text in a numeric field, an empty body β€” comes in the same body, with the framework's message in errors.

The name that appears in the message is the attribute's, not the property's: that is why the Cpf field produces Field.Invalid;Document, the default of DocumentValidationAttribute. To align the two, pass the propertyName:

[DocumentValidation("CPF", propertyName: "Cpf")]
public string Cpf { get; set; } = null!;

Missing field without the framework's message

using Tooark.AspNetCore.Injections;

builder.Services.AddControllers();
builder.Services.AddTooarkModelStateEnvelope();
builder.Services.AddTooarkValidationAttributes();

A positional record, with the attribute on the constructor parameter:

using Tooark.Attributes;

public sealed record CreateContactDto([EmailValidation] string Email, string Name);

POST /contacts with {"name": "Ana"}, in en-US:

{
  "data": null,
  "errors": ["Field Email is required"],
  "pagination": null,
  "metadata": []
}

Without AddTooarkValidationAttributes, errors would also carry "The Email field is required.". With {"email": "ana@test.com"}, the missing Name answers "The Name field is required.": the field has no Tooark attribute, and the MVC inference still applies to it.

Reading the ModelState errors in the action

Without [ApiController], ASP.NET Core does not check the ModelState before the action, and the action does it. In a controller with the attribute this check never runs: the framework answers 400 first.

using Microsoft.AspNetCore.Mvc;
using Tooark.AspNetCore.Extensions;

[Route("people")]
public sealed class PersonController : ControllerBase
{
  [HttpPost]
  public IActionResult Create([FromBody] CreatePersonDto dto)
  {
    if (!ModelState.IsValid)
    {
      // ["Field.Invalid;Document", "Field.Invalid;Email"]
      var errors = ModelState.GetErrors();

      return BadRequest(errors);
    }

    return Ok();
  }
}

With translated messages

The keys become text through the IStringLocalizer, which comes from Tooark.Extensions and is registered there:

using Tooark.Extensions.Injections;

builder.Services.AddTooarkExtensions();
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Localization;
using Tooark.AspNetCore.Extensions;

[Route("people")]
public sealed class PersonController(IStringLocalizer localizer) : ControllerBase
{
  [HttpPost]
  public IActionResult Create([FromBody] CreatePersonDto dto)
  {
    if (!ModelState.IsValid)
    {
      // ["Field Document is invalid", "Field Email is invalid"]
      var errors = ModelState.GetErrors().Select(error => localizer[error].Value);

      return BadRequest(errors);
    }

    return Ok();
  }
}

A filter for controllers without [ApiController]

The same check in every endpoint calls for a filter. The package does not ship one, because in [ApiController] controllers the validation response already does the job. For controllers without the attribute it fits in a few lines, and with ResponseDto it answers with the same body:

using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;
using Tooark.AspNetCore.Extensions;
using Tooark.Dtos;

public sealed class ValidationFilter : IActionFilter
{
  public void OnActionExecuting(ActionExecutingContext context)
  {
    if (!context.ModelState.IsValid)
    {
      context.Result = new BadRequestObjectResult(new ResponseDto<object>(context.ModelState.GetErrors()));
    }
  }

  public void OnActionExecuted(ActionExecutedContext context) { }
}
builder.Services.AddControllers(options => options.Filters.Add<ValidationFilter>());

πŸ“‹ Dependencies

Package Version Description
Tooark.Attributes 4.x The attributes recognized by AddTooarkValidationAttributes
Tooark.Dtos 4.x ResponseDto, the body of the validation response
Tooark.Extensions 4.x Error message localization
Microsoft.AspNetCore.App (shared framework) 8.x/10.x ModelStateDictionary, ApiBehaviorOptions and MvcOptions

🀝 Contributing

Contributions are welcome! Start with CONTRIBUTING.md β€” it covers the development workflow, the coding and commit conventions and the pull request checklist. Bugs and feature requests go through the issue templates of the Tooark repository.

By participating you agree to the Code of Conduct.


πŸ†˜ Help & Security

  • ❓ Questions, bugs, feature ideas β€” see SUPPORT.md for the right channel
  • πŸ”’ Security vulnerabilities β€” do not open a public issue; follow SECURITY.md

πŸ’– Support

If Tooark helps your projects, consider supporting its development:

Every contribution helps keep the project maintained and improving. Thank you! πŸ™


πŸ“„ License

This project is licensed under the BSD 3-Clause License.

Product Compatible and additional computed target framework versions.
.NET net8.0 is compatible.  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 is compatible.  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. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages (1)

Showing the top 1 NuGet packages that depend on Tooark.AspNetCore:

Package Downloads
Tooark

Package with all Tooark resources for .NET applications.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
4.7.0 108 9/30/2026
4.6.0 138 9/29/2026
4.5.0 109 9/29/2026
4.4.0 107 9/28/2026
4.3.0 112 9/26/2026
4.2.1 109 9/25/2026
4.2.0 128 9/20/2026
4.1.0 107 9/18/2026
4.0.1 138 8/25/2026
4.0.0 129 8/25/2026