Comprehensive utilities library with cross-cutting concerns and general-purpose tools. Includes encryption, IO operations, text processing, time utilities, API helpers, JSON utilities, and security features for enterprise applications.
$ dotnet add package Acontplus.UtilitiesA comprehensive .NET utility library providing common functionality for business applications. Async, extension methods, minimal API support, and more.
GetFilterValue<T>() and TryGetFilterValue<T>()Note: For barcode generation features, see the separate Acontplus.Barcode package which provides QR codes, Code 128, EAN-13, and other barcode formats.
This package provides a clean architectural separation between domain logic (in Acontplus.Core) and API conversion logic (in Acontplus.Utilities). All domain-to-API conversions are consolidated here for maintainability.
The following domain extensions have been consolidated into this package for clean separation:
DomainError, DomainErrors, and Result<T> to ApiResponse<T> with proper HTTP status codesPagedResult<T> with navigation metadataDomainWarnings and SuccessWithWarnings<T> conversions// Domain layer (Acontplus.Core) - Pure domain logic
public Result<User, DomainError> GetUser(int id) { /* domain logic */ }
// API layer (Acontplus.Utilities) - Clean conversions
public IActionResult GetUser(int id)
{
var result = _userService.GetUser(id);
return result.ToActionResult(); // Automatic conversion with error handling
}
// Minimal API
app.MapGet("/users/{id}", (int id) =>
{
var result = userService.GetUser(id);
return result.ToMinimalApiResult(); // Clean, type-safe responses
});
Install-Package Acontplus.Utilities
dotnet add package Acontplus.Utilities
<ItemGroup>
<PackageReference Include="Acontplus.Utilities" Version="1.3.7" />
</ItemGroup>
The ResultApiExtensions class provides comprehensive conversions from domain Result<T> types to API responses:
using Acontplus.Utilities.Extensions;
// Domain result to API response (automatic error handling)
public IActionResult GetUser(int id)
{
var result = userService.GetUser(id);
return result.ToActionResult(); // Handles success/error automatically
}
// With custom success message
var result = userService.CreateUser(user);
return result.ToActionResult("User created successfully");
// Minimal API support
app.MapGet("/users/{id}", (int id) =>
{
var result = userService.GetUser(id);
return result.ToMinimalApiResult();
});
using Acontplus.Utilities.Extensions;
// Automatic domain error to API response conversion
public IActionResult UpdateUser(int id, UserDto dto)
{
var result = userService.UpdateUser(id, dto);
return result.ToActionResult(); // DomainError automatically becomes 400/404/500 etc.
}
// Multiple errors with severity-based HTTP status
var errors = DomainErrors.Multiple(new[] {
DomainError.Validation("EMAIL_INVALID", "Invalid email format"),
DomainError.NotFound("USER_NOT_FOUND", "User not found")
});
return errors.ToActionResult<UserDto>();
using Acontplus.Utilities.Extensions;
// Build pagination links automatically
public IActionResult GetUsers(PaginationQuery query)
{
var result = userService.GetUsers(query);
var links = result.BuildPaginationLinks("/api/users", query.PageSize);
return result.ToActionResult();
}
var encryptionService = new SensitiveDataEncryptionService();
byte[] encrypted = await encryptionService.EncryptToBytesAsync("password", "data");
string decrypted = await encryptionService.DecryptFromBytesAsync("password", encrypted);
var metadata = new Dictionary<string, object>()
.WithPagination(page: 1, pageSize: 10, totalItems: 100);
using Acontplus.Utilities.Extensions;
app.MapGet("/api/lookups", async (FilterQuery filterQuery, ILookupService service) =>
{
// Extract typed filter values with defaults - never throws
var category = filterQuery.GetFilterValue<string>("category");
var isActive = filterQuery.GetFilterValue<bool>("isActive", true);
var minPriority = filterQuery.GetFilterValue<int>("minPriority", 1);
// Safe retrieval with try pattern
if (filterQuery.TryGetFilterValue<Guid>("entityId", out var entityId))
{
// entityId successfully converted to Guid
}
return await service.GetLookupsAsync(filterQuery);
});
Benefits:
Convert.ChangeType()The PaginationQuery record provides automatic parameter binding in minimal APIs with support for multiple filters, sorting, and pagination.
// Program.cs or endpoint definition
app.MapGet("/api/users", async (PaginationQuery pagination, IUserService userService) =>
{
var result = await userService.GetPaginatedUsersAsync(pagination);
return Results.Ok(result);
})
.WithName("GetUsers")
.WithOpenApi();
// Service implementation
public async Task<PagedResult<UserDto>> GetPaginatedUsersAsync(PaginationQuery pagination)
{
var spParameters = new Dictionary<string, object>
{
["@PageIndex"] = pagination.PageIndex,
["@PageSize"] = pagination.PageSize,
["@SearchTerm"] = pagination.SearchTerm ?? (object)DBNull.Value,
["@SortBy"] = pagination.SortBy ?? "CreatedAt",
["@SortDirection"] = (pagination.SortDirection ?? SortDirection.Asc).ToString()
};
// Add filters from PaginationQuery.Filters
if (pagination.Filters != null)
{
foreach (var filter in pagination.Filters)
{
var paramName = $"@{filter.Key}";
spParameters[paramName] = filter.Value ?? DBNull.Value;
}
}
// Execute stored procedure with all parameters
var dataSet = await _adoRepository.GetDataSetAsync("sp_GetPaginatedUsers", spParameters);
// Process results and return PagedResult
}
// API client function
async function getUsers(filters: UserFilters = {}) {
const params = new URLSearchParams({
pageIndex: '1',
pageSize: '20',
searchTerm: filters.searchTerm || '',
sortBy: 'createdAt',
sortDirection: 'desc'
});
// Add filters
if (filters.status) params.append('filters[status]', filters.status);
if (filters.role) params.append('filters[role]', filters.role);
if (filters.isActive !== undefined) params.append('filters[isActive]', filters.isActive.toString());
if (filters.createdDate) params.append('filters[createdDate]', filters.createdDate);
const response = await fetch(`/api/users?${params.toString()}`);
return response.json();
}
// Usage examples
const users = await getUsers({
searchTerm: 'john',
status: 'active',
role: 'admin',
isActive: true
});
// URL generated: /api/users?pageIndex=1&pageSize=20&searchTerm=john&sortBy=createdAt&sortDirection=desc&filters[status]=active&filters[role]=admin&filters[isActive]=true
import React, { useState, useEffect } from 'react';
interface UserFilters {
searchTerm?: string;
status?: string;
role?: string;
isActive?: boolean;
}
const UserList: React.FC = () => {
const [users, setUsers] = useState([]);
const [filters, setFilters] = useState<UserFilters>({});
const [pagination, setPagination] = useState({ pageIndex: 1, pageSize: 20 });
const fetchUsers = async () => {
const params = new URLSearchParams({
pageIndex: pagination.pageIndex.toString(),
pageSize: pagination.pageSize.toString(),
sortBy: 'createdAt',
sortDirection: 'desc'
});
// Add search term
if (filters.searchTerm) {
params.append('searchTerm', filters.searchTerm);
}
// Add filters
Object.entries(filters).forEach(([key, value]) => {
if (value !== undefined && key !== 'searchTerm') {
params.append(`filters[${key}]`, value.toString());
}
});
const response = await fetch(`/api/users?${params.toString()}`);
const data = await response.json();
setUsers(data.items);
};
useEffect(() => {
fetchUsers();
}, [filters, pagination]);
return (
<div>
{/* Filter controls */}
<input
type="text"
placeholder="Search users..."
onChange={(e) => setFilters(prev => ({ ...prev, searchTerm: e.target.value }))}
/>
<select onChange={(e) => setFilters(prev => ({ ...prev, status: e.target.value }))}>
<option value="">All Status</option>
<option value="active">Active</option>
<option value="inactive">Inactive</option>
</select>
{/* User list */}
{users.map(user => (
<div key={user.id}>{user.name}</div>
))}
</div>
);
};
# Basic pagination
GET /api/users?pageIndex=1&pageSize=20
# With search and sorting
GET /api/users?pageIndex=1&pageSize=20&searchTerm=john&sortBy=name&sortDirection=asc
# With multiple filters
GET /api/users?pageIndex=1&pageSize=20&filters[status]=active&filters[role]=admin&filters[isActive]=true
# Complex filter combinations
GET /api/users?pageIndex=1&pageSize=20&searchTerm=john&filters[status]=active&filters[role]=admin&filters[createdDate]=2024-01-01&filters[departments][]=IT&filters[departments][]=HR
[HttpGet("{id:int}")]
public async Task<IActionResult> GetUsuario(int id)
{
var result = await _usuarioService.GetByIdAsync(id);
return result.ToGetActionResult();
}
[HttpPost]
public async Task<IActionResult> CreateUsuario([FromBody] UsuarioDto dto)
{
var usuario = ObjectMapper.Map<UsuarioDto, Usuario>(dto);
var result = await _usuarioService.AddAsync(usuario);
if (result.IsSuccess && result.Value is not null)
{
var locationUri = $"/api/Usuario/{result.Value.Id}";
return ApiResponse<Usuario>.Success(result.Value, new ApiResponseOptions { Message = "Usuario creado exitosamente." }).ToActionResult();
}
return result.ToActionResult();
}
[HttpPut("{id:int}")]
public async Task<IActionResult> UpdateUsuario(int id, [FromBody] UsuarioDto dto)
{
var usuario = ObjectMapper.Map<UsuarioDto, Usuario>(dto);
var result = await _usuarioService.UpdateAsync(id, usuario);
if (result.IsSuccess)
{
return ApiResponse<Usuario>.Success(result.Value, new ApiResponseOptions { Message = "Usuario actualizado correctamente." }).ToActionResult();
}
return result.ToActionResult();
}
[HttpDelete("{id:int}")]
public async Task<IActionResult> DeleteUsuario(int id)
{
var result = await _usuarioService.DeleteAsync(id);
return result.ToDeleteActionResult();
}
app.MapGet("/usuarios/{id:int}", async (int id, IUsuarioService service) =>
{
var result = await service.GetByIdAsync(id);
return result.ToMinimalApiResult();
});
public async Task<Result<Usuario, DomainErrors>> AddAsync(Usuario usuario)
{
var errors = new List<DomainError>();
if (string.IsNullOrWhiteSpace(usuario.Username))
errors.Add(DomainError.Validation("USERNAME_REQUIRED", "Username is required"));
if (string.IsNullOrWhiteSpace(usuario.Email))
errors.Add(DomainError.Validation("EMAIL_REQUIRED", "Email is required"));
if (errors.Count > 0)
return DomainErrors.Multiple(errors);
// ... check for existing user, add, etc.
}
using Acontplus.Utilities.Data;
// Convert DataTable to JSON
string json = DataConverters.DataTableToJson(myDataTable);
// Convert DataSet to JSON
string json = DataConverters.DataSetToJson(myDataSet);
// Convert JSON to DataTable
DataTable table = DataConverters.JsonToDataTable(jsonString);
// Serialize any object (with DataTable/DataSet support)
string json = DataConverters.SerializeObjectCustom(myObject);
// Serialize and sanitize complex objects
string json = DataConverters.SerializeSanitizedData(myObject);
using Acontplus.Utilities.Data;
// Map a DataRow to a strongly-typed model
var model = DataTableMapper.MapDataRowToModel<MyModel>(dataRow);
// Map a DataTable to a list of models
List<MyModel> models = DataTableMapper.MapDataTableToList<MyModel>(dataTable);
using Acontplus.Utilities.Json;
// Validate JSON
var result = JsonHelper.ValidateJson(jsonString);
if (!result.IsValid) Console.WriteLine(result.ErrorMessage);
// Get a property value from JSON
string? value = JsonHelper.GetJsonProperty<string>(jsonString, "propertyName");
// Merge two JSON objects
string merged = JsonHelper.MergeJson(json1, json2);
// Compare two JSON strings (ignoring property order)
bool areEqual = JsonHelper.AreEqual(json1, json2);
using Acontplus.Utilities.Json;
// Validate JSON using extension
bool isValid = jsonString.IsValidJson();
// Get property value using extension
int? id = jsonString.GetJsonProperty<int>("id");
// Merge JSON using extension
string merged = json1.MergeJson(json2);
// Compare JSON using extension
bool equal = json1.JsonEquals(json2);
using Acontplus.Utilities.Mapping;
// Map between objects (AutoMapper-like)
var target = ObjectMapper.Map<SourceType, TargetType>(sourceObject);
// Configure custom mapping
ObjectMapper.CreateMap<SourceType, TargetType>()
.ForMember(dest => dest.SomeProperty, src => src.OtherProperty)
.Ignore(dest => dest.IgnoredProperty);
// Map with configuration
var mapped = ObjectMapper.Map<SourceType, TargetType>(sourceObject);
string safeName = FileExtensions.SanitizeFileName("my*illegal:file?.txt");
string base64 = FileExtensions.GetBase64FromByte(myBytes);
byte[] compressed = CompressionUtils.CompressGZip(data);
byte[] decompressed = CompressionUtils.DecompressGZip(compressed);
ResultApiExtensions - Comprehensive Result to IActionResult/IResult conversions with domain error handlingDomainErrorExtensions - Convert DomainError/DomainErrors to ApiResponse with HTTP status mappingPagedResultExtensions - Build pagination links and metadata for API responsesDomainWarningsExtensions - Handle DomainWarnings and SuccessWithWarnings conversionsSensitiveDataEncryptionService - AES encryption/decryption helpers with BCrypt supportFileExtensions - File name sanitization and byte array utilitiesCompressionUtils - GZip/Deflate compression helpersTextHandlers - String formatting and splitting utilitiesDirectoryHelper / EnvironmentHelper - Runtime and environment utilitiesDataConverters - DataTable/DataSet JSON conversionJsonHelper - JSON validation and manipulationObjectMapper - Object-to-object mapping utilitiesWe welcome contributions! Please see our Contributing Guidelines for details.
git clone https://github.com/acontplus/acontplus-dotnet-libs.git
cd acontplus-dotnet-libs
dotnet restore
dotnet build
This project is licensed under the MIT License - see the LICENSE file for details.
Ivan Paz - @iferpaz7
Acontplus - Software solutions
Built with ❤️ for the .NET community