Friday, March 18, 2022

Note : .Net 6 / C# Adding timestamp and user created modified to when saving data

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.

No comments:

Post a Comment