Advanced foundational library with cutting-edge C# features and 17 global business enums for multi-application standardization. Includes domain-driven design patterns, comprehensive error handling with HTTP mapping, specification pattern interfaces, functional result types, JSON utilities, validation helpers, and enterprise-ready abstractions. Features ready-to-use enums for business status, priorities, document types, demographics, currencies, payment methods, user roles, industries, company sizes, event types, and internationalization. Built with collection expressions, required properties, record structs, and source generation for optimal performance. Clean architecture with no persistence dependencies.
$ dotnet add package Acontplus.CoreA cutting-edge .NET 9+ foundational library leveraging the latest C# language features and business patterns. Built with performance, type safety, and developer experience in mind.
[] syntax for efficient collection initializationrequired keywordswitch expressions and is patternsIAsyncEnumerable<T> for memory-efficient processing// Entity with required properties and collection expressions
public class Product : BaseEntity
{
public required string Name { get; set; }
public required decimal Price { get; set; }
public string? Description { get; set; }
public required int CategoryId { get; set; }
// Factory method with syntax
public static Product Create(int id, string name, decimal price, string description, int categoryId, int createdByUserId) =>
new()
{
Id = id,
Name = name,
Price = price,
Description = description,
CategoryId = categoryId,
CreatedAt = DateTime.UtcNow,
CreatedByUserId = createdByUserId
};
}// Result pattern with record structs
public async Task<Result<Product>> GetProductAsync(int id)
{
var product = await _repository.GetByIdAsync(id);
return product is not null
? Result<Product>.Success(product)
: Result<Product>.Failure(DomainError.NotFound("PRODUCT_NOT_FOUND", $"Product {id} not found"));
}
// Pattern matching with syntax
var response = result.Match(
success: product => ApiResponse<Product>.Success(product),
failure: error => error.ToApiResponse<Product>()
);// Collection expressions and initialization
var pagination = new PaginationDto
{
PageIndex = 1,
PageSize = 20,
SortBy = "CreatedAt",
SortDirection = SortDirection.Descending,
SearchTerm = "search term",
Filters = new Dictionary<string, object>
{
["CategoryId"] = 1,
["IsActive"] = true
}
};
// Specification pattern
public class ActiveProductsSpecification : BaseSpecification<Product>
{
public ActiveProductsSpecification(PaginationDto pagination) : base(p => p.IsActive)
{
ApplyPaging(pagination);
AddOrderBy(p => p.CreatedAt, isDescending: true);
AddInclude(p => p.Category);
}
}// Memory-efficient async streaming
await foreach (var product in repository.FindAsyncEnumerable(p => p.IsActive))
{
await ProcessProductAsync(product);
}
// High-performance projections
var summaries = await repository.GetPagedProjectionAsync(
pagination,
p => new ProductSummary(p.Id, p.Name, p.Price),
p => p.IsActive
);// Business-optimized JSON with source generation
var json = myObject.SerializeOptimized();
var obj = jsonString.DeserializeOptimized<MyType>();
var clone = myObject.CloneDeep();
// JSON options for different scenarios
var options = JsonExtensions.DefaultOptions; // Production
var prettyOptions = JsonExtensions.PrettyOptions; // Development
var strictOptions = JsonExtensions.StrictOptions; // APIs// Error creation with record structs
var validationError = DomainError.Validation(
code: "PRODUCT_INVALID_PRICE",
message: "Product price must be greater than zero",
target: "price"
);
// Error aggregation with LINQ
var errors = new List<DomainError>();
if (string.IsNullOrWhiteSpace(request.Name))
errors.Add(DomainError.Validation("INVALID_NAME", "Product name is required"));
if (errors.Any())
return Result<Product>.Failure(errors.GetMostSevereError());// Data validation with pattern matching
var validationResult = input switch
{
{ Length: 0 } => DomainError.Validation("EMPTY_INPUT", "Input cannot be empty"),
{ Length: > 100 } => DomainError.Validation("TOO_LONG", "Input too long"),
_ => null
};
// JSON validation
if (!DataValidation.IsValidJson(jsonContent))
{
return DomainError.Validation("INVALID_JSON", "Invalid JSON format");
}
// XML validation
var xmlErrors = XmlValidator.Validate(xmlContent, xsdSchema);
if (xmlErrors.Any())
{
return DomainError.Validation("INVALID_XML", "XML validation failed");
}// Pagination extensions
var pagination = new PaginationDto()
.WithSearch("laptop")
.WithSort("Price", SortDirection.Asc)
.WithFilters(new Dictionary<string, object>
{
["categoryId"] = 1,
["isActive"] = true
});
// Repository audit extensions
await repository.AddWithAuditAsync(entity, userId);
await repository.UpdateWithAuditAsync(entity, userId);
// JSON extensions
var json = myObject.SerializeOptimized();
var obj = jsonString.DeserializeOptimized<MyType>();
// Nullable extensions
var result = nullableValue.OrElse("default");
var safeValue = nullableValue.ThrowIfNull("Value is required");// API metadata keys for consistent responses
var metadata = new Dictionary<string, object>
{
[ApiMetadataKeys.Page] = 1,
[ApiMetadataKeys.PageSize] = 20,
[ApiMetadataKeys.TotalItems] = 100,
[ApiMetadataKeys.TotalPages] = 5
};
// API response helpers
var response = ApiResponseHelpers.CreateSuccessResponse(data, "Operation successful");
var errorResponse = ApiResponseHelpers.CreateErrorResponse("Operation failed", "ERROR_CODE");Install-Package Acontplus.Coredotnet add package Acontplus.Core<PackageReference Include="Acontplus.Core" Version="1.3.3" />// Entity with required properties
public class Product : BaseEntity
{
public required string Name { get; set; }
public required decimal Price { get; set; }
public string? Description { get; set; }
public required int CategoryId { get; set; }
// Factory method with syntax
public static Product Create(int id, string name, decimal price, string description, int categoryId, int createdByUserId) =>
new()
{
Id = id,
Name = name,
Price = price,
Description = description,
CategoryId = categoryId,
CreatedAt = DateTime.UtcNow,
CreatedByUserId = createdByUserId
};
}// Error creation with record structs
var validationError = DomainError.Validation(
code: "PRODUCT_INVALID_PRICE",
message: "Product price must be greater than zero",
target: "price"
);
// Pattern matching with syntax
var response = result.Match(
success: product => ApiResponse<ProductDto>.Success(product.ToDto()),
failure: error => error.ToApiResponse<ProductDto>()
);// Repository interface
public interface IProductRepository : IRepository<Product>
{
Task<IReadOnlyList<Product>> GetByCategoryAsync(int categoryId);
Task<bool> ExistsByNameAsync(string name);
}
// Repository implementation
public class ProductRepository : BaseRepository<Product>, IProductRepository
{
public ProductRepository(DbContext context) : base(context) { }
public async Task<IReadOnlyList<Product>> GetByCategoryAsync(int categoryId) =>
await FindAsync(p => p.CategoryId == categoryId);
public async Task<bool> ExistsByNameAsync(string name) =>
await ExistsAsync(p => p.Name == name);
}// Specification with primary constructor
public class ActiveProductsSpecification : BaseSpecification<Product>
{
public ActiveProductsSpecification(PaginationDto pagination) : base(p => p.IsActive)
{
ApplyPaging(pagination);
AddOrderBy(p => p.CreatedAt, isDescending: true);
AddInclude(p => p.Category);
}
}
// Usage with syntax
var spec = new ActiveProductsSpecification(new PaginationDto { PageIndex = 1, PageSize = 10 });
var products = await _repository.FindWithSpecificationAsync(spec);[HttpGet("{id}")]
public async Task<ApiResponse<ProductDto>> GetProduct(int id)
{
var result = await _productService.GetByIdAsync(id);
return result.Match(
success: product => ApiResponse<ProductDto>.Success(product.ToDto()),
failure: error => error.ToApiResponse<ProductDto>()
);
}The PaginationDto provides advanced pagination capabilities with search, sorting, and multiple filters for business applications.
[HttpGet]
public async Task<ActionResult<PagedResult<ProductDto>>> GetProducts([FromQuery] PaginationDto pagination)
{
var result = await _productService.GetPaginatedProductsAsync(pagination);
return Ok(result);
}
// Service implementation
public async Task<PagedResult<ProductDto>> GetPaginatedProductsAsync(PaginationDto 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.ToString()
};
// Add filters using the built-in methods
var filterParams = pagination.BuildSqlParameters();
if (filterParams != null)
{
foreach (var param in filterParams)
{
spParameters[param.Key] = param.Value;
}
}
// Execute stored procedure with all parameters
var dataSet = await _adoRepository.GetDataSetAsync("sp_GetPaginatedProducts", spParameters);
// Process results and return PagedResult
var products = ProcessStoredProcedureResults(dataSet);
var totalCount = GetTotalCountFromDataSet(dataSet);
return new PagedResult<ProductDto>(products, pagination.PageIndex, pagination.PageSize, totalCount);
}// API client function
async function getProducts(filters: ProductFilters = {}) {
const params = new URLSearchParams({
pageIndex: '1',
pageSize: '20',
searchTerm: filters.searchTerm || '',
sortBy: 'createdAt',
sortDirection: 'desc'
});
// Add filters
if (filters.categoryId) params.append('filters[categoryId]', filters.categoryId.toString());
if (filters.isActive !== undefined) params.append('filters[isActive]', filters.isActive.toString());
if (filters.minPrice) params.append('filters[minPrice]', filters.minPrice.toString());
if (filters.maxPrice) params.append('filters[maxPrice]', filters.maxPrice.toString());
if (filters.createdDate) params.append('filters[createdDate]', filters.createdDate);
const response = await fetch(`/api/products?${params.toString()}`);
return response.json();
}
// Usage examples
const products = await getProducts({
searchTerm: 'laptop',
categoryId: 1,
isActive: true,
minPrice: 500,
maxPrice: 2000
});
// URL generated: /api/products?pageIndex=1&pageSize=20&searchTerm=laptop&sortBy=createdAt&sortDirection=desc&filters[categoryId]=1&filters[isActive]=true&filters[minPrice]=500&filters[maxPrice]=2000import React, { useState, useEffect } from 'react';
interface ProductFilters {
searchTerm?: string;
categoryId?: number;
isActive?: boolean;
minPrice?: number;
maxPrice?: number;
}
const ProductList: React.FC = () => {
const [products, setProducts] = useState([]);
const [filters, setFilters] = useState<ProductFilters>({});
const [pagination, setPagination] = useState({ pageIndex: 1, pageSize: 20 });
const fetchProducts = 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/products?${params.toString()}`);
const data = await response.json();
setProducts(data.items);
};
useEffect(() => {
fetchProducts();
}, [filters, pagination]);
return (
<div>
{/* Filter controls */}
<input
type="text"
placeholder="Search products..."
onChange={(e) => setFilters(prev => ({ ...prev, searchTerm: e.target.value }))}
/>
<select onChange={(e) => setFilters(prev => ({ ...prev, categoryId: Number(e.target.value) }))}>
<option value="">All Categories</option>
<option value="1">Electronics</option>
<option value="2">Clothing</option>
</select>
<input
type="number"
placeholder="Min Price"
onChange={(e) => setFilters(prev => ({ ...prev, minPrice: Number(e.target.value) }))}
/>
<input
type="number"
placeholder="Max Price"
onChange={(e) => setFilters(prev => ({ ...prev, maxPrice: Number(e.target.value) }))}
/>
{/* Product list */}
{products.map(product => (
<div key={product.id}>{product.name} - ${product.price}</div>
))}
</div>
);
};// Create pagination with extension methods
var pagination = new PaginationDto()
.WithSearch("laptop")
.WithSort("Price", SortDirection.Asc)
.WithFilters(new Dictionary<string, object>
{
["categoryId"] = 1,
["isActive"] = true,
["minPrice"] = 500,
["maxPrice"] = 2000
});
// Use built-in filter building methods
var sqlParams = pagination.BuildSqlParameters();
// Result: { ["@categoryId"] = 1, ["@isActive"] = true, ["@minPrice"] = 500, ["@maxPrice"] = 2000 }
var customParams = pagination.BuildFiltersWithPrefix(":");
// Result: { [":categoryId"] = 1, [":isActive"] = true, [":minPrice"] = 500, [":maxPrice"] = 2000 }# Basic pagination
GET /api/products?pageIndex=1&pageSize=20
# With search and sorting
GET /api/products?pageIndex=1&pageSize=20&searchTerm=laptop&sortBy=price&sortDirection=asc
# With multiple filters
GET /api/products?pageIndex=1&pageSize=20&filters[categoryId]=1&filters[isActive]=true&filters[minPrice]=500
# Complex filter combinations
GET /api/products?pageIndex=1&pageSize=20&searchTerm=laptop&filters[categoryId]=1&filters[isActive]=true&filters[minPrice]=500&filters[maxPrice]=2000&filters[createdDate]=2024-01-01
CREATE OR ALTER PROCEDURE sp_GetPaginatedProducts
@PageIndex INT = 1,
@PageSize INT = 10,
@SearchTerm NVARCHAR(100) = NULL,
@SortBy NVARCHAR(50) = 'CreatedAt',
@SortDirection NVARCHAR(4) = 'ASC',
-- Dynamic filters
@CategoryId INT = NULL,
@IsActive BIT = NULL,
@MinPrice DECIMAL(18,2) = NULL,
@MaxPrice DECIMAL(18,2) = NULL,
@CreatedDate DATE = NULL
AS
BEGIN
SET NOCOUNT ON;
DECLARE @SQL NVARCHAR(MAX);
DECLARE @WhereClause NVARCHAR(MAX) = '';
-- Build WHERE clause dynamically
IF @SearchTerm IS NOT NULL
SET @WhereClause = @WhereClause + ' AND (Name LIKE @SearchTerm OR Description LIKE @SearchTerm)';
IF @CategoryId IS NOT NULL
SET @WhereClause = @WhereClause + ' AND CategoryId = @CategoryId';
IF @IsActive IS NOT NULL
SET @WhereClause = @WhereClause + ' AND IsActive = @IsActive';
IF @MinPrice IS NOT NULL
SET @WhereClause = @WhereClause + ' AND Price >= @MinPrice';
IF @MaxPrice IS NOT NULL
SET @WhereClause = @WhereClause + ' AND Price <= @MaxPrice';
IF @CreatedDate IS NOT NULL
SET @WhereClause = @WhereClause + ' AND CAST(CreatedAt AS DATE) = @CreatedDate';
-- Remove leading ' AND ' if exists
IF LEN(@WhereClause) > 0
SET @WhereClause = ' WHERE ' + SUBSTRING(@WhereClause, 6, LEN(@WhereClause));
-- Build and execute final query with pagination
SET @SQL = '
WITH PaginatedData AS (
SELECT
Id, Name, Description, Price, CategoryId, IsActive, CreatedAt,
ROW_NUMBER() OVER (ORDER BY ' + @SortBy + ' ' + @SortDirection + ') AS RowNum
FROM Products
WHERE IsDeleted = 0' + @WhereClause + '
)
SELECT
Id, Name, Description, Price, CategoryId, IsActive, CreatedAt
FROM PaginatedData
WHERE RowNum BETWEEN (@PageIndex - 1) * @PageSize + 1
AND @PageIndex * @PageSize;
-- Get total count for pagination
SELECT COUNT(*) AS TotalCount
FROM Products
WHERE IsDeleted = 0' + @WhereClause + ';';
EXEC sp_executesql @SQL,
N'@PageIndex INT, @PageSize INT, @SearchTerm NVARCHAR(100), @CategoryId INT, @IsActive BIT, @MinPrice DECIMAL(18,2), @MaxPrice DECIMAL(18,2), @CreatedDate DATE',
@PageIndex, @PageSize, @SearchTerm, @CategoryId, @IsActive, @MinPrice, @MaxPrice, @CreatedDate;
END// Domain event with record
public record ProductCreatedEvent(int ProductId, string ProductName, DateTime OccurredOn) : IDomainEvent
{
public ProductCreatedEvent(int productId, string productName) : this(productId, productName, DateTime.UtcNow) { }
}
// Entity with domain events
public class Product : BaseEntity
{
public void MarkAsCreated() =>
AddDomainEvent(new ProductCreatedEvent(Id, Name));
}// High-performance bulk operations
await repository.BulkInsertAsync(products);
await repository.BulkUpdateAsync(p => p.CategoryId == 1, p => p.Status, "Active");
await repository.BulkDeleteAsync(p => p.CreatedAt < DateTime.UtcNow.AddDays(-30));
// Projections for efficient data transfer
var summaries = await repository.GetPagedProjectionAsync(
pagination,
p => new ProductSummary(p.Id, p.Name, p.Price),
p => p.IsActive
);// Memory-efficient processing with async streaming
await foreach (var product in repository.FindAsyncEnumerable(p => p.IsActive))
{
await ProcessProductAsync(product);
}// Business-optimized JSON with source generation
var json = myObject.SerializeOptimized();
var obj = jsonString.DeserializeOptimized<MyType>();
var clone = myObject.CloneDeep();
// JSON options for different scenarios
var options = JsonExtensions.DefaultOptions; // Production
var prettyOptions = JsonExtensions.PrettyOptions; // Development
var strictOptions = JsonExtensions.StrictOptions; // APIs// Validation with pattern matching
var validationResult = input switch
{
{ Length: 0 } => DomainError.Validation("EMPTY_INPUT", "Input cannot be empty"),
{ Length: > 100 } => DomainError.Validation("TOO_LONG", "Input too long"),
_ => null
};
// JSON validation
if (!DataValidation.IsValidJson(jsonContent))
{
return DomainError.Validation("INVALID_JSON", "Invalid JSON format");
}
// XML validation
var xmlErrors = XmlValidator.Validate(xmlContent, xsdSchema);
if (xmlErrors.Any())
{
return DomainError.Validation("INVALID_XML", "XML validation failed");
}Entity<TId> - Base entity with domain eventsBaseEntity - Base for int-keyed entities with audit trailIAuditableEntity - Interface for audit trail supportIEntityWithDomainEvents - Interface for domain event supportApiResponse<T> - API response format with record structsPaginationDto - Pagination with search and filteringPagedResult<T> - Paginated results with metadataApiError - Error information with record structsDomainError - Business logic errors with HTTP mappingResult<T> - Functional result pattern with record structsErrorType - Error type categorizationErrorTypeExtensions - Error type utilitiesIRepository<TEntity> - Comprehensive repository interfaceISpecification<T> - Query specification patternBaseSpecification<T> - Base class for specificationsPagedResult<T> - Paginated query resultsDataValidation - Data validation utilitiesXmlValidator - XML validation with XSD schemasValidationExtensions - Extension methods for validationPaginationExtensions - Pagination builder methodsRepositoryAuditExtensions - Audit trail extensionsJsonExtensions - JSON serialization utilities (with optimized and deprecated methods)NullableExtensions - Nullable type helpersEnumExtensions - Enum manipulation utilitiesApiMetadataKeys - Standard API metadata keysApiResponseHelpers - API response creation utilities[] syntax for collection initializationrequired keywordIAsyncEnumerable<T>[] syntax for efficient initializationrequired keywordswitch expressions and is patternsIAsyncEnumerable<T>We welcome contributions! Please see our Contributing Guidelines for details.
git clone https://github.com/Acontplus-S-A-S/acontplus-dotnet-libs.git
cd acontplus-dotnet-libs
dotnet restore
dotnet buildThis project is licensed under the MIT License - see the LICENSE file for details.
Ivan Paz - @iferpaz7
Acontplus S.A.S. - Software solutions
Built with ❤️ for the .NET community using the latest .NET 9 features