ASP.NET Core 3.1 Clean Architecture – Invoice Management App (Part 2 Auditing in EF Core with CreatedBy and LastModifiedBy)

In the previous article ASP.NET Core 3.1 Clean Architecture – Invoice Management App (Part 1) we structure our project to prepare for our clean architecture app. Now we will create our domain entities and implement EF Core auditing by saving the user who created the record and the last user who modified the record.

Advertisements

In the Domain Layer a new folder and name it Common. Then inside the Common folder, create a new class and name it AuditEntity.

public class AuditEntity
    {
        public string CreatedBy { get; set; }

        public DateTime Created { get; set; }

        public string LastModifiedBy { get; set; }

        public DateTime? LastModified { get; set; }
    }

Every entity that we want to have the functionality of audit will inherit our AuditEntity. In this project, we only have two entities and both of them will inherit AuditEntity.

Create a new folder and call it Entities. Then inside the entities folder create a new class Invoice.

public class Invoice : AuditEntity
    {
        public Invoice()
        {
            this.InvoiceItems = new List<InvoiceItem>();
        }

        public int Id { get; set; }
        public string InvoiceNumber { get; set; }
        public string Logo { get; set; }
        public string From { get; set; }
        public string To { get; set; }
        public DateTime Date { get; set; }
        public string PaymentTerms { get; set; }
        public DateTime? DueDate { get; set; }
        public DiscountType DiscountType { get; set; }
        public double Discount { get; set; }
        public TaxType TaxType { get; set; }
        public double Tax { get; set; }
        public double AmountPaid { get; set; }
        public IList<InvoiceItem> InvoiceItems { get; set; }
    }

then create another class called InvoiceItem

public class InvoiceItem : AuditEntity
    {
        public int Id { get; set; }
        public int InvoiceId { get; set; }
        public string Item { get; set; }
        public double Quantity { get; set; }
        public double Rate { get; set; }
        public Invoice Invoice { get; set; }
    }

In invoice we can add discount as percentage or flat rate. The same rule applies to tax. We will handle this by using an enum. Create a new folder called it Enums then add a new item called DiscountType.cs inside Enums folder.

public enum DiscountType
    {
        Flat,
        Percentage
    }

Add another file TaxType.cs in Enums folder.

public enum TaxType
    {
        Flat,
        Percentage
    }

If you notice, there is no validation or data annotation in my entities. This approach makes my domain entities independent to any framework like ef core or nhibernate. We still have data validation, but we will do it in Application layer with the help of a library called FluentValidation.

Now, your domain layer should look like this.

Advertisements

In Application layer (InvoiceManagementApp.Application). Install Microsoft.EntityFrameworkCore using Nuget Package Manager.

Add reference of Domain layer (InvoiceManagementApp.Domain) in Application layer.

Create a folder called Common. Then inside the Common folder add a folder called Interfaces. In Interfaces folder add an interface called IApplicationDbContext

public interface IApplicationDbContext
    {
        DbSet<Invoice> Invoices { get; set; }

        DbSet<InvoiceItem> InvoiceItems { get; set; }

        Task<int> SaveChangesAsync(CancellationToken cancellationToken);
    }

We will use this interface later in our existing DbContext in Infrastructure layer. We also need to override the SaveChangesAsync of ApplicationDbContext, that’s why we have to declare the method here in our interface.

Add another interface called ICurrentUserService. We will use this interface to get the current user id of the logged in user.

public interface ICurrentUserService
    {
        string UserId { get; }
    }
Advertisements

In InvoiceManagementApp.Infrastructure add reference for InvoiceManagementApp.Application

Update our ApplicationDbContext in InvoiceManagementApp.Infrastructure

public class ApplicationDbContext : ApiAuthorizationDbContext<ApplicationUser>, IApplicationDbContext
    {
        private readonly ICurrentUserService _currentUserService;
        public ApplicationDbContext(
            DbContextOptions options,
            IOptions<OperationalStoreOptions> operationalStoreOptions,
            ICurrentUserService currentUserService) : base(options, operationalStoreOptions)
        {
            _currentUserService = currentUserService;
        }

        public DbSet<Invoice> Invoices { get; set; }
        public DbSet<InvoiceItem> InvoiceItems { get; set; }

        public override Task<int> SaveChangesAsync(CancellationToken cancellationToken = new CancellationToken())
        {
            foreach (var entry in ChangeTracker.Entries<BaseEntity>())
            {
                switch (entry.State)
                {
                    case EntityState.Added:
                        entry.Entity.CreatedBy = _currentUserService.UserId;
                        entry.Entity.Created = DateTime.UtcNow;
                        break;
                    case EntityState.Modified:
                        entry.Entity.LastModifiedBy = _currentUserService.UserId;
                        entry.Entity.LastModified = DateTime.UtcNow;
                        break;
                }
            }

            return base.SaveChangesAsync(cancellationToken);
        }
    }

Now whenever we call the ApplicationDbContext Savechanges, it will check if the entity we are saving inheritsAuditEntity. If it does, the properties for audit will be populated before saving.

Register IApplicationDbContext in InvoiceManagementApp.Infrastructure dependency injection. Open the class DependencyInjection then add the dependency injection for IApplicationDbContext.

public static class DependencyInjection
    {
        public static IServiceCollection AddInfrastructure(this IServiceCollection services, IConfiguration configuration)
        {
            services.AddDbContext<ApplicationDbContext>(options =>
                options.UseSqlServer(
                    configuration.GetConnectionString("DefaultConnection"),
                    b => b.MigrationsAssembly(typeof(ApplicationDbContext).Assembly.FullName)));

            services.AddScoped<IApplicationDbContext>(provider => provider.GetService<ApplicationDbContext>());

            services.AddDefaultIdentity<ApplicationUser>()
                .AddEntityFrameworkStores<ApplicationDbContext>();

            services.AddIdentityServer()
                .AddApiAuthorization<ApplicationUser, ApplicationDbContext>();

            services.AddAuthentication()
                .AddIdentityServerJwt();

            return services;
        }
    }
Advertisements

In InvoiceManagementApp.Api, add reference for InvoiceManagementApp.Application.

Then add a folder called Services. Inside the Services folder add a new class called CurrentUserService. This class will implement our interface ICurrentUserService.

public class CurrentUserService : ICurrentUserService
    {
        public CurrentUserService(IHttpContextAccessor httpContextAccessor)
        {
            UserId = httpContextAccessor.HttpContext?.User?.FindFirstValue(ClaimTypes.NameIdentifier);
        }

        public string UserId { get; }
    }

Open the Startup.cs and register ICurrentUserService in the services.

public void ConfigureServices(IServiceCollection services)
        {
            services.AddApplication();
            services.AddInfrastructure(Configuration);

            services.AddScoped<ICurrentUserService, CurrentUserService>();

            services.AddControllersWithViews();
            services.AddRazorPages();

            // In production, the React files will be served from this directory
            services.AddSpaStaticFiles(configuration =>
            {
                configuration.RootPath = "ClientApp/build";
            });
        }

Since we made some changes in ApplicationDbContext earlier, we need to add migration to update our database. Make sure to set the InvoiceManagementApp.Infrastructure as Default project. Then update the database by running the code update-database.

In the next article ASP.NET Core 3.1 Clean Architecture – Invoice Management App (Part 3 MediatR and FluentValidation) we will create an api with the help of MediatR and FluentValidation for validating or model.

Advertisements

RELATED ARTICLES:

ASP.NET Core 3.1 Clean Architecture – Invoice Management App (Part 1)

ASP.NET Core 3.1 Clean Architecture – Invoice Management App (Part 2 Auditing in EF Core with CreatedBy and LastModifiedBy)

ASP.NET Core 3.1 Clean Architecture – Invoice Management App (Part 3 MediatR and FluentValidation)

ASP.NET Core 3.1 Clean Architecture – Invoice Management App (Part 4 AutoMapper – Map object properties to another object)

ASP.NET Core 3.1 Clean Architecture – Invoice Management App (Part 5 NSwag – Setting up Swagger and Auto generate API client code)

ASP.NET Core 3.1 Clean Architecture – Invoice Management App (Part 6 React – How To Convert ReactJs To Typescript)

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s