DotNetToolbox.Core
8.1.1
dotnet add package DotNetToolbox.Core --version 8.1.1
NuGet\Install-Package DotNetToolbox.Core -Version 8.1.1
<PackageReference Include="DotNetToolbox.Core" Version="8.1.1" />
paket add DotNetToolbox.Core --version 8.1.1
#r "nuget: DotNetToolbox.Core, 8.1.1"
// Install DotNetToolbox.Core as a Cake Addin #addin nuget:?package=DotNetToolbox.Core&version=8.1.1 // Install DotNetToolbox.Core as a Cake Tool #tool nuget:?package=DotNetToolbox.Core&version=8.1.1
Core (DotNetToolbox.Core)
Introduction
DotNetToolbox.Core is a versatile C# library for .NET 8, designed to enhance development by providing a range of utilities and patterns. It simplifies complex tasks and improves code testability.
Table of Contents
- Installation
- Dependencies
- Result Pattern
- System Utilities
- Pagination Utilities
- Singleton and Options Patterns
- Extension Classes
- Other Utilities
Installation
PM> Install-Package DotNetToolbox.Core
Dependencies
- .NET 8
Result Pattern
The exploration of the Result
class and its specialized forms, including CrudResult
, HttpResult
, and SignInResult
, underscores the pattern's versatility and utility in diverse application domains. By providing a structured approach to operation outcome management, these patterns enhance the clarity, maintainability, and reliability of software, facilitating robust error handling and streamlined communication of operation results.
ValidationError and ValidationErrors
The ValidationError
class encapsulates information about errors that occur during validation processes. It is designed as a sealed record for immutability and type safety, with a default error message indicating invalid values. This class supports initializing with custom messages and an optional source, enhancing error specificity and traceability.
var error = new ValidationError("Custom error message", "FieldName");
An implicit conversion from string to ValidationError
is provided, allowing for straightforward error creation:
ValidationError error = "This is an error message";
ValidationErrors
acts as a collection for ValidationError
objects, implementing standard list and collection interfaces for easy manipulation. It supports initializing from various sources, including single errors, arrays, lists, and hash sets, each ensuring uniqueness of error entries:
ValidationErrors errors = new ValidationError[] {
"Error 1",
new ValidationError("Error 2"),
"Error 3"
};
This collection facilitates aggregating and handling multiple validation errors in a coherent and structured manner, streamlining error management in validation scenarios:
if (errors.Any()) {
foreach (var err in errors) {
Console.WriteLine(err);
}
}
These utilities serve as foundational elements within the Result Pattern, enabling detailed and flexible error reporting in validation operations.
Result Pattern Implementation
The Result pattern is a robust framework feature designed to encapsulate the outcome of operations, offering a unified approach to error handling and validation. It provides a clear and consistent method for functions to return success or failure information, along with any relevant data or errors.
IResult and Result Usage
The IResult
interface is the foundation of the Result pattern, defining a contract for operation outcomes. Implementing classes can specify more details, such as success status, error messages, or returned data. The Result
class, deriving from ResultBase
, offers a concrete implementation that can be used directly in applications:
public Result DoSomething() {
try {
// Operation logic
return Result.Success();
} catch (Exception ex) {
return Result.Failure(ex.Message);
}
}
Integrating Validation with IValidatable
The IValidatable
interface allows for the integration of custom validation logic into models or data transfer objects, returning a Result
to indicate the validation outcome. This pattern ensures that validation logic can be encapsulated within the objects themselves, promoting a clean and maintainable codebase:
public class MyModel : IValidatable {
public string Property { get; set; }
public Result Validate() {
if (string.IsNullOrEmpty(Property)) {
return Result.Failure("Property cannot be null or empty.");
}
return Result.Success();
}
}
Handling Validation Errors
When validation fails, a ValidationException
can be thrown, leveraging the Result
pattern for consistent error handling across the application. This exception specifically addresses validation failures, allowing for targeted catch blocks and error processing:
try {
var model = new MyModel();
var result = model.Validate();
if (!result.IsSuccess) {
throw new ValidationException(result.Errors);
}
} catch (ValidationException ex) {
// Handle validation errors
}
Result Types and Error Management
The ResultType
enum and ResultBase
class support extending the Result pattern with custom outcomes and sophisticated error management strategies. Developers can define additional result types beyond the basic success and failure, accommodating complex business logic and operation outcomes:
public enum MyCustomResultType : int {
CustomSuccess = 1,
CustomFailure = -1
}
public class MyCustomResult : ResultBase {
// Custom implementation
}
CrudResult Pattern for CRUD Operations
The CrudResult
class extends the foundational Result
pattern specifically for CRUD (Create, Read, Update, Delete) operations, providing a clear and expressive way to communicate the outcomes of these common database or resource manipulation tasks.
Understanding CrudResult Types
At the heart of the CrudResult
design is the CrudResultType
enum, which categorizes the outcomes of CRUD operations into statuses like Success
, NotFound
, and Conflict
. This detailed classification aids in handling the varied results of CRUD operations with precision:
public enum CrudResultType {
Invalid, // The request validation failed.
NotFound, // The requested resource was not found.
Conflict, // A conflict has occured blocking the operation.
Success, // The operation was successful.
Error, // An internal error has occurred.
}
Using CrudResult in Operations
CrudResult
simplifies returning detailed operation outcomes. For instance, when updating a resource, CrudResult
can distinctly indicate whether the operation was successful, the target resource was not found, or a conflict occurred:
public CrudResult UpdateResource(Resource resource) {
if (!ResourceExists(resource.Id)) {
return CrudResult.NotFound();
}
if (ResourceConflictExists(resource)) {
return CrudResult.Conflict();
}
// Update logic here
return CrudResult.Success();
}
Leveraging Generic CrudResult
For operations that return a value, such as retrieving a resource, CrudResult<T>
comes into play, allowing the inclusion of the resource itself in the success case:
public CrudResult<Resource> GetResource(int id) {
var resource = FindResourceById(id);
if (resource == null) {
return CrudResult<Resource>.NotFound();
}
return CrudResult<Resource>.Success(resource);
}
Benefits of CrudResult
The CrudResult
class and its generic counterpart CrudResult<T>
provide a robust, type-safe way to handle the outcomes of CRUD operations, enhancing code clarity and maintainability. By clearly communicating operation results, developers can implement more reliable and understandable error handling and response logic in applications dealing with data manipulation.
HttpResult for Web API Responses
The HttpResult
class is a specialized adaptation of the Result pattern, tailored specifically for handling the outcomes of web API operations. It provides a seamless way to align the internal operation results with the standardized semantics of HTTP responses.
Overview of HttpResult
HttpResult
facilitates the expression of HTTP response statuses through its methods, which correspond to common HTTP status codes. This design allows developers to directly return HTTP responses from API operations, enhancing clarity and consistency:
public enum HttpResultType {
Ok = HttpStatusCode.OK, // 200
Created = HttpStatusCode.Created, // 201
BadRequest = HttpStatusCode.BadRequest, // 400
Unauthorized = HttpStatusCode.Unauthorized, // 401
NotFound = HttpStatusCode.NotFound, // 404
Conflict = HttpStatusCode.Conflict, // 409
Error = HttpStatusCode.InternalServerError, // 500
}
Utilizing HttpResult in APIs
HttpResult
can be employed to indicate the outcome of API operations succinctly, mapping directly to HTTP response codes. Here's how it can be used to return different responses based on operation results:
public HttpResult GetUser(int id) {
var user = UserRepository.FindById(id);
if (user == null) {
return HttpResult.NotFound("User not found.");
}
return HttpResult.Ok(user);
}
Including Data in Responses
One of the key features of HttpResult
is its ability to carry data along with the response status. This capability is particularly useful for returning entities or detailed error information in API responses:
public HttpResult CreateUser(UserDto newUser) {
var validationResult = ValidateNewUser(newUser);
if (!validationResult.IsSuccess) {
return HttpResult.BadRequest(validationResult.Errors);
}
var user = CreateUserFromDto(newUser);
return HttpResult.Created(user);
}
Advantages of Using HttpResult
Employing HttpResult
in web API development offers several benefits:
Consistency: Ensures that API responses are standardized across different operations, facilitating easier consumption and integration.
Type Safety: Reduces the risk of returning incorrect HTTP status codes, as responses are defined through explicit methods.
Simplicity: Simplifies error handling and response construction, allowing developers to focus on the core logic of API operations.
SignInResult: Designed for authentication processes.
SignInResult for Authentication Outcomes
The SignInResult
class is specifically tailored to encapsulate the outcomes of authentication processes, providing a clear and nuanced way to indicate the success or specific reasons for failure of sign-in attempts. This specialized result type ensures that authentication logic is both expressive and consistent.
public enum SignInResultType {
Error, // An internal error has occurred.
Invalid, // request validation failed. No attempt was made.
Failed, // attempt failed.
Blocked, // account is blocked. (8 | 4) Counts as Failed.
Locked, // account is locked. (16 | 4) Counts as Failed.
Success, // attempt succeeded.
ConfirmationPending, // attempt succeeded but email is not confirmed. (64 | 32).
TwoFactorRequired, // attempt succeeded, but requires 2-factor authentication. (128 | 32).
}
Example Usage of SignInResult
public SignInResult SignInUser(string username, string password) {
var user = FindUserByUsername(username);
if (user == null || !VerifyPassword(user, password)) {
return SignInResult.Failed();
}
if (user.IsLockedOut) {
return SignInResult.LockedOut();
}
// Additional sign-in logic here
return SignInResult.Success();
}
Advantages of SignInResult
- Clarity and Precision: Distinguishes between various outcomes of sign-in attempts, such as failure due to invalid credentials or account lockout, enhancing error handling and user feedback.
- Seamless Integration: Fits neatly within the broader Result pattern framework, maintaining a consistent approach to handling operation outcomes across the application.
- Customization and Extension: Allows for the introduction of additional
SignInResultType
values to cater to complex authentication scenarios, offering flexibility and adaptability.
System Utilities
System utilities offer essential abstractions over system resources such as Date and Time, GUIDs, File System, Assembly Information, and Console Input/Output. By enabling dependency injection, these abstractions support the creation of testable code, allowing the substitution of system classes with mock objects during testing.
- DateTimeProvider: Facilitates working with dates and times in a testable way by abstracting system-specific implementations.
Examples:
Injecting DateTimeProvider into a service for testability:
public class TimeSensitiveService { private readonly DateTimeProvider _dateTimeProvider; public TimeSensitiveService(DateTimeProvider dateTimeProvider) { _dateTimeProvider = dateTimeProvider; } public bool IsBusinessHour() { var currentHour = _dateTimeProvider.Now.Hour; return currentHour >= 9 && currentHour < 17; } }
Mocking DateTimeProvider for unit tests:
var fixedDateTime = Substitute.For<DateTimeProvider>(); fixedDateTime.Now.Returns(new DateTimeOffset(2023, 1, 1, 12, 0, 0, TimeSpan.Zero)); var service = new TimeSensitiveService(fixedDateTime); var result = service.IsBusinessHour(); result.Should().BeTrue(); // FluentAssertions used here
- GuidProvider: Allows generation of GUIDs that can be controlled in a testing environment.
Examples:
Using GuidProvider to generate GUIDs in a service:
public class IdentifierService { private readonly GuidProvider _guidProvider; public IdentifierService(GuidProvider guidProvider) { _guidProvider = guidProvider; } public Guid GetUniqueIdentifier() { return _guidProvider.New(); } }
Mocking GuidProvider in tests:
var expectedGuid = Guid.NewGuid(); var fixedGuidProvider = Substitute.For<GuidProvider>(); fixedGuidProvider.New().Returns(expectedGuid); var service = new IdentifierService(fixedGuidProvider); var id = service.GetUniqueIdentifier(); id.Should().Be(expectedGuid); // Asserting that the ID is indeed a GUID
- FileSystem: Provides an abstraction over file system operations, enabling better testing of file-related functionality.
Examples:
Injecting FileSystem into a component:
public class FileManager { private readonly FileSystem _fileSystem; public FileManager(FileSystem fileSystem) { _fileSystem = fileSystem; } public bool FileExists(string path) { return _fileSystem.FileExists(path); } }
Mocking FileSystem during testing:
var mockedFileSystem = Substitute.For<FileSystem>(); mockedFileSystem.FileExists("test.txt").Returns(true); var fileManager = new FileManager(mockedFileSystem); var exists = fileManager.FileExists("test.txt"); exists.Should().BeTrue(); // Validate the expected behavior
- Input and Output: Utilities for input and output operations, making console interactions testable.
Examples:
Using Output in a service to write messages:
public class ConsoleService { private readonly Input _input; private readonly Output _output; public ConsoleService(Input input, Output output) { _input = input; _output = output; } public string ReadUserInputOrEmpty() { return _input.ReadLine() ?? string.Empty; } public void DisplayMessage(string target) { _output.WriteLine($"Hello {target}!"); } }
Testing console input and output interactions:
var mockedOutput = Substitute.For<Output>(); var mockedInput = Substitute.For<Input>(); var consoleService = new ConsoleService(mockedInput, mockedOutput); mockedInput.ReadLine().Returns("Test") var target = consoleService.ReadUserInputOrEmpty(); var consoleService.DisplayMessage(target); outputMock.Received().WriteLine("Hello Test!"); // NSubstitute used to verify interaction
- Assembly Information (AssemblyAccessor and AssemblyDescriptor): These utilities provide detailed access to assembly metadata, supporting a wide range of operations from version checking to attribute inspection.
Here is the information available through the IAssemblyDescriptor
interface:
- Name: The short name of the assembly.
- FullName: The full name of the assembly, including the version, culture, and public key token.
- Version: The version of the assembly.
- RuntimeVersion: The runtime version of the assembly.
- CultureInfo: The culture information of the assembly.
- PublicKey: The public key of the assembly.
- PublicKeyToken: The public key token of the assembly.
- Location: The location of the assembly file.
- EntryPoint: The entry point of the assembly.
- ManifestModule: The manifest module of the assembly.
- GetCustomAttributes() : Returns an array of all custom attributes applied to the assembly.
- GetCustomAttributes(Type attributeType) : Returns an array of custom attributes applied to the assembly and identified by the specified type.
- GetCustomAttributes<TAttribute>(): Returns an array of custom attributes applied to the assembly and identified by the specified type.
- GetCustomAttribute<TAttribute>(): Returns the custom attribute applied to the assembly and identified by the specified type.
- GetCustomAttribute(Type attributeType): Returns the custom attribute applied to the assembly and identified by the specified type.
- DefinedTypes: Returns a collection of types defined in the assembly.
- ExportedTypes: Returns a collection of types that are visible outside the assembly.
- Modules: Returns a collection of modules in the assembly.
Examples:
- Retrieving Assembly Version:
public class VersionService { private readonly IAssemblyAccessor _accessor; public VersionService(IAssemblyAccessor accessor) { _accessor = accessor; } public string GetCurrentAssemblyVersion() { return _accessor.GetExecutingAssembly().Version.ToString(); } }
- Environment Abstraction (Environment): Offers a unified interface to access various system utilities, facilitating easy mocking and dependency management.
Examples:
- Accessing System Utilities through Environment:
public class ApplicationService { private readonly IEnvironment _environment; public ApplicationService(IEnvironment environment) { _environment = environment; } public void PerformOperation() { var assemblyVersion = _environment.Assembly.Version.ToString(); var currentTime = _environment.DateTime.Now; // Use other system utilities as needed } }
Pagination Utilities
Pagination utilities provide a standardized way to handle the slicing of large datasets into smaller, more manageable blocks or pages. These utilities can be used to define and access specific segments of data, often necessary when implementing APIs that support pagination.
IBlock / Block: Interfaces and classes that represent a contiguous block of items with a specific size and an offset that indicates the starting point or key item.
IPage / Page: Extends the concept of a block to include pagination-specific information, such as the total count of items, allowing for the calculation of the total number of pages.
BlockSettings: Provides constants related to pagination, such as default block size and maximum allowable values.
Examples:
Creating a Block of items:
var items = new List<string> { "Item1", "Item2", "Item3" }; var block = new Block<string>(items, "Item1"); // block now contains a block of items starting from "Item1"
Creating a Page with total count information:
var items = new List<string> { "Item1", "Item2", "Item3" }; var page = new Page<string>(items, 0, BlockSettings.DefaultBlockSize, 100); // page now contains a page of items with information about the total count of items
Singleton and Options Patterns
Pattern utilities offer standardized ways to define and access special instances of classes, such as singleton, default, or empty instances. These utilities are designed to implement common design patterns in a consistent manner across your applications.
IHasInstance / HasInstance: Allows for defining and accessing a singleton instance of a class.
IHasDefault / HasDefault: Provides a way to access a default instance of a class.
IHasEmpty / HasEmpty: Enables defining an 'empty' instance, often representing the absence of a value.
INamedOptions / NamedOptions: Used for options classes with a standard way to define the section name used for configuration binding.
Examples:
Accessing a singleton instance:
public class SingletonService : HasInstance<SingletonService> { // Service implementation } var instance = SingletonService.Instance;
Defining default options:
public class ServiceOptions : NamedOptions<ServiceOptions> { public string ConnectionString { get; set; } = "DefaultConnectionString"; } var defaultOptions = ServiceOptions.Default;
Here is an example of the options class being used in a configuration binding scenario:
// In appsettings.json { // ... "Service": { "ConnectionString": "SomeConnectionString" } // ... }
public class Service { private readonly ServiceOptions _options; public Service(IOptions<ServiceOptions> options) { _options = options.Value; } public string GetConnectionString() { return _options.ConnectionString; } } // In Startup.cs services.Configure<ServiceOptions>(Configuration.GetSection("Service"));
Using an empty instance:
public class CommandResult : HasEmpty<CommandResult> { public bool Success { get; set; } } var noResult = CommandResult.Empty;
Extension Classes
Extension classes provide additional methods to existing types, enhancing their capabilities and simplifying common operations.
- Enumerable Extensions: Extends
IEnumerable<T>
to provide additional transformation and collection generation methods.
Examples:
Transforming IEnumerable to Array with custom projection:
IEnumerable<int> numbers = new[] { 1, 2, 3, 4 }; int[] squaredNumbers = numbers.ToArray(x => x * x); // squaredNumbers would be [1, 4, 9, 16]
Creating a Dictionary from another IDictionary with a value transformation:
IDictionary<string, int>> original = new Dictionary<string, int> { ["one"] = 1, ["two"] = 2 }; var newDictionary = original.ToDictionary(v => v * 10); // Multiplies each value by 10 // newDictionary would have values [10, 20]
- Queryable Extensions: Provides projection and conversion extensions for
IQueryable<T>
.
Examples:
Projecting IQueryable to Array:
IQueryable<Person> people = GetPeopleQuery(); PersonDto[] dtos = people.ToArray(person => new PersonDto(person.Name, person.Age)); // dtos contains PersonDto objects projected from the Person source
Transforming IQueryable into HashSet with projection:
IQueryable<string> names = GetNamesQuery(); HashSet<string> upperCaseNames = names.ToHashSet(name => name.ToUpper()); // upperCaseNames contains upper case versions of the names from the source
- Task Extensions: Adds methods to
Task
andValueTask
to handle fire-and-forget scenarios with optional exception handling callbacks.
Examples:
Using FireAndForget with a Task:
Task someTask = DoWorkAsync(); someTask.FireAndForget(); // No need to await, exceptions will be ignored
Handling exceptions with FireAndForget:
async Task DoWorkAsync() { // Simulate work await Task.Delay(1000); throw new InvalidOperationException("Something went wrong"); } Task anotherTask = DoWorkAsync(); anotherTask.FireAndForget( onException: ex => LogError(ex) // Log error if exception occurs );
Other Utilities
Other utilities provide additional functionalities and helpers to extend existing concepts, making them more convenient and robust for everyday use.
- Ensure: Offers a variety of methods to ensure arguments meet certain conditions, throwing exceptions if they do not. This utility is very useful for validating method inputs and maintaining contracts within the code.
You can add the
Ensure
class as a static using to simplify the call.
Examples:
Using Ensure to validate arguments:
public void ProcessData(string input) { input = Ensure.IsNotNullOrWhiteSpace(input); // Rest of the method logic }
Using Ensure as a static using:
using static DotNetToolbox.Ensure; // class dedinition and other code public void ProcessList(IList<string> items) { items = HasNoNullOrWhiteSpace(items); // Rest of the method logic }
- CreateInstance: Provides methods to dynamically create instances of types, which can be very helpful for creating types with non-public constructors or when types need to be created based on runtime data.
Examples:
Creating an instance of a class with private constructor:
var instance1 = CreateInstance.Of<SomeClassWithPrivateConstructor>(); var instance2 = CreateInstance.Of<SomeClassWithArguments>(arg1, arg2);
Creating an instance using dependency injection:
public class SomeClassWithArgumentsAndServices { public SomeClassWithArgumentsAndServices(string arg1, string arg2, ILoggerFactory loggerFactory) { // ... } } // ... var serviceProvider = // ... build your service provider var instance = CreateInstance.Of<SomeClassWithArgumentsAndServices>(serviceProvider, arg1, arg2); // the injected services do not need to be added.
- UrlSafeBase64String: A record struct that represents a base64 encoded string safe for URL use. It can be used to encode and decode bytes, strings, and GUIDs to a URL-safe base64 format.
Examples:
Encoding and decoding a URL-safe base64 string:
byte[] original = { 0x1, 0x2, 0x3 }; UrlSafeBase64String encoded = original; byte[] decoded = encoded; encoded.Should().BeOfType<string>(); // Outputs URL-safe base64 string decoded.Should().BeEquivalentTo(original); // Outputs True
Working with GUIDs and base64 strings:
Guid original = Guid.NewGuid(); UrlSafeBase64String encoded = original; Guid decoded = encoded; encoded.Should().BeOfType<string>(); // Outputs URL-safe base64 string decoded.Should().Be(original); // Outputs True
- IValidatable: An interface that classes can implement to provide custom validation logic, returning a
Result
that indicates whether the validation was successful or not.
Examples:
Implementing IValidatable in a class:
using static DotNetToolbox.Result; public class Person : IValidatable { public string? Name { get; set; } public int Age { get; set; } public Result Validate(IDictionary<string, object?>? context = null) { var result = Success(); if (string.IsNullOrWhiteSpace(Name)) { result += InvalidData(nameof(Name), "Name cannot be empty or whitespace."); } if (Age < 0 || Age > 120) { result += InvalidData(nameof(Age), "Age must be between 0 and 120."); } return result; // may return multiple errors } }
Using Ensure to validate an IValidatable object:
using static DotNetToolbox.Ensure; public void ProcessPerson(Person person) { person = IsValid(person); // throws ValidationException if validation fails. // Rest of the method logic }
- DisposableStateHolder: A utility designed to hold and manage the lifecycle of a disposable state. It ensures that if the state is disposable, it is properly disposed of, simplifying resource management and cleanup.
Examples:
- Using DisposableStateHolder with Disposable State:
var disposableResource = new MemoryStream(); using var stateHolder = new DisposableStateHolder(disposableResource); // The MemoryStream will be disposed of when stateHolder is disposed.
- Indexed: This struct pairs elements with their indices, enhancing the handling of sequences by keeping track of each element's position. It's invaluable for operations requiring index information alongside elements.
Examples:
- Enumerating Items with Indices:
var items = new[] { "apple", "banana", "cherry" }; var indexedItems = items.Select((value, index) => new Indexed<string>(index, value)); foreach (var item in indexedItems) { Console.WriteLine($"Item {item.Index}: {item.Value}"); }
Product | Versions 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. |
-
net8.0
- Microsoft.Extensions.Configuration.Abstractions (>= 8.0.0)
- Microsoft.Extensions.Configuration.Binder (>= 8.0.2)
- Microsoft.Extensions.DependencyInjection.Abstractions (>= 8.0.1)
- Microsoft.Extensions.Logging.Abstractions (>= 8.0.1)
- NUlid (>= 1.7.2)
NuGet packages (8)
Showing the top 5 NuGet packages that depend on DotNetToolbox.Core:
Package | Downloads |
---|---|
DotNetToolbox.Console
A library with powerful tools to simplify the creation of CLI (Command-Line Interface) and Shell-like console applications. |
|
DotNetToolbox.Http
DotNetToolbox.Http: A comprehensive utility library for handling http connections and more. |
|
DotNetToolbox.Azure
DotNetToolbox.Azure: A comprehensive utility library for handling Azure assets. |
|
DotNetToolbox.Security
DotNetToolbox.Security: A comprehensive utility library for hashing, encryption and decryption. |
|
DotNetToolbox.ValidationBuilder
DotNetToolbox.ValidationBuilder: A comprehensive utility library for building object validation and more. |
GitHub repositories
This package is not used by any popular GitHub repositories.
DotNetToolbox.Core Version 8.1.1
Stable release of the DotNetToolbox.Core library, a comprehensive suite of utilities and patterns designed to enhance .NET 8 development. This library provides developers with a robust set of tools to simplify complex tasks, improve testability, and streamline everyday coding activities.