This project is a template that installs the basic architectural structure for those who want to develop a project using the onion architecture.
You can access the template here.
The extension creates your project with the name you gave, together with the basic building blocks of onion architecture, Application, Domain, Infrasturucture, Persistence and Presentation Layers.
It contains the applied generic Repository pattern separated as Read Write, together with an extension where you can perform Dependency operations in the Application layer.
IReadRepository
using Microsoft.EntityFrameworkCore.Query;
using Simple_Onion_Architecture1.Domain.Entities.Common;
using System.Linq.Expressions;
namespace Simple_Onion_Architecture1.Application.Abstractions.Repositories.Common
{
public interface IReadRepository<TEntity> where TEntity : class, IEntity, new()
{
Task<TEntity> GetAsync(Expression<Func<TEntity, bool>> predicate, Func<IQueryable<TEntity>,
IIncludableQueryable<TEntity, object>>? include = null, bool enableTracking = true,
CancellationToken cancellationToken = default);
Task<IQueryable<TEntity>> GetListAsync(Expression<Func<TEntity, bool>>? predicate = null,
Func<IQueryable<TEntity>, IOrderedQueryable<TEntity>>? orderBy = null,
Func<IQueryable<TEntity>, IIncludableQueryable<TEntity, object>>? include = null,
bool enableTracking = true,
CancellationToken cancellationToken = default);
}
}
IWriteRepository
using Simple_Onion_Architecture1.Domain.Entities.Common;
namespace Simple_Onion_Architecture1.Application.Abstractions.Repositories.Common
{
public interface IWriteRepository<TEntity> where TEntity : class, IEntity, new()
{
Task AddAsync(TEntity entity);
Task AddRangeAsync(IEnumerable<TEntity> entities);
Task Update(TEntity entity);
Task UpdateRange(IEnumerable<TEntity> entities);
Task Remove(TEntity entity);
Task RemoveRange(IEnumerable<TEntity> entities);
Task<int> SaveChangesAsync();
}
}
IRepository
using Simple_Onion_Architecture1.Domain.Entities.Common;
namespace Simple_Onion_Architecture1.Application.Abstractions.Repositories.Common
{
public interface IRepository<TEntity> : IWriteRepository<TEntity>, IReadRepository<TEntity> where TEntity : class, IEntity, new()
{
IQueryable<TEntity> Query { get; }
}
}
Dependency Resolver
using Microsoft.Extensions.DependencyInjection;
namespace Simple_Onion_Architecture1.Application.Extensions
{
public static class ApplicationServiceRegistration
{
// Bu projede kullanacağınız servisleri IoC mekanizmasına ekleyecek olan fonksiyondur.
// This is the function that will add the services you will use in this project to the IoC mechanism.
public static IServiceCollection AddApplicationServiceRegistration(this IServiceCollection services)
{
return services;
}
}
}
Apart from the "Base Entity" class where you can define the common properties of the entities you create in the domain layer, there is the "IEntity" interface where you can mark the entities in the Generic Repository.
BaseEntity
namespace Simple_Onion_Architecture1.Domain.Entities.Common
{
// Burada tüm Entity'ler için geçerli olacak değişkenleri belirtirsiniz, onları buradan miras alarak tekrardan kaçınabilirsiniz.
// Here you specify variables that will be valid for all Entities, you can avoid duplication by inheriting them from here.
public abstract class BaseEntity : BaseEntity<int>
{
}
//Burada Entitydeki Id nin tipini kendiniz değiştirebilirsiniz: Guid ,string vb bi şekilde değişken tanımlayabilirsiniz
//You can change the type of the Id in Entity here by yourself: you can define a variable in various ways such as Guid or string
public abstract class BaseEntity<TIdentity> : IEntity where TIdentity : struct, IComparable, IComparable<TIdentity>, IEquatable<TIdentity>, IFormattable
{
public TIdentity Id { get; set; }
public DateTime CreatedDate { get; set; }
public DateTime? ModifiedDate { get; set; }
}
}
IEntity
namespace Simple_Onion_Architecture1.Domain.Entities.Common
{
public interface IEntity
{
DateTime CreatedDate { get; set; }
DateTime? ModifiedDate { get; set; }
}
}
The Infrastructure layer contains an extension class for Dependency injection, just like the Application layer. Since this layer is used for External service implementation, there is nothing else.
Dependency Resolver
using Microsoft.Extensions.DependencyInjection;
namespace Simple_Onion_Architecture1.Infrastructure.Extensions
{
public static class InfrastructureServiceRegistration
{
// Bu projede kullanacağınız servisleri IoC mekanizmasına ekleyecek olan fonksiyondur.
// This is the function that will add the services you will use in this project to the IoC mechanism.
public static IServiceCollection AddInfrastructureServiceRegistration(this IServiceCollection services)
{
return services;
}
}
}
Persistence layer is the layer where the repository design pattern interfaces that we created in the Application layer are applied together with ef core. We have developed a function that edits and saves the CreatedDate and UpdatedDate properties that we want to be all entities with IEntity by overriding the SaveChanges method in DbContet, without depending on the user. As in the other layers, a Dependency injection extension class is also available, but there is a code block that automatically adds it to the IoC container for the classes that are suitable for the generic repository pattern you will apply.
The reading and writing processes for this project were also written in one piece, if you wish, you can do it by breaking it up.
Repository
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Query;
using Simple_Onion_Architecture1.Application.Abstractions.Repositories.Common;
using Simple_Onion_Architecture1.Domain.Entities.Common;
using System.Linq.Expressions;
namespace Simple_Onion_Architecture1.Persistence.Concretes.Repositories.Common
{
public class Repository<TEntity, TContext> : IRepository<TEntity> where TEntity : class, IEntity, new() where TContext : DbContext
{
private readonly TContext _context;
public Repository(TContext context)
{
_context = context;
}
private DbSet<TEntity> Table => _context.Set<TEntity>();
public IQueryable<TEntity> Query => Table.AsQueryable();
#region Read-Metods
public async Task<TEntity> GetAsync(Expression<Func<TEntity, bool>> predicate, Func<IQueryable<TEntity>, IIncludableQueryable<TEntity, object>>? include = null, bool enableTracking = true, CancellationToken cancellationToken = default)
{
IQueryable<TEntity> queryable = Query;
if (!enableTracking) queryable = Query.AsNoTracking();
if (include != null) queryable = include(queryable);
return await queryable.FirstOrDefaultAsync(predicate, cancellationToken);
}
public async Task<IQueryable<TEntity>> GetListAsync(Expression<Func<TEntity, bool>>? predicate = null, Func<IQueryable<TEntity>, IOrderedQueryable<TEntity>>? orderBy = null, Func<IQueryable<TEntity>, IIncludableQueryable<TEntity, object>>? include = null, bool enableTracking = true, CancellationToken cancellationToken = default)
{
IQueryable<TEntity> queryable = Query;
if (!enableTracking) queryable = queryable.AsNoTracking();
if (include != null) queryable = include(queryable);
if (predicate != null) queryable = queryable.Where(predicate);
if (orderBy != null)
return await Task.FromResult(orderBy(queryable));
return await Task.FromResult(queryable);
}
#endregion
#region Write-Metods
public async Task AddAsync(TEntity entity)
{
await Table.AddAsync(entity);
}
public async Task AddRangeAsync(IEnumerable<TEntity> entities)
{
await Table.AddRangeAsync(entities);
}
public Task Remove(TEntity entity)
{
Table.Remove(entity);
return Task.CompletedTask;
}
public Task RemoveRange(IEnumerable<TEntity> entities)
{
Table.RemoveRange(entities);
return Task.CompletedTask;
}
public async Task<int> SaveChangesAsync()
{
return await _context.SaveChangesAsync();
}
public Task Update(TEntity entity)
{
Table.Update(entity);
return Task.CompletedTask;
}
public Task UpdateRange(IEnumerable<TEntity> entities)
{
Table.UpdateRange(entities);
return Task.CompletedTask;
}
#endregion
}
}
DbContext
using Microsoft.EntityFrameworkCore;
using Simple_Onion_Architecture1.Domain.Entities.Common;
namespace Simple_Onion_Architecture1.Persistence.Context
{
public class ApplicaitonDbContext : DbContext
{
public ApplicaitonDbContext(DbContextOptions options) : base(options)
{
}
// Entityleri db ye kaydederken içerdiği dateTime propertylerini otomatik olarak ekleyen bir metod
// Not : Eğer PostgreSql kullanacaksanız DateTime yerine DateTime.Utc.Now Kullanmanız gerekiyor
// A method that automatically adds dateTime properties contained in entities while saving to the database
// Note: If you're using PostgreSql, you need to use DateTime.Utc.Now instead of DateTime
public async override Task<int> SaveChangesAsync(CancellationToken cancellationToken = default)
{
var datas = ChangeTracker.Entries<IEntity>();
foreach (var data in datas)
{
_ = data.State switch
{
EntityState.Added => data.Entity.CreatedDate = DateTime.Now,
EntityState.Modified => data.Entity.ModifiedDate = DateTime.Now,
_ => DateTime.Now
};
}
return await base.SaveChangesAsync(cancellationToken);
}
}
}
Dependency Resolver
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Simple_Onion_Architecture1.Application.Abstractions.Repositories.Common;
using Simple_Onion_Architecture1.Persistence.Context;
using System.Reflection;
namespace Simple_Onion_Architecture1.Persistence.Extensions
{
public static class PersistenceServiceRegistration
{
// Bu projede kullanacağınız servisleri IoC mekanizmasına ekleyecek olan fonksiyondur.
// This is the function that will add the services you will use in this project to the IoC mechanism.
public static IServiceCollection AddPersistenceServiceRegistration(this IServiceCollection services, IConfiguration configuration)
{
services.AddDbContext<ApplicaitonDbContext>(opt => opt.UseSqlServer(configuration.GetConnectionString("SqlConnection")));
AddRepositoryToIoC(services, Assembly.GetExecutingAssembly());
return services;
}
// Repository lerin otomatik olarak IoC Container a eklenmesini sağlayan metod
//Method that enables automatic addition of repositories to IoC Container
private static IServiceCollection AddRepositoryToIoC(IServiceCollection services, Assembly assembly)
{
var reposiories = assembly.GetTypes().Where(x => x.IsAssignableToGenericType(typeof(IRepository<>)) && !x.IsGenericType);
foreach (var item in reposiories)
{
var @interface = item.GetInterfaces().FirstOrDefault(x => !x.IsGenericType) ?? throw new ArgumentNullException();
services.AddScoped(@interface, item);
}
return services;
}
//Type in verilen generic türden türeyip türemediğini kontrol eden fonksiyon
//Function that checks whether a given type is implementing a generic interface
private static bool IsAssignableToGenericType(this Type givenType, Type genericType)
{
return givenType.GetInterfaces().Any(t => t.IsGenericType && t.GetGenericTypeDefinition() == genericType) ||
givenType.BaseType != null && (givenType.BaseType.IsGenericType && givenType.BaseType.GetGenericTypeDefinition() == genericType ||
givenType.BaseType.IsAssignableToGenericType(genericType));
}
}
}
In the Presentation layer, extensions written in other layers are added to program.cs and a sample controller has been created so that they are not deleted as an empty folder. A key was created for the Conneciton string in AppSetting and this key was used in the persistence layer.
Program.cs
using Simple_Onion_Architecture1.Application.Extensions;
using Simple_Onion_Architecture1.Infrastructure.Extensions;
using Simple_Onion_Architecture1.Persistence.Extensions;
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
builder.Services.AddControllers();
// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddApplicationServiceRegistration();
builder.Services.AddPersistenceServiceRegistration(builder.Configuration);
builder.Services.AddInfrastructureServiceRegistration();
builder.Services.AddSwaggerGen();
var app = builder.Build();
// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
app.UseSwagger();
app.UseSwaggerUI();
}
app.UseHttpsRedirection();
app.UseAuthorization();
app.MapControllers();
app.Run();
AppSettings.json
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*",
"ConnectionStrings": {
"SqlConnection" : ""
}
}