Sayfalar

2 Ekim 2013 Çarşamba

ASP.NET MVC - KOMPLE BİR PROJE YAPISI OLUŞTURMAK - 2

Repository, Service, Unit of Work, Unit Testing

Bu yazıda repository ve unit of work desenlerini ve service katmanını oluşturacağız. Önceki makalede,

  1. Boş bir solution oluşturduk.
  2. Web, test ve gerekli olan sınıf kütüphanesi projeleri ekledik.
  3. Veritabanını temsil eden modelleri oluşturduk.
  4. Eklediğimiz modeller ile code first automigration kullanarak veritananını oluşturduk.
Buraya kadar oluşturduğumuz proje ile yeni modeller ve bu modellere yeni alanlar ekleyip, sürekli console dan, update-database komutunu çalıştırarak veritabanı tabloları ile oynayabiliriz. 

Bu arada modellerde ve mapping klasörünün altındaki sınıflarda küçük değişiklikler yaptım. Bunlar yapısal değişiklikler değil, sadece işlevsel değişiklikler.  Yaptığım değişiklikler;

Domain projesini silip, Core adında yeni bir class library projesi oluşturdum. Core projesi içerisine Domain adında bir klasör oluşturup, Entity sınıflarını bu klasör içerisine aldım(Domain>Entity>classes). Tabiki sizinki aynı kalabilir. Projenin adı önemli değil. Burada önemli olan, Entity sınfındaki değişiklik. Yeni entity sınıfının adı BaseEntity.cs içeriği ise;

BaseEntity.cs
public abstract class BaseEntity
{
    public int Id { getset; }
}

User ve Role sınıflarımız artık bu sınıftan kalıtım alıyor. User ve Role sınıfı içerisindeki Entity sınıfını BaseEntity yapalım. Ayrıca, User ve Role sınıfı içerisindeki, özellikler üzerindeki ayarlamalarıda mapping sınıflarına aldım. Yani User ve Role sınıflarının özellikleri üzerindeki annotations ları da kaldıralım.

User.cs
namespace MvcProject.Core.Domain.Entity
{
    public class User : BaseEntity
    {
        public User()
        {
            Roles = new HashSet<Role>();
        }
 
        public string UserName { getset; }
        public string DisplayName { getset; }
        public string Password { getset; }
        public string Email { getset; }
        public string ProfileImageUrl { getset; }
        public DateTime? LastLoginDate { getset; }
        public string LastLoginIp { getset; }
 
        public virtual ICollection<Role> Roles { getset; }
    }
}

Role.cs
namespace MvcProject.Core.Domain.Entity
{
    public class Role : BaseEntity
    {
        public Role()
        {
            Users = new HashSet<User>();
        }
 
        public string RoleName { getset; }
        public virtual ICollection<User> Users { getset; }
    }
}

Sınıfların güncel hali bunlar. Sınıfların namespace değerlerine dikkat edin, ben Domain projesini kaldırdım ve Core projesi eklediğim için namespace farklı, eğer sizde Domain i silip, Core eklerseniz, yukarıdaki kodları direk kullanabilirsiniz. Gördüğünüz gibi User ve Role sınıflarındaki Id özelliğini tanımlamadık, Id artık, BaseEntity sınıfından gelecek. Peki özellikler üzerindeki ayarları nasıl yapacağız. Yani stringlerin uzunlukları ve ya başka ayarlar. Bunların hepsini mapping sınıflarında yapılacak. UserMap sınıfının yeni hali:

UserMap
namespace MvcProject.Data.Mapping
{
    public class UserMap : EntityTypeConfiguration<User>
    {
        public UserMap()
        {
            ToTable("User");
            HasKey(x => x.Id);
            Property(x => x.DisplayName).HasMaxLength(100);
            Property(x => x.Email).HasMaxLength(250);
            Property(x => x.LastLoginIp).HasMaxLength(20);
            Property(x => x.Password).HasMaxLength(50);
            Property(x => x.ProfileImageUrl).HasMaxLength(500);
            Property(x => x.UserName).HasMaxLength(50);
 
            HasMany(h => h.Roles).
            WithMany(e => e.Users).
            Map(
                m =>
                {
                    m.MapLeftKey("UserId");
                    m.MapRightKey("RoleId");
                    m.ToTable("User_Role");
                }
            );
        }
    }
}

ve birde RoleMap.cs ekliyoruz aynı klasörün altına.

RoleMap.cs
namespace MvcProject.Data.Mapping
{
    public class RoleMap : EntityTypeConfiguration<Role>
    {
        public RoleMap()
        {
            ToTable("Role");
            HasKey(x => x.Id);
            Property(x => x.RoleName).HasMaxLength(100);
        }
    }
}

Bu mapping sınıflarını context sınıfına ekliyoruz.

MvcProjectContext.cs
namespace MvcProject.Data.Context
{
    public class MvcProjectContext : DbContext
    {
        public MvcProjectContext()
            : base("DefaultConnection")
        {
            Configuration.LazyLoadingEnabled = true;
        }
 
        public DbSet<Role> Role { getset; }
        public DbSet<User> User { getset; }
 
        protected override void OnModelCreating(DbModelBuilder modelBuilder)
        {
            modelBuilder.Configurations.Add(new UserMap());
            modelBuilder.Configurations.Add(new RoleMap());
 
            base.OnModelCreating(modelBuilder);
        }
    }
}

Projemizin yeni görüntüsü: (Core ve Data)

Şimdi Repository, UnitOfWork ve Service Layer, desenlerimizi ekleyip, test projesi içerisinde test edelim. Repository sınıfımız generic olacak. Sınıflar, IGenericRepository.cs ve GenericRepository.cs

MvcProject.Data>Repository>IGenericRepository.cs
using System.Linq;
 
namespace MvcProject.Data.Repository
{
    public interface IGenericRepository<TEntity> where TEntity : class
    {
        /// <summary>
        /// Tüm kayıtlar.
        /// </summary>
        /// <returns></returns>
        IQueryable<TEntity> GetAll();
 
        /// <summary>
        /// Kayıt bul.
        /// </summary>
        /// <param name="id"></param>
        /// <returns></returns>
        TEntity Find(int id);
 
        /// <summary>
        /// Kayıt ekle.
        /// </summary>
        /// <param name="entity"></param>
        void Insert(TEntity entity);
 
        /// <summary>
        /// Kayıt güncelle.
        /// </summary>
        /// <param name="entityToUpdate"></param>
        void Update(TEntity entityToUpdate);
 
        /// <summary>
        /// Kayıt sil.
        /// </summary>
        /// <param name="id">Kayıt id</param>
        void Delete(int id);
 
        /// <summary>
        /// Kayıt sil.
        /// </summary>
        /// <param name="entityToDelete">Kayıt</param>
        void Delete(TEntity entityToDelete);
    }
}

MvcProject.Data>Repository>GenericRepository.cs
using System.Data;
using System.Data.Entity;
using System.Linq;
using MvcProject.Data.Context;
 
namespace MvcProject.Data.Repository
{
    public class GenericRepository<TEntity> : IGenericRepository<TEntity> where TEntity : class
    {
        private readonly MvcProjectContext _context;
        private readonly DbSet<TEntity> _dbSet;
 
        public GenericRepository(MvcProjectContext context)
        {
            _context = context;
            _dbSet = context.Set<TEntity>();
        }
 
        /// <summary>
        /// Tüm kayıtlar.
        /// </summary>
        /// <returns></returns>
        public virtual IQueryable<TEntity> GetAll()
        {
            return _dbSet;
        }
 
        /// <summary>
        /// Kayıt bul.
        /// </summary>
        /// <param name="id"></param>
        /// <returns></returns>
        public virtual TEntity Find(int id)
        {
            return _dbSet.Find(id);
        }
 
        /// <summary>
        /// Kayıt ekle.
        /// </summary>
        /// <param name="entity"></param>
        public virtual void Insert(TEntity entity)
        {
            _dbSet.Add(entity);
        }
 
        /// <summary>
        /// Kayıt güncelle.
        /// </summary>
        /// <param name="entityToUpdate"></param>
        public virtual void Update(TEntity entityToUpdate)
        {
            _dbSet.Attach(entityToUpdate);
            _context.Entry(entityToUpdate).State = EntityState.Modified;
        }
 
        /// <summary>
        /// Kayıt sil.
        /// </summary>
        /// <param name="id">Kayıt id</param>
        public virtual void Delete(int id)
        {
            TEntity entityToDelete = _dbSet.Find(id);
            Delete(entityToDelete);
        }
 
        /// <summary>
        /// Kayıt sil.
        /// </summary>
        /// <param name="entityToDelete">Kayıt</param>
        public virtual void Delete(TEntity entityToDelete)
        {
            if (_context.Entry(entityToDelete).State == EntityState.Detached)
            {
                _dbSet.Attach(entityToDelete);
            }
            _dbSet.Remove(entityToDelete);
        }
    }
}

Generic reposiyory, kendisine hangi modeli verirsek onu DbSet nesnesine atıyor. Tüm repositorylerde, Insert, update, delete olacağı için bunu generic repository ile yapıyoruz. Böylece, her model için bir repository yazmayacağız. Bu bir stratejidir. Her model için repository de yazabilirsiniz. Böylece o modele ait özel metodlar da eklemiş olursunuz.

Generic Repository ve Unit Of Work hakkında detaylı bilgi...

Generic repository sınıfı içerisinde SaveChanges metodu yok. Bu işi unit of work desenine vereceğiz. Ayrıca istediğimiz modelin repository katmanını oluşturma işinide unit of work e vereceğiz.

MvcProject.Data>UnitOfWork>IUnitOfWork.cs
using MvcProject.Data.Repository;
using System;
 
namespace MvcProject.Data.UnitOfWork
{
    public interface IUnitOfWork : IDisposable
    {
        IGenericRepository<TEntity> GetRepository<TEntity>() where TEntity : class;
        int SaveChanges();
    }
}

MvcProject.Data>UnitOfWork>UnitOfWork.cs
using System;
using System.Data.Entity;
using System.Data.Entity.Validation;
using MvcProject.Data.Context;
using MvcProject.Data.Repository;
 
namespace MvcProject.Data.UnitOfWork
{
    public class UnitOfWork : IUnitOfWork
    {
        private readonly MvcProjectContext _context;
 
        public UnitOfWork(MvcProjectContext context)
        {
            Database.SetInitializer<MvcProjectContext>(null);
 
            if (context == null)
                throw new ArgumentNullException("context");
 
            _context = context;
        }
 
        public IGenericRepository<TEntity> GetRepository<TEntity>() where TEntity : class
        {
            return new GenericRepository<TEntity>(_context);
        }
 
        public int SaveChanges()
        {
            try
            {
                if (_context == null)
                    throw new ArgumentNullException("_context");
 
                return _context.SaveChanges();
            }
            catch (DbEntityValidationException dbEx)
            {
                var msg = string.Empty;
 
                foreach (var validationErrors in dbEx.EntityValidationErrors)
                    foreach (var validationError in validationErrors.ValidationErrors)
                        msg += string.Format("Property: {0} Error: {1}", validationError.PropertyName, validationError.ErrorMessage) + Environment.NewLine;
 
                var fail = new Exception(msg, dbEx);
 
                throw fail;
            }
        }
 
        private bool disposed = false;
 
        protected virtual void Dispose(bool disposing)
        {
            if (!this.disposed)
            {
                if (disposing)
                {
                    _context.Dispose();
                }
            }
            this.disposed = true;
        }
 
        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }
    }
}

Bunları kullandıkça daha iyi anlayacaksınız. Şimdilik anlaşılması zor gibi görünüyor. Sınıfları ekledikten sonra Data projesinin son görüntüsü.

Service projemize de service sınıflarını ekleyelim.

MvcProject.Service>Users>IUserService.cs
using MvcProject.Core.Domain.Entity;
using System.Linq;
namespace MvcProject.Service.Users
{
    public interface IUserService
    {
        /// <summary>
        /// Tüm kullanıcılar.
        /// </summary>
        /// <returns></returns>
        IQueryable<User> GetAll();
 
        /// <summary>
        /// Role göre kullanıcılar.
        /// </summary>
        /// <param name="userName"></param>
        /// <returns></returns>
        IQueryable<User> GetUsersByRole(string roleName);
 
        /// <summary>
        /// Kullanıcı sistemde kayıtlı mı.
        /// </summary>
        /// <param name="userName"></param>
        /// <param name="password"></param>
        /// <returns></returns>
        bool ValidateUser(string userName, string password);
 
        /// <summary>
        /// Kullanıcı bul.
        /// </summary>
        /// <param name="userId"></param>
        /// <returns></returns>
        User Find(int userId);
 
        /// <summary>
        /// Kullanıcı ekle.
        /// </summary>
        /// <param name="user"></param>
        void Insert(User user);
 
        /// <summary>
        /// Kullanıcı güncelle.
        /// </summary>
        /// <param name="user"></param>
        void Update(User user);
 
        /// <summary>
        /// Kullanıcı sil.
        /// </summary>
        /// <param name="user">Kullanıcı</param>
        void Delete(User user);
 
        /// <summary>
        /// Kullanıcı sil.
        /// </summary>
        /// <param name="userId">Kullanıcı Id</param>
        void Delete(int userId);
    }
}

MvcProject.Service>Users>UserService.cs
using MvcProject.Core.Domain.Entity;
using MvcProject.Data.Repository;
using MvcProject.Data.UnitOfWork;
using System.Linq;
namespace MvcProject.Service.Users
{
    public class UserService : IUserService
    {
        private readonly IUnitOfWork _uow;
        private readonly IGenericRepository<Role> _roleRepository;
        private readonly IGenericRepository<User> _userRepository;
 
        public UserService(IUnitOfWork uow)
        {
            _uow = uow;
            _roleRepository = uow.GetRepository<Role>();
            _userRepository = uow.GetRepository<User>();
        }
 
        /// <summary>
        /// Tüm kullanıcılar.
        /// </summary>
        /// <returns></returns>
        public IQueryable<User> GetAll()
        {
            return _userRepository.GetAll();
        }
 
        /// <summary>
        /// Role göre kullanıcılar.
        /// </summary>
        /// <param name="userName"></param>
        /// <returns></returns>
        public IQueryable<User> GetUsersByRole(string roleName)
        {
            return _roleRepository.GetAll().FirstOrDefault(x => x.RoleName == roleName).Users.AsQueryable();
        }
 
        /// <summary>
        /// Kullanıcı sistemde kayıtlı mı.
        /// </summary>
        /// <param name="userName"></param>
        /// <param name="password"></param>
        /// <returns></returns>
        public bool ValidateUser(string userName, string password)
        {
            return _userRepository.GetAll().Any(x => x.UserName == userName && x.Password == password);
        }
 
        /// <summary>
        /// Kullanıcı bul.
        /// </summary>
        /// <param name="userId"></param>
        /// <returns></returns>
        public User Find(int userId)
        {
            return _userRepository.Find(userId);
        }
 
        /// <summary>
        /// Kullanıcı ekle.
        /// </summary>
        /// <param name="user"></param>
        public void Insert(User user)
        {
            _userRepository.Insert(user);
        }
 
        /// <summary>
        /// Kullanıcı güncelle.
        /// </summary>
        /// <param name="user"></param>
        public void Update(User user)
        {
            _userRepository.Update(user);
        }
 
        /// <summary>
        /// Kullanıcı sil.
        /// </summary>
        /// <param name="user">Kullanıcı</param>
        public void Delete(User user)
        {
            _userRepository.Delete(user);
        }
 
        /// <summary>
        /// Kullanıcı sil.
        /// </summary>
        /// <param name="userId">Kullanıcı Id</param>
        public void Delete(int userId)
        {
            _userRepository.Delete(userId);
        }
    }
}

MvcProject.Service>Roles>IRoleService.cs
using MvcProject.Core.Domain.Entity;
using System.Linq;
 
namespace MvcProject.Service.Roles
{
    public interface IRoleService
    {
        /// <summary>
        /// Tüm roller.
        /// </summary>
        /// <returns></returns>
        IQueryable<Role> GetAll();
 
        /// <summary>
        /// Kullanıcıya göre roller.
        /// </summary>
        /// <param name="userName"></param>
        /// <returns></returns>
        IQueryable<Role> GetRolesByUser(string userName);
 
        /// <summary>
        /// Rol bul.
        /// </summary>
        /// <param name="roleId"></param>
        /// <returns></returns>
        Role Find(int roleId);
 
        /// <summary>
        /// Kullanıcı role sahip mi.
        /// </summary>
        /// <param name="userName"></param>
        /// <param name="roleName"></param>
        /// <returns></returns>
        bool IsUserInRole(string userName, string roleName);
 
        /// <summary>
        /// Kullanıcı ekle.
        /// </summary>
        /// <param name="role"></param>
        void Insert(Role role);
 
        /// <summary>
        /// Kullanıcı güncelle.
        /// </summary>
        /// <param name="role"></param>
        void Update(Role role);
 
        /// <summary>
        /// Kullanıcı sil.
        /// </summary>
        /// <param name="role">Rol</param>
        void Delete(Role role);
 
        /// <summary>
        /// Kullanıcı sil.
        /// </summary>
        /// <param name="roleId">Rol Id</param>
        void Delete(int roleId);
    }
}

MvcProject.Service>Roles>RoleService.cs
using System.Linq;
using MvcProject.Core.Domain.Entity;
using MvcProject.Data.Repository;
using MvcProject.Data.UnitOfWork;
 
namespace MvcProject.Service.Roles
{
    public class RoleService : IRoleService
    {
        private readonly IUnitOfWork _uow;
        private readonly IGenericRepository<Role> _roleRepository;
        private readonly IGenericRepository<User> _userRepository;
 
        public RoleService(IUnitOfWork uow)
        {
            _uow = uow;
            _roleRepository = uow.GetRepository<Role>();
            _userRepository = uow.GetRepository<User>();
        }
 
        /// <summary>
        /// Tüm roller.
        /// </summary>
        /// <returns></returns>
        public IQueryable<Role> GetAll()
        {
            return _roleRepository.GetAll();
        }
 
        /// <summary>
        /// Kullanıcıya göre roller.
        /// </summary>
        /// <param name="userName"></param>
        /// <returns></returns>
        public IQueryable<Role> GetRolesByUser(string userName)
        {
            return _userRepository.GetAll().FirstOrDefault(x => x.UserName == userName).Roles.AsQueryable();
        }
 
        /// <summary>
        /// Rol bul.
        /// </summary>
        /// <param name="roleId"></param>
        /// <returns></returns>
        public Role Find(int roleId)
        {
            return _roleRepository.Find(roleId);
        }
 
        /// <summary>
        /// Kullanıcı role sahip mi.
        /// </summary>
        /// <param name="userName"></param>
        /// <param name="roleName"></param>
        /// <returns></returns>
        public bool IsUserInRole(string userName, string roleName)
        {
            return _userRepository.GetAll().FirstOrDefault(x => x.UserName == userName).Roles.Any(x => x.RoleName == roleName);
        }
 
        /// <summary>
        /// Kullanıcı ekle.
        /// </summary>
        /// <param name="role"></param>
        public void Insert(Role role)
        {
            _roleRepository.Insert(role);
        }
 
        /// <summary>
        /// Kullanıcı güncelle.
        /// </summary>
        /// <param name="role"></param>
        public void Update(Role role)
        {
            _roleRepository.Update(role);
        }
 
        /// <summary>
        /// Kullanıcı sil.
        /// </summary>
        /// <param name="role">Rol</param>
        public void Delete(Role role)
        {
            _roleRepository.Delete(role);
        }
 
        /// <summary>
        /// Kullanıcı sil.
        /// </summary>
        /// <param name="roleId">Rol Id</param>
        public void Delete(int roleId)
        {
            _roleRepository.Delete(roleId);
        }
    }
}

Repository sınıfıları, ORM aracılıgı ile veritabanı ile konusuyor. Tabi bizim projemizde EntityFramework olduğundan bu ORM yi kullanarak konusuyor. Ama direk saf sql ile de bağlantıya geçebilir. Service sınıfları ise, repository sınıflarından aldığı artık uygulama tarafında olan verileri uygulamanın servisine sunar.  Service projesinin son hali.

Şimdide buraya kadar yazdıklarımızı test projesi içerisinde test edelim. Test projesi çok önemlidir. Ama malesef test projesinin önemi çok bilinmiyor. Şimdilik testlerin önemini geçiyorum, başka bir makalede ne için test yazılmalı, faydaları nelerdir gibi konulara da değiniriz.

UserService sınıfımızı test edelim. Test projesine Service adında bir klasör açıp, klasöre sağ tıklayıp, Add>UnitTest diyerek bir test sınıfı ekleyelim. Bu test sınıfımızın içeriği aşağıdaki gibidir.
NOT: Test projesine de , web projesi içerisindeki web.config dosyasındaki connection string i ekleyelim.

MvcProject.Test>Service>UnitTestUser.cs
using System;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using MvcProject.Core.Domain.Entity;
using MvcProject.Data.Context;
using MvcProject.Data.UnitOfWork;
using MvcProject.Service.Users;
 
namespace MvcProject.Test.Service
{
    [TestClass]
    public class UnitTestUser
    {
        private MvcProjectContext _context;
        private IUnitOfWork _uow;
        private IUserService _userService;
 
        [TestInitialize]
        public void TestInitialize()
        {
            _context = new MvcProjectContext();
            _uow = new UnitOfWork(_context);
            _userService = new UserService(_uow);
        }
 
        [TestCleanup]
        public void TestCleanup()
        {
            _context.Dispose();
            _uow.Dispose();
            _userService = null;
        }
 
        [TestMethod]
        public void TestMethodInsertUser()
        {
            var user = new User
            {
                DisplayName = "test display name",
                Email = "test_email@mail.com",
                LastLoginDate = DateTime.Now,
                LastLoginIp = "192.168.1.1",
                Password = "12345",
                ProfileImageUrl = "profile image",
                UserName = "test_user_insert"
            };
 
            _userService.Insert(user);
            Assert.AreEqual(1, _uow.SaveChanges());
 
            _userService.Delete(user);
            _uow.SaveChanges();
        }
 
        [TestMethod]
        public void TestMethodUpdateUser()
        {
            var user = new User
            {
                DisplayName = "test display name",
                Email = "test_email@mail.com",
                LastLoginDate = DateTime.Now,
                LastLoginIp = "192.168.1.1",
                Password = "12345",
                ProfileImageUrl = "profile image",
                UserName = "test_user_update"
            };
 
            _userService.Insert(user);
            _uow.SaveChanges();
 
            user.DisplayName = "new display name";
            user.LastLoginDate = DateTime.Now;
            _userService.Update(user);
            Assert.AreEqual(1, _uow.SaveChanges());
 
            var updatedUser = _userService.Find(user.Id);
            Assert.AreEqual(user, updatedUser);
 
            _userService.Delete(user);
            _uow.SaveChanges();
        }
 
        [TestMethod]
        public void TestMethodDeleteUser()
        {
            var user = new User
            {
                DisplayName = "test display name",
                Email = "test_email@mail.com",
                LastLoginDate = DateTime.Now,
                LastLoginIp = "192.168.1.1",
                Password = "12345",
                ProfileImageUrl = "profile image",
                UserName = "test_user_delete"
            };
 
            _userService.Insert(user);
            _uow.SaveChanges();
 
            _userService.Delete(user);
            Assert.AreEqual(1, _uow.SaveChanges());
            Assert.IsNull(_userService.Find(user.Id));
        }
 
        [TestMethod]
        public void TestMethodValidateUser()
        {
            var user = new User
            {
                DisplayName = "test display name",
                Email = "test_email@mail.com",
                LastLoginDate = DateTime.Now,
                LastLoginIp = "192.168.1.1",
                Password = "12345",
                ProfileImageUrl = "profile image",
                UserName = "test_user_to_validate"
            };
 
            _userService.Insert(user);
            _uow.SaveChanges();
 
            Assert.IsTrue(_userService.ValidateUser(user.UserName, user.Password));
 
            _userService.Delete(user);
            _uow.SaveChanges();
        }
    }
}

Test projesinde Dependency injection yapmadık. Yani hazırladığımız tasarım desenlerini amaçlarına uygun olarak oluşturmadık. Test projesinde sadece servislerin calışıp çalışmadıgını test edeceğiz. Dependency Injection desenini web projesinde kullanacağız. Test metodlarını test etmek için Visual Studio üzerindeki Test menusunden >windows>test explorer diyerek test metodlarını çalıştırdığımızda sonuçları alacağız.

RoleService için test metodlarını da kendiniz yazabilirsiniz. Tüm bu kullanılan teknolojilerin anlatımı aslında bir kaç makaleye sığabilir. Bu makalelerimizde bir proje altyapısı oluşturmaya çalışıyoruz. Genel anlamda fikir verecek açıklamaları yapıyorum. Burada önemli nokta, bilmediğiniz teknolojileri, kavramları araştırarak bu kodları gözden geçirmek. Hepsini tek tek detaylı anlatmak, bu makalelerin içeriği değil. Ama başka makalelerle tek tek anlatmaya çalışacağım.

Projemiz artık veritabanından verileri çekip işleyip tekrar ekleyecek bir yapıya kavuştu. Bundan sonrası artık, uygulama tarafının geliştirilmesi. Yani, üyelik oluşturma, kullanıcı-rol ekleme, ... gibi işlemler. Uygulamanın alt katmanlarının nasıl çalıştığını artık biliyoruz. Bu katmanlara yeni modeller, servisler falan ekleyebiliriz.

Sonraki makalelerde üst katmanlarda çalışarak devam edeceğiz. İyi çalışmalar...

51 yorum:

  1. Merhaba,
    Makale için öcelikle teşekkür ederim. İncelemeye başlıyorum. Takıldığım yerleri yazarım.
    Öncelikle
    abstrack sınıf üretmenin amacı ortak "Id" den class'ları türetmek için mi?
    Tablolar içinde birden fazla ortak alan olsaydı onlarıda bu sınıfın içinde tanımlayacak mıydık?
    Ayrıca bu sınıfı oluşturduğumuz başka tabloları türetmek içinde kullanacağız galiba ?


    YanıtlaSil
    Yanıtlar
    1. Abstract sınıf aynen düşündüğünüz işi yapıyor. Id tüm tablolar için ortak bundan dolayı bunu BaseEntity e aldık. Başka tablo eklersek, bu sınıfta BaseEntity den kalıtım alacak. Eğer tablolarınız içerisinde başka ortak alan olacaksa bunu da, BaseEntity sınıfına taşıyabilirsiniz.

      Sil
  2. 1. Sınıflardaki özellikleri neden Mappings'te ayarladık bunu bize avantajı nedir?
    2. Tablolar arasındaki ilişkiyi kurmak için API içindeki HasKey, HasMany, WithMany metodlarını kullanıyoruz. Şimdi.
    Haskey : Tablonun PrimaryKey ini ifade etmek için kullanılır.
    Hasmany: Rulles kolleksiyonuna yönlendiriyoruz. Kullanıcı Tipi için sanırım.
    Withmany nedir? Neden burda kullanıcılar koleksiyonuyla bir bağlantı kuruyoruz.

    YanıtlaSil
    Yanıtlar
    1. 1. Sınıflarımız daha temiz ve anlaşılır görünecek. Kodların okunurluğu artacak. Sınıfların veritabanı ile olan ilişkisinin ayarlarını tamamen başka bir sınıfa vermiş oluyoruz. Modülerlik açısından olumlu katkısı olacaktır. Neyin nerde oldugu daha iyi anlaşılır.

      2. Kullanıcılar ile Roller tablosun arasında many-to-many ilişki vardır. İlk makalede de belirttiğim gibi, eğer bu tanımlamaları yapmazsak, zaten EF kendisi bu ilişkiyi kuruyor ve bir ara tabloyu kendisi otomatik ekliyor. Peki ben neden yinede böyle bir tanımlama yaptım? Bunun sebebi ise, foreign key ve ara tablonun adını kendim belirlemek istiyorum ondan. Bu mapping işlemini yapmadan, veritabanını bir oluşturup sonucu görün. Birde mapping yaparak görün.

      Örneğin siz, Id nin primary key olduğunu söylemeseniz bile, EF bunu biliyor ve Id ismi ile tanımlanmış alanı, primary key olarak ayarlıyor.

      Mapping işlemlerinin zorunlu olacağı noktalar olacaktır. Örneğin, one-to-many ilişkide, Delete ve Update işlemleri için ayar yapmalısınız. Yani bir kayıt silindiğinde ona bağlı kayıtların durumu ne olacaktır gibi...

      Sil
  3. Makale için çok teşekkürler hocam. Elinize sağlık. Yanlız ben Role için test metodunu oluşturduğumda MvcProject.Core.Domain.Entity kısmının altında bulunan Role.cs içeriği Database ile eşleşmiyor. Orda RoleName diye geçen yer sadece Name olacak ha onu nerden buldum derseniz test metodu söyledi :)) Kontroledebilirmisiniz ali hocam..

    YanıtlaSil
    Yanıtlar
    1. Evet haklısınız, RoleName olarak değiştirmişim. Sizde değiştirip, update-database komutunu çalıştırarak, veritabanını güncelleyin ve ya modeli değiştirin. Bu arada test metodunun bir faydasını yakaladınız. Kodları denemek için uygulama yazmak gerekmedi. Diğer türlü, sürekli controller içerisinde debug yaparak, kodlardaki hatayı bulmaya çalışmak biraz sıkıcı oluyor.

      Sil
    2. Update-Database yaptığımda Keyword not supported: 'integrated security'. hatası alıyorum.

      Web Config'de aşağıdaki bağlantı cümleciği kullanıyorum



      Bu bağlantı cümleciğinden "Security=True; " çıkarmam mı gerekli ? Çıkarmadan Update-Database komutunu çalıştıramıyormuyum ?

      Sil
    3. Integrated Security = True yaparsanız, user id ve password yazmanıza gerek yok. Integrated security, on an PC de aktif kullanıcı için, veritabanına bağlanır. Yani ikisinden birini secmelisiniz. Ya integrated security ya da user id ve password ile baglanmalısınız veritabanına. Connection String cümlenizi iyi kontrol edin, yanlış birşey varsa bu hatayı alırsınız.

      Sil
  4. Repository kısmında sonra benim balatalar yandı. Herhalde anlamam biraz zaman alacak.
    Neden hem GenericRepository.cs ve IGenericRepository.cs oluşturduk ikiside aynı şeyi yapıyor gibi. Tek class ta olmuyormu ben açıkcası aradaki farkı pek anlamadım. Diğer classlarda da aynı mantığı anlayamadım.

    YanıtlaSil
    Yanıtlar
    1. IGenericRepository bir interface. Biz şu an verileri çekerken, EF kullanıyoruz, ilerde başka teknoloji olursa onu eklemek için bu interfaceden kalıtım alarak ekleyebiliriz. Dependency injection kullanırken biz sadece bu interface i enjecte edeceğiz. Dependency Injection konusuna gelince, bunu daha iyi anlayacaksınız. şimdilik cok kafanıza takmayın: Dependency injection için vereceğim linkteki resim, size herşeyi anlatacaktır.

      http://www.mikesdotnetting.com/Article/117/Dependency-Injection-and-Inversion-of-Control-with-ASP.NET-MVC

      Sil
  5. Hocam makaleler için çok teşekkür ederim. Şöyle mantıklı bir katmanlı yapıyı nasıl oluşturacağımı öğrenmek istiyordum. Makalenin devamını heyecanla bekliyoruz. Kolaylıklar diliyorum.

    YanıtlaSil
  6. Merhaba.
    Projenin sonlarında takıldım. VS 2012 Ultimate ile çalışıyorum.
    Hata şöyle:

    using System;
    using Microsoft.VisualStudio.TestTools.UnitTesting;
    using KompleProje2.Core.Domain.Entity;
    using KompleProje2.Data.Context;
    using KompleProje2.Data.UnitOfWork;
    using KompleProje2.Service.Users;

    UITest ve UITesting görünüyor ama 'UnitTesting' bulunamıyor.

    'UnitTesting' bulunamadığu için sayfada hatalar oluşuyor.

    The type or namespace name 'UnitTesting' does not exist in the namespace 'Microsoft.VisualStudio.TestTools' (are missing an assembly referance?)

    Metin Akbaş

    YanıtlaSil
  7. Hocam makalenin devamı gelecek mi acaba?

    YanıtlaSil
    Yanıtlar
    1. Evet. Musatit oldukca yaziyorum. En kisa zamanda yayinlayacagim

      Sil
    2. teşekkürler heyecanla bekliyoruz :)

      Sil
  8. Güzel olmuş eline sağlık katmanlı mimariyi öğreniyoruz sayende eline sağlık devamını heyecanla bekliyorz :)

    YanıtlaSil
  9. Hocam 40 gün olmuş sanırım gelmeyecek :(

    YanıtlaSil
  10. Merhbaba Services ve Repository kullanarak her tablo icin ayri bir sinif yazmak yerine tek bir IService modelinden yararlanmak daha az maaliyetli olacagini dusunuyorum yani her DomainModel icin Service yazmak uzun surebiliyor Services de yazilan kodlar hepsihemen hemen ayni boyleyece kod tekrari yapmamis oluruz bunu bir dusunurmusunuz hocam.

    Ozet: Generic Repository gibi Generic Service nasil yapabiliriz.

    YanıtlaSil
    Yanıtlar
    1. Generic yapılar, ortak kullanılan işlevler için uygundur. Örneğin, repository için Add, Delete, Insert ve Update işlemleri tüm entity sınıfları için ortak. Ama servis içerisinde bu dönen verileri, uygulama katmanına aktarırken, her entity için farklı bir veri yapısı kullanmak isteyebiliriz. Yani service katmanı her entity sınıfına ait özel metodlar içerebilir.

      Sil
    2. Evet benimde bahsettigim ortak metodlari tekrar tekrar her entity servicesinde yazmamak, ornek Add metodu icin gelen Entity Generic Type da parametre verirsek her servicesde kullanabiliriz gibi ortak bir servis olusturmak. Yani UserService, RoleService degilde GenericService uzerinden islemleri yaptirsak.

      Sil
    3. Mesela bir entity, string bir değere göre filtrelenebilir, diger bir entity, int bir değer için filtrelenebilir. Örneğin rolü, kullanıcı ID sine göre cekmek, ve ya kullanıcıyı rol adına göre cekmek. Bu iş için iki farklı servise metodu lazım. Birisi rol-servis içerisinde diger kullanıcı-servis içerisinde olmalı. Bunlar entity ye gore özelleşmiş metodlardır, ortak değil. Ama her ikiside repository içerisindeki, GetAll() metodunu kullanarak bu filtrelemeyi yapar. GetAll her ikisi için de ortak ama, filtreleme metodları, kendine özel. Bazı durumlarda, Generic Repository yerine, özelleşmiş repository kullanmak bile daha esnek yapı sağlar. Biz tasarım desenlerini sadece koddan tasarruf için yazmayız, esneklik te önemlidir. Ama siz isterseniz generic bir service tanımlayıp görebilirsiniz.

      Sil
    4. Anladim hocam tesekkurler.

      Sil
    5. Bu yorum yazar tarafından silindi.

      Sil
    6. Ali Rıza Bey, öncelikle bu güzel anlatımlarınız için teşekkür ederim. Arkadaşın sorduğu soruya belki şöyle bir yanıt verilebilir.
      IUserService ve IRoleService interfacelerinde ortak olan en az üç metod prototipi var.
      Insert(Role/user, role/user), Update(Role/user, role/user) v.b. gibi.
      Bunlar için her modelin interface tanımlaması yaparken tek tek bunları yazmaktan kurtulabiliriz.
      IGenericService <<"T">> şeklinde tasarlanan bir generic interface altında, ortak prototip metodlar tanımlanır. Her modelin kendine özgü Interface'de bu generic interface yapısını miras alır.
      IUserService:IGenericService<<"User">>
      IRoleService:IGenericService<<"Role">>

      Bu şekilde Interfacelerde aynı ortak metot tanımlamasından bir ölçüde kurtulabilir ve modele özgü diğer metotlar/işlemler rahatlıkla yapılmaya devam edilebilir. Tabi Interface'i uygulayan servis sınıflarında yine de tekrar eden işlemler olacaktır.

      Not: " işareti olmadan <<>> ifadeleri içerisine yazdıklarım görünmediği için "ekledim.

      Saygılar,


      Sil
    7. evet hocam haklısınız. service sınıflarının hepsi ortak bir generic service den kalıtım alabilir. boylece dediğiniz gibi hem ortak alanları hemde kendine ozel metodları içermiş olur. pek ortak alan olmuyor ama genelde ondan yazmıyorum. acık kaynak projelerde repository sınıfları bile genelde generic degil. tabi ikisininde avantaj ve dezavantajları var.

      teşekkürler...

      Sil
  11. Merhaba hocam. veritabanı oluşturma işlemleri tamam. Fakar test metodlarını çalıştıramadım. "The underlying provider failed on Open" hatası alıyorum. :S Cannot open database \"MvcProjectContext\" requested by the login. The login failed.\r\nLogin failed for user şeklinde açıklaması olan hatalar alıyorum.

    YanıtlaSil
    Yanıtlar
    1. Bunun birçok nedeni olabilir. ConnectionString dogru mu diyecegim ama hatalı olsa veritabanı oluşmazdı. Localdeki Mssql de mi çalıştırıyorsunuz? Öyleyse conn.String i görme şansımız var mı?

      Sil
    2. Bu yorum yazar tarafından silindi.

      Sil
    3. add name="MvcProjectContext" providerName="System.Data.SqlClient" connectionString="Data Source=.; Database=HaberSitesiDB;Integrated Security=True;"

      Sil
    4. Veritabanını oluşturup, sonra baglanamaması garip. Ben biraz araştırayım bir çözüm bulursam size yazacağım. Bu arada Test projesine connString eklediniz değil mi? (app.config içerisine)

      Sil
    5. Test Name: TestMethodInsertUser
      Test FullName: MvcProject.Test.Service.UnitTestUser.TestMethodInsertUser
      Test Source: c:\Users\mgurkan\Documents\Visual Studio 2012\Projects\MvcProject\MvcProject.Test\Service\UnitTestUser.cs : line 35
      Test Outcome: Failed
      Test Duration: 0:00:00,2588408

      Result Message: Initialization method MvcProject.Test.Service.UnitTestUser.TestInitialize threw exception. System.TypeInitializationException: System.TypeInitializationException: The type initializer for 'System.Data.Entity.Internal.AppConfig' threw an exception. ---> System.Configuration.ConfigurationErrorsException: Configuration system failed to initialize ---> System.Configuration.ConfigurationErrorsException: Only one element allowed per config file and if present must be the first child of the root element. (C:\Users\mgurkan\Documents\Visual Studio 2012\Projects\MvcProject\MvcProject.Test\bin\Debug\MvcProject.Test.dll.config line 7).
      Result StackTrace:
      at System.Configuration.ConfigurationSchemaErrors.ThrowIfErrors(Boolean ignoreLocal)
      at System.Configuration.BaseConfigurationRecord.ThrowIfParseErrors(ConfigurationSchemaErrors schemaErrors)
      at System.Configuration.BaseConfigurationRecord.ThrowIfInitErrors()
      at System.Configuration.ClientConfigurationSystem.EnsureInit(String configKey)
      --- End of inner exception stack trace ---
      at System.Configuration.ConfigurationManager.PrepareConfigSystem()
      at System.Configuration.ConfigurationManager.get_ConnectionStrings()
      at System.Data.Entity.Internal.AppConfig..ctor()
      at System.Data.Entity.Internal.AppConfig..cctor()
      --- End of inner exception stack trace ---
      at System.Data.Entity.Internal.AppConfig.get_DefaultInstance()
      at System.Data.Entity.Internal.LazyInternalConnection..ctor(String nameOrConnectionString)
      at System.Data.Entity.DbContext..ctor(String nameOrConnectionString)
      at MvcProject.Data.Context.MvcProjectContext..ctor() in c:\Users\mgurkan\Documents\Visual Studio 2012\Projects\MvcProject\MvcProject.Data\Context\MvcProjectContext.cs:line 17
      at MvcProject.Test.Service.UnitTestUser.TestInitialize() in c:\Users\mgurkan\Documents\Visual Studio 2012\Projects\MvcProject\MvcProject.Test\Service\UnitTestUser.cs:line 20

      MvcProjectContext.cs:line 17 = Bu satırda da

      " public MvcProjectContext()
      : base("MvcProjectContext")
      {
      Configuration.LazyLoadingEnabled = true;

      }
      "

      bu şekilde hocam :S

      Sil
    6. test projesi içerisindeki, app.config dosyasında connectionSring tanımlı mı?

      Sil
    7. Evet hocam tanımlı. Web projesindeki connectionstring düğümünün aynısı test projesindeki app.configte de var.

      Sil
    8. tamm ben bir araştırayım bulursam size dönerim

      Sil
  12. Merhaba Hocam
    Şöyle bir senaryo düşünelim benim 100 tane müşterim var tablo yapısında bir değişiklik yaptım. ve bu 100 müşterinin de db'nin güncellenmesi gerekiyor ben 100 müşterinin conn string yazıp tek tek update-databese mi demem gerekiyor yoksa program çalıştığında bu işi yapıyormu? ve Yapıyorsa nasıl oluyor?
    Teşekkürler

    YanıtlaSil
    Yanıtlar
    1. Her müşteri uygulamayı kendi sunucusunda tutuyorsa, hepsinde güncelleme yapmanız gerekiyor elbette

      Sil
    2. Ben projeden database yapısını değiştirsem ve program açıldığında kontrol etse değişiklik varmı diye var ise o değişikliği uygulasa. Böyle bir seçenek varmıdır acaba. Var ise tablomdaki kayıtları etkilermi?

      Sil
  13. Hocam Biz Burda Ado.Net Enterprize Library Gibi Framworkler kullanmak istesek sadece RoleService,UserService gibi clas dosyalarında bu framworkları mı kullanacağız benim yapmak isteğim Entity Framwork Bağımlı kalmadan ileride veri erişimi değiştiğinde sadece o kodlamayı yapmak istiyorum onun için küçük bir örnek alabilir misiniz makalenize hocam şimdiden tsk ederim

    YanıtlaSil
    Yanıtlar
    1. Evet bu çok önemli bir konu. Projeye, yapıyı bozmadan ado.net repository de ekleyeceğim. Yani proje bir ORM aracı kullanmadan da veritabanına bağlanabilecek. Webforms ile alakalı paylaştığım bundan önceki makalede anlattığım saf sql yapısını ekleyeceğim

      Sil
  14. Tekrar ellerinize sağlık çok güzel bir makale olmuş yine. tüm hepsini uyguladım ve role görede aynı şekilde sizin yazılarınızdan kopya çekerek yazdım. Yalnız bir şey sormak istiyorum ilişkili veritabanlarında örneğin User_Role de nasıl yazabiliriz testi. Yukarıdaki testinizde yer alan userInRole methodunu da paylaşa bilirmisiniz oradan esinlenerek halledebilirim. İlk kez test yazdım ama çok zevkli ya. biraz daha yazarak mantığını oturtmaya çalışacağım.

    YanıtlaSil
    Yanıtlar
    1. Tam olarak ne demek istediginizi anlayamadım? userInRole u test etmek için, mesela user oluşturun (yukarıdaki örneklerden), ama bu user nesnesini kayıt etmeden once ona bir de rol ekleyin, yani;

      user.Roles.Add(new Role{ roleName = "role name" });
      _userService.Insert(user);
      _uow.SaveChanges();

      daha sonra da aşağıdaki metod ile test edin;

      Assert.IsTrue(_roleService.UserInRole(user.UserName, "role name"));

      Tabi _roleService nesnesini de test initialize metodu içerisinde tanımlanız gerekiyor. (userService ile aynı sekilde tanımlayacaksınız)

      Sil
  15. Ali Rıza bey öncelikle verdiğiniz bilgiler için teşekkürler. Emeğinize sağlık.

    Aşağıdaki satırlarda update Metodu Role'u güncellemek için yazılmışken yorum bilgisi olarak "Kullanıcı güncelle" yazılmış. Sanırım role güncelle olacak değil mi?

    Teşekkürler.

    void Update(Role role) << bu method için yazılan summary alanını kastediyorum.

    YanıtlaSil
  16. Merhaba _context.Dispose() metodunu bulamıyor MyProjectContext.cs sayfasında Dispose metedou yok
    yada ben bir yerde hata yaptım.

    YanıtlaSil
    Yanıtlar
    1. MyProjectContext sınıfı DbContext sınıfından kalıtım alır. DbContext sınıfı ise IDisposable interface ini implement eder, bundan dolayı, _context nesnesi otomatik olarak disposable dır yani Dispose metodu yazmanıza gerek yok zaten tanımlıdır. Yanlışlık yapmadıysanız, bu metodun gelmesi gerekir.

      Sil
    2. Test projesi içerisinde EF yi yüklerseniz sorununuz çözülecektir sanıyorum...

      Sil
    3. Nuget'den entity framework yüklenilince düzeliyor.DbContext'i yoksa göremiyor.

      Sil
  17. Tüm işlemleri anlattığınız gibi yapmama rağmen
    Enable-Migrations –EnableAutomaticMigrations yazdıktan sonra aşğıdaki hatayı alıyorum.
    The type initializer for 'System.Data.Entity.Migrations.DbMigrationsConfiguration`1' threw an exception.

    YanıtlaSil
  18. Teşekkürler makale için hakketen çok faydalı oluyor biz yeniler için.
    Şöyle bir sorum olucak

    HasMany(h => h.Roles).
    WithMany(e => e.Users).
    Map(
    m =>
    {
    m.MapLeftKey("UserId");
    m.MapRightKey("RoleId");
    m.ToTable("User_Role");
    }
    );

    Yukardaki kodu UserMap'a değilde RoleMap'e şu şekilde yazsaydıkda aynı işi görmüş olur muydu?

    HasMany(e => e.Users).
    WithMany(h => h.Roles).
    Map(
    m =>
    {
    m.MapLeftKey("UserId");
    m.MapRightKey("RoleId");
    m.ToTable("User_Role");
    }
    );


    Ben denedim şöyle bir hata aldım: 'FK_dbo.User_Role_dbo.User_UserId' is not a constraint.
    Could not drop constraint. See previous errors.

    YanıtlaSil
  19. Selamlar hocam,

    UserService nin Delete metodlarında geçilen parametrelerde bir gözden kaçma var sanırım.


    public void Delete(User user)
    {
    // Bu metod aşağıda int tipi beklediği için burada da user.Id göndermeliydik sanki??
    _userRepository.Delete(user);
    }


    public void Delete(int userId)
    {
    _userRepository.Delete(userId);
    }

    YanıtlaSil
  20. Merhaba,Örnekler gayet güzel.NopCommerce'deki gibi bir yapı oluşturmuşsunuz bende onun basit halini bulmaya çalışıyordum işin içine kendo da girince olay çok karışıyor siz temelden sade bir şekilde anlatmışsınız makaleler için çok teşekkürler

    YanıtlaSil
  21. Elinize sağlık, çok güzel ve bilgilendirici bir yazı olmuş. Teşekkürler.

    YanıtlaSil