As a Laravel user for quite sometime, it's quite easy for me to add User creator, modifier and timestamp for each model.
In the case of .Net 6 (or even earlier) it can be handled at DbContext and creating your base class that has the generic properties for the timestamps (created at, modified at) and user details (created_by / modified_by)
using System.ComponentModel.DataAnnotations;
namespace YourApp.Models{
public class BaseEntity
{
public DateTime DateCreated { get; set; }
[Required]
//id of the user created
public string UserCreated { get; set; }
public DateTime? DateModified { get; set; }
[Required]
//id of the user edited
public string UserModified { get; set; }
}
}
Below is my subclass using the super class BaseEntity
using System.ComponentModel.DataAnnotations;using System.ComponentModel.DataAnnotations.Schema;namespace YourApp.Models;/** Patient Model*/public class MyCustomModel : BaseEntity{[StringLength(125)]public string FirstName { get; set; }[StringLength(125)]public string LastName { get; set; }[StringLength(125)]public string Middlename { get; set; } = string.Empty;}
So how to automatically do the timestamps and user details? I implemented the tutorial from the blog that I followed here. According to the blog, you can implement it at the override of your DbContext' SaveChangesAsync.
My DbContext below. I highlighted from the code below the important parts where the magic happens.
using Microsoft.EntityFrameworkCore;using YourApp.Models;using System.Security.Claims;namespace YourApp.Contexts{public class DataContext : DbContext{private readonly IHttpContextAccessor _httpContextAccessor;public DataContext(DbContextOptions <DataContext> options, IHttpContextAccessor httpContextAccessor) : base(options){_httpContextAccessor = httpContextAccessor;}public DbSet<MyCustomModel > CustomModels{ get; set; }protected override void OnModelCreating(ModelBuilder builder){base.OnModelCreating(builder);}public override Task<int> SaveChangesAsync(CancellationToken cancellationToken = default){var insertedEntries = this.ChangeTracker.Entries().Where(x => x.State == EntityState.Added).Select(x => x.Entity);var userId = _httpContextAccessor.HttpContext.User.FindFirst(ClaimTypes.NameIdentifier).Value;var currentUsername = !string.IsNullOrEmpty(userId)? userId: "Anonymous";foreach (var insertedEntry in insertedEntries){var auditableEntity = insertedEntry as BaseEntity;//If the inserted object is an Auditable.if (auditableEntity != null){auditableEntity.DateCreated = DateTime.Now;auditableEntity.UserCreated = currentUsername;auditableEntity.UserModified = currentUsername;}}var modifiedEntries = this.ChangeTracker.Entries().Where(x => x.State == EntityState.Modified).Select(x => x.Entity);foreach (var modifiedEntry in modifiedEntries){//If the inserted object is an Auditable.var auditableEntity = modifiedEntry as BaseEntity;if (auditableEntity != null){auditableEntity.DateModified = DateTime.Now;auditableEntity.UserModified = currentUsername;}}return base.SaveChangesAsync(cancellationToken);}}}
Aside from that, ensure also from your login that ClaimTypes.NameIdentifier is not empty
var userId = _httpContextAccessor.HttpContext.User.FindFirst(ClaimTypes.NameIdentifier).Value;
I set my value during login. See login method below.
[HttpPost][Route("login")]public async Task<IActionResult> Login([FromBody] UserLogin model){var user = await _userManager.FindByNameAsync(model.Username);if (user != null && await _userManager.CheckPasswordAsync(user, model.Password)){var userRoles = await _userManager.GetRolesAsync(user);var authClaims = new List<Claim>{new Claim(ClaimTypes.Name, user.UserName),new Claim(ClaimTypes.NameIdentifier, user.Id),new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()),};foreach (var userRole in userRoles){authClaims.Add(new Claim(ClaimTypes.Role, userRole));}var token = GetToken(authClaims);return Ok(new{token = new JwtSecurityTokenHandler().WriteToken(token),expiration = token.ValidTo});}return Unauthorized();}
That pretty much of it. Hope that helps you too.