.NET 6 ile Object Mapping Kütüphanelerinin Generic Yapıda Kullanılması

Veri tabanındaki ilgili tabloya karşılık gelen entity sınıfı ile uygulamalarda entity sınıflarından göstermek istediğimiz propertylerden oluşan view model sınıfı arasında dinamik olarak mapping işlemi yapılmasını sağlayan object mapping kütüphaneleri mevcuttur. 

.NET dünyasında sıklıkla kullanılan kütüphaneler aşağıdaki gibi olup; mapping performansı, kolay kullanılabilirlik ve dokümantasyonu bakımından tercih edilebilmektedir.
AutoMapper, Mapster, TinyMapper vs.
Bu yazımda; projelerde kullanılacak kütüphane değişirse ya da yeni bir kütüphane eklenmek istenirse, kodun yapısını değiştirmeden kullanmak için dependency injection tekniği vasıtasyla generic bir yapı oluşturulmasına değindim. 


GenericObjectMapper isminde .NET 6 Web API projesi başta olmak üzere katmanlı mimari yapısında projeler oluşturuldu. Projem, açık kaynak olarak Github'da bulunmaktadır ve geliştirilmeye devam edilebilir durumdadır. 

Github Linki: GenericObjectMapper  

Generic Object Mapping konusunun projedeki önemli kodlarını özetleyecek olursak;

IMapper.cs

namespace GenericObjectMapper.Core.Entities
{
public interface IMapper
{
TDestination Map<TSource, TDestination>(TSource source);
}
}
view raw IMapper.cs hosted with ❤ by GitHub
TSource: Kaynak sınıf, dönüştürülecek tip. 
    Örn: Entity

TDestination: Hedef sınıf, dönüştürülmesi hedeflenen tip. 
    Örn: ViewModel, DTO (Data Transfer Object)

IMapper interface dosyasındaki Map metodu, kaynaktan hedefe dönüştürme yapacak metot olup yazının devamında oluşturulacak mapping kütüphaneleri için oluşturulan sınıflara entegre edilecektir. 

AutoMapper kütüphanesi için oluşturulan sınıf: AutoMapperMapping

namespace Northwind.Core.Entities.Mapping.AutoMapper
{
public class AutoMapperMapping<TProfile> : GenericObjectMapper.Core.Entities.IMapper where TProfile : Profile, new()
{
public TDestination Map<TSource, TDestination>(TSource source)
{
var mapper = new MapperConfiguration(config =>
{
config.AddProfile(new TProfile());
}).CreateMapper();
return mapper.Map<TDestination>(source);
}
}
}
Bu sınıf, bütün mappingleri barındıran Profile türünden sınıf gönderilebilecek generic bir yapıdadır. Entegre edilen IMapper arayüzündeki Map metodunda önce, gelen profile sınıfı yapılandırmaya dahil edilerek AutoMapper kütüphanesine ait MapperConfiguration sınıfının CreateMapper metoduyla ayarlanır sonra, kaynak tipten hedef tipe mapping işlemini gerçekleştirir.

Mapster kütüphanesi için oluşturulan sınıf: MapsterMapping

namespace Northwind.Core.Entities.Mapping.Mapster
{
public class MapsterMapping : IMapper
{
public TDestination Map<TSource, TDestination>(TSource source)
{
return source.Adapt<TDestination>();
}
}
}
Bu sınıf, entegre edilen IMapper arayüzündeki Map metodunda Mapster kütüphanesine ait Adapt metoduyla, kaynak tipten hedef tipe mapping işlemini gerçekleştirir.

TinyMapper kütüphanesi için oluşturulan sınıf: TinyMapperMapping

namespace Northwind.Core.Entities.Mapping.Tinymapper
{
public class TinyMapperMapping : IMapper
{
public TDestination Map<TSource, TDestination>(TSource source)
{
TinyMapper.Bind<TSource, TDestination>();
return TinyMapper.Map<TDestination>(source);
}
}
}
Bu sınıf, entegre edilen IMapper arayüzündeki Map metodunda TinyMapper kütüphanesine ait Bind metodu, kaynak tiple hedef tip arasında tek yölü mapping yapısı oluşturur ve thread-safedir. Map metoduyla mapping işlemini gerçekleştirir. 

Örnek senaryoda Northwind veri tabanı kullanılacak ve kategori detayı getirecek sorgudan kategori adı ve açıklamasını getirecek bir nesne döndürülecektir. Projede ORM aracı olarak EntityFrameworkCore kütüphanesi kullanıldı.

ICategoryService: Kategori işlemleri için kullanılan interfacedir.

namespace GenericObjectMapper.Business.Abstract
{
public interface ICategoryService
{
IDataResult<CategoryDTO> GetById(int categoryId);
IDataResult<List<Category>> GetAll();
}
}
CategoryManager : Kategori işlemlerinin yapıldığı iş metotlarını barındıran sınıftır. GetById metodunda kategori detayı getirilecek ve Category sınıfından CategoryDTO nesnesine mapping işlemi yapacaktır. 

namespace GenericObjectMapper.Business.Concrete
{
public class CategoryManager : ICategoryService
{
private ICategoryDal _categoryDal;
private IMapper _mapper;
public CategoryManager(ICategoryDal categoryDal, IMapper mapper)
{
_categoryDal = categoryDal;
_mapper = mapper;
}
public IDataResult<List<Category>> GetAll()
{
return new SuccessDataResult<List<Category>>(_categoryDal.GetAll());
}
public IDataResult<CategoryDTO> GetById(int categoryId)
{
return new SuccessDataResult<CategoryDTO>(_mapper.Map<Category, CategoryDTO>(_categoryDal.Get(c => c.CategoryId == categoryId)));
}
}
}
CategoryDTO: Göstermek istenilen propertyleri barındıran sınıftır.

namespace GenericObjectMapper.Entities.DTOs
{
public class CategoryDTO : IDTO
{
public string CategoryName { get; set; }
public string Description { get; set; }
}
}
view raw CategoryDTO.cs hosted with ❤ by GitHub
Category: Kategori için Northwind veri tabanındaki Categories tablosunun kolonlarını property olarak barındırır.

namespace GenericObjectMapper.Entities.Concrete
{
public class Category : IEntity
{
public int CategoryId { get; set; }
public string CategoryName { get; set; }
public string Description { get; set; }
public virtual List<Product> Products { get; set; }
}
}
view raw Category.cs hosted with ❤ by GitHub
IOC yapısında kütüphane olarak Autofac kullanıldı. Sebebi, Program.cs dosyasındaki kalabalık kod yapısından kurtararak, bağımlılıkları tek bir dosyadan kontrol edebilmektir. AutofacBusinessModule sınıfı bu işi yapacaktır.

namespace Business.DependencyResolvers.Autofac
{
public class AutofacBusinessModule:Module
{
protected override void Load(ContainerBuilder builder)
{
builder.RegisterType<CategoryManager>().As<ICategoryService>().SingleInstance();
builder.RegisterType<ProductManager>().As<IProductService>().SingleInstance();
builder.RegisterType<EfProductDal>().As<IProductDal>().SingleInstance();
builder.RegisterType<EfCategoryDal>().As<ICategoryDal>().SingleInstance();
builder.RegisterType<HttpContextAccessor>().As<IHttpContextAccessor>();
//AutoMapper
//builder.RegisterType<AutoMapperMapping<CategoryProfile>>().As<IMapper>().SingleInstance();
//Mapster
builder.RegisterType<MapsterMapping>().As<IMapper>().SingleInstance();
//TinyMapper
//builder.RegisterType<TinyMapperMapping>().As<IMapper>().SingleInstance();
var assembly = System.Reflection.Assembly.GetExecutingAssembly();
builder.RegisterAssemblyTypes(assembly).AsImplementedInterfaces()
.EnableInterfaceInterceptors(new ProxyGenerationOptions()
{
Selector = new AspectInterceptorSelector()
}).SingleInstance();
}
}
}
Business ve Data katmanındaki bağımlılıkları verdikten sonra Mapping bağımlılıklarını yönetebileceği kodların eklediği IOC yapısındaki sınıftır.

Mapster kütüphanesini kullanmak için aşağıdaki kod bloğu kullanılmalıdır. 
builder.RegisterType<MapsterMapping>().As<IMapper>().SingleInstance();
AutoMapper kütüphanesini kullanmak için aşağıdaki kod bloğu kullanılmalıdır. 
builder.RegisterType<AutoMapperMapping<CategoryProfile>>().As<IMapper>().SingleInstance();
namespace GenericObjectMapper.Entities.Mapping.AutoMapper
{
public class CategoryProfile : Profile
{
public CategoryProfile()
{
CreateMap<Category, CategoryDTO>();
CreateMap<CategoryDTO, Category>();
}
}
}
CategoryProfile sınıfı, AutoMapper kütüphanesine ait Profile sınıfından miras alır. Kategori işlemlerine ait entity ve DTO sınıfları arasındaki mapping işlemlerinin kontrolü buradan yapılır.

TinyMapper kütüphanesini kullanmak için aşağıdaki kod bloğu kullanılmalıdır. 
builder.RegisterType<TinyMapperMapping>().As<IMapper>().SingleInstance();
Program.cs dosyasında Autofac entegrasyonu aşağıdaki gibi yapıldı. AutofacBusinessModule sınıfı, container yapılandırma ayarlarını yapan ConfigureContainer metodunda modül olarak verildi.

var builder = WebApplication.CreateBuilder(args);
builder.Host.UseServiceProviderFactory(new AutofacServiceProviderFactory());
builder.Host.ConfigureContainer<ContainerBuilder>(builder => builder.RegisterModule(new AutofacBusinessModule()));
/*
Add your services - Servislerinizi ekleyebilirsiniz.
*/
var app = builder.Build();
view raw Startup.cs hosted with ❤ by GitHub
Kategori detayını test edecek olursak alınacakçıktı aşağıdaki gibidir. Sonuç olarak CategoryDTO tipinden bir veri beklenir.


Bu yazımda AutoMapper, Mapster, TinyMapper object mapping kütüphaneleri için generic yapıda ve dependency injection tekniği ile bağımlılığın yönetebileceğini örnek bir proje ile anlatarak değinildi.

Keyifli ve faydalı olmasını umarak, çalışmalarınızda kolaylıklar diliyorum.

Yorumlar