Open
WMC Eticaret Aşamaları

 


Visual Studio'yu açalım ve App adında bir solution (Çözüm oluşturalım) (Farklı bir isim de verebiliriz.)


Projemiz Soğan Mimarisi Prensibine uyum sağlayacaktır.
Solid prensibinin kurallarına uyacaktır.
Projede bilgilere, Interfaceler'e sorgu vererek erişeceğiz.Interfaceler, üzerinden nesneler çağıracağız.
Projemiz, metotların gövdelerini değil, imzalarından crud işlemleri yapacağı için Ef, Dapper,Nhybernate ve vsb. teknolojilere esneklik sağlayacaktır.

Projemiz 5 katmandan oluştmaktadır.Bunlar;

 
    1.Core Katmanı  - Çekirdek Katmandır.Tüm projelerde, ortak kullanılan katmandır.
     Projenin en temel verilerini barıdıracak, her katmandan erişim sağlayacak.
IData adında bir interface sınıfımız olacak, veritabanı modeli olduğunu gösteren bir imza gibi düşünebiliriz.
Generic sınıfımız olacak. Abstract ve Concrete dosyalara metotlarımızın kurallarını ve gövdelerini tanımlayacağız.

   2.Dal Katmanı -  Dal Katmanı herzaman yazılımcıya özel bir katmandır.
    Veritabanına bağlantımız bu katmanda olacak. 
 Modellerimiz için Repository ler oluşturcaz ve BaseRespository den türediklerini belirteceğiz.


    3.Bll Katmanı - Bu katmanda servislerimiz, ek metodlarımız ve tasarım desenini uygulayacağız.
    
    4.Model Katmanı -  Entity sınıflarımız (modellerimiz) olacaktır.
ViewModel entitilerimizi Web katmanında belirtebiliriz.

    5.Ui(Web) Katmanı -  Kullanıcının arayüzüdür.Kullanıcı bu katmanda işlemlerini görecek.



Oluşturduğumuz Model katmanına, ilk önce Base adında bir sınıf ekleyeceğiz.Temel propertyleri olacak.
Tüm ya da dilediğimiz modeller, bu modelden kalıtım alabilecek.
Category ve Product adında entityler belirleyeceğiz ve iki tablo arasındaki ilişki Many to Many (çoka çok) olacak.
1 kategorinin birden çok ürünü olucak. 1 ürününde aynı zamanda birden çok kategorisi olabilecek.
Entity Framework Many-Many ilişkili tabloları otomatik algılar ve kendi oluşturabilir.
Fakat Entity Framework Core bu yapıyı desteklemiyor.O yüzden bu ilişkiyi biz elle belirteceğiz.

   public class Base
    {
        public int Id { get; set; }
        public bool IsActive { get; set; }
        public DateTime AddedTime { get; set; }
    }


    public class Category:Base
    {
        public string Name { get; set; }
        public string ImageUrl { get; set; }
        public int? ParentId { get; set; }


        [ForeignKey("ParentId")]
        public virtual Category ParentCategory { get; set; }
        public ICollection<CategoryProduct> CategoryProducts { get; set; }
    }


  public  class Product:Base
    {
        public string Name { get; set; }
        public string Description { get; set; }
        public string Body { get; set; }
        public string ImageUrl { get; set; }
        public double Price { get; set; }
        public bool IsFeatured { get; set; }
        public bool IsHome { get; set; }

        public ICollection<CategoryProduct> CategoryProducts { get; set; }
        public ICollection<ProductAttribute> ProductAttributes { get; set; }
        public ICollection<Image> Images { get; set; }
    }


    public class CategoryProduct
    {  
        public int CategoryId { get; set; }
        public int ProductId { get; set; }

        public Category Category { get; set; }
        public Product Product { get; set; }
    }


    public class ProductAttribute
    {
        public int Id { get; set; }
        public string Attribute { get; set; }
        public string Value { get; set; }
        public int ProductId { get; set; }
        public Product Product { get; set; }

    }


   public class Image
    {
        public int Id { get; set; }
        public string ImageUrl { get; set; }
        public int ProductId { get; set; }
        public Product Product { get; set; }
    }


    public class Order:Base
    { 
        public string OrderNumber { get; set; }  
        public double Total { get; set; }  
    }

   public class Campaign:Base
    {
        public string ImageUrl { get; set; }
        public string Description { get; set; }
        public string Link { get; set; }
        public int  Click { get; set; }
    }
CategoryProduct Modelimiz de iki birincil anahtar oluşturmamız gerek.ProductId ve CategoryId. Bunun nedeni örneğin; cat1-pro2 değeri girdik, daha sonra yine aynı değerleri yazarsak bize hata dönmesini isteriz.Context dosyamızda eklerken fluent api ile gerçekleştiriceğiz.

Veritabanı oluşturacağız ve modellerimizi migration aracılığıyla, Veritabanına ekleyeceğiz.
Bunun için birkaç Nugget kütüphanesinden paket indireceğiz. Ef Core,Ef Core Design,Ef Core SqlServer.
Tablolar arasındaki ilişkileri belirleyeceğiz.
Dal Katmanında Abstract ve Concrete adında 2 klasörümüz var.
Concrete klasörümüze, ProjeAdiDbContext adında bir sınıf oluşturalım. (İstediğimiz adı verebiliriz)
AppDbContext:DbContext, DbContextinden kalıtım alacak.
override yazıp ilgili 2 metodu ezicez. 1 Configure , 2 Model Creating.
1-Configure metodunda, SqlServer bilgisi girer yolunu belirleriz.
2- ModelCreating metoduyla modellerin, ara tabloların ilişkilerini belirleriz.fluent api yazacağız. (EF Core henüz otomatik algılamıyor.)
DbSet kullanarak Modellerimin , veritabanındaki tablolara eşleştireceğiz.
Startup.cs dosyasında Configure servisinde DbContextimizi ekler, use diyerek proje başlar başlamaz Veritabanı oluştur diyeceğiz.
Araçlar ve Package Manage Console ile varsayılan Dal Katmanını seçip add-migration v diyeceğiz.Update-database demeyeceğiz.
, update-database -verbose deriz

Not:Veritabanı yolunu yazdığımızda server=/ (slash) kullanmamız gerektiğinde, \\ iki ters slash koyarız.Eğer bu ifadeyi strignleştirmek yani satır haline çevirmek istersek örn; @"Server=(localdb)\MSSQLLocalDB gireriz.
Fluent Api esnasında Delete Behaviourlar 3 çeşittir.
  • Cascade: Child/dependent entity should be deleted
  • Restrict: Dependents are unaffected
  • SetNull: The foreign key values in dependent rows should update to NULL
  public  class AppDbContext:DbContext
    {
        protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
        {
            optionsBuilder.UseSqlServer("server=.; Database=WorldMarketCenterDB; Integrated Security=True");
            base.OnConfiguring(optionsBuilder);
        }

        public DbSet<Category> Category { get; set; }
        public DbSet<Product> Product { get; set; }
        public DbSet<CategoryProduct> CategoryProduct { get; set; }
        public DbSet<ProductAttribute> ProductAttribute { get; set; }
        public DbSet<Image> Image { get; set; }
        public DbSet<Order> Order { get; set; }
        public DbSet<Campaign> Campaign { get; set; }
        protected override void OnModelCreating(ModelBuilder m)
        {
            m.Entity<Category>().Property(x => x.Name).HasMaxLength(100);
            m.Entity<Product>().Property(x => x.Name).HasMaxLength(150);
            m.Entity<CategoryProduct>().HasKey(x=> new {x.CategoryId,x.ProductId });
            m.Entity<ProductAttribute>().HasOne(p => p.Product).WithMany(p => p.ProductAttributes).HasForeignKey(p => p.ProductId).OnDelete(DeleteBehavior.ClientSetNull);
            m.Entity<Image>().HasOne(p => p.Product).WithMany(p => p.Images).HasForeignKey(p => p.ProductId).OnDelete(DeleteBehavior.ClientSetNull);
            m.Entity<Campaign>(x =>
            { 
                x.Property(e => e.Description).HasMaxLength(500);
                x.Property(e => e.Link).IsRequired().HasMaxLength(100);
                x.Property(e => e.AddedTime).HasColumnType("datetime");  
            });
             
            base.OnModelCreating(m);
        }
    }
 


   public void ConfigureServices(IServiceCollection services)
        {
            services.AddMvc(option => option.EnableEndpointRouting = false);
            services.AddControllers(options => options.EnableEndpointRouting = false);
            services.AddControllersWithViews(options => options.EnableEndpointRouting = false);
            services.AddRazorPages().AddMvcOptions(options => options.EnableEndpointRouting = false);
            services.AddDbContext<AppDbContext>(); 
        }


        public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }
            using (var serviceScope = app.ApplicationServices.GetRequiredService<IServiceScopeFactory>().CreateScope())
            {
                var context = serviceScope.ServiceProvider.GetService<AppDbContext>();
                context.Database.Migrate(); 
            }
         
            app.UseStaticFiles();
            app.UseCookiePolicy();
            app.UseMvc(x =>
            {
                x.MapRoute("areas", "{area:exists}/{controller}/{action}/{id?}", new { controller = "Home", Action = "Index" });
                x.MapRoute( name: "products",template: "products/{category?}", defaults: new { controller = "Products", action = "Index" });
                x.MapRoute("default", "{controller}/{action}/{id?}", new { controller = "Home", Action = "Index" });
            });

        }
    } 


Migration yapacağız.Bu paketleri eklediğimize emin olalım.
Dal Katmanına, Ef Core, Ef Core Design, Ef Core Tools, Ef Core SqlServer ve Web katmanına Ef Core DEsign eklememiz gerekir.
Package Console Manager'ı açalım, varsayılan Dal Katmanı seçerek Add-Migration -v1 diyelim, Update-database -verbose diyelim
Şimdi Web katmanınını F5 ile projeyi başlatalım. Aynı anda veritabanı oluşacaktır.

Not:Eğer yeni bir entity oluşturup onu veritabanına eklemek istersek, modeli yazalım ve add-migration v(farkli isim), update-database -verbose girelim.Migration klasörünü silmeyelim aksi tkdirde çözümleyiciler paketinde sorun yaşayabiliriz.

Abstract ve Concrete dosyalarımız olacak.
IData abstractımız bizim veritabanı işlemleri yapacağımızı belirtine imzamız olacaktır.
public interface IData { }
Core katmanındaki,Soyut,IRepository adında interface arabirimimiz şöyledir;

   public  interface IRepository<T> where T:class, IData, new()
    {
        List<T> GetAll(Expression<Func<T, bool>> filter = null);
        T Get(Expression<Func<T, bool>> filter = null);
         IQueryable@T> GetAllWithIQueryable();

        T Add(T prm);
        Task<T> AddAsync(T prm);

        T Update(T prm);
        Task<T> UpdateAsync(T prm);
        
        void Delete(T prm);
        T SoftDelete(T prm);
        Task DeleteAsync(T prm);

    }


    

Core katmanında , Somut, Metot gövdelerini oluşturacağımız BaseRepository sınıfımız ise;



    public class BaseRepository<T, TContext> : IRepository<T> where T : class, IData, new() where TContext : DbContext, new()
    {
        protected TContext context;  //Repository lerde kullanalım diye protected erişime açık ettik.
        public BaseRepository(TContext _context)
        {
            context = _context;
        }



        public List<T> GetAll(Expression<Func<T, bool>> filter = null)
        {
            using (var context = new TContext())
            {
                return filter == null
                    ? context.Set<T>().ToList()
                    : context.Set<T>().Where(filter).ToList();
            }
        }
        public T Get(Expression<Func<T, bool>> filter = null)
        {
            using (var context = new TContext())
            {
                return context.Set<T>().SingleOrDefault(filter);
            }
        }

        public IQueryable@T> GetAllWithIQueryable()
        {
            return context.Set();
        }




        public T Add(T prm)
        {
            context.Entry(prm).State = Microsoft.EntityFrameworkCore.EntityState.Added;
            context.SaveChanges();
            return prm;
        }
        public async Task<T> AddAsync(T prm)
        { 
                var addedEntity = context.Entry(prm);
                addedEntity.State = EntityState.Added;
                await context.SaveChangesAsync();
                return prm; 
        }




        public T Update(T prm)
        {
            context.Entry(prm).State = Microsoft.EntityFrameworkCore.EntityState.Modified;
            context.SaveChanges();
            return prm;
        }

        public async Task<T> UpdateAsync(T prm)
        { 
                var updatedEntity = context.Entry(prm);
                updatedEntity.State = EntityState.Modified;
                await context.SaveChangesAsync();
                return prm; 
        }
        public void Delete(T prm)
        {
            context.Entry(prm).State = Microsoft.EntityFrameworkCore.EntityState.Modified;
            context.SaveChanges();
        }

        public async Task DeleteAsync(T prm)
        { 
                var deletedEntity = context.Entry(prm);
                deletedEntity.State = EntityState.Deleted;
                await context.SaveChangesAsync(); 
        }


        public T SoftDelete(T prm)
        {
            context.Entry(prm).State = Microsoft.EntityFrameworkCore.EntityState.Modified;
            context.SaveChanges();
            return prm;
        }

    }

BaseRepository de sürekli savechanges() metodunu çağırdık.Öyleki birden fazla tablo eklemek istesek teker teker bunları veritabanına kaydedecek. Bunun yerine UnitOfWork kullanalım crud işlerini lokalde yapsın ve unitofworkda savechanges() çağırdığımızda hepsini birden eklesin. Daha verimli, performanslı kullanmış oluruz.Öyleyse Core katmanında, SaveChages() metotlarını silelim.

Dal Katmanında abstract ve concrete adında 2 klasör oluşturalım.
Abstract klasöründe soyut sınıfımız(metodların imzası), Concrete klasöründe ise somut sınıflarımız olacak.(metodların gövdesi)
Abstract klasöründe, Modellerimizin interface repositoryleri olacak ve hepsi Core katmanındaki Generic yapılı IRepositoryde kalıtım alacak.
Aynı şekilde Concrete klasöründe, modellerimizin repositoryleri olacak ve hepsi Core katmanındaki Generic yapılı BaseRepositoryden kalıtım alacak.

Abstrct klasörümüzde ICategoryRepository interface oluşturalım ve IRepository den kalıtım alsın.

    public  interface ICategoryRepository : IRepository<Category>
    {
    }

Concrete klasörümüzde CategoryRepository sınıfı oluşturalım ve ilk BaseRepositorymizden daha sonra ICategoryRepositoryden kalıtm alsın.

  public  class CategoryRepository : EFRepository<Category, AppDbContext>,ICategoryRepository
    {
        public CategoryRepository(AppDbContext prm):base(prm)
        {
        }
    }

Eğer metot tnımlaycaksk repository mizde tanımlarız. Ekstra metotlarımız burda yer alır.

namespace Dal.Abstract
{
    public interface IProductRepository : IRepository
    {
        ICollection@Product> GetProductsIncludeCategory();
        ICollection@Product> GetProductDetails();

    }
}


namespace Dal.Concrete.EfCore
{
   public class ProductRepository : BaseRepository@Product, AppDbContext>, IProductRepository
    {
        public ProductRepository(AppDbContext context) : base(context)
        {

        }

        public ICollection@Product> GetProductsIncludeCategory()
        {
            return context.Product
                    .Include(x=>x.CategoryProducts)
                    .ThenInclude(i => i.Category).ToList();
        }

        public ICollection@Product> GetProductDetails()
        {
            return context.Product
                .Include(x => x.Images)
                .Include(x => x.ProductAttributes)
                .Include(x => x.CategoryProducts)
                .ThenInclude(i => i.Category).ToList();
        }
    }
}

Not:Dal Katmanımızdaki Repository lerimiz AppDbContext ile crud işlemleri yapacak.
Bll Katmanımızdaki Repository lerimiz ise Dal Katmanındaki Repository vasıtasıyla crud işlemleri yapacaktır.
Bir Hata mı var? Base modelinizin :IData dan kalıtım aldığına emin olun.



Dal Katmanında tüm modelleri DbContext ile crud işlemleri uyguladık. Extra metodlar yazdık.
Bll katmanında ise artık crud işlemlerini , Dal katmanındaki repository ler ile ayağa kaldıracağız.
İstediğimiz Dal katmanındaki metodu Bll katmanında uygulayacağız.
Dal katmanında yazılımcının istediği kadar metoda bağımlıyken, Bll katmanı ise sadece kullanıcının yararlanmasını istediğimiz metodlar sınır koyulacak.
Bll de yetkiler kısıtlanacak, kullanıcıların (admin,mod,misafir) ihtiyaç duydukları metodlar, hazırlanacak.
(örneğin, Kullanıcı, güncelleme, ekleme yapsın ama silme işlemi yapamasın)
Bll de Abstract ve Conrete klasörler olacak.Repository sonlarına Manager,Service gibi ekler ekleyebiliriz. Ya da size kalmış. Bll Katmanının Abstract klasöründe IOfferService adında interface oluşturalım.


   public interface ICategoryService :IDisposable
    {
        ICollection<Category> GetAll(Expression<Func<Category, bool>> filter = null);
        IQueryable@Category> GetAllWithQuery();
        Category Get(Expression<Func<Category, bool>> filter = null);
        Category GetById(int id);

        Category Add(Category prm);
        Task<Category> AddAsync(Category prm);

        Category Update(Category prm);
        Task<Category> UpdateAsync(Category prm);

        void Delete(Category prm);
        Category SoftDelete(Category prm);
        Task DeleteAsync(Category prm);

    }
 public class CategoryService : ICategoryService
    {
        ICategoryRepository repo;
        public CategoryService(ICategoryRepository prm)
        {
            repo = prm;
        }

        public ICollection<Category> GetAll(Expression<Func<Category, bool>> filter = null)
        {
            return repo.GetAll();
        }
        public Category Get(Expression<Func<Category, bool>> filter = null)
        {
            throw new NotImplementedException();
        }

        public Category GetById(int id)
        {
            return repo.Get(x => x.Id == id);
        }
        public IQueryable@Category> GetAllWithQuery()
        {
            return repo.GetAllWithIQueryable();
        }

     }


ProductService için Repository de extra metot tanımlamıştık. Bll katmanında da o aynı metot kurlı ve gövdesini tanımlıycaz.
Gövde de sadece return Repository deki metot diye belirteceğiz.

        ICollection@Product> GetProductsIncludeCategory();
        ICollection@Product> GetProductDetails();
        public ICollection@Product> GetProductsIncludeCategory()
        {
            return repo.GetProductsIncludeCategory();
        }
        public ICollection@Product> GetProductDetails()
        {
            return repo.GetProductDetails();
        }

Bir controller da birden çok servis kullanabiliriz. Herbirini tek tek kullanmak yerine. Bu servisleri bir konteyner da tutalım. Controllerda konteyneri çağıralım ve içindeki istenilen servisten faydalanalım.
Abstract klasöründe UnitOfWork adında bir interface oluşturalım.

    namespace Bll.Abstract
    {
       public interface IUnitOfWork:IDisposable
        {
            ICategoryService cat { get; }
            int Save { get; }
        }
    }

Interface, new instance alınmaz. Bundan dolyı encapsulation ya da cunstroctor yöntemi ile çağrılır.
Encapsulation Yöntemi; ilk önce field oluşturulur sonra property.
örn;Field: ICategoryServis _cat; Onun Propertysi: public ICategoryService Categoryuow => _cat (Categoryuow onun Interface adı) Property bir field’ in değerini geri döndürmeye (Get) ve değerini ayarlamaya (Set) olanak sağlar.
Şimdi Concrete klasörümüzde UnitOfWork adında bir sınıf ekleyelim.

namespace Bll.Concrete
{
    public class UnitOfWork : IUnitOfWork
    {
        protected AppDbContext context;
        public UnitOfWork(AppDbContext _context)
        {
            context = _context;
        }

        ICategoryService _cat;
        IProductService _product;

        public ICategoryService Categoryuow => _cat ?? (_cat = new CategoryService(new CategoryRepository(context))); // ya da
        public IProductService Productuow { get { return _product ?? (_product = new ProductService(new ProductRepository(context))); } } 
        public int Save => context.SaveChanges();
    }

Property lerimizde sadece get ile okunabilir olduğunu belirtiyoruz.



Startup.Cs Dosyamız.

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using Dal.Concrete;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.FileProviders;
using Microsoft.Extensions.Hosting;

namespace Web
{
    public class Startup
    {
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddMvc(option => option.EnableEndpointRouting = false);
            services.AddControllers(options => options.EnableEndpointRouting = false);
            services.AddControllersWithViews(options => options.EnableEndpointRouting = false);
            services.AddRazorPages().AddMvcOptions(options => options.EnableEndpointRouting = false);

            //services.AddDbContext<AppDbContext>();
            //services.AddScoped<IUnitOfWork, UnitOfWork>();
        }


        public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }

            // using (var serviceScope = app.ApplicationServices.GetRequiredService<IServiceScopeFactory>().CreateScope())
            //{
            //    var context = serviceScope.ServiceProvider.GetService<AppDbContext>();
            //    context.Database.Migrate(); 
            //}

            //app.UseStaticFiles(new StaticFileOptions
            //{
            //    FileProvider = new PhysicalFileProvider(
            //    Path.Combine(Directory.GetCurrentDirectory(), "NewFolder")),
            //    RequestPath = "/tmp"
            //});

            app.UseStaticFiles();
            app.UseCookiePolicy();
            app.UseMvc(x =>
            {
                x.MapRoute("areas", "{area:exists}/{controller}/{action}/{id?}", new { controller = "Home", Action = "Index" });
                x.MapRoute( name: "products",template: "products/{category?}", defaults: new { controller = "Products", action = "Index" });
                x.MapRoute("default", "{controller}/{action}/{id?}", new { controller = "Home", Action = "Index" });
            });

        }
    }
}

Varsayılan MVC Gerekenleri

Models,Controllers,Views ve wwwroot klasörünü oluşturalım.
(wwwroot klasörü Asp.Net Core da gösterilen tek klasördür.Adını Startup.cs'de değiştirebiliriz.
Views klasöründe 2 adet view dosyası oluşturalım.
(a) _ViewImports , içeriğine;

    _ViewImports sayfası, bizim import edeceğimiz dosyaları belirler.

    @addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers  //Taghelper namespace'ini tanıttık.(Tüm sayfalara etkilenmiş oldu.)
    @using Model
    @using Web.Models
    @addTagHelper Web.Models.Infrastructure.*,Web 
    (b)  _ViewStart  , içeriğine    
       Proje çalıştığında sayfaları etkileyecek stil, webmaster sayfası,title burda tanımlanır.
    @{     ViewData["Title"] = "_ViewStart";  Layout = "_Layout";     }

    (c)Bu sefer bir klasör oluşturup içerisine view ekleyelim.Shared klasörü, _Layout view dosyası ekleyelim.
     Asp.Net WebForms daki web master sayfamızdır.Tüm sayfalar bu anasayfadan istediğimizde etkilenebilecek.
    @{  Layout = "~/Views/Shared/_Layout.cshtml"; }   

Temel bir Tasarım Düzeni

Bootstrap,jquery gibi son sürüm css ve javascriptleri projemize dahil edelim.
Bunun için projeye sağ click, istemci Tarafı Kitaplığını tıklayalım
Sağlayıcı=> Nugget kütüphanesi olsun (unpkg kısaltılmasıdır)
Arama paneline örn; "bootstrap css" yazıp ve wwwroot klasörü içerisinde oluşturucağımızı belirtelim.


Projemize bir template giydireceksek, template'i wwwroot klasörüne kopyalayalım.
kendi oluşturduğumuz js,css,images gibi klasörlere , templatteki assetsleri copy/paste edelim.
Projemizdeki css,js tabanlı dosyaları optimize etmemiz gerekir performans için
Bunun için Bundler ve Minifier aracından yararlanalım. https://marketplace.visualstudio.com/items?itemName=MadsKristensen.BundlerMinifier sitesini ziyaret edip bundler ve minifer özelliğini indirelim.Projeyi resetleyip açtığımızda artık css ya da js dosylarına sağ tıkladığımızda add bundler&minifier adında bir özellik gelecektir.İstediğiniz css ler'e ctrl ile seçip add bundler&minifier özelliğini tıklatarak wwwroot-> css klasöründe birleştirebiliriz.Burda Dikkat etmemiz gereken bir husus vardır. Templateki ilgili css ve ya jslerin üzerine ctrl seçerken, sırasına dikkat etmeliyiz.Aksi takdirde çakışmalar yaranır. Sağ click add bundler&minimizer özelliğini kullanıp wwwroot içerisindeki css klasörümüze bir dosya oluştrup birleştirelim.Bu dosyayı incelediğimizde,sırasının doğru olduğunu kontrol edelim,değilse düzenleyelim.


Bilgilendirme

Program.cs, projemizin beynidir. Bu dosya ile proje ayağa kalkar. Webhosting oluşur.Server türü kestreldir.Proje derlenip çalışır.Burdaki ayarlara, bu proje için dokunmayacağız.
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.

İlk önce Kampanya listesini aldık.İlk kampanya daha büyük ve orta sekmede yer alıcak.O yüzden onu es geçtik.


    public class HomeController : Controller
    {
        IUnitOfWork uow;
        public HomeController(IUnitOfWork _uow)
        {
            uow = _uow;
        }
      
        public IActionResult Index()
        {
            var data = uow.Campaignuow.GetAll().ToList();
            return View(data );
        }
    }
}

       
    @foreach (var item in Model.Where(x=>x.Id != 1))
    {
    <div class="hero-carousel__slide">
    <img src="/images/campaign/@item.ImageUrl" alt="" class="img-fluid">
    <a href="@item.Link" class="hero-carousel__slideOverlay">
    <h3>@Html.Raw(item.Description)</h3> 
    </a> 
    </div>
    }

Sonra ProductCategory de extra metot oluşturduğumuz IncludeKategori ismi verdiğimiz metodu, Component'e ilave ettik.
ViewsComponent Klasörümüze ProductsHome adında sınıf oluşturduk.
Önce Dal ve Bll deki metodumuzu hatırlaylım.

    Dal Katmsnı
    namespace Dal.Abstract
    {
    public interface IProductRepository : IRepository@Product>
    {
    ICollection<Product> GetProductsIncludeCategory();
    }
    }
    
namespace Dal.Concrete.EfCore { public class ProductRepository : BaseRepository<Product, AppDbContext>, IProductRepository { public ProductRepository(AppDbContext context) : base(context) { } public ICollection<Product> GetProductsIncludeCategory() { return context.Product .Include(x=>x.CategoryProducts) .ThenInclude(i => i.Category).ToList(); } } }
Bll Katamnı

namespace Bll.Abstract
{
    public interface IProductService : IDisposable
    { 
        ICollection<Product> GetProductsIncludeCategory();
    }
}


namespace Bll.Concrete
{
    public class ProductService : IProductService
    {
        IProductRepository repo;
        public ProductService(IProductRepository prm)
        {
            repo = prm;
        }
        public ICollection<Product> GetProductsIncludeCategory()
        {
            return repo.GetProductsIncludeCategory();
        }
...
namespace Web.ViewsComponent
{
    public class ProductsHome:ViewComponent
    {
        IUnitOfWork uow;
        public ProductsHome(IUnitOfWork _prm)
        {
            uow = _prm;
        }
        public IViewComponentResult Invoke() //Invoke çağırmak demektir.
        {
            var producthome = uow.Productuow.GetProductsIncludeCategory().Where(x=> x.IsHome == true && x.IsActive);
            return View(producthome);
        }
    }
}

Daha sonra Views-Shred klasöründe ProductsHome dında klasör oluşturup içerisine Default adında görünüm ilave ettik.

@model IEnumerable<Product>

    @foreach (var item in Model)
    {
          <div class="col-md-6 col-lg-4 col-xl-3">
            <div class="card text-center card-product">
              <div class="card-product__img">
                <img class="card-img" src="/images/@item.ImageUrl" width="263" alt="">
                <ul class="card-product__imgOverlay">
                  <li><button  onclick="location.href='/products/details/@item.Id'"><i class="ti-search"></i></button></li>
                  <li><button><i class="ti-shopping-cart"></i></button></li>
                  <li><button><i class="ti-heart"></i></button></li>
                </ul>
              </div>
              <div class="card-body">       
                @foreach (var item2 in item.CategoryProducts)
                {
                <a  asp-controller="Products" asp-action="Index" asp-route-category="@Web.Models.Infrastructure.F_Tool.URLDuzelt(item2.Category.Name)">  @item2.Category.Name</a>
                } 
                <h4 class="card-product__title"><a href="/products/details/@item.Id">@item.Name</a></h4>
                <p class="card-product__price">@item.Price TL</p>
              </div>
            </div>
          </div>
    }

View görünüm pneline çağırdık

 @await Component.InvokeAsync("ProductsHome") 

Aynı iki işlemi tekrar uygladık fakat bu sefer, kampanyanın ilk ID sini aldık, Productsların IsFetured = true özelliğini aldık.


Tüm ürünlerimizi göstereceğiz ve Tag Helper ile sayfalama yapıp istenilen adet kadar ürünlerimizi listeleyeceğiz.
Ürünler sayfaya göre listelenecek. E_Page adında bir entity oluşturalım.Propertylerimizi girelim.
Ürün sayfsına çağıracağımız modelVM hazırlayalım. Product modeli ve Sayfalama (E_Page) modeli olacak.

namespace Web.Models
{
    public class E_Page
    {
        public int CurrentPage { get; set; }  //o anki sayfa
        public int ItemsPerPage { get; set; }  //Syf başına ürün
        public int TotalItems { get; set; }  //toplam eleman 

        public int TotalPages()   //Toplam sayfa
        {
            // yuvarlama yapıcaz. toplam ürünleri, sayfa başına bölersek.Toplam sayfayı bulmuş oluruz.
            return (int)Math.Ceiling((decimal)TotalItems / ItemsPerPage);
        }
    }
namespace Web.Models
{
    public class ProductPagesVM
    {
        public IEnumerable<Product> Products { get; set; }
        public E_Page E_Page { get; set; }
    }
}

TagHelper yardımıyla bir sayfalama navigasyonu oluşturcaz.
Herhangi bir alana Infrastructure klasörü oluşturup PageButtonTagHelper diyebiliriz.İsimler isteğe bağlıdır.
Bu metodu ana sayfada ürünlerin alt kısmına yerleştireceğiz.Bunun için önce _ViewImports sayfamıza import etmeyi unutmylım.

    @addTagHelper Web.Models.Infrastructure.*,Web 
    
namespace Web.Models.Infrastructure
{
    [HtmlTargetElement("div", Attributes = "E_Page")] //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_Page E_Page { get; set; } 
        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 <= E_Page.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 == E_Page.CurrentPage)
                {
                    tag.AddCssClass("btn active");
                }
                else
                {
                    tag.AddCssClass("btn btn-primary");
                }
                result.InnerHtml.AppendHtml(tag);
            }
            output.Content.AppendHtml(result.InnerHtml);
        }
    }
}
Not: Çağırmak için <div page-action="Index" my_attr="@Model.Pagination" class="style-page"></div>
   public class ProductsController : Controller
    {
        IUnitOfWork uow;
        public ProductsController(IUnitOfWork uow)
        {
            this.uow = uow;
        }

       
        // page 1 ise 1-1 =0  0*Adet=0 0 kayıt öteleyip , Adet kadar ürün alıcaz.
        // page 2 ise 2-1=1  1*Adet =2  2 kayıt öteleyip, sonraki 2 kayıtı alıcaz.
        public int Adet = 2; //şimdilik 2 ürün göstersin.
        public IActionResult Index(string category, int page = 1) //kulanıcı page adında query gödermezse, page default değeri 1 gelsin.
        {
            var data = uow.Productuow.GetProductsIncludeCategory();

            if (!string.IsNullOrEmpty(category))//Kategori boş değilse kategoriyi filtreliyoruz.
            {
                data = data.Where(i => i.CategoryProducts.Any(a => a.Category.Name == category)).ToList();
            }
   
            var dataCount = data.Count();
            data = data.Skip((page - 1) * Adet).Take(Adet).ToList();

            return View(
                new ProductPagesVM()
                {
                    Products = data,
                    E_Page = new E_Page() { CurrentPage = page, ItemsPerPage = Adet, TotalItems = dataCount }
                }
                );
        }
    }
}

Ürünler sayfası için bir ProductPageVM ekleyeceğiz.İçeriğinde hem Product modelimizin List hali hem de E_Page modelimiz var.
Listemizi bir partial içine alalım. ihtiyaç halinde farklı sayfalard kullanalım.
Views-Shared içinde PArtial klasörü oluşturlım ve Products görünümü ekleyelim.

    //Index sayfamıza foreach dongüsüyle Partialı tanımladık
    @model ProductPagesVM
     <section class="lattest-product-area pb-40 category-list">
    <div class="row">

        @foreach (var item in Model.Products)
        {
        @Html.Partial("Partial/Products", item)
        } 

    </div>
    <div  page-action="Index"  E_Page="@Model.E_Page" class="style-page"></div> 
    </section>

@model  Product    


<div class="col-md-6 col-lg-4">
<div class="card text-center card-product">
    <div class="card-product__img">
    <img class="card-img" src="/images/@Model.ImageUrl" alt="">
    <ul class="card-product__imgOverlay">
        <li><button onclick="location.href='/products/details/@Model.Id'"><i class="ti-search"></i></button></li>
        <li><button onclick="location.href='/products/details/@Model.Id'"><i class="ti-shopping-cart"></i></button></li>
        <li><button><i class="ti-heart"></i></button></li>
    </ul>
    </div>
    <div class="card-body">
    @foreach (var item2 in Model.CategoryProducts)
    {
    <a  asp-controller="Products" asp-action="Index" asp-route-category="@Web.Models.Infrastructure.F_Tool.URLDuzelt(item2.Category.Name)">  @item2.Category.Name</a>
    } 
    <h4 class="card-product__title"><a  asp-controller="Products" asp-action="Details" asp-route-category="@Model.Id"> @Model.Name</a></h4>
    <p class="card-product__price"> @Model.Price TL</p>
    </div>
</div>
</div>

Amacımız Ürünler sayfasının hemen sol tarafına Kategorileri belirtmek.
Kategorilerin birde kaç adet urune sahip olduğu yazılacak Bunun için ViewModel oluşturup Count adında property oluşturduk.
Bunun için bir component oluşturcaz.Öyleki ViewsComponent klasörümüze CategoryLeftSide adında sınıf oluşturduk.


namespace Web.Models
{
    public class CategoryVM :Category
    {
        public int Count { get; set; }
    }
}

Core ve bll katmanımızda metotlar tanımladık.(Tabi Dalda tanımlasak daha güvenilir olurdu.)

  IQueryable<T> GetAllWithIQueryable();

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


       IQueryable@T> GetAllWithIQueryable();
        public IQueryable@Category> GetAllWithQuery()
        {
            return repo.GetAllWithIQueryable();
        }
namespace Web.ViewsComponent
{
    public class CategoryLeftSide : ViewComponent
    {
        IUnitOfWork uow;
        public CategoryLeftSide(IUnitOfWork _prm)
        {
            uow = _prm;
        }

        public IViewComponentResult Invoke() 
        {
            // count IQueryable ile filetrli alınır. Getall ise ICollection filtresiz direk Veritabanından data alır filtre ypılmıyor:(
            var categoryside = uow.Categoryuow.GetAllWithQuery().Where(x => x.ParentId !=null  && x.IsActive).Select(x => new CategoryVM
            {
                Id = x.Id,
                Name = x.Name,
                Count = x.CategoryProducts.Count()
            });
            return View(categoryside);  
        }
    }
}


 
@model IEnumerable<CategoryVM>
 

    <div class="sidebar-categories">
    <div class="head">Browse Categories</div>
    <ul class="main-categories m-0 p-0">
     
    @foreach (var item in Model)
    {
    <li class="filter-list"> <i class="fa fa-angle-right fa-fw" aria-hidden="true"></i> <a asp-controller="Products" asp-action="Index" asp-route-category="@Web.Models.Infrastructure.F_Tool.URLDuzelt(item.Name)" class="text-black-50">@item.Name <span class="pull-right">@item.Count</span></a> </li>
    }
    </ul>
    </div>

 
 //Çağırmak istersek.
  @await  Component.InvokeAsync("CategoryLeftSide")

ViewModel tasarlıyoruz ve ürünle ilişkilendirmiş tüm tabloları çağırıyoruz.

 namespace Web.Models
{
    public class ProductDetailVM
    {
        public Product Product { get; set; }
        public ICollection<Image> Images { get; set; }
        public ICollection<ProductAttribute> ProductAttributes { get; set; }
        public ICollection<Category> Categories { get; set; }
    }
}

Controller da Details adında bir ActionResult metodu oluşturuyoruz.
Ama önce Dal ve Bll deki detay product metodumuzu hatırlıyoruz

 ICollection<roduct> GetProductDetails();
public ICollection<Product> GetProductDetails()
{
    return context.Product
        .Include(x => x.Images)
        .Include(x => x.ProductAttributes)
        .Include(x => x.CategoryProducts)
        .ThenInclude(i => i.Category).ToList();
}


ICollection<Product> GetProductDetails();
public ICollection<Product> GetProductDetails()
{
    return repo.GetProductDetails();
}


        public IActionResult Details(int id)
        {
            var data = uow.Productuow.GetProductDetails().Where(x => x.Id == id)
                .Select(x => new ProductDetailVM()
                {
                    Product = x,
                    Images = x.Images,
                    ProductAttributes = x.ProductAttributes,
                    Categories = x.CategoryProducts.Select(a => a.Category).ToList()
                })
                .FirstOrDefault();
            return View(data);

            #region Direk IQueryable ile yazabilirdik.  
            //return View(uow.Productuow.GetAllWithQuery()
            //.Include(x => x.Images)
            //.Include(x => x.ProductAttributes)
            //.Include(x => x.CategoryProducts)
            //.ThenInclude(x => x.Category).Where(x => x.Id == id)
            //.Select(x => new ProductDetailVM()
            //{
            //Product = x,
            //Images = x.Images,
            //ProductAttributes = x.ProductAttributes,
            //Categories = x.CategoryProducts.Select(a => a.Category).ToList()
            //})
            //.FirstOrDefault());
            #endregion  
       
        }

 
@model ProductDetailVM



    @foreach (var item in Model.Images)
    {
        <div class="single-prd-item">
        <img alt="" class="img-responsive" src="/images/@item.ImageUrl" />
        </div>
    }

    <h3>@Model.Product.Name</h3>
    <h2>@Model.Product.Price TL</h2>

      <span>Category</span> 
        @foreach (var item in Model.Categories)
        {
        <a href="#">@item.Name</a>
        }
    
    <p>@Model.Product.Description</p>
       @Html.Raw(Model.Product.Body)
		 
    <table class="table">
        @foreach (var item in Model.ProductAttributes)
        {
        <tr>
        <td><b>@item.Attribute</b></td>
        <td>@item.Value</td>
        </tr>
        }
    </table>







Core Katmanı BaseRepository için Ef Core 3.1.2 paketi yüklenmiştir.
Dal Katmanı AppDbContext için Ef Core 3.1.2 paketi yüklenmiştir
Dal, AppDbContext Configure etmek için Ef Core SqlServer 3.1.2. paketi yüklenmiştir.
Dal, Add-Migration için Entity Framework Core Tools 3.1.2. paketi yüklenmiştir.
Web, Add-Migration için Microsoft.EntityFrameworkCore.Desig 3.1.2. paketi yüklenmiştir.
Web, Controller oluşturduğumuzda Microsoft.EntityFrameworkCore.Design 3.1.2 paketi yüklenmiştir.
Web, Controller oluşturduğumuzda Microsoft.VisualStudio.Web.CodeGeneration.Design 3.1.1 paketi yüklenmiştir.
Web, ctrl+s ile browserda F5 yaptığımızda değişiklik görünsün diye Microsoft.AspNetCore.Mvc.Razor.RuntimeCompilation 3.1.2 eklendi




neden IUniOfWork sırasında :IDisposable kullandıkta , Core IRepository de kullanmadık ya da Bll abstract sınıfında.




Kodlarımızı yazarken bir çok nesneden oluşan gruplara ihtiyaç duyarız.
.NET Framework bunun için bize System.Collections isim alanında sınıflar sunmaktadır.
Koleksiyon türü sınıflarda birbirinden farklı türdeki nesneleri aynı grup içinde tutabiliriz.
Bunlara erişebilmek için foreach döngüsünü kullanabiliriz.

ICollection Interface : Bütün koleksiyon sınıflarının desteklediği arabirimdir.
IEnumarable Interface : Numaratör olarak tanımlanabilir.IEnumerable<T> memory’de çalışır,
IList Interface : Koleksiyon içindeki elemanlara indeksleri ile erişmeyi sağlayan arayüzdür. ICollection ve IEnumarable arayüzlerinden türemiştir.
IDictionary Interface : Key ? Value mantığında çalışır.

Eğer bir grubta Include ile modellerinizi birleştirmek isterseniz, kullanmanız gereken IQueryable metodudur.
Bir grubu collection'a çevirirseniz. .ToList() diyerek, Costrutor'ında IEnumerable create eder.IQueryable oluşmaz.
Include metodunu IEnumable ile kullanamyız sadece IQueryable ile olur.Çünkü IEnumerable datayı filtrelemeden hafızasında tutar.
Orj. Kaynak: You can not use Include with a IEnumerable, It only works with IQueryable, when you invoke in your repository query.ToList(); you query is retrieved from database to memory in a IEnumerable and when your data is in memory Include doesn't work
Include halindeki dataya ihtiyaç halinde sonra collection'a dahil ederiz.
   public override Product GetByID(object ID, List<string> includes)
    {

        var query = db.Products.AsQueryable();
        foreach (string include in includes)
        {
            query = query.Include(include);
       }

       return query.SingleOrDefault(p => p.ID == ID);
    }

IQueryable veri tabanından verileri geniş kapsamlı sorgulamak için kullanılır.
IQueryable arayüzü IEnumarable arayüzünü implement etmektedir. Bu sayede IQuaryable IEnumarable özelliklerinin hepsini barındıracaktır.
IQueryable kullanıldığında sorgu alınırken öncelikle filtrelendirme yapılıp sorgu gönderilir.
IEnumarable kullansaydık eğer, önce listeleyip ondan sonra o liste üzerinde filtrelendirme yapıp döndürecekti bize bu da performans kaybı yaşatacaktır.
örn; Liste döndürcez ve Take20 ile filtrelendireceğiz. IQueryable 1 sorguyla select top 20 ve liste döndürür. IEnumrable ise ilk Liste döndürür sonr top 20 der 2 işlem yapar.
02.02.2020