Skip to main content

Framework Application

The Application layer implements business logic, orchestrates domain operations, and provides the foundation for building robust, maintainable business services with comprehensive CRUD operations, validation, and mapping.

🎯 Purpose

The Application layer serves as the orchestrator between your domain entities and external concerns:

  • Business logic implementation with generic CRUD services
  • Input validation using FluentValidation
  • Object mapping with Mapster for DTO transformations
  • Pagination support (both offset-based and cursor-based)
  • Authorization context with user-aware operations

📦 Installation

dotnet add package MasLazu.AspNet.Framework.Application

🏗️ Core Components

Architecture Overview

ICrudService Interface

The core contract for business operations:

public interface ICrudService<TDto, TCreateRequest, TUpdateRequest>
where TDto : BaseDto
where TUpdateRequest : BaseUpdateRequest
{
// Read operations
Task<TDto?> GetByIdAsync(Guid userId, Guid id, CancellationToken ct = default);
Task<IEnumerable<TDto>> GetAllAsync(Guid userId, CancellationToken ct = default);
Task<PaginatedResult<TDto>> GetPaginatedAsync(Guid userId, PaginationRequest request, CancellationToken ct = default);
Task<CursorPaginatedResult<TDto>> GetCursorPaginatedAsync(Guid userId, CursorPaginationRequest request, CancellationToken ct = default);

// Write operations
Task<TDto> CreateAsync(Guid userId, TCreateRequest createRequest, CancellationToken ct = default);
Task<IEnumerable<TDto>> CreateRangeAsync(Guid userId, IEnumerable<TCreateRequest> createRequests, CancellationToken ct = default);
Task<TDto> UpdateAsync(Guid userId, Guid id, TUpdateRequest updateRequest, CancellationToken ct = default);
Task DeleteAsync(Guid userId, Guid id, CancellationToken ct = default);
Task DeleteRangeAsync(Guid userId, IEnumerable<Guid> ids, CancellationToken ct = default);

// Utility operations
Task<bool> ExistsAsync(Guid userId, Guid id, CancellationToken ct = default);
Task<int> CountAsync(Guid userId, CancellationToken ct = default);
}

CrudService Base Class

Generic implementation providing complete CRUD functionality:

public abstract class CrudService<TEntity, TDto, TCreateRequest, TUpdateRequest>
: ICrudService<TDto, TCreateRequest, TUpdateRequest>
where TEntity : BaseEntity
where TDto : BaseDto
where TUpdateRequest : BaseUpdateRequest
{
protected readonly IRepository<TEntity> Repository;
protected readonly IReadRepository<TEntity> ReadRepository;
protected readonly IUnitOfWork UnitOfWork;
protected readonly IValidator<TCreateRequest>? CreateValidator;
protected readonly IValidator<TUpdateRequest>? UpdateValidator;
// ... additional dependencies
}

🚀 Usage Examples

1. Basic Service Implementation

// Domain Entity
public class Product : BaseEntity
{
public string Name { get; set; } = string.Empty;
public string Description { get; set; } = string.Empty;
public decimal Price { get; set; }
public string Category { get; set; } = string.Empty;
}

// DTO
public class ProductDto : BaseDto
{
public string Name { get; set; } = string.Empty;
public string Description { get; set; } = string.Empty;
public decimal Price { get; set; }
public string Category { get; set; } = string.Empty;
}

// Request Models
public class CreateProductRequest
{
public string Name { get; set; } = string.Empty;
public string Description { get; set; } = string.Empty;
public decimal Price { get; set; }
public string Category { get; set; } = string.Empty;
}

public class UpdateProductRequest : BaseUpdateRequest
{
public string? Name { get; set; }
public string? Description { get; set; }
public decimal? Price { get; set; }
public string? Category { get; set; }
}

// Service Implementation
public class ProductService : CrudService<Product, ProductDto, CreateProductRequest, UpdateProductRequest>
{
public ProductService(
IRepository<Product> repository,
IReadRepository<Product> readRepository,
IUnitOfWork unitOfWork,
IEntityPropertyMap<Product> propertyMap,
IPaginationValidator<Product> paginationValidator,
ICursorPaginationValidator<Product> cursorPaginationValidator,
IValidator<CreateProductRequest>? createValidator = null,
IValidator<UpdateProductRequest>? updateValidator = null)
: base(repository, readRepository, unitOfWork, propertyMap,
paginationValidator, cursorPaginationValidator,
createValidator, updateValidator)
{
}

// Override for custom business logic
public override async Task<ProductDto> CreateAsync(Guid userId, CreateProductRequest createRequest, CancellationToken ct = default)
{
// Custom validation
if (await Repository.AnyAsync(p => p.Name == createRequest.Name, ct))
throw new BusinessException("Product name already exists");

// Call base implementation
return await base.CreateAsync(userId, createRequest, ct);
}
}

2. Advanced Service with Custom Logic

public class OrderService : CrudService<Order, OrderDto, CreateOrderRequest, UpdateOrderRequest>
{
private readonly IProductService _productService;
private readonly IInventoryService _inventoryService;
private readonly IEmailService _emailService;

public OrderService(
IRepository<Order> repository,
IReadRepository<Order> readRepository,
IUnitOfWork unitOfWork,
IEntityPropertyMap<Order> propertyMap,
IPaginationValidator<Order> paginationValidator,
ICursorPaginationValidator<Order> cursorPaginationValidator,
IProductService productService,
IInventoryService inventoryService,
IEmailService emailService,
IValidator<CreateOrderRequest>? createValidator = null,
IValidator<UpdateOrderRequest>? updateValidator = null)
: base(repository, readRepository, unitOfWork, propertyMap,
paginationValidator, cursorPaginationValidator,
createValidator, updateValidator)
{
_productService = productService;
_inventoryService = inventoryService;
_emailService = emailService;
}

public override async Task<OrderDto> CreateAsync(Guid userId, CreateOrderRequest createRequest, CancellationToken ct = default)
{
// Business logic before creation
await ValidateInventoryAsync(createRequest.Items, ct);

var order = await base.CreateAsync(userId, createRequest, ct);

// Business logic after creation
await ReserveInventoryAsync(createRequest.Items, ct);
await SendOrderConfirmationAsync(order, ct);

return order;
}

public async Task<OrderDto> ConfirmOrderAsync(Guid userId, Guid orderId, CancellationToken ct = default)
{
var order = await Repository.GetByIdAsync(orderId, ct);
if (order == null)
throw new NotFoundException("Order not found");

if (order.Status != OrderStatus.Draft)
throw new BusinessException("Only draft orders can be confirmed");

order.Status = OrderStatus.Confirmed;
order.ConfirmedAt = DateTime.UtcNow;

await Repository.UpdateAsync(order, ct);
await UnitOfWork.SaveChangesAsync(ct);

// Send confirmation email
await _emailService.SendOrderConfirmationAsync(order, ct);

return order.Adapt<OrderDto>();
}

private async Task ValidateInventoryAsync(IEnumerable<OrderItemRequest> items, CancellationToken ct)
{
foreach (var item in items)
{
var available = await _inventoryService.GetAvailableQuantityAsync(item.ProductId, ct);
if (available < item.Quantity)
throw new BusinessException($"Insufficient inventory for product {item.ProductId}");
}
}
}

3. Validation Implementation

public class CreateProductRequestValidator : AbstractValidator<CreateProductRequest>
{
public CreateProductRequestValidator()
{
RuleFor(x => x.Name)
.NotEmpty().WithMessage("Product name is required")
.MaximumLength(100).WithMessage("Name cannot exceed 100 characters");

RuleFor(x => x.Description)
.NotEmpty().WithMessage("Description is required")
.MaximumLength(1000).WithMessage("Description cannot exceed 1000 characters");

RuleFor(x => x.Price)
.GreaterThan(0).WithMessage("Price must be greater than 0")
.LessThan(1000000).WithMessage("Price cannot exceed 1,000,000");

RuleFor(x => x.Category)
.NotEmpty().WithMessage("Category is required")
.Must(BeValidCategory).WithMessage("Invalid category");
}

private bool BeValidCategory(string category)
{
var validCategories = new[] { "Electronics", "Clothing", "Books", "Home" };
return validCategories.Contains(category);
}
}

4. Pagination Usage

public class ProductController : ControllerBase
{
private readonly IProductService _productService;

[HttpGet]
public async Task<ActionResult<PaginatedResult<ProductDto>>> GetProducts(
[FromQuery] PaginationRequest request,
CancellationToken ct)
{
var userId = GetCurrentUserId();
var result = await _productService.GetPaginatedAsync(userId, request, ct);
return Ok(result);
}

[HttpGet("cursor")]
public async Task<ActionResult<CursorPaginatedResult<ProductDto>>> GetProductsCursor(
[FromQuery] CursorPaginationRequest request,
CancellationToken ct)
{
var userId = GetCurrentUserId();
var result = await _productService.GetCursorPaginatedAsync(userId, request, ct);
return Ok(result);
}
}

🎯 Key Features

1. Built-in Validation

2. Dual Pagination Support

// Offset-based pagination (traditional)
var request = new PaginationRequest
{
Page = 1,
PageSize = 20,
OrderBy = "Name",
OrderDirection = "asc",
Filters = new[]
{
new FilterRequest { Field = "Category", Operator = "eq", Value = "Electronics" }
}
};

// Cursor-based pagination (performance optimized)
var cursorRequest = new CursorPaginationRequest
{
Limit = 20,
Cursor = "eyJpZCI6IjEyMyJ9", // Base64 encoded cursor
OrderBy = "CreatedAt",
OrderDirection = "desc"
};

3. Property Mapping & Validation

public class ProductPropertyMap : IEntityPropertyMap<Product>
{
public IDictionary<string, Expression<Func<Product, object>>> PropertyMappings =>
new Dictionary<string, Expression<Func<Product, object>>>
{
["name"] = p => p.Name,
["description"] = p => p.Description,
["price"] = p => p.Price,
["category"] = p => p.Category,
["createdAt"] = p => p.CreatedAt,
["updatedAt"] = p => p.UpdatedAt ?? DateTime.MinValue
};

public IDictionary<string, Type> PropertyTypes =>
new Dictionary<string, Type>
{
["name"] = typeof(string),
["description"] = typeof(string),
["price"] = typeof(decimal),
["category"] = typeof(string),
["createdAt"] = typeof(DateTime),
["updatedAt"] = typeof(DateTime)
};
}

📁 Project Structure

MasLazu.AspNet.Framework.Application/
├── Interfaces/
│ ├── ICrudService.cs # Main service contract
│ ├── IRepository.cs # Write repository interface
│ ├── IReadRepository.cs # Read repository interface
│ ├── IUnitOfWork.cs # Transaction management
│ ├── IPaginationValidator.cs # Pagination validation
│ └── IEntityPropertyMap.cs # Property mapping contract
├── Services/
│ └── CrudService.cs # Generic CRUD implementation
├── Models/
│ ├── BaseDto.cs # Base DTO class
│ ├── BaseUpdateRequest.cs # Base update request
│ ├── PaginationRequest.cs # Offset pagination
│ ├── PaginatedResult.cs # Offset pagination result
│ ├── CursorPaginationRequest.cs # Cursor pagination
│ └── CursorPaginatedResult.cs # Cursor pagination result
├── Validators/
│ ├── PaginationValidator.cs # Pagination validation logic
│ └── CursorPaginationValidator.cs # Cursor validation logic
├── Exceptions/
│ ├── BusinessException.cs # Business logic exceptions
│ └── ValidationException.cs # Validation exceptions
├── Extensions/
│ └── FrameworkApplicationExtension.cs # DI registration
└── Utils/
└── PaginationUtils.cs # Pagination utilities

🔗 Dependencies

<PackageReference Include="MasLazu.AspNet.Framework.Domain" />
<PackageReference Include="FluentValidation" />
<PackageReference Include="Mapster" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" />
<PackageReference Include="FastEndpoints" />

⚙️ Registration

// Startup.cs or Program.cs
services.AddFrameworkApplication();

// Register your services
services.AddScoped<IProductService, ProductService>();
services.AddScoped<IOrderService, OrderService>();

// Register validators
services.AddValidatorsFromAssemblyContaining<CreateProductRequestValidator>();

// Register property maps
services.AddScoped<IEntityPropertyMap<Product>, ProductPropertyMap>();

✅ Best Practices

✅ Do's

  • Inherit from CrudService for consistent functionality
  • Implement validation using FluentValidation
  • Use property maps for dynamic field access
  • Handle user context for authorization and auditing
  • Override methods for custom business logic
  • Use cancellation tokens for responsive applications

❌ Don'ts

  • Don't bypass validation in service methods
  • Don't ignore user context for authorization
  • Don't forget error handling for business operations
  • Don't implement data access directly in services
  • Don't use primitive types for complex operations

🎯 Next Steps

After implementing your application services:

  1. Framework.EfCore - Add repository implementations
  2. Framework.Endpoint - Create API endpoints
  3. Modules Overview - Explore pre-built modules