Diiwo.Core 0.2.0

dotnet add package Diiwo.Core --version 0.2.0
                    
NuGet\Install-Package Diiwo.Core -Version 0.2.0
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="Diiwo.Core" Version="0.2.0" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="Diiwo.Core" Version="0.2.0" />
                    
Directory.Packages.props
<PackageReference Include="Diiwo.Core" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add Diiwo.Core --version 0.2.0
                    
#r "nuget: Diiwo.Core, 0.2.0"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package Diiwo.Core@0.2.0
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=Diiwo.Core&version=0.2.0
                    
Install as a Cake Addin
#tool nuget:?package=Diiwo.Core&version=0.2.0
                    
Install as a Cake Tool

Diiwo.Core

[Build Status](https://github.com/diiwo/diiwo-core/actions License: MIT

Universal base entities, audit trails, and core functionality for all .NET applications.

Diiwo.Core is a lightweight, universal library that provides base entities, automatic audit trails, soft delete functionality, and essential interfaces for building robust .NET applications. Designed to be framework-agnostic and highly reusable across web, console, desktop, and service applications.

โœจ Key Features

Core Entities & Audit

  • ๐Ÿ—๏ธ Universal Base Entities - BaseEntity, AuditableEntity, UserTrackedEntity, DomainEntity
  • ๐Ÿ” Zero-Code Audit Trails - Automatic audit field population with zero manual code required
  • ๐Ÿ—‘๏ธ Enterprise Soft Delete - Preserve data for compliance while marking as terminated
  • ๐Ÿ“Š Entity State Management - Complete lifecycle tracking (Created, Active, Inactive, Terminated)
  • ๐Ÿ‘ค User Attribution - Automatic tracking of who made changes and when
  • ๐Ÿข Compliance Ready - Meet regulatory requirements with comprehensive audit trails

API Response Patterns (New in v0.2.0)

  • ๐Ÿ“ฆ ApiResponse<T> - Standardized API response wrapper for consistent formatting
  • ๐Ÿ“„ PagedResponse<T> - Built-in pagination support with metadata
  • โœ… ValidationResponse - Structured validation error responses

Exception Handling (New in v0.2.0)

  • โš ๏ธ BusinessException - Base exception for business logic violations
  • ๐Ÿ” NotFoundException - Resource not found exceptions
  • ๐Ÿ”’ UnauthorizedException - Authentication/authorization failures
  • โœ”๏ธ ValidationException - Input validation errors with field-level details
  • โšก ConflictException - Data conflict exceptions

Architecture & Compatibility

  • ๐Ÿ›๏ธ Clean Architecture Ready - Follows DDD and clean architecture principles
  • ๐ŸŒ Framework Agnostic - Works with any .NET application type (Web, Console, Desktop, Services)
  • โšก Minimal Dependencies - Only essential EF Core and DI dependencies
  • ๐Ÿงช Production Proven - Powers enterprise solutions like Diiwo.Identity

๐Ÿš€ Quick Start

Installation

# Install via .NET CLI
dotnet add package Diiwo.Core

# Install via Package Manager Console
Install-Package Diiwo.Core

# Install via PackageReference
<PackageReference Include="Diiwo.Core" Version="0.2.0" />
Option 2: GitHub Packages
# Add GitHub Packages as a source
dotnet nuget add source https://nuget.pkg.github.com/Diiwo/index.json --name github --username YOUR_GITHUB_USERNAME --password YOUR_GITHUB_TOKEN

# Install via .NET CLI
dotnet add package Diiwo.Core --version 0.2.0
Option 3: Project Reference
<ProjectReference Include="..\DIIWO-Core\DIIWO.Core.csproj" />
Option 4: GitHub Release

Download the .nupkg file from Releases and install locally:

dotnet add package Diiwo.Core --source /path/to/downloaded/packages

Basic Setup

// Program.cs or Startup.cs
using Diiwo.Core.Extensions;

// Register Diiwo.Core with your CurrentUserService implementation
services.AddDiiwoCore<MyCurrentUserService>();

// Or if you already have ICurrentUserService registered elsewhere
services.AddDiiwoCoreWithExistingUserService();

Your Entity

using Diiwo.Core.Domain.Entities;

// Simple entity with basic audit fields
public class Product : AuditableEntity
{
    public string Name { get; set; }
    public decimal Price { get; set; }
    public string Description { get; set; }
    
    // IsActive, CreatedAt, UpdatedAt, State automatically included!
}

// Entity with user tracking
public class Order : UserTrackedEntity  
{
    public string OrderNumber { get; set; }
    public decimal Total { get; set; }
    
    // Includes all AuditableEntity fields PLUS CreatedBy, UpdatedBy
}

// Multi-tenant entity
public class Document : UserOwnedEntity
{
    public string Title { get; set; }
    public string Content { get; set; }
    
    // Includes UserTrackedEntity fields PLUS UserId for ownership
}

DbContext Setup

using Diiwo.Core.Interceptors;
using Microsoft.EntityFrameworkCore;

public class MyDbContext : DbContext
{
    private readonly AuditInterceptor _auditInterceptor;

    public MyDbContext(DbContextOptions<MyDbContext> options, AuditInterceptor auditInterceptor) 
        : base(options)
    {
        _auditInterceptor = auditInterceptor;
    }

    public DbSet<Product> Products { get; set; }
    public DbSet<Order> Orders { get; set; }

    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    {
        // Add audit interceptor for automatic audit trail population
        optionsBuilder.AddInterceptors(_auditInterceptor);
    }
}

๐ŸŽฏ Zero-Code Enterprise Audit Trail

// โœ… Enterprise-grade auditing with ZERO manual code!
var product = new Product
{
    Name = "iPhone 15",
    Price = 999.00m,
    Description = "Latest iPhone"
    // โœ… NO audit code needed - everything automatic!
};

context.Products.Add(product);
await context.SaveChangesAsync();  // ๐ŸŽฏ Triggers AuditInterceptor

// โœ… Automatically populated by Diiwo.Core:
// product.Id = Guid.NewGuid()
// product.CreatedAt = DateTime.UtcNow
// product.UpdatedAt = DateTime.UtcNow
// product.State = EntityState.Active
// product.CreatedBy = currentUserId (from ICurrentUserService)
// product.UpdatedBy = currentUserId

// โœ… Enterprise benefits:
// - Complete audit trail for compliance
// - User attribution for security
// - State management for lifecycle tracking
// - Soft delete preserves data history

๐Ÿ—๏ธ Architecture Overview

Base Entity Hierarchy

BaseEntity (Id, CreatedAt, UpdatedAt)
โ”œโ”€โ”€ AuditableEntity (+ State, Soft Delete)
    โ”œโ”€โ”€ UserTrackedEntity (+ CreatedBy, UpdatedBy)
        โ”œโ”€โ”€ DomainEntity (for business entities)
        โ””โ”€โ”€ UserOwnedEntity (+ UserId for multi-tenant)

Entity States

public enum EntityState
{
    Created = 0,     // Created but not yet active
    Inactive = 1,    // Temporarily inactive  
    Active = 2,      // Active and available
    Effective = 3,   // Effective and operational
    Terminated = 4   // Soft deleted
}

Entity State Management

Entities automatically transition through states during their lifecycle:

  • Created - Initial state when entity is created
  • Inactive - Temporarily disabled but can be reactivated
  • Active - Normal operational state (default)
  • Effective - Fully operational and effective
  • Terminated - Soft deleted, preserves audit history

๐Ÿ“– Usage Examples

Implementing ICurrentUserService

// For ASP.NET Core applications
public class WebCurrentUserService : ICurrentUserService
{
    private readonly IHttpContextAccessor _httpContextAccessor;

    public WebCurrentUserService(IHttpContextAccessor httpContextAccessor)
    {
        _httpContextAccessor = httpContextAccessor;
    }

    public Guid? UserId
    {
        get
        {
            var userIdClaim = _httpContextAccessor.HttpContext?.User?
                .FindFirst(ClaimTypes.NameIdentifier)?.Value;
            return Guid.TryParse(userIdClaim, out var userId) ? userId : null;
        }
    }

    public string? UserName => _httpContextAccessor.HttpContext?.User?.Identity?.Name;
    public string? UserEmail => _httpContextAccessor.HttpContext?.User?
        .FindFirst(ClaimTypes.Email)?.Value;
    public bool IsAuthenticated => _httpContextAccessor.HttpContext?.User?.Identity?.IsAuthenticated ?? false;
    
    public Task<bool> IsInRoleAsync(string role)
    {
        var user = _httpContextAccessor.HttpContext?.User;
        return Task.FromResult(user?.IsInRole(role) ?? false);
    }
}

// For Console/Service applications  
public class SystemCurrentUserService : ICurrentUserService
{
    public Guid? UserId => Guid.Parse("00000000-0000-0000-0000-000000000001"); // System user
    public string? UserName => "System";
    public string? UserEmail => "system@diiwo.com";
    public bool IsAuthenticated => true;
    public Task<bool> IsInRoleAsync(string role) => Task.FromResult(true);
}

Working with Soft Deletes

// Instead of hard delete, mark as terminated
product.SoftDelete(); // Sets State = EntityState.Terminated
await context.SaveChangesAsync();

// Query only active entities
var activeProducts = await context.Products
    .Where(p => p.IsActive)  // Built-in property
    .ToListAsync();

// Include soft-deleted entities
var allProducts = await context.Products
    .IgnoreQueryFilters()
    .ToListAsync();

// Restore soft-deleted entity
product.Restore(); // Sets State = EntityState.Active  
await context.SaveChangesAsync();

Entity State Control

// Deactivate entity temporarily
product.State = EntityState.Inactive;
await context.SaveChangesAsync();

// Check state in your business logic
if (!product.IsActive)
{
    throw new InvalidOperationException("Product is not available");
}

// Reactivate entity
product.State = EntityState.Active;
await context.SaveChangesAsync();

Multi-Tenant Usage

// Create user-owned entity
var document = new Document 
{
    Title = "My Document",
    Content = "...",
    UserId = currentUserId  // Automatically set by UserOwnedEntity
};

// Check ownership
if (document.IsOwnedBy(currentUserId))
{
    // User can access this document
}

// Query user's documents
var userDocs = await context.Documents
    .Where(d => d.UserId == currentUserId || d.IsGlobal)
    .ToListAsync();

Using API Response Patterns (v0.2.0)

using Diiwo.Core.Responses;

// Success response
[HttpGet("{id}")]
public async Task<ActionResult> GetUser(Guid id)
{
    var user = await _userService.GetByIdAsync(id);
    if (user == null)
        return NotFound(ApiResponse<User>.ErrorResponse("User not found"));

    return Ok(ApiResponse<User>.SuccessResponse(user, "User retrieved successfully"));
}

// Paginated response
[HttpGet]
public async Task<ActionResult> GetUsers(int page = 1, int pageSize = 10)
{
    var users = await _userService.GetPagedAsync(page, pageSize);
    var totalCount = await _userService.CountAsync();

    return Ok(PagedResponse<User>.Create(users, page, pageSize, totalCount));
}

// Validation response
[HttpPost]
public async Task<ActionResult> CreateUser(UserDto dto)
{
    if (!ModelState.IsValid)
    {
        var errors = ModelState.ToDictionary(
            kvp => kvp.Key,
            kvp => kvp.Value.Errors.Select(e => e.ErrorMessage).ToList()
        );
        return BadRequest(ValidationResponse.Create(errors));
    }

    // ... create user
}

Using Exception Classes (v0.2.0)

using Diiwo.Core.Exceptions;

// Service layer
public class UserService
{
    public async Task<User> GetByIdAsync(Guid id)
    {
        var user = await _repository.FindAsync(id);
        if (user == null)
            throw new NotFoundException("User", id);

        return user;
    }

    public async Task<User> CreateAsync(string email)
    {
        var existing = await _repository.FindByEmailAsync(email);
        if (existing != null)
            throw new ConflictException("User", "Email", email);

        // ... create user
    }

    public async Task ValidateAsync(UserDto dto)
    {
        var errors = new Dictionary<string, List<string>>();

        if (string.IsNullOrEmpty(dto.Email))
            errors.Add(nameof(dto.Email), new List<string> { "Email is required" });

        if (errors.Any())
            throw new ValidationException(errors);
    }
}

// Global exception handler middleware
public class ExceptionMiddleware
{
    public async Task InvokeAsync(HttpContext context)
    {
        try
        {
            await _next(context);
        }
        catch (NotFoundException ex)
        {
            context.Response.StatusCode = 404;
            await context.Response.WriteAsJsonAsync(
                ApiResponse<object>.ErrorResponse(ex.Message)
            );
        }
        catch (ValidationException ex)
        {
            context.Response.StatusCode = 400;
            await context.Response.WriteAsJsonAsync(
                ValidationResponse.Create(ex.ValidationErrors)
            );
        }
        catch (UnauthorizedException ex)
        {
            context.Response.StatusCode = 401;
            await context.Response.WriteAsJsonAsync(
                ApiResponse<object>.ErrorResponse(ex.Message)
            );
        }
        catch (ConflictException ex)
        {
            context.Response.StatusCode = 409;
            await context.Response.WriteAsJsonAsync(
                ApiResponse<object>.ErrorResponse(ex.Message)
            );
        }
        catch (BusinessException ex)
        {
            context.Response.StatusCode = 400;
            await context.Response.WriteAsJsonAsync(
                ApiResponse<object>.ErrorResponse(ex.Message)
            );
        }
    }
}

๐Ÿ”ง Advanced Configuration

Custom Audit Behavior

public class CustomAuditInterceptor : AuditInterceptor
{
    public CustomAuditInterceptor(ICurrentUserService currentUserService) 
        : base(currentUserService) { }

    protected override void UpdateAuditFields(DbContext? context)
    {
        base.UpdateAuditFields(context);
        
        // Add custom audit logic here
        foreach (var entry in context.ChangeTracker.Entries<IAuditable>())
        {
            if (entry.State == EntityState.Added)
            {
                // Custom creation logic
            }
        }
    }
}

// Register custom interceptor
services.AddScoped<AuditInterceptor, CustomAuditInterceptor>();

Multiple DbContexts

// Each DbContext can have its own audit interceptor
services.AddDbContext<ProductDbContext>((provider, options) =>
{
    var interceptor = provider.GetRequiredService<AuditInterceptor>();
    options.UseSqlServer(connectionString).AddInterceptors(interceptor);
});

services.AddDbContext<OrderDbContext>((provider, options) =>
{
    var interceptor = provider.GetRequiredService<AuditInterceptor>();
    options.UseNpgsql(connectionString).AddInterceptors(interceptor);
});

๐Ÿข Enterprise Identity Integration

Diiwo.Core powers the enterprise features in Diiwo.Identity, providing automatic audit trails for complete identity management solutions.

Real-World Example: User Management with Audit Trail

// Using Diiwo.Identity with Diiwo.Core automatic auditing
public class AppUser : DomainEntity  // โœ… Inherits full audit capabilities
{
    public required string Email { get; set; }
    public required string PasswordHash { get; set; }
    public string? FirstName { get; set; }
    public string? LastName { get; set; }

    // โœ… CreatedAt, UpdatedAt, CreatedBy, UpdatedBy automatically managed!
    // โœ… Soft delete preserves user history for compliance
    // โœ… State management tracks user lifecycle
}

// Service layer - zero manual audit code needed!
public class UserService
{
    public async Task<AppUser> CreateUserAsync(string email, string password)
    {
        var user = new AppUser
        {
            Email = email,
            PasswordHash = hashPassword(password)
            // โœ… All audit fields populated automatically by AuditInterceptor!
        };

        _context.Users.Add(user);
        await _context.SaveChangesAsync();  // Triggers automatic audit population

        return user;  // user.CreatedAt, CreatedBy, etc. are now populated
    }

    public async Task DeactivateUserAsync(Guid userId)
    {
        var user = await _context.Users.FindAsync(userId);
        user.State = EntityState.Terminated;  // Soft delete

        await _context.SaveChangesAsync();
        // โœ… UpdatedAt and UpdatedBy automatically set!
        // โœ… User preserved for audit/compliance requirements
    }
}

Enterprise Permission System with Audit

// Permission entities with full audit trail
public class AppPermission : DomainEntity
{
    public required string Resource { get; set; }
    public required string Action { get; set; }
    // โœ… Every permission change tracked automatically
}

public class AppUserPermission : DomainEntity
{
    public Guid UserId { get; set; }
    public Guid PermissionId { get; set; }
    public bool IsGranted { get; set; }
    // โœ… Full audit trail for permission assignments
}

// Permission assignments automatically audited
await _permissionService.GrantUserPermissionAsync(userId, permissionId);
// โœ… Who granted the permission and when - automatically tracked!

Compliance & Regulatory Requirements

// Query audit trail for compliance reports
var userAuditTrail = await _context.Users
    .Where(u => u.Id == userId)
    .Select(u => new AuditReport
    {
        EntityId = u.Id,
        CreatedAt = u.CreatedAt,
        CreatedBy = u.CreatedBy,
        LastUpdatedAt = u.UpdatedAt,
        LastUpdatedBy = u.UpdatedBy,
        CurrentState = u.State,
        IsActive = u.IsActive
    })
    .FirstOrDefaultAsync();

// Soft-deleted entities preserved for audit
var deletedUsers = await _context.Users
    .IgnoreQueryFilters()  // Include soft-deleted
    .Where(u => u.State == EntityState.Terminated)
    .ToListAsync();
// โœ… Full history preserved for regulatory compliance

๐Ÿ›๏ธ Integration Examples

With Clean Architecture

// Domain Layer - Pure entities
public class Customer : DomainEntity
{
    public string Name { get; private set; }
    public Email Email { get; private set; }
    
    // Domain methods
    public void UpdateEmail(Email newEmail)
    {
        Email = newEmail;
        // UpdatedAt and UpdatedBy automatically set on save
    }
}

// Infrastructure Layer - DbContext
public class ApplicationDbContext : DbContext
{
    // ... DbContext implementation with AuditInterceptor
}

With MediatR

public class CreateProductCommand : IRequest<Guid>
{
    public string Name { get; set; }
    public decimal Price { get; set; }
}

public class CreateProductHandler : IRequestHandler<CreateProductCommand, Guid>
{
    private readonly ApplicationDbContext _context;
    
    public async Task<Guid> Handle(CreateProductCommand request, CancellationToken cancellationToken)
    {
        var product = new Product 
        {
            Name = request.Name,
            Price = request.Price
            // Audit fields automatically populated
        };
        
        _context.Products.Add(product);
        await _context.SaveChangesAsync(cancellationToken);
        
        return product.Id;
    }
}

With Repository Pattern

public interface IRepository<T> where T : BaseEntity
{
    Task<T?> GetByIdAsync(Guid id);
    Task<IEnumerable<T>> GetActiveAsync();
    Task AddAsync(T entity);
    Task UpdateAsync(T entity);
    Task SoftDeleteAsync(Guid id);
}

public class Repository<T> : IRepository<T> where T : AuditableEntity
{
    private readonly DbContext _context;
    
    public async Task<IEnumerable<T>> GetActiveAsync()
    {
        return await _context.Set<T>()
            .Where(x => x.IsActive)
            .ToListAsync();
    }
    
    public async Task SoftDeleteAsync(Guid id)
    {
        var entity = await GetByIdAsync(id);
        if (entity != null)
        {
            entity.SoftDelete(); // Uses Diiwo.Core functionality
            await _context.SaveChangesAsync();
        }
    }
}

๐ŸŒ Framework Compatibility

Framework Support Notes
ASP.NET Core โœ… Full Recommended for web applications
Console Apps โœ… Full Perfect for background services
Desktop (WPF/WinUI) โœ… Full Great for desktop applications
Blazor โœ… Full Works with both Server and WebAssembly
Worker Services โœ… Full Ideal for background processing
Azure Functions โœ… Full Serverless applications
Minimal APIs โœ… Full Lightweight web APIs

๐Ÿ“Š Performance

  • Zero Runtime Overhead - Entities are POCOs with no runtime proxies
  • Minimal Memory Footprint - Only essential properties added to base entities
  • EF Core Optimized - Interceptors use EF Core's native change tracking
  • Lazy Evaluation - Computed properties use lazy evaluation where possible

๐Ÿ› ๏ธ Contributing

We welcome contributions! Please see our Contributing Guide for details.

Development Setup

# Clone the repository
git clone https://github.com/diiwo/diiwo-core.git
cd diiwo-core

# Restore dependencies
dotnet restore

# Build the project
dotnet build

# Run tests
dotnet test
  • Diiwo.Identity - Complete identity management with dual architectures

๐Ÿ“„ License

This project is licensed under the MIT License - see the LICENSE file for details.

๐Ÿ†˜ Support


<div align="center">

Built with โค๏ธ by the DIIWO Team

</div>

Product 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.  net9.0 was computed.  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. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages (1)

Showing the top 1 NuGet packages that depend on Diiwo.Core:

Package Downloads
Diiwo.Identity

Dual-architecture user management and authentication library for Diiwo applications. Choose between App (simple) or AspNet (enterprise) architectures.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
0.2.0 298 10/26/2025
0.1.0 327 10/22/2025

Version 0.2.0 - Added Response Patterns and Exceptions:
     โ€ข Added ApiResponse<T> for standardized API responses
     โ€ข Added PagedResponse<T> for paginated data
     โ€ข Added ValidationResponse for validation errors
     โ€ข Added standard exceptions (BusinessException, NotFoundException, UnauthorizedException, ValidationException, ConflictException)
     โ€ข Improved error handling patterns

     Version 0.1.0 - Initial Release:
     โ€ข Universal base entities (BaseEntity, AuditableEntity, UserTrackedEntity, DomainEntity, UserOwnedEntity)
     โ€ข Automatic audit trail with AuditInterceptor
     โ€ข Built-in soft delete functionality
     โ€ข Entity state management and locking
     โ€ข Multi-tenant support with UserOwnedEntity
     โ€ข Framework-agnostic design with minimal dependencies
     โ€ข Full Entity Framework Core integration
     โ€ข Comprehensive documentation and examples