Odex.AspNetCore.Clarc.Infrastructure
0.1.0
See the version list below for details.
dotnet add package Odex.AspNetCore.Clarc.Infrastructure --version 0.1.0
NuGet\Install-Package Odex.AspNetCore.Clarc.Infrastructure -Version 0.1.0
<PackageReference Include="Odex.AspNetCore.Clarc.Infrastructure" Version="0.1.0" />
<PackageVersion Include="Odex.AspNetCore.Clarc.Infrastructure" Version="0.1.0" />
<PackageReference Include="Odex.AspNetCore.Clarc.Infrastructure" />
paket add Odex.AspNetCore.Clarc.Infrastructure --version 0.1.0
#r "nuget: Odex.AspNetCore.Clarc.Infrastructure, 0.1.0"
#:package Odex.AspNetCore.Clarc.Infrastructure@0.1.0
#addin nuget:?package=Odex.AspNetCore.Clarc.Infrastructure&version=0.1.0
#tool nuget:?package=Odex.AspNetCore.Clarc.Infrastructure&version=0.1.0
🏗️ Odex.AspNetCore.Clarc.Infrastructure
Infrastructure layer implementation for DDD-based ASP.NET Core applications
Provides data access abstractions, query builders, and infrastructure-specific exceptions.
📦 Overview
Odex.AspNetCore.Clarc.Infrastructure is the companion infrastructure layer to Odex.AspNetCore.Clarc.Domain. It provides concrete implementations and utilities for:
- Query Building – Fluent, extensible query builders with pagination support.
- Infrastructure Exceptions – Typed exceptions for configuration, database, external services, serialization, and more.
- Data Access Abstractions – Base classes for building dynamic LINQ queries with includes, filters, and sorting.
✨ Features
| Feature | Description |
|---|---|
| 🔍 ClarcQueryBuilder | Base query builder with modification pipeline (ModifyQuery), include tracking, and sort flags. |
| 📄 ClarcPaginatedQueryBuilder | Extends query builder with automatic Skip/Take pagination using PagedRequest. |
| ⚠️ Infrastructure Exceptions | Typed exceptions for common infrastructure failures (DB, config, external services, etc.). |
| 🏷️ State Tracking | Built-in flags (IsFiltered, HasIncludes, HasSorts) to know what was applied to the query. |
🏗️ Core Components
1. Query Builders
ClarcQueryBuilder<TEntity, TIncludes>
Base abstract class for building dynamic LINQ queries:
namespace Odex.AspNetCore.Clarc.Infrastructure.Data.QueryBuilders;
public abstract class ClarcQueryBuilder<TEntity, TIncludes>(IQueryable<TEntity> query)
where TEntity : class
{
private IQueryable<TEntity> _query = query;
protected bool IsFiltered;
protected bool HasIncludes;
protected bool HasSorts;
protected void MarkAsFiltered() => IsFiltered = true;
protected void MarkAsHasIncludes() => HasIncludes = true;
protected void MarkAsHasSorts() => HasSorts = true;
protected IQueryable<TEntity> ModifyQuery(Func<IQueryable<TEntity>, IQueryable<TEntity>> expression)
{
_query = expression(_query);
return _query;
}
protected virtual void ApplyIncludes(IReadOnlyList<TIncludes>? includes)
{
MarkAsHasIncludes();
}
protected virtual void ApplySorts()
{
MarkAsHasSorts();
}
public virtual IQueryable<TEntity> Build() => _query;
}
ClarcPaginatedQueryBuilder<TEntity, TIncludes>
Extends the base builder with automatic pagination:
using Odex.AspNetCore.Clarc.Domain.ValueObjects.Requests;
namespace Odex.AspNetCore.Clarc.Infrastructure.Data.QueryBuilders;
public abstract class ClarcPaginatedQueryBuilder<TEntity, TIncludes>(IQueryable<TEntity> query, PagedRequest request)
: ClarcQueryBuilder<TEntity, TIncludes>(query)
where TEntity : class
{
protected virtual void ApplyPagination()
{
if (request.PageSize > 0) ModifyQuery(q => q.Skip(request.SkipCount).Take(request.PageSize));
}
}
2. Infrastructure Exceptions
All exceptions inherit from InfrastructureException and include an ExceptionType enum for programmatic handling.
| Exception | Use Case |
|---|---|
ConfigurationException |
Missing/invalid configuration values. |
DatabaseConnectionException |
Failed connection to database. |
ExternalServiceException |
HTTP call failures to external APIs. |
GeneratorException |
ID generation or token generation failures. |
RepositoryException |
Generic repository operation failures. |
SerializationException |
JSON/XML serialization/deserialization errors. |
TransactionException |
Transaction rollback or commit failures. |
Exception Types Enum:
namespace Odex.AspNetCore.Clarc.Infrastructure.Constants;
public enum ExceptionType
{
Unknown,
Configuration,
DbConnection,
ExternalServiceFailed,
Generator,
Repository,
Serialization,
Transaction
}
🚀 Usage Examples
Creating a Custom Query Builder
public class UserQueryBuilder : ClarcQueryBuilder<User, UserInclude>
{
public UserQueryBuilder(IQueryable<User> query) : base(query) { }
public UserQueryBuilder WithName(string name)
{
if (!string.IsNullOrEmpty(name))
{
ModifyQuery(q => q.Where(u => u.Name.Contains(name)));
MarkAsFiltered();
}
return this;
}
public UserQueryBuilder WithEmail(string email)
{
if (!string.IsNullOrEmpty(email))
{
ModifyQuery(q => q.Where(u => u.Email == email));
MarkAsFiltered();
}
return this;
}
protected override void ApplyIncludes(IReadOnlyList<UserInclude>? includes)
{
if (includes == null) return;
foreach (var include in includes)
{
ModifyQuery(q => include switch
{
UserInclude.Orders => q.Include(u => u.Orders),
UserInclude.Profile => q.Include(u => u.Profile),
_ => q
});
}
base.ApplyIncludes(includes);
}
}
// Usage
var query = new UserQueryBuilder(_context.Users)
.WithName("John")
.WithEmail("john@example.com")
.Build();
Using Paginated Query Builder
public class PaginatedProductQueryBuilder : ClarcPaginatedQueryBuilder<Product, ProductInclude>
{
public PaginatedProductQueryBuilder(IQueryable<Product> query, PagedRequest request)
: base(query, request) { }
public PaginatedProductQueryBuilder WithCategory(int categoryId)
{
if (categoryId > 0)
{
ModifyQuery(q => q.Where(p => p.CategoryId == categoryId));
MarkAsFiltered();
}
return this;
}
public PaginatedProductQueryBuilder WithPriceRange(decimal min, decimal max)
{
ModifyQuery(q => q.Where(p => p.Price >= min && p.Price <= max));
MarkAsFiltered();
return this;
}
public override IQueryable<Product> Build()
{
ApplyIncludes(null);
ApplySorts();
ApplyPagination();
return base.Build();
}
}
// Usage (with automatic pagination)
var request = new PagedRequest { Page = 1, PageSize = 20 };
var query = new PaginatedProductQueryBuilder(_context.Products, request)
.WithCategory(5)
.WithPriceRange(10, 100)
.Build();
var products = await query.ToListAsync();
Handling Infrastructure Exceptions
public class ProductRepository
{
public async Task<Product> GetByIdAsync(int id)
{
try
{
var product = await _context.Products.FindAsync(id);
if (product == null)
throw new RepositoryException(nameof(Product), "GetById");
return product;
}
catch (SqlException ex)
{
throw new DatabaseConnectionException("ProductDb", ex.Message);
}
}
public async Task<string> ExportToJsonAsync()
{
try
{
var json = JsonSerializer.Serialize(products);
return json;
}
catch (JsonException ex)
{
throw new SerializationException("Product export", ex);
}
}
public async Task CallExternalApiAsync()
{
try
{
var response = await _httpClient.GetAsync("https://api.example.com/products");
if (!response.IsSuccessStatusCode)
{
throw new ExternalServiceException("ProductApi", (int)response.StatusCode, response.ReasonPhrase ?? "Unknown error");
}
}
catch (HttpRequestException ex)
{
throw new ExternalServiceException("ProductApi", 0, ex.Message);
}
}
}
// Exception handling in service layer
try
{
await _productRepository.CallExternalApiAsync();
}
catch (ExternalServiceException ex)
{
_logger.LogError(ex, "External service failed");
return Result.Failure($"Service error: {ex.Message}");
}
catch (DatabaseConnectionException ex)
{
_logger.LogCritical(ex, "Database unavailable");
return Result.Failure("System temporarily unavailable");
}
catch (InfrastructureException ex)
{
_logger.LogWarning(ex, "Infrastructure error of type {Type}", ex.Type);
return Result.Failure("Operation failed");
}
📂 Namespace Map
| Namespace | Purpose |
|---|---|
Odex.AspNetCore.Clarc.Infrastructure.Constants |
ExceptionType enum |
Odex.AspNetCore.Clarc.Infrastructure.Data.QueryBuilders |
ClarcQueryBuilder, ClarcPaginatedQueryBuilder |
Odex.AspNetCore.Clarc.Infrastructure.Exceptions |
All infrastructure-specific exceptions |
🔗 Related Packages
- Odex.AspNetCore.Clarc.Domain – Domain layer with aggregates, events, specifications, and policies.
🤝 Contributing
Contributions are welcome! Please follow the standard GitHub flow:
- Fork the repository
- Create a feature branch (
git checkout -b feature/amazing-feature) - Commit changes (
git commit -m 'Add amazing feature') - Push to branch (
git push origin feature/amazing-feature) - Open a Pull Request
📄 License
This project is licensed under the MIT License – see the LICENSE file for details.
Built with ❤️ for clean DDD infrastructure on ASP.NET Core
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net9.0 is compatible. 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 was computed. 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. |
-
net9.0
- Odex.AspNetCore.Clarc.Domain (>= 0.1.2)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.