Receber por email
← Voltar para o catálogo

dotnet-audit-trail

Generates audit trail infrastructure for entities. Implements IAuditable interface, EF Core SaveChanges interceptor, and automatic population of CreatedAt, UpdatedAt, CreatedBy, and UpdatedBy fields.

66estrelas
Atualizado há 5 meses

Ver no GitHub ↗

Como adicionar

/plugin marketplace add ronnythedev/dotnet-clean-architecture-skills

O comando exato pode variar conforme o repositório. Confira o README no GitHub.

Para o autor da skill

Cole no README do seu repo

Mostra que sua skill está catalogada na Skillteca, gera backlink e tráfego rastreável.

[![Skillteca](https://www.skillteca.com.br/api/badge/dotnet-audit-trail/svg)](https://www.skillteca.com.br/skills/dotnet-audit-trail)

Skills relacionadas

Ver todas de Desenvolvimento →

Alerta por categoria

Receba novas skills de Desenvolvimento toda segunda

1 email curto, só com as skills novas de Desenvolvimento. 4 minutos de leitura, sem spam, cancela com 1 clique.

Você confirma o email no primeiro envio. Sem spam. Cancela com 1 clique.

Ler descrição completa

Audit Trail Generator

Overview

Automatic audit trail tracking for entities:

  • IAuditable interface - Standard audit fields
  • SaveChanges interceptor - Automatic field population
  • User context integration - Track who made changes
  • Soft delete support - Track deletions without removing

Quick Reference

| Component | Purpose | |-----------|---------| | IAuditable | Interface for auditable entities | | AuditableEntity | Base class with audit fields | | AuditSaveChangesInterceptor | Auto-populates audit fields | | SoftDeletable | Interface for soft delete |

Audit Structure

/Domain/Abstractions/
├── IAuditable.cs
├── ISoftDeletable.cs
└── AuditableEntity.cs

/Infrastructure/
├── Interceptors/
│   └── AuditSaveChangesInterceptor.cs
└── ApplicationDbContext.cs

Template: Audit Interfaces

// src/{name}.domain/Abstractions/IAuditable.cs
namespace {name}.domain.abstractions;

/// <summary>
/// Interface for entities that track creation and modification metadata
/// </summary>
public interface IAuditable
{
    /// <summary>
    /// UTC timestamp when the entity was created
    /// </summary>
    DateTime CreatedAtUtc { get; }

    /// <summary>
    /// ID of the user who created the entity
    /// </summary>
    Guid? CreatedBy { get; }

    /// <summary>
    /// UTC timestamp when the entity was last modified
    /// </summary>
    DateTime? UpdatedAtUtc { get; }

    /// <summary>
    /// ID of the user who last modified the entity
    /// </summary>
    Guid? UpdatedBy { get; }
}
// src/{name}.domain/Abstractions/ISoftDeletable.cs
namespace {name}.domain.abstractions;

/// <summary>
/// Interface for entities that support soft delete
/// </summary>
public interface ISoftDeletable
{
    /// <summary>
    /// Whether the entity has been soft deleted
    /// </summary>
    bool IsDeleted { get; }

    /// <summary>
    /// UTC timestamp when the entity was deleted
    /// </summary>
    DateTime? DeletedAtUtc { get; }

    /// <summary>
    /// ID of the user who deleted the entity
    /// </summary>
    Guid? DeletedBy { get; }
}

Template: Auditable Entity Base Class

// src/{name}.domain/Abstractions/AuditableEntity.cs
namespace {name}.domain.abstractions;

/// <summary>
/// Base class for entities that track audit information
/// </summary>
public abstract class AuditableEntity : Entity, IAuditable, ISoftDeletable
{
    // ═══════════════════════════════════════════════════════════════
    // AUDIT FIELDS (IAuditable)
    // ═══════════════════════════════════════════════════════════════
    
    public DateTime CreatedAtUtc { get; private set; }
    public Guid? CreatedBy { get; private set; }
    public DateTime? UpdatedAtUtc { get; private set; }
    public Guid? UpdatedBy { get; private set; }

    // ═══════════════════════════════════════════════════════════════
    // SOFT DELETE FIELDS (ISoftDeletable)
    // ═══════════════════════════════════════════════════════════════
    
    public bool IsDeleted { get; private set; }
    public DateTime? DeletedAtUtc { get; private set; }
    public Guid? DeletedBy { get; private set; }

    protected AuditableEntity() : base()
    {
    }

    protected AuditableEntity(Guid id) : base(id)
    {
    }

    // ═══════════════════════════════════════════════════════════════
    // AUDIT METHODS (called by interceptor or manually)
    // ═══════════════════════════════════════════════════════════════

    /// <summary>
    /// Sets creation audit fields. Called automatically by interceptor.
    /// </summary>
    internal void SetCreatedAudit(DateTime utcNow, Guid? userId)
    {
        CreatedAtUtc = utcNow;
        CreatedBy = userId;
    }

    /// <summary>
    /// Sets modification audit fields. Called automatically by interceptor.
    /// </summary>
    internal void SetModifiedAudit(DateTime utcNow, Guid? userId)
    {
        UpdatedAtUtc = utcNow;
        UpdatedBy = userId;
    }

    /// <summary>
    /// Soft deletes the entity
    /// </summary>
    public virtual void SoftDelete(DateTime utcNow, Guid? userId)
    {
        if (IsDeleted)
        {
            return;
        }

        IsDeleted = true;
        DeletedAtUtc = utcNow;
        DeletedBy = userId;
    }

    /// <summary>
    /// Restores a soft-deleted entity
    /// </summary>
    public virtual void Restore()
    {
        IsDeleted = false;
        DeletedAtUtc = null;
        DeletedBy = null;
    }
}

Template: SaveChanges Interceptor

// src/{name}.infrastructure/Interceptors/AuditSaveChangesInterceptor.cs
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.ChangeTracking;
using Microsoft.EntityFrameworkCore.Diagnostics;
using {name}.application.abstractions.authentication;
using {name}.domain.abstractions;

namespace {name}.infrastructure.interceptors;

/// <summary>
/// Interceptor that automatically populates audit fields on SaveChanges
/// </summary>
public sealed class AuditSaveChangesInterceptor : SaveChangesInterceptor
{
    private readonly IUserContext _userContext;

    public AuditSaveChangesInterceptor(IUserContext userContext)
    {
        _userContext = userContext;
    }

    public override ValueTask<InterceptionResult<int>> SavingChangesAsync(
        DbContextEventData eventData,
        InterceptionResult<int> result,
        CancellationToken cancellationToken = default)
    {
        if (eventData.Context is not null)
        {
            UpdateAuditFields(eventData.Context);
        }

        return base.SavingChangesAsync(eventData, result, cancellationToken);
    }

    public override InterceptionResult<int> SavingChanges(
        DbContextEventData eventData,
        InterceptionResult<int> result)
    {
        if (eventData.Context is not null)
        {
            UpdateAuditFields(eventData.Context);
        }

        return base.SavingChanges(eventData, result);
    }

    private void UpdateAuditFields(DbContext context)
    {
        var utcNow = DateTime.UtcNow;
        var userId = GetCurrentUserId();

        foreach (var entry in context.ChangeTracker.Entries<IAuditable>())
        {
            switch (entry.State)
            {
                case EntityState.Added:
                    SetCreatedAudit(entry, utcNow, userId);
                    break;

                case EntityState.Modified:
                    SetModifiedAudit(entry, utcNow, userId);
                    break;
            }
        }

        // Handle soft delete
        foreach (var entry in context.ChangeTracker.Entries<ISoftDeletable>())
        {
            if (entry.State == EntityState.Deleted)
            {
                // Convert hard delete to soft delete
                entry.State = EntityState.Modified;
                
                if (entry.Entity is AuditableEntity auditableEntity)
                {
                    auditableEntity.SoftDelete(utcNow, userId);
                }
            }
        }
    }

    private void SetCreatedAudit(EntityEntry<IAuditable> entry, DateTime utcNow, Guid? userId)
    {
        if (entry.Entity is AuditableEntity auditableEntity)
        {
            auditableEntity.SetCreatedAudit(utcNow, userId);
        }
        else
        {
            // For entities implementing IAuditable but not inheriting AuditableEntity
            entry.Property(nameof(IAuditable.CreatedAtUtc)).CurrentValue = utcNow;
            entry.Property(nameof(IAuditable.CreatedBy)).CurrentValue = userId;
        }
    }

    private void SetModifiedAudit(EntityEntry<IAuditable> entry, DateTime utcNow, Guid? userId)
    {
        if (entry.Entity is AuditableEntity auditableEntity)
        {
            auditableEntity.SetModifiedAudit(utcNow, userId);
        }
        else
        {
            entry.Property(nameof(IAuditable.UpdatedAtUtc)).CurrentValue = utcNow;
            entry.Property(nameof(IAuditable.UpdatedBy)).Curr
Compartilhar X LinkedIn