Search code examples
auditaudit-trailaudit-logging

How to create audit log / audit trail in asp.net mvc


When we are used code first or entity framework then there is easiest way to audit trail the actions like add , update and delete.


Solution

  • Create a class for capture the changes or track the changes when entity added, modifies or deleted.

    using System;
    using System.Collections.Generic;
    using System.ComponentModel.DataAnnotations.Schema;
    using System.Data.Entity;
    using System.Data.Entity.Infrastructure;
    using System.Linq;
    using System.Text;
    using System.Web;
    
    namespace MVC_AuditTrail.Models
    {
    public class AuditTrailFactory
    {
        private DbContext context;
    
        public AuditTrailFactory(DbContext context)
        {
            this.context = context;
        }
        public Audit GetAudit(DbEntityEntry entry)
        {
            Audit audit = new Audit();
            // var user = (User)HttpContext.Current.Session[":user"];
            audit.UserId = "swapnil";// user.UserName;
            audit.TableName = GetTableName(entry);
            audit.UpdateDate = DateTime.Now;
            audit.TableIdValue = GetKeyValue(entry);
    
            //entry is Added 
            if (entry.State == EntityState.Added)
            {
                var newValues = new StringBuilder();
                SetAddedProperties(entry, newValues);
                audit.NewData = newValues.ToString();
                audit.Actions = AuditActions.I.ToString();
            }
            //entry in deleted
            else if (entry.State == EntityState.Deleted)
            {
                var oldValues = new StringBuilder();
                SetDeletedProperties(entry, oldValues);
                audit.OldData = oldValues.ToString();
                audit.Actions = AuditActions.D.ToString();
            }
            //entry is modified
            else if (entry.State == EntityState.Modified)
            {
                var oldValues = new StringBuilder();
                var newValues = new StringBuilder();
                SetModifiedProperties(entry, oldValues, newValues);
                audit.OldData = oldValues.ToString();
                audit.NewData = newValues.ToString();
                audit.Actions = AuditActions.U.ToString();
            }
    
            return audit;
        }
    
        private void SetAddedProperties(DbEntityEntry entry, StringBuilder newData)
        {
            foreach (var propertyName in entry.CurrentValues.PropertyNames)
            {
                var newVal = entry.CurrentValues[propertyName];
                if (newVal != null)
                {
                    newData.AppendFormat("{0}={1} || ", propertyName, newVal);
                }
            }
            if (newData.Length > 0)
                newData = newData.Remove(newData.Length - 3, 3);
        }
    
        private void SetDeletedProperties(DbEntityEntry entry, StringBuilder oldData)
        {
            DbPropertyValues dbValues = entry.GetDatabaseValues();
            foreach (var propertyName in dbValues.PropertyNames)
            {
                var oldVal = dbValues[propertyName];
                if (oldVal != null)
                {
                    oldData.AppendFormat("{0}={1} || ", propertyName, oldVal);
                }
            }
            if (oldData.Length > 0)
                oldData = oldData.Remove(oldData.Length - 3, 3);
        }
    
        private void SetModifiedProperties(DbEntityEntry entry, StringBuilder oldData, StringBuilder newData)
        {
            DbPropertyValues dbValues = entry.GetDatabaseValues();
            foreach (var propertyName in entry.OriginalValues.PropertyNames)
            {
                var oldVal = dbValues[propertyName];
                var newVal = entry.CurrentValues[propertyName];
                if (oldVal != null && newVal != null && !Equals(oldVal, newVal))
                {
                    newData.AppendFormat("{0}={1} || ", propertyName, newVal);
                    oldData.AppendFormat("{0}={1} || ", propertyName, oldVal);
                }
            }
            if (oldData.Length > 0)
                oldData = oldData.Remove(oldData.Length - 3, 3);
            if (newData.Length > 0)
                newData = newData.Remove(newData.Length - 3, 3);
        }
    
        public long? GetKeyValue(DbEntityEntry entry)
        {
            var objectStateEntry = ((IObjectContextAdapter)context).ObjectContext.ObjectStateManager.GetObjectStateEntry(entry.Entity);
            long id = 0;
            if (objectStateEntry.EntityKey.EntityKeyValues != null)
                id = Convert.ToInt64(objectStateEntry.EntityKey.EntityKeyValues[0].Value);
    
            return id;
        }
    
        private string GetTableName(DbEntityEntry dbEntry)
        {
            TableAttribute tableAttr = dbEntry.Entity.GetType().GetCustomAttributes(typeof(TableAttribute), false).SingleOrDefault() as TableAttribute;
            string tableName = tableAttr != null ? tableAttr.Name : dbEntry.Entity.GetType().Name;
            return tableName;
        }
    }
    
    public enum AuditActions
    {
        I,
        U,
        D
    }
    }
    

    Then create audit table entity and context class.

    And Override savechanges method in this method get audit changes and save before base entity saved.

    using System;
    using System.Collections.Generic;
    using System.Data.Entity;
    using System.Data.Entity.Infrastructure;
    using System.Linq;
    using System.Web;
    
    namespace MVC_AuditTrail.Models
    {
        public class Student
        {
            public int StudentID { get; set; }
    
            public string Name { get; set; }
    
            public string  mobile { get; set; }
        }
    
        public  class Audit
        {
            public long Id { get; set; }
            public string TableName { get; set; }
            public string UserId { get; set; }
            public string Actions { get; set; }
            public string OldData { get; set; }
            public string NewData { get; set; }
            public Nullable<long> TableIdValue { get; set; }
            public Nullable<System.DateTime> UpdateDate { get; set; }
        }
    
    
        public class StdContext : DbContext
        {
            private AuditTrailFactory auditFactory;
            private List<Audit> auditList = new List<Audit>();
            private List<DbEntityEntry> objectList = new List<DbEntityEntry>();
            public StdContext() : base("stdConnection")
            {
                Database.SetInitializer<StdContext>(new CreateDatabaseIfNotExists<StdContext>());
            }
    
            public DbSet<Student> Student { get; set; }
            public DbSet<Audit> Audit { get; set; }
    
            public override int SaveChanges()
            {
                auditList.Clear();
                objectList.Clear();
                auditFactory = new AuditTrailFactory(this);
    
                var entityList = ChangeTracker.Entries().Where(p => p.State == EntityState.Added || p.State == EntityState.Deleted || p.State == EntityState.Modified);
                foreach (var entity in entityList)
                {
                    Audit audit = auditFactory.GetAudit(entity);
                    bool isValid = true;
                    if (entity.State == EntityState.Modified && string.IsNullOrWhiteSpace(audit.NewData) && string.IsNullOrWhiteSpace(audit.OldData))
                    {
                        isValid = false;
                    }
                    if (isValid)
                    {
                        auditList.Add(audit);
                        objectList.Add(entity);
                    }
                }
    
                var retVal = base.SaveChanges();
                if (auditList.Count > 0)
                {
                    int i = 0;
                    foreach (var audit in auditList)
                    {
                        if (audit.Actions == AuditActions.I.ToString())
                            audit.TableIdValue = auditFactory.GetKeyValue(objectList[i]);
                        this.Audit.Add(audit);
                        i++;
                    }
    
                    base.SaveChanges();
                }
    
                return retVal;
            }
        }
    }