Generic Repository ve Unit of Work Pattern, Entity Framework, Unit Testing, Autofac IoC Container ve ASP.NET MVC [Part 1]

Bu makalemde Asp.Net MVC içinde Entity Framework, IOC Container, Dependency Injection ile Generic Repository Pattern’ine base olmuş ayrılmış, Unit Test edilebilir, N katmanlı mimarili bir uygulamanın nasıl implement edildiğini göstereceğim.

Bu Uygulama İçin Kullanılan Teknoloji ve Toollar : 

1. .NET Framework 4.5
2. ASP.NET MVC 5.1
3. Entity Framework 6.0
4. Sql Server LocalDB v11
5. Autofac 3.4
5. Moq
6. Effort
7. Visual Studio 2012

Amaç :

  1.  Unit Test uygulamak ve test işlemini kolayca entegre edebilmek.
  2. IOC ve Unit Test ile Repository ve Unit of Work üzerinde örnek uygulamayı tamamlamak.

Repository Pattern: Proje için abstract bir data erişim katmanı oluşturmak, tüm veri erişim mantığının tek bir yerde toplamasını sağlar. Generic özellik ile ise ortak senaryolar için kod miktarını azaltabilir, tek bir yerden yönetebiliriz.

Unit of Work pattern: Etkileşimleri birleştirmek ve bir kez işlem kullanarak onları bağlamak için kullanılan desendir.

Daha fazla bilgi için aşağıdaki linklere göz gezdirebilirsiniz:

Repository Pattern.

Design pattern – Inversion of control and Dependency injection

Uygulamamıza farklı katmanlar da içerebilir:

1. Database
2. Data Access Katmanı (ORM Araçlarını (EF, Linq), Modelleri ve DataContext içeren katman)
3. Servis Katmanı (Business ve Domain Mantığı)
4. MVC Web Katmanı (UI Part)
5. Test Katmanı (Unit Test vb..)

Solution’u Hazırlamak:

1. Visual Studio’yu açın ve yeni bir  “ASP.NET MVC 5 Empty Project” oluşturun ve ismine “SampleArch“ verin.
2. Solutiona sağ tıklayın ve “Class Library” Projeleri ekleyin.

SampleArch.Model
SampleArch.Repository
SampleArch.Service

Default olarak oluşturulan Class1.cs isimli classı silebilirsiniz.

3. Add project > Test > “Unit Test project” isimli bir test projesi ekleyin.

SampleArch.Test

Default olarak oluşturulan UnitTest1.cs isimli classı silin.

folder-structure

Model:

Öncelikle Entity Framework yüklemeliyiz. Bunun için Package Manager Console ekranından command satırına Install-Package EntityFramework yazarak Entity Framework’un son sürümü olan 6.1 package’ı soluitona eklenmiş olacaktır.

SampleArch.Model projemizin içine bazı base classlar ekleyelim.

IEntity.cs:

public interface IEntity<T>
{
    T Id { get; set; }
}

Entity.cs:

public abstract class BaseEntity {
}
public abstract class Entity<T> : BaseEntity, IEntity<T>
{
    public virtual T Id { get; set; }
}

IAuditableEntity.cs:

public interface IAuditableEntity
   {
       DateTime CreatedDate { get; set; }
    
       string CreatedBy { get; set; }
       DateTime UpdatedDate { get; set; }
            
       string UpdatedBy { get; set; }
   }

AuditableEntity.cs:

public abstract class AuditableEntity<T> : Entity<T>, IAuditableEntity   
   {
       [ScaffoldColumn(false)]
       public DateTime CreatedDate { get; set; }
     
       [MaxLength(256)]
       [ScaffoldColumn(false)]
       public string CreatedBy { get; set; }
       [ScaffoldColumn(false)]
       public DateTime UpdatedDate { get; set; }
       [MaxLength(256)]
       [ScaffoldColumn(false)]
       public string UpdatedBy { get; set; }
   }

ScaffoldColumn(false) ise şu şekilde kullanılır : ASP.NET MVC Scaffolding bu propertyleri View içinde generate etmeyecektir. Biz bu propertyleri context içindeki SaveChanges methodunda ele almış olacağız.

Person and Country Modellerini Oluşturalım :

[Table("Person")]
   public class Person : AuditableEntity<long>
   {      
       [Required]
       [MaxLength(50)]
       public string Name { get; set; }
       [Required]
       [MaxLength(20)]
       public string Phone { get; set; }
       [Required]
       [MaxLength(100)]
       public string Address { get; set; }
       [Required]
       [MaxLength(50)]
       public string State { get; set; }
       [Display(Name="Country")]
       public int CountryId { get; set;  }
       [ForeignKey("CountryId")]
       public virtual Country Country { get; set; }
   }
public class Country : Entity<int>
  {     
      [Required]
      [MaxLength(100)]
      [Display(Name="Country Name")]
      public string Name { get; set; }
      public virtual IEnumerable<Person> Persons { get; set; }
  }
DB Context Ekleyelim : 
public class SampleArchContext : DbContext
  {
      public SampleArchContext()
          : base("Name=SampleArchContext")
      {
      }
      public DbSet<Person> Persons { get; set; }
      public DbSet<Country> Countries { get; set; }
     
      public override int SaveChanges()
      {
          var modifiedEntries = ChangeTracker.Entries()
              .Where(x => x.Entity is IAuditableEntity
                  && (x.State == System.Data.Entity.EntityState.Added || x.State == System.Data.Entity.EntityState.Modified));
          foreach (var entry in modifiedEntries)
          {
              IAuditableEntity entity = entry.Entity as IAuditableEntity;
              if (entity != null)
              {
                  string identityName = Thread.CurrentPrincipal.Identity.Name;
                  DateTime now = DateTime.UtcNow;
                  if (entry.State == System.Data.Entity.EntityState.Added)
                  {
                      entity.CreatedBy = identityName;
                      entity.CreatedDate = now;
                  }
                  else {
                      base.Entry(entity).Property(x => x.CreatedBy).IsModified = false;
                      base.Entry(entity).Property(x => x.CreatedDate).IsModified = false;                  
                  }
                  entity.UpdatedBy = identityName;
                  entity.UpdatedDate = now;
              }
          }
          return base.SaveChanges();
      }
  }

Burada SaveChanges yöntemi override ederek denetimi yapılan sütun değerlerini set ediyoruz.

Şimdi ise web config içerisinde SampleArchContext isminde bir ConnectionString ekliyoruz.

<connectionStrings>
  <add name="SampleArchContext"
    connectionString="Data Source=(LocalDb)\v11.0;Initial Catalog=SampleArch;Integrated Security=SSPI;AttachDBFilename=|DataDirectory|\SampleArch.mdf"
    providerName="System.Data.SqlClient"/>
</connectionStrings>

class-model

Repository Ekleme:

SampleArch.Repository” projesinin içerisine, “SampleArch.Model” projesinin referansını eklemeliyiz. Aşağıdaki base classlarımı projemize ekleyelim:

IGenericRepository.cs

public interface IGenericRepository<T> where T : BaseEntity
  {
      IEnumerable<T> GetAll();
      IEnumerable<T> FindBy(Expression<Func<T, bool>> predicate);
      T Add(T entity);
      T Delete(T entity);
      void Edit(T entity);
      void Save();
  }

GenericRepository.cs

public abstract class GenericRepository<T> : IGenericRepository<T>
      where T : BaseEntity
   {
       protected DbContext _entities;
       protected readonly IDbSet<T> _dbset;
       public GenericRepository(DbContext context)
       {
           _entities = context;
           _dbset = context.Set<T>();
       }
       public virtual IEnumerable<T> GetAll()
       {
           return _dbset.AsEnumerable<T>();
       }
       public IEnumerable<T> FindBy(System.Linq.Expressions.Expression<Func<T, bool>> predicate)
       {
           IEnumerable<T> query = _dbset.Where(predicate).AsEnumerable();
           return query;
       }
       public virtual T Add(T entity)
       {
           return _dbset.Add(entity);
       }
       public virtual T Delete(T entity)
       {
           return _dbset.Remove(entity);
       }
       public virtual void Edit(T entity)
       {
           _entities.Entry(entity).State = System.Data.Entity.EntityState.Modified;
       }
       public virtual void Save()
       {
           _entities.SaveChanges();
       }
   }

Person ve Country Repositorylerini ekleyelim:

IPersonRepository.cs

public interface IPersonRepository : IGenericRepository<Person>
{
    Person GetById(long id);
}

PersonRepository.cs

public class PersonRepository : GenericRepository<Person>, IPersonRepository
   {
       public PersonRepository(DbContext context)
           : base(context)
       {
          
       }
       public override IEnumerable<Person> GetAll()
       {
           return _entities.Set<Person>().Include(x=>x.Country).AsEnumerable();
       }
       public Person GetById(long id)
       {
           return _dbset.Include(x=>x.Country).Where(x => x.Id == id).FirstOrDefault();           
       }
   }

Burada GetAll() methodunu Country classı içerisinde override ettik.

ICountryRepository.cs

public interface ICountryRepository : IGenericRepository<Country>
   {
       Country GetById(int id);
   }

CountryRepository.cs

public class CountryRepository : GenericRepository<Country>, ICountryRepository
    {
      public CountryRepository(DbContext context)
            : base(context)
        {
           
        }
        public Country GetById(int id)
        {
            return FindBy(x => x.Id == id).FirstOrDefault();           
        }
    }

Unit Of Work:

IUnitOfWork.cs

public interface IUnitOfWork : IDisposable
   {
       /// <summary>
       /// Saves all pending changes
       /// </summary>
       /// <returns>The number of objects in an Added, Modified, or Deleted state</returns>
       int Commit();
   }

UnitOfWork.cs

/// <summary>
   /// The Entity Framework implementation of IUnitOfWork
   /// </summary>
   public sealed class UnitOfWork : IUnitOfWork
   {
       /// <summary>
       /// The DbContext
       /// </summary>
       private DbContext _dbContext;
       /// <summary>
       /// Initializes a new instance of the UnitOfWork class.
       /// </summary>
       /// <param name="context">The object context</param>
       public UnitOfWork(DbContext context)
       {        
           _dbContext = context;
       }
       /// <summary>
       /// Saves all pending changes
       /// </summary>
       /// <returns>The number of objects in an Added, Modified, or Deleted state</returns>
       public int Commit()
       {
           // Save changes with the default options
           return _dbContext.SaveChanges();
       }
       /// <summary>
       /// Disposes the current object
       /// </summary>
       public void Dispose()
       {
           Dispose(true);
           GC.SuppressFinalize(this);
       }
       /// <summary>
       /// Disposes all external resources.
       /// </summary>
       /// <param name="disposing">The dispose indicator.</param>
       private void Dispose(bool disposing)
       {
           if (disposing)
           {
               if (_dbContext != null)
               {
                   _dbContext.Dispose();
                   _dbContext = null;
               }
           }
       }
   }

Service Layer:

SampleArch.Service projesi içine, SampleArch.Model ve SampleArch.Repository projelerinin referanslarını ekleyelim, ve projeye şu base classları ekleyelim.

IService.cs

public interface IService
    {
    }

IEntityService.cs

public interface IEntityService<T> : IService
    where T : BaseEntity
   {
       void Create(T entity);
       void Delete(T entity);
       IEnumerable<T> GetAll();     
       void Update(T entity);
   }

EntityService.cs

public abstract class EntityService<T> : IEntityService<T> where T : BaseEntity
   {
       IUnitOfWork _unitOfWork;
       IGenericRepository<T> _repository;
       public EntityService(IUnitOfWork unitOfWork, IGenericRepository<T> repository)
       {
           _unitOfWork = unitOfWork;
           _repository = repository;
       }    
       public virtual void Create(T entity)
       {
           if (entity == null)
           {
               throw new ArgumentNullException("entity");
           }
           _repository.Add(entity);
           _unitOfWork.Commit();        
       }
       public virtual void Update(T entity)
       {
           if (entity == null) throw new ArgumentNullException("entity");
           _repository.Edit(entity);
           _unitOfWork.Commit();
       }
       public virtual void Delete(T entity)
       {
           if (entity == null) throw new ArgumentNullException("entity");
           _repository.Delete(entity);
           _unitOfWork.Commit();
       }
       public virtual IEnumerable<T> GetAll()
       {
           return _repository.GetAll();
       }
   }

Person ve Country modelleri için Servis classlarını ekleyelim.

ICountryService.cs

public interface ICountryService : IEntityService<Country>
  {
      Country GetById(int Id);
  }

CountryService.cs

public class CountryService : EntityService<Country>, ICountryService
  {
      IUnitOfWork _unitOfWork;
      ICountryRepository _countryRepository;
      
      public CountryService(IUnitOfWork unitOfWork, ICountryRepository countryRepository)
          : base(unitOfWork, countryRepository)
      {
          _unitOfWork = unitOfWork;
          _countryRepository = countryRepository;
      }
      public Country GetById(int Id) {
          return _countryRepository.GetById(Id);
      }
  }

IPersonService.cs

public interface IPersonService : IEntityService<Person>
   {
       Person GetById(long Id);
   }

PersonService.cs

public class PersonService : EntityService<Person>, IPersonService
   {
       IUnitOfWork _unitOfWork;
       IPersonRepository _personRepository;
       
       public PersonService(IUnitOfWork unitOfWork, IPersonRepository personRepository)
           : base(unitOfWork, personRepository)
       {
           _unitOfWork = unitOfWork;
           _personRepository = personRepository;
       }
       public Person GetById(long Id) {
           return _personRepository.GetById(Id);
       }
   }

class-service

Bir sonraki makalemde autofac kurulumunu ve CRUD işlemlerini ele alacağım.

İyi Çalışmalar diliyorum..

Kaynak : http://techbrij.com/service-layer-entity-framework-asp-net-mvc-unit-testing

Reklamlar

Unit Testte Action’ın Modelin’inin Veri Türüne Erişim

Action methodlarından dönen modelin türü, durumlara göre farklı olabilir. Biz bu action methodundan dönen modelin tipini test etmek isteyebiliriz modelimizde. Bunun için Assert.InstanceOfType test methodu ile actiondan dönen modelin türünü karşılaştırabiliriz. Örnek action methodumuz şöyle olsun:

public ActionResult Urunler()
{
   return View(new List());
}

Test Methodumuz:

[TestMethod]
public void Urunler_Liste()
{
   HomeController controller = new HomeController();
   ViewResult sonuc = controller.Urunler() as ViewResult;
   Assert.IsInstanceOfType(sonuc.Model, typeof(List));
}

ASP.net MVC NEDİR?

“ASP.NET HTML, CSS, JavaScript ve sunucu taraflı programlama ile web sayfaları ve web siteleri yapmaya yarayan bir geliştirme yapısıdır.
ASP.NET 3 farklı geliştirme modeli sunar: Web pages, MVC (Model View Controller) ve Web Forms.”

“w3school”


MVC, uygulamanın kullanıcı arayüzünü 3 temel yapıya ayırır:

Model: Verinin nasıl değişeceğini ve nasıl yönetileceğini belirleyen iş kurallarını (Business Rules) içeren sınıfların tamamının bulunduğu katmandır. Genelde bunlar veritabanını temsil eden sınıflarlar veya domaini temsil eden nesnelerdir. EntityFramework, NHibernate gibi, entity-data-model ler de, model katmanında yer alabilir. Yani uygulamamızda kullanacağımız nesneler bu katmandadır.

View: Kullanıcı arabiriminin (User Interface – UI) gösterileceği katman. Dinamik olarak üretilen HTML şablonu bu katmandadır. Kısaca veri gösterim katmanı diyebiliriz.

Controller: Tüm sistem akışının, kullanıcı ile olan etkileşimi kontrol eden ve olayları yöneten sınıfların tamamı. View ve Model katmanları arasındaki ilişkiyi yönetir. Kullanıcıdan girdi alır, modelle iletişime geçer ve ne gösterileceğine karar verir.

ASP.NET MVC 1

13 MART 2009 da resmi olarak kodlar ve birim testlerle MVC mimarisi yayınlanmıştır. MVC mimarisinin bugün kullanılan bir çok özelliği aslında MVC 2 de gelecektir.

ASP.NET MVC 2

MVC 2 ilkinden 1 yıl sonra yayınlanmıştır (MART 2010). MVC 2 de olan bazı ana özellikler şunlardır:

  • Otomatik olarak oluşturulan kullanıcı arabirimi yardımcıları (UI Helpers) ve özelleştirilebilir şablonlar.
  • Hem server, hem de istemci taraflı, nitelik tabanlı (attribute-based) bir model doğrulama (model-validation) yapısı.
  • Strongly typed HTML Helpers
  • Geliştirilen Visual Studio araçları.


ASP.NET MVC 3

MVC 3, MVC 2 den 10 ay sonra yayınlanmıştır. Eklenen ana başlıklar:

  • Razor görüntüleme motoru (Razor View Engine).
  • .NET 4 dataannotation desteği.
  • Gelişmiş model doğrulama (model validation).
  • Unobtrusive JavaScript, jQuery Validation, ve JSON için daha iyi bir JavaScript desteği.
  • Yazılım ve platforma bağlı güncellemeler ve yönetim için NuGet kullanımı.

Razor View Engine

MVC 1 den itibaren bugüne kadar yapılan en büyük HTML görüntüleme güncellemesi Razor olmuştur. MVC 1 ve MVC 2 de genel olarak kullanılan görüntüleme motoru Web Forms görüntüleme motoru ile aynı sözdizimini ve dosya uzantısındadır. (ASPX/ASCX/MASTER)

Örnek Web Forms sözdizimi:

 
<%@ Page Language="C#" MasterPageFile="</Views/Shared/Site.Master" Inherits="System.Web.Mvc.ViewPage<MvcMusicStore.ViewModels.StoreBrowseViewModel>" %>
<asp:Content ID="Content1" ContentPlaceHolderID="TitleContent" runat="server">
    Browse Albums
</asp:Content>
<asp:Content ID="Content2" ContentPlaceHolderID="MainContent" runat="server">
    <div class="genre">
        <h3><em><%: Model.Genre.Name %></em> Albums</h3>
        <ul id="album-list">
            <% foreach (var album in Model.Albums) { %>
                <li>
                    <a href="<%: Url.Action("Details", new { id = album.AlbumId }) %>">
                    <img alt="<%: album.Title %>" src="<%: album.AlbumArtUrl %>" />
                    <span><%: album.Title %></span>
                    </a>
                </li>
            <% } %>
        </ul>
    </div>
</asp:Content>

Razorun söz dizimi tamamen kod odaklı bir şablonu vardır. İlk bakıldığı anda HTML ve .Net kodları hemen anlaşılır, okunması ve anlaşılması çok kolaydır. Yukarıdaki kodun Razor ile yazılmış hali aşağıdaki gibidir:

@model MvcMusicStore.Models.Genre
@{ ViewBag.Title = "Browse Albums"; }
<div class="genre">
    <h3><em>@Model.Name</em> Albums</h3>
    <ul id="album-list">
        @foreach (var album in Model.Albums)
        {
            <li>
                <a href="@Url.Action("Details", new { id = album.AlbumId })">
                    <img alt="@album.Title" src="@album.AlbumArtUrl" />
                    <span>@album.Title</span>
                </a>
            </li>
        }
    </ul>
</div>