Asp.Net Core 3.1 Unit Of Work & Generic Repository Pattern Implementation – Custom Authentication & Authorization – Real World Application – Part2

Selamlar

Bir önceki makalemizde girişini yaptığımız A&A uygulaması için şimdi Repository Pattern ile Unit Of Work Yaklaşımını projemize ekleyeceğiz.

Önceki Makale : http://aliburakbasaran.com/2020/01/07/asp-net-core-3-1-custom-authentication-authorization-real-world-application-part1-asp-net-core-3-1-entity-framework-core-code-first-implementation/

Github Reposu : https://github.com/aburakbasaran/Authentication

Bu blogda uygulamaların nasıl çalıştığı , mantığından çok gerçek dünya uygulamalarında nasıl kullanacağımızı anlatmak istiyorum. Fakat kısaca Unit Of Work ve Repository Patternlerinden bahsedecek olursak ;

Neden Generic Repository Pattern ? (GRP)

Bu patterni veritabanına veri ekleme, güncelleme ve okuma gibi CRUD (Create Read Update Delete) işlemlerimiz için yazdığımız kodların , terkar kullanılabilirliğini sağlamak , kod tekrarından kurtulmak ve yanlışları en aza indirmek istediğimizde kullanırız.

Yazılım geliştirmenin en önemli prensiplerinden birisi de DRY (Don’t repeat yourself)  kendini tekrar etmedir. Generic Repository Patterni bizi kod tekrarından kurtarır. Oluşturduğumuz bir sınıf ile tüm tablolar için CRUD işlemlerimizi yapabilmemizi sağlar. Örneğin orta ölçekli bir gerçek dünya uygulamasında belki 100 adet DB tablosu bulunur. Bu 100 tablo için generic bir insert metodu yazıp onu çağırmak işlerimizi kolaylaştıracaktır.

Neden Unit Of Work ? (UOW)

Öncelikle Uow patterni genelde GRP ile kullanılıyor fakat tek başına veya başka kullanım pratikleride vardır. Bizim konumu GRP ile UOW kullanımıdır.

Gelelim ne işe yaradığına. Uygulama içerisinde ardışık olarak 4 crud işlemi yapmamız gerektiğini düşünün. Bu 4 işlemde 1 – 2 – 3. işlemler sorunsuz olmasına karşı 4. işlemde bir sorun meydana geldiğini düşünün. Böyle bir durumda bu ilk 3 işlemin de geri alınması gerekir. Aksi takdirde hatalı işlem gerçekleşmiş olacaktır. UOW pattern bizim için bu sorunu çözer. Çünkü işlemlerimizi hafızada tutar ve tek bir kanal üzerinden gerçekleştirir.

Sonuç olarak UOW kısaca ;veritabanı ile yapılacak olan tüm işlemleri, tek bir kanal aracılığı ile gerçekleştirme ve hafızada tutma işlemlerini sunmaktadır. Bu sayede işlemlerin toplu halde gerçekleştirilmesi ve hata durumunda geri alınabilmesi sağlamaktadır.

Şimdi gelelim bizim uygulamamıza bu patternleri implemente etmeye.

ilk iş olarak Core klasörü altına Authentication.Domain.Repository adında bir Class Lib. projesi ekliyorum. Ardından Base , Repository ve Uow adında 3 klasör açıyorum.

Base klasörü altına BaseRepository sınıfı ve bu sınıfın interface olan IBaseRepository interfacesini ekliyorum. Bu sınıf içerisinde kullanacağım genel metodları yazıyorum.

  public class BaseRepository<TEntity> where TEntity : BaseEntity
    {
        private readonly AuthenticationContext context;

        DbSet<TEntity> dbSet;

        public BaseRepository(AuthenticationContext _context)
        {
            context = _context;
            dbSet = context.Set<TEntity>();
        }

        public async Task InsertAsync(IEnumerable<TEntity> entities)
        {
            await dbSet.AddRangeAsync(entities);
        }

        public void Insert(IEnumerable<TEntity> entities)
        {
            dbSet.AddRange(entities);
        }

        public void Insert(TEntity entitiy)
        {
            dbSet.Add(entitiy);
        }

        public async Task InsertAsync(TEntity entitiy)
        {
            await dbSet.AddAsync(entitiy);
        }

        public TEntity Get(Expression<Func<TEntity, bool>> expression)
        {
            return this.dbSet.Where(expression).FirstOrDefault();
        }

        public Task<TEntity> GetAsync(Expression<Func<TEntity, bool>> expression)
        {
            return this.dbSet.FirstOrDefaultAsync(expression);
        }

        public IEnumerable<TEntity> GetList(Expression<Func<TEntity, bool>> expression)
        {
            return this.dbSet.Where(expression).AsEnumerable();
        }

        public IEnumerable<TEntity> GetList(Expression<Func<TEntity, bool>> expression, string include)
        {
            return this.dbSet.Where(expression).Include(include).AsEnumerable();
        }

        public TEntity GetInclude(Expression<Func<TEntity, bool>> expression, string include)
        {
            return this.dbSet.Include(include).FirstOrDefault(expression);
        }

        public IEnumerable<TEntity> GetPage(Expression<Func<TEntity, bool>> expression, int? pageIndex, int? pageSize, out int totalCount)
        {
            IEnumerable<TEntity> entityList = null;

            var query = this.dbSet.Where(expression).AsQueryable();

            int rowsCount = query.Count();

            int skipCount = 0;

            if (pageSize == null || pageSize <= 0)
            {
                pageSize = rowsCount;
            }

            if (pageIndex == null || pageIndex <= 0)
            {
                pageIndex = 1;
            }


            skipCount = (pageIndex.Value - 1) * pageSize.Value;
            entityList = query.OrderBy(x => x.Id).Skip(skipCount).Take(pageSize.Value);


            totalCount = rowsCount;

            return entityList;
        }

        public bool Delete(TEntity entitiy)
        {
            var removed = false;
            if (entitiy != null)
            {
                removed = true;
                dbSet.Remove(entitiy);
            }
            return removed;
        }

        public bool Delete(long id)
        {
            var removed = false;
            var entity = this.Get(x => x.Id == id);
            if (entity != null)
            {
                removed = true;
                dbSet.Remove(entity);
            }
            return removed;
        }

        public bool DeleteAll(IEnumerable<TEntity> entities)
        {
            var removed = false;
            if (entities != null)
            {
                removed = true;
                dbSet.RemoveRange(entities);
            }
            return removed;
        }
    }
 public interface IBaseRepository<TEntity>
        where TEntity : BaseEntity
    {
        public Task InsertAsync(IEnumerable<TEntity> entities);

        public void Insert(IEnumerable<TEntity> entities);

        public void Insert(TEntity entitiy);

        public Task InsertAsync(TEntity entitiy);

        public TEntity Get(Expression<Func<TEntity, bool>> expression);

        public IEnumerable<TEntity> GetList(Expression<Func<TEntity, bool>> expression);

        public IEnumerable<TEntity> GetList(Expression<Func<TEntity, bool>> expression, string include);

        public IEnumerable<TEntity> GetPage(Expression<Func<TEntity, bool>> expression, int? pageIndex, int? pageSize, out int totalCount);

        public bool Delete(TEntity entitiy);

        public bool DeleteAll(IEnumerable<TEntity> entities);

        public bool Delete(long id);

        public Task<TEntity> GetAsync(Expression<Func<TEntity, bool>> expression);

        TEntity GetInclude(Expression<Func<TEntity, bool>> expression, string include);
    }

Daha sonra Repository klasörü altına IUserRepository ve UserRepository interfacesi ve sınıfını oluşturuyorum ve Base sınıfda yazan metodları kullanması için gerekli tanımları yapıyorum.

public class UserRepository : BaseRepository<User>, IUserRepository
    {
        public UserRepository(AuthenticationContext _context) : base(_context)
        {

        }
    }
 public interface IUserRepository : IBaseRepository<User>
    {

    }

Gördüğünüz gibi içerisinde herhangi bir metod yok. User tablosuna ait özel bir işlem yapacağımız zaman burada yapabiliriz. Örneğin validateuser vs.

Şimdi sıra UOW implementasyonunda. Uow klasörü altına IUow ve Uow interfacesi ve sınıfını tanımlıyorum.

 public interface IUnitOfWork
    {
        IUserRepository User { get; }
        Task<int> CompleteAsync();
        int Complete();
    }
 public class UnitOfWork : IUnitOfWork
    {
        private readonly AuthenticationContext _dbContext;
        private IUserRepository _user;

        public UnitOfWork(AuthenticationContext dbContext)
        {
            this._dbContext = dbContext;
        }

        public IUserRepository User
        {
            get
            {
                if (this._user == null)
                {
                    this._user = new UserRepository(_dbContext);
                }
                return this._user;
            }
        }

        public async Task<int> CompleteAsync()
        {
            return await _dbContext.SaveChangesAsync();
        }
        public int Complete()
        {
            return _dbContext.SaveChanges();
        }
        public void Dispose()
        {
            _dbContext.Dispose();
        }
    }
Solution dan görünüm

Bu patternleri kullanmak için Application altına AuthenticationService imizi oluşturuyoruz.(Daha sonra controllerdan çağırmak için metodları burada yazacağız)

  public class AuthenticationService : IAuthenticationService
    {

        private readonly IUnitOfWork uow;

        public AuthenticationService(IUnitOfWork _uow)
        {
            uow = _uow;
        }

        public async Task<bool> InsertUser()
        {
            User usr = new User("burak1", "burakuser", "buraksurname", "aburakbasaran@gmail.com", "22323", "323");
            User usr2 = new User("burak2", "burakuser", "buraksurname", "aburakbasaran@gmail.com", "22323", "323");
            User usr3 = new User("burak3", "burakuser", "buraksurname", "aburakbasaran@gmail.com", "22323", "323");
            User usr4 = new User("burak4", "burakuser", "buraksurname", "aburakbasaran@gmail.com", "22323", "323");

            User usr5 = new User();
         
            await uow.User.InsertAsync(usr);
            await uow.User.InsertAsync(usr2);
            await uow.User.InsertAsync(usr3);
            await uow.User.InsertAsync(usr4);
            await uow.User.InsertAsync(usr5);

            await uow.CompleteAsync();


            return false;
        }
    }

Yukarıda gördüğünüz üzere tamda size anlattığım gibi 5 CRUD işlemi var ve 5. işlemde hata almasını istediğimden boş class yolluyorum. Ve hata döndüğünü ve diğer kayıtlarında geri alındığını görüyorum.

public interface IAuthenticationService
    {
        public  Task<bool> InsertUser();
            
    }

Şimdi sıra geldi bu eklediğimiz sınıfları ve patternleri Asp.Net Core DI araçlarını kullarak sisteme tanıtmaya.

Bunun için Infrastructure projesi altına DI klasörü açıp içerisine gerekli installerları yazıyorum.

Solution Görünümü
 public static class DbInstaller
    {
        public static IServiceCollection AddPersistence(this IServiceCollection services, IConfiguration configuration)
        {
            services.AddDbContext<AuthenticationContext>(options =>
                options.UseSqlServer(configuration.GetConnectionString("DefaultConnection")));

            services.AddScoped<IUnitOfWork, UnitOfWork>();
            return services;
        }

    }
 public static class  RepositoryInstaller 
    {
        public static IServiceCollection AddRepository(this IServiceCollection services)
        {
            services.AddScoped<IUserRepository, UserRepository>();
            return services;
        }

    }
 public static class ServiceInstaller
    {
        public static IServiceCollection AddServices(this IServiceCollection services)
        {
            services.AddScoped<IAuthenticationService, AuthenticationService>();
            return services;
        }
    }

Bu yazdığımız installerları startupa ekliyoruz.

            services.AddPersistence(Configuration);
            services.AddRepository();
            services.AddServices();
            services.AddControllers();

En son olarak Controllerım aşağıda ki gibidir.

  private readonly IAuthenticationService AuthenticationService;

        public WeatherForecastController(IAuthenticationService authenticationService)
        {
            this.AuthenticationService = authenticationService;
        }

        [HttpGet]
        public async Task<bool> Get()
        {
           return await this.AuthenticationService.InsertUser();
        }

A&A uygulamasında 2. makalemizinde sonuna geldik. Bu konuda bir sorunuz olursa çekinmeden ulaşabilirsiniz.

İyi Kodlar!

Bir cevap yazın

E-posta hesabınız yayımlanmayacak. Gerekli alanlar * ile işaretlenmişlerdir

Post comment