Open
Mini Eticaret Aşamaları

Visual Studio'yu açalım ve Core Empty Projesi oluşturalım.



(2) Startup.cs dosyasını yapılandıralım.
Middleware denilen, akan giden bir süreç içerisinde belli işlemleri entegre etme işlemini gerçekleştirelim. Servis Konfigür metoduna -> services.AddMvc();ekleyelim.Mvc framework'ü ekliyoruz.
Konfigüre metodunda -> app.useMVC( route açıklayalım) ekleyelim. Daha sonra Durum kodlarına ve static Files oluşumunu sağlayalım.
        public void ConfigureServices(IServiceCollection services)
        {
           services.AddMvc(option => option.EnableEndpointRouting = false);
        }

        public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
            app.UseMvc(routes =>
            {
                routes.MapRoute(
                    name: "default",
                    template: "{controller=Home}/{action=Index}/{id?}"
                    );

            });
            app.UseStaticFiles();
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            } 
        }

(3)Uygun klasörleri oluşturalım. Models,Controllers,Views ve en son wwwroot klasörü.
(3a)Models klasörünü oluşturalım içerisine Entity ve Dal klasörünü yaratalım. Dal klasöründe Abstract ve Concrete klasörümüz olacak.Concrete'te bir adet Ef (DbContext için) klasörümüz olacak.Abstract uygulamanın hangi teknolojiykullandığını önemsemeyen bir inerface yapısı olacaktır.
(3b) Views klasörüne 1 adet klasör oluşturup adını Shared koyalım.
(I) Shared klasörüne _Layout ve _AdminLayout adında 2 adet view ekleyelim. (II)View klasörünün içerisine _ViewImports ve _ViewStart adında 2 view ekleyelim.
    _ViewImports sayfası bizim import edeceğimiz dosyalar belirtsin.
    @namespace Web1.Models //proje adı ve Models klasörümüzü tanıtıcağız.
    @addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers //Taghelper namespace tagini tüm sayfalara etkileştirmiş oldu.
    

(III) _ViewStart , içeriğine
@{
    Layout = "_Layout";
     }

(3c)Controllers klasörüne, HomeController ekleyelim.Çıkan pencerede Layout kullanacağımızı sekmeye tıklayarak belirtelim.Bir index sayfası oluşturalım.(Proje otomatik paketler yükleyebilir.(efcore,Tools,Logging,Design gibi)
İlerde sorunsuz kodlama yapalım diye biz bu kütüphanleri indirelim.EfCore, Ef Core SqlServer, EfCore Design, EfCore Tools)




Entity Katmanında Many-To-Many ilişkili tablo oluşturacağız.(Çoka çok Entity Katmanı Many-to-Many Gösterimi)
(8) Models klasöründe Entity klasörü olusturduk.İçersine Category ve Product adında sınıflar ekleyelim.Many to Many ilişkisi olacak.
  using System.ComponentModel.DataAnnotations;


    public class Category
    {
        [Key]
        public int ID { get; set; }
        public string CategoryName { get; set; }
        public ICollection<ProductCategory> ProductCategories  { get; set; }
    }

   public class Product
    {
        [Key]
        public int ID { get; set; }
        public string ProductName { get; set; }
        public long Price { get; set; }


        public ICollection<ProductCategory> ProductCategories { get; set; }

    } 

Many to Many ilişkisi kuracağız. 1 kategorinin birden çok ürünü olucak. 1 ürününde aynı zamanda birden çok kategorisi
EF tarafından bu ilişki, oto. algılanıp kendi kendine oluşturuluyordu 3. bir ara tablo yaratmak zorunda değildik.
Ama Ef Core, yeni baştan yaratıldığı için henüz 3.ara tablolar oto. desteklenmiyor. Bundan dolayı model ve Migration create anında bir kaç kod ilave edeceğiz.

    public class ProductCategory
    {
        public int CategoryID { get; set; }
        public Category Category { get; set; }
        public int ProductID { get; set; }
        public Product Product { get; set; }
    }

(9)Dal katmanı->Concrete klasörü-> Ef klasörü oluşturalım ve WebDbContext adında bir sınıf yaratalım.
Bir ara tablomuz mevcuttu ModelCreate anında bu yapıyı ilişkilendirelim.

public class WebDbContext:DbContext
{
    public WebDbContext( DbContextOptions<WebDbContext> options) : base (options) // options'ı base class'a göndermemiz gerekir.
    {

    }
    public DbSet<Category> Category { get; set; }
    public DbSet<Product> Product { get; set; }

    //3. ara tablomuzun, 2 adet ilişkilenmiş ID si var. ID,ProductID,CategoryID. Bunu create anında belirteceğiz.
    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
      modelBuilder.Entity<ProductCategory>().HasKey(fk => new { fk.CategoryID, fk.ProductID });  //iki tane foreign keyi var
    }

}

Startup.cs de, var olan DbContextimizi belirtelim.

    using Microsoft.AspNetCore.Builder;
    using Microsoft.AspNetCore.Hosting;
    using Microsoft.EntityFrameworkCore;
    using Microsoft.Extensions.Configuration;
    using Microsoft.Extensions.DependencyInjection;
    using Web.Models.Dal.Concrete.Ef;

        public IConfiguration Configuration { get; }

        public Startup(IConfiguration configuration)
        {
            Configuration = configuration;
        }
        public void ConfigureServices(IServiceCollection services)
        {
           services.AddDbContext<WebDbContext>(x => x.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));
        }

Projemizin appsettigs.json dosyasına girelim (yoksa add item ile oluşturalım) ve connection string yolunu belirtelim.
Bu json dosya, Startup.cs deki DefaultConnection string'e değer verecek.

    "ConnectionStrings": {
      "DefaultConnection": "Server=.;Database=ArcMDB;Trusted_Connection=True; "
    }

Dal Katmanına geliyoruz. Package Console Manager açıyoruz.İlk önce projeyi derliyoruz.Hata olmamalı.
Araçta, ilgili projemizin Dal katmanı olduğunu belirtiyoruz. "add-migration v1" "update-database -verbose" diyelim.
Eğer uyarı hataları vermişse araç lütfen sayfanın en altındaki faydalı bilgiler'e bakınız.




Şu an sadece Ne yapacağımızı gözden geçerelim, bundan sonra açıklama yapalım.
(10)Dal klasörüne gelelim. Abstract klasöründe bir adet IProductRepository arabirimi(interface) oluşturalım.

    public interface IProductRepository
    {
        IQueryable<Product> ListProduct { get; }  //Not:IQueryable kullanımı abstracta tersdir.IEnumerable kullanın.
    }

Concrete klasöründe ise ProductRepository oluşturalım.ve abstract yapımızdan kalıtım alalım.

    public class ProductRepository : IProductRepository  
    {
        private WebDbContext context; 
        public ProductRepository(WebDbContext _prm)
        {
            context = _prm;
        }
        public IQueryable<Product> ListProduct => context.Product;
    }

Startup.cs de ilgili soyut metodumuzu somut olarak görmesini sağlayalım.

   public void ConfigureServices(IServiceCollection services)
        { 
            services.AddTransient<IProductRepository, ProductRepository>();
        }

Projemizi çalıştırmak için HomeController ile Index action oluşturalım.

    public class HomeController : Controller
    {
        private IProductRepository pRepository;
        public HomeController(IProductRepository prm)
        {
            pRepository = prm;
        }
        public IActionResult Index()
        {
            return View(pRepository.ListProduct);
        }
    }

Dal katmanımızın, Abtract klasöründe; tablolarımızın interface metod-imzalarını yazacağız. Concrete klasöründe ise onların body metodlarını yazacağız.
Bu prosesten sonra Controller oluşturaacğız. Interfaceden bağımlılık alacağız.Lakin interface, abstract gibi yapılara maalesef new instance yapamıyoruz. Hem bağımlılıkların azaltılması adına constructor oluşturup dışardan alınan parametreyi, oluşturulan nesne ile geri döndür diyeceğiz.
Sonra , Startup.cs dosyasına gidip; dependency Injection kavramına uyarak, "Interface kullandığımız yapı aslında dolu bir sınıf olan concretetir" dememiz gerekecektir.
Not:Eğer ilerde farklı bir teknoloji kullanmak istersek, örneğin Ado.Net gibi. O vakitte, abstract ve concrete repository ler oluşturup, Controllera geldiğimizde constructor ile inteface bağımlılığı oluşturacağız. Unutmayalım ki, Controller dolu bir sınıfa değil, sanal imzalı bir arabirime bağlı olacaktır

Not:Neden IQuerable kullandık, IEnumerable değil.IQuareble tipi,tüm veriler gelmez, üzerie filtreleme işlemini yapmak mantıklı olur. IEnumerable ile Tüm ürünlerini getiririz.Örneğin 100 tane varsa hepsi getirilir.Geldikten sonra filtreleme yapar.Bununda anlamı pek yoktur.

namespace Web.Models.Dal.Abstract
{
    public interface IProductRepository
    {
        Product Get(int id);
        IQueryable<Product> GetAll();
        IQueryable<Product> Find(Expression<Func<Product, bool>> predicate); //using System.Linq.Expressions; linq sorgumuzu içine yazmak için.
        void Add(Product prm);
        void Delete(Product prm);
        void Update(Product prm);
        void Save();  //Görevi context üzerinden saveChanges() i çağırmak.

        IQueryable<Product> ListProduct { get; }

    }
namespace Web.Models.Dal.Concrete
{
    public class ProductRepository : IProductRepository  
    {
        private WebDbContext context; 
        public ProductRepository(WebDbContext _prm)
        {
            context = _prm;
        }
        public IQueryable<Product> ListProduct => context.Product;

        public void Add(Product prm)
        {
            context.Add(prm);
        }

        public void Delete(Product prm)
        {
            context.Product.Remove(prm);
        }

        public void Update(Product prm)
        {
            context.Entry<Product>(prm).State = Microsoft.EntityFrameworkCore.EntityState.Modified;
        }

        public IQueryable<Product> Find(Expression<Func<Product, bool>> predicate)
        {
            return context.Product.Where(predicate);
        }

        public Product Get(int id)
        {
            return context.Product.FirstOrDefault(x => x.ID == id);
        }

        public IQueryable<Product> GetAll()
        {
            return context.Product;
        }

        public void Save()
        {
            context.SaveChanges();
        }
    }
}
    public class HomeController : Controller
    {
        private IProductRepository pRepository;
        public HomeController(IProductRepository prm)
        {
            pRepository = prm;
        }
        public IActionResult Index()
        {
            return View(pRepository.GetAll());
        }
    }
@model IEnumerable

@foreach (var item in Model)
{
    @item.ProductName

}

Farkedersek, Repository yazarken Tablo isimleri farklı ama içindeki metotlar hep aynı kalır. Biz bir metot yazalım ve farklı tablo isimler ile onları çağıralım. Daha mantıklı olmaz mı?
Abstract klasörüe 1 adet IGenericRepostory adında arayüz ekleylim.
Tablo tipinde tanıtığımız herşeyin yerine T yazacağız.

  public  interface IGenericRepository <T>  where T : class
    {
        T Get(int id);
        IQueryable<T> GetAll(); 
        IQueryable<T> Find(Expression<Func<T, bool>> predicate);
        void Add(T prm);
        void Delete(T prm);
        void Update(T prm);
        void Save();
    }
  
 public class GenericRepository<T> : IGenericRepository<T> where T : class
    {
        //T üzerinden işlemler yapıcaz. 
        protected readonly DbContext context;  //using Microsoft.EntityFrameworkCore;
        public GenericRepository(DbContext prm)
        {
            context = prm;
        }
        public void Add(T prm)
        {
            context.Set<T>().Add(prm);     //Set metodu ile context üzerindeki hangi entity yi gideceğimizi söyleriz
        }

        public void Delete(T prm)
        {
            context.Set<T>().Remove(prm);
        }

        public void Update(T prm)
        {
            context.Entry(prm).State = EntityState.Modified;
        }
        public IQueryable<T> Find(Expression<Func<T, bool>> predicate)
        {
            return context.Set<T>().Where(predicate);
        }

        public T Get(int id)
        {
            return context.Set<T>().Find(id);
        }

        public IQueryable<T> GetAll()
        {
            return context.Set<T>();
        }

        public void Save()
        {
            context.SaveChanges();
        }
 
    }
}

(Interface ler için)Artık IKategori, IÜrün oluşturduğumuz interfaceleri, IGeneric yapıdan kalıtım alabilecek.

    public interface IProductRepository:IGenericRepository<Product>
    {
        //Burda dilersek özel metotlar tanımlayabiiriz.
        List<Product> GetTop10Product();
     }

   public interface ICategoryRepository:IGenericRepository<Category>
    {
    }

(Concrete için ) Repository Tablolar da hem GenericRepositorylerden hem de InterfaceRepository den oluşacak.
Interface kısmını sona yazmamız hiyerarşik yapıdan doğrudur.
ÜrünRepository, Generic Repository den türemiştir. Onu da IProdutRepositorye implement edeceğiz

 
    public class CategoryRepository : GenericRepository<Category>, ICategoryRepository
    {
        public CategoryRepository(WebDbContext prm) : base (prm)  // prm base-i GenericRepositorydeki DbContext contextidir base onun yeridir anlamını verir.
        {
        }

        //Extra metodlarımız varsa onlar için context almamız gerekecek. 
//Bunun için webdbcontext tipli property oluştururuz.
//public Webdbcontext context { context değeri, kendisinin türediği GenericRepositoryde oluşmuştu.
//(Base clası içindeki)Basedeki, context nesnesini, WebDbContext olarak döndürüyoruz. public WebDbContext Cntxt { get { return context as WebDbContext; } } public Category GetbyName(string Name) { return Cntxt.Category.Where(x => x.CategoryName == Name).FirstOrDefault(); //Cntxt tabi base clasımız, sınıın kalıtım aldığı } }

Hatırlayalım: GenericRepository de Constructor yapımızdaki değişken şu şekildeydi.

     private WebDbContext context;
    public class GenericRepository<T> : IGenericRepository<T> where T : class
    {   
        public GenericRepository(DbContext prm) //burdaki prm WebDbContexti temsil ediyor o yüzden context= prm dedik.
        { context= prm;   
        }
    }  

Şimdi de test edelim. CategoryController oluşturalım ve GetAll metodu ile listeleyelim.

    public class CategoryController : Controller
    {
        private ICategoryRepository repo;
        public CategoryController(ICategoryRepository prm)
        {
            repo = prm;
        }
        public IActionResult Index()
        {
            return View(repo.GetAll());
        }
    }
    @model IEnumerable<Category>
    @foreach (var item in Model)
    {
        @item.CategoryName
    }
            services.AddTransientIProductRepository, ProductRepository>();
            services.AddTransientICategoryRepository, CategoryRepository>();

Modellerimizin üzerinde işlemler görelim diye Repository sınıfı oluşturduk.
Herbiri DbContext ile Constructorda bağımlı.
Controller oluşturduk ve constructor içerisinde repositorylerden nesneler türettik.
Birden çok repository nesnesi oluşturup controllerda göstermemiz, bize performans düşüklüğü yaratır.
Onun yerine, repository leri bir nesne içerisinde almamız daha mantıklı olmaz mıydı?
Bu sürece Unit of Work Paterni denilir.Unit of Work sayesinde, tüm repository nesnelerii bir çatı altına alacağız.
Başlayalım; ilk önce Dal katmanına interface IUnitofWork sonra concrete alanında UnitOfWork oluşturalım.

    public interface IUnitofWork: IDisposable   
    {
        IProductRepository Products { get; }
        ICategoryRepository Categories { get; }
 
        int SaveChanges();  //İstediğimiz ismi verebiliriz Kendi metodumuz.
    }
    //Kısaca, kaç tane ITabloRepository varsa tanımlıyoruz.
    //Onları  isimlerle çağıracağız. Sadece get dememiz kafidir.

Şimdi UnitOfWork sınıfı oluşturalım.

  public class UnitofWork:IUnitofWork
    {
        private readonly WebDbContext context;
        public UnitofWork(WebDbContext prm)
        {
            context = prm ?? throw new ArgumentNullException("DbContext cannot be null");
            //dışardan gönderilen DbContext boş ise bir exception fırlatalım.
        }

        //ilk önce Repositoryimilere değişken oluşturcaz.
        private IProductRepository _products;
        private ICategoryRepository _categories;

        //arabirimi uygulayıp, productsları istediğimiz zaman get  return ile gönderelim ancak null ise productstan yeni bir nesne üretip döndürelim.
        public IProductRepository Products { get { return _products ?? (_products = new ProductRepository(context)); } }
        public ICategoryRepository Categories { get { return _categories ?? (_categories = new CategoryRepository(context)); } }

        public void Dispose()
        { context.Dispose(); }

        public int SaveChanges()
        {
            try
            {  return context.SaveChanges(); }
            catch (Exception)
            {  throw; }
        }
    }

Startup.cs dosyasında Konfigure Servisinde kullanacağımız belirtelim.1 adet yazmamız yeterli artık.
services.AddTransient<IUnitofWork, UnitofWork>();
Controller oluşturalım ve test edelim.

    public class HomeController : Controller
    { 
        private IUnitofWork uow;

        public HomeController(  IUnitofWork prm2)
        { 
            uow = prm2;
        } 
        public IActionResult Index()
        {
            return View(uow.Products.GetAll());
        }






Projeye sağ click, istemci Tarafı Kitaplığı ekleyelim.
Sağlayıcı alıştığımız Nugget kütüphanesi olsun (unpkg kısaltılması) arama paneline bootstrap css deyin ve wwwroot klasörü içerisinde oluşturucağımızı belirtelim.
wwwroot klasörüne lib klasörüne bootstrap kütüphanesini ekledik.Daha sonra kişisel images, css ve js klasörülerini oluşturalım.
Bu araçtan istifade ederek ; font awesome css, Jquery gibi dilediğimiz kütüphaneleri indirelim.
css style larımıza bundler vermek için https://marketplace.visualstudio.com/items?itemName=MadsKristensen.BundlerMinifier sitesini ziyaret edip bundler ve minifer özelliğini indirelim. Projemize gelip stillerimizi ctrl ile tıklatıp sağ click bundler&minifier diyelim.
Bir template indirelim ve taslağı projemize giydirelim. Bunun için;
Template i komple sürükle bırak yöntemi ile -wwwRoot klasörü içerisine atalım.

Not:AspNet Core, proje içerisindeki herhangi custom yarattığınız klasörleri tanımaz. Url de bildirseniz bile size o klasörü içindeki herhangi bir dosyayı vermez.Tanıdığı klasörler wwwRoot içerisinde olmalıdır. ya da tanıtmak için startup.cs de staticfiles metodunda belirtmeniz gerekir.

Template istediğiniz sayayı anasaya yapmak için, sayfanın içerisine girelim. Tüm kodları kopyalayıp Layout içerisine yapıştıralım.(Tabi custom css ve js niz varsa onları muhafıza edelim.)
Örneğin;Category sidebarleft adlı bir dosya için , layout a gelip tüm kodlarını içerisine dahil edelim.
css ve jslerine göz gezdirelim bunu bir bundle içerisine alalım ama sırasına dikkat edelim.Bunun için;
Templateki ilgili css ve ya jslerin üzerine sırası na dikkat ederek ctrl'e basılı tutarak seçelim. Not:Sırsıyla seçmezsek çakışmalar olacaktır.Sağ click add bundler%minimizer , karşımıza gelen ilgili klasörü seçip ekleyelim.
Bu araç bize bundleconfig.json adında bir dosyada olşacak ilgili dosyyı açıp sırasını düzenleyelim.
Templates deki images klasörünü , wwwwroot klasörüne kopyala/yapıştır yapalım.

[
  {
    "outputFileName": "wwwroot/css/bundle-css.css",
    "inputFiles": [
      "wwwroot/Template/style.css",
      "wwwroot/Template/styles/lunar.css",
      "wwwroot/Template/custom.css",
      "wwwroot/Template/demo/demo.css"
    ]
  }
]


Bu template göre bizim content in aşağıdaki classlarını silip RenderBody() dememiz gerek.

Product Tablosuna, IsActive, IsHome, IsFeatured(öne çıkan) gibi bit türlü ve ImageUrl string tipli kolonlar ilave edelim.
Hemen Paket Yönetici Konsolundan add-migration , update database -verbose edelim.
Sonra Category Tablosuna el değmeden, CategoryViewModel oluşturalım Count adında int tipli bir kolon ekleyelim.Maksat ürünlerinin sayısını yazdırmak.Models-> Entity->ViewModels adında bir klasör oluşturalım ve CategoryVM adında bir sınıf yaratalım.

    public class CategoryVM
    { 
        [Key]
        public int ID { get; set; }
        public string CategoryName { get; set; }
        public int Count { get; set; }
        public List ProductCategories { get; set; }
    }
    public interface IUnitofWork: IDisposable   
    {
        IProductRepository Products { get; }
        int SaveChanges();  //DbContextin SaveChangesi 
    }
 public class UnitofWork:IUnitofWork
{
    private readonly WebDbContext context;
    public UnitofWork(WebDbContext prm)
    {
        context = prm ?? throw new ArgumentNullException("DbContext cannot be null"); 
    } 
    private IProductRepository _products;
 
    public IProductRepository Products { get { return _products ?? (_products = new ProductRepository(context)); } } 
 

    public int SaveChanges()
    {
        return context.SaveChanges();
    }
}
    public class HomeController : Controller
    {
        private IUnitofWork uow;
        public HomeController(IUnitofWork _prm)
        {
            uow = _prm;
        } 
        public IActionResult Index()
        {
            return View(uow.Products.GetAll().Where(x => x.IsActive && x.IsHome).ToList());
        }
    }  

Component oluşturalım

Bir klasör oluşturun ismi Components, ViewComponents, ViewsComponents olabilir. (ViewComponent ten kalıtım alacak, public ViewComponentResult Invoke() metodu oluşturacağız.)
İçerisine bir sınıf oluşturalım adı LeftCategoryMenu olsun
a-)Metot oluşturalım

    public class LeftCategoryMenu : ViewComponent //using Microsoft.AspNetCore.Mvc;
    { 
            private ICategoryRepository repo;
            public LeftCategoryMenu(ICategoryRepository _prm)
            {
                repo = _prm;
            }
            public IViewComponentResult Invoke()
            {
               // return View(repo.GetAll());
               return View(repo.GetProductsCount());
            }
    }

b-)İlgili View Görünüm oluşturalım.Views->Shared->Components->LeftCategoryMenu klasörü ve içnde Default view oluşturalım.
(Aynı Controller Action ismi ve ilgili Action ismindeki klasörün içinde Default View gibi)

@model IEnumerable<CategoryVM>
 
<div class="widget">
<h4>CATEGORIES</h4>
<ul class="list-unstyled link-list">
    @foreach (var item in Model)
    {
     <li><i class="fa fa-angle-right fa-fw" aria-hidden="true"></i> <a asp-controller="Product" asp-action="List" asp-route-category="@item.CategoryName"  >@item.CategoryName <span class="pull-right">@item.Count</span></a></li>
    }
</ul>
</div>

CategoryMEnu Componentin Modeli CategoryViewModel olacak orda count propertisi, Count metoduna bağlanacak.

   public interface ICategoryRepository:IGenericRepository<Category>
    {
        Category GetbyName(string Name);
        IEnumerable<CategoryVM> GetProductsCount();
    }
}


    public class CategoryRepository : GenericRepository<Category>, ICategoryRepository
    {
        public CategoryRepository(WebDbContext prm) : base (prm)  // base i GenericRepositorydeki DbContext contextidir base onun yeridir demektir.
        {
        }
        public WebDbContext Cntxt { get { return context as WebDbContext; } }
        public IEnumerable<CategoryVM> GetProductsCount()
        {
            return GetAll().Select(x => new CategoryVM
            {
                ID = x.ID,
                CategoryName = x.CategoryName,
                Count = x.ProductCategories.Count()
            });
        }
    }

Artık tek yapmamız gerek Componenti çağırmak.Index sayfasına gidip @await Component.InvokeAsync("LeftCategoryMenu") ekleyelim.




Şimdi aynı component düzenini, öne çıkan ürünler için uygulayalım. ViewsComponent klasörü içinde FeaturedProducts adında bir Component olşturalım.

    public class FeaturedProducts:ViewComponent
    {
        IProductRepository repo;
        public FeaturedProducts(IProductRepository _prm)
        {
            repo = _prm;
        }

        public IViewComponentResult Invoke()
        {
            return View(repo.GetAll().Where(x=> x.IsFeatured==true && x.IsActive).ToList());

        }
    }

Views-Shared-Components - FeaturedProducts klasörüne bir adet Default sayfası oluşturalım.

@model ICollection<Product>

 <div class="widget">
<h4>FEATURED</h4>
<div class="products-list">

    @foreach (var item in Model)
    {
    <div class="media">
        <a class="media-left" href="#">
            <img alt="" class="img-responsive product-thumb" src="/images/products/@item.ImageUrl">
        </a>
        <div class="media-body">
            <a  class="product-title"  asp-controller="Product" asp-action="Details" asp-route-id="@item.ID">@item.ProductName & lt;/a>
            <p class="product-price">@item.Price</p>
        </div>
    </div>
    }
</div>
</div>

Tek yapmamız gereken index sayfasına gelip @await Component.InvokeAsync("FeaturedProducts") demek.
Ayrıca Index sayfasındaki ürünler IsHome ve IsActive kolonları true olanlardı. Biz Model.Count() dersek sayfadaki ürünlerin sayısını görmüş oluruz. Bu kodu ekleyebiliriz.

Her bir ürünün birden fazla görseli, birden fazla da detay bilgisi olacaktır.
Product modelimize, datetime türünde AddedTime, string türlerinde Description ve Body kolonları ekleyelim.
Bire çok ilişkiledirme yapacağız. Bir ürünün birden çok görseli olabilir ama 1 resim 1 ürüne ait olur.
Aynı şekilde Ürünün özellikleri adında bir tablo oluşturcaz. Bire çok bir ilişki olacak.O zaman Models klasörüne sağ click ve add Class.

    public class Image
    {
        [Key]
        public int ID { get; set; }
        public string ImageName { get; set; }

        [ForeignKey("IProduct")]
        public int ProductID { get; set; }
        public Product IProduct { get; set; }  //nav property
    }
    public class ProductAttribute
    {
        public int ID { get; set; }
        public string Attribute { get; set; }
        public string Value { get; set; }

        [ForeignKey("Product")]
        public int ProductID { get; set; }
        public Product Product { get; set; }  //nav property
    }
    public class Product
    {
        [Key]
        public int ID { get; set; }
        public string ProductName { get; set; }
        public long Price { get; set; }
        public bool IsActive { get; set; }
        public bool IsHome { get; set; }
        public bool IsFeatured { get; set; }
        public string   ImageUrl { get; set; }

        public string Description { get; set; }
        public string Body { get; set; }
        public DateTime AddedTime { get; set; }

        public ICollection<ProductCategory> ProductCategories { get; set; }
        public ICollection<Image> Images { get; set; }
        public ICollection<ProductAttribute> ProductAttributes { get; set; } 

    }

DbContext sayfamızda bu tabloları ekleyelim.

        public DbSet<Category> Category { get; set; }
        public DbSet<Product> Product { get; set; }
        public DbSet<Order> Order { get; set; }
        public DbSet<ProductAttribute> ProductAttribute { get; set; }
        public DbSet<Image> Image { get; set; }

Sıra migratinda. Add-migration v.. Update-Database -verbose
Controller alanına ProductController adında bir denetleyici daha oluşturalım. Index ve Details ActionResultlarımız olsun, ilgili viewleri ekleyelim.
Template deki details sayfasını ekleyelim. Eğer Slider paneliniz varsa ; Detay sayfayı tarayıcınızdan açın ve content sayfasını kopyalayıp yapıştıralım.
Detay sayfamızda; URunlerin Fotograf Listesi, Urun Detayları ve Urunun kendisi olacak. O halde Bir ViewModel tasarlayalım ve Viewde tanımlayalım.View için tasarlandığı için genelde, model sınıfına o viewin sayfa adını koyarız.

    public class ProductDetailsVM
    {
        public Product Product { get; set; }
        public ICollection<Image> ProductImages { get; set; }
        public ICollection<ProductAttribute> ProductAttributes { get; set; }
        public ICollection<Category> Categories { get; set; }
    }

ProductController oluşturalım

   public class ProductController : Controller
    {
        private IProductRepository repo;
        public ProductController(IProductRepository _prm)
        {
            repo = _prm;
        }
        public IActionResult Details(int ID)
        {
            return View(repo.GetAll().Where(x => x.ID == ID)
                .Include(x => x.Images)  //include ile Listeleri(ICollection) productCategories getirelim
                .Include(x => x.ProductAttributes)
                .Include(x => x.ProductCategories)
                .ThenInclude(x => x.Category) //ProductCategories deki Category yi alacağımız için bir altına ThenInclude kullandık.
                .Select(x => new ProductDetailsVM() //View de kullanacağımız ViewModel
                {
                    Product = x,
                    ProductImages = x.Images,
                    ProductAttributes = x.ProductAttributes,
                    Categories =x.ProductCategories.Select(a=>a.Category).ToList()
                })
                .FirstOrDefault());
        }

Details sayfasını oluşturalım.

@model ProductDetailsVM
@foreach (var item in Model.ProductImages)
{
<div class="item">
<img alt="" class="img-responsive" src="/images/products/@item.ImageName" />
</div>
}


@Model.Product.ProductName

@foreach (var item in Model.Categories)
{
<a href="#">@item.CategoryName</a>
}@foreach (var item in Model.Categories)
{
<a href="#">@item.CategoryName</a>
}

@foreach (var item in Model.ProductAttributes)
{
<tr>
<td><b>@item.Attribute</b></td>
<td>@item.Value</td>
</tr>
}



Startup dosyamızda product için bir route işlemi yapalım


            app.UseMvc(routes =>
            {
                routes.MapRoute(
                name: "product",
                template: "products/{category?}",
                defaults: new {controller= "Product", action = "List"});

                routes.MapRoute(
                name: "default",
                template: "{controller=Home}/{action=Index}/{id?}");
            });

        public int PageSize = 2;  //şimdilik 2 ürün göstersin.
        public IActionResult List(string cat, int page = 1) //kulanıcı page isminde bir wuery gödermezse page default olarak 1 gelsin
        {
            var pQuery = repo.GetAll();
            if (!string.IsNullOrEmpty(cat))  //Kategori boş değilse kategoriyi filtreliyoruz.
            {
                pQuery = pQuery
                     .Include(x => x.ProductCategories)
                     .ThenInclude(x => x.Category)
                     .Where(x => x.ProductCategories.Any(a => a.Category.CategoryName == cat));
            }
            else
            {
                pQuery = pQuery.Skip((page - 1) * PageSize).Take(PageSize);
            }
            return View(pQuery);
        }
        // page 1 ise 1-1 =0  0*pagesize=0 0 kayıt öteleyip , pagesize kadar ürün alıcaz.
        // page 2 ise 2-1=1  1*pagesize =2  2 kayıt öteleyip, sonraki 2 kayıtı alıcaz.

Index sayfasını kopyalayıp, list sayfasına yapıştıralım.
Views-Shared-Partials adında klasöre _ProductItems adında bir view ekleyelim.Maksat ürünler bu sayfada olsun, partial çağıralım.


@model  Product    

<div class="col-md-4 wow fadeInUp">
    <div class="text-center product-item"> 
    <div class="product-item-top">
        <img alt="" class="img-responsive" src="/images/products/@Model.ImageUrl">
        <div class="mask"></div>
        <ul class="list-unstyled list-inline">
        <li><a href="product/details/@Model.ID"><i class="fa fa-link fa-fw"></i></a></li>
        <li><a href="#"><i class="fa fa-cart-plus fa-fw"></i></a></li>
        </ul>
    </div>
    <div class="product-item-inner">
    <h3 class="product-title"><a href="product/details/@Model.ID"> @Model.ProductName</a></h3>
    <p class="product-price"> @Model.Price</p>
    </div> 
    </div>
    </div>

Indexve List deki ürünleri göstermesi için oluşturduğumuz foreach döngüsünün, içindeki div satırları silip direk Partial _ProductItems ekleyelim.Maksat daha da sadeleştirmek.¨

Not: Partial i bir klasöre eklemişseniz, yol olarak hep Shared klasörüne bakar o yüzden klasör/Partialadı nı giriniz.
    @foreach (var item in Model)
    { 
        @Html.Partial("Partial/_ProductItems", item)
    } 

Uygulamayı çalıştıralım ve farkı görelim. http://localhost:50003/products?page=1 ya da http://localhost:50003/products?page=2

PagingVMs adında bir sınıf oluşturalım.ViewModels 2 adet metod tanımlayacağız farklı isimlerde.İlki E_Pagination ikincisi de productViewModelimiz ve butonlinki tanımlaayacak. ViewMdels->add sınıf PagingVM

namespace Web2.Models.Entity.ViewModels
{
      public class E_Pagination
    {
        public int CurrentPage { get; set; }  //o anki sayfa
        public int ItemsPerPage { get; set; }  //Kaç ürün gelecek sayfa başına
        public int TotalItems { get; set; }  //toplam eleman (örn;100 eleman)

        public int TotalPages()   //Toplam kaç sayfası var
        {
            //örneğin yuvarlamayapıcaz. toplam ürünlerini, kaç tane ürün gelicek ona böl
            return (int)Math.Ceiling((decimal)TotalItems / ItemsPerPage);
        }

    }

    public class ProductVM
    {
        public IEnumerable<Product> Products { get; set; }
        public E_Pagination Pagination { get; set; }
    }
}
 

Custom Tag Helper yazacağız bunun için Sharde klasöründe bir Infrastructure dosyası oluşturalım.İçerisine bir adet PageLinkTagHelper adında sınıf yaratalım.
_ViewImports sayfasına @addTagHelper Web.Views.Shared.Infrastructure.*,Web ekleyelim.

namespace Web.Views.Shared.Infrastructure
{ 
    
    [HtmlTargetElement("div", Attributes = "my_attr")] //bir div içinde tanımlanacak, adı my_attr olsun
    public class PageButtonTagHelper : TagHelper
    {
        private IUrlHelperFactory urlhelper;

        public PageButtonTagHelper(IUrlHelperFactory _prm)
        {
            urlhelper = _prm;
        }

        [ViewContext]
        [HtmlAttributeNotBound] //Attribute ile bağlanmayacak Contextimiz
        public ViewContext ViewContext { get; set; }

        public E_Pagination my_attr { get; set; }//bu sınıfı page-model attribute ile alınıcak
        public string PageAction { get; set; }//Sayfanın Linki

        public override void Process(TagHelperContext context, TagHelperOutput output)
        {
            var urlHelper = urlhelper.GetUrlHelper(ViewContext);

            var result = new TagBuilder("div");

            for (int i = 1; i <= my_attr.TotalPages(); i++)
            {
                var tag = new TagBuilder("a");//her sayfaya göre bir a etiketi oluşturalım.

                tag.Attributes["href"] = urlHelper.Action(PageAction, new { page = i });
                tag.InnerHtml.Append(i.ToString());

                if (i == my_attr.CurrentPage)
                {
                    tag.AddCssClass("btn active");
                }
                else
                {
                    tag.AddCssClass("btn btn-primary");
                }
                result.InnerHtml.AppendHtml(tag);
            }
            output.Content.AppendHtml(result.InnerHtml);
        }
    }
}

Hadi şimdi, List sayfamıza gelelim.İlk önce PagingVMs de yarattığımız modeli ekleyelim.@model ProductVM
This page contains @Model.Count() products Toplam sayısını bu şekilde değil artık,Model.@Model.Pagination.TotalItems products alacağız.
foreach döngüler de @foreach (var item in Model.Products) olacak.
List sayfamıza, ürünlerin bir altına sayfalama buttonlarını oluşturalım.

    //page-model Model.PagingButton dedikten sonra Customtaghelperdaki işlemlere ulaşacak.
<div  page-action="List"  my_attr="@Model.Pagination" ></div> 
 //4 sayfan var her sayfada 10 ürün gösteriyorsun toplam 40 ürün var dersin.
        public int PageSize = 2; //şimdilik 2 ürün göstersin.
        public IActionResult List(string category, int page = 1) //kulanıcı page isminde bir query gödermezse, page default olarak 1 gelsin
        {
            var pQuery = repo.GetAll();

            if (!string.IsNullOrEmpty(category))//Kategori boş değilse kategoriyi filtreliyoruz.
            {
                pQuery = pQuery
                    .Include(i => i.ProductCategories)
                    .ThenInclude(i => i.Category)
                    .Where(i => i.ProductCategories.Any(a => a.Category.CategoryName == category));
            }
            //filtreleme işlemi bittikten sonra count yapalım.örn; 4 sayfan var her sayfada 10 ürün gösteriyorsun toplam 40 ürün var dersin.
            var count = pQuery.Count();

            pQuery = pQuery.Skip((page - 1) * PageSize).Take(PageSize);

            return View(
                new ProductVM()
                {
                    Products = pQuery,
                    Pagination = new E_Pagination(){ CurrentPage = page, ItemsPerPage = PageSize,TotalItems = count}
                }
                );
        }
        // page 1 ise 1-1 =0  0*pagesize=0 0 kayıt öteleyip , pagesize kadar ürün alıcaz.
        // page 2 ise 2-1=1  1*pagesize =2  2 kayıt öteleyip, sonraki 2 kayıtı alıcaz.

17.02.2020