Ecosyste.ms: Awesome
An open API service indexing awesome lists of open source software.
https://github.com/rabbal/DNTFrameworkCore
Lightweight and Extensible Infrastructure for Building Web Applications - Web Application Framework
https://github.com/rabbal/DNTFrameworkCore
aop aspnet-core cqrs crud design-patterns dotnet-core efcore event-driven framework integration-testing master-detail multitenancy solid transaction unit-testing validation
Last synced: 3 months ago
JSON representation
Lightweight and Extensible Infrastructure for Building Web Applications - Web Application Framework
- Host: GitHub
- URL: https://github.com/rabbal/DNTFrameworkCore
- Owner: rabbal
- License: apache-2.0
- Created: 2019-02-08T19:30:07.000Z (almost 6 years ago)
- Default Branch: master
- Last Pushed: 2023-11-18T05:53:26.000Z (about 1 year ago)
- Last Synced: 2024-05-29T01:34:11.548Z (8 months ago)
- Topics: aop, aspnet-core, cqrs, crud, design-patterns, dotnet-core, efcore, event-driven, framework, integration-testing, master-detail, multitenancy, solid, transaction, unit-testing, validation
- Language: C#
- Homepage:
- Size: 3.87 MB
- Stars: 314
- Watchers: 27
- Forks: 76
- Open Issues: 2
-
Metadata Files:
- Readme: README.md
- Funding: .github/FUNDING.yml
- License: LICENSE.md
Awesome Lists containing this project
- awesome-dotnet-core - DNTFrameworkCore - Lightweight and Extensible Infrastructure for Building High Quality Web Applications Based on ASP.NET Core. (Frameworks, Libraries and Tools / Application Frameworks)
- fucking-awesome-dotnet-core - DNTFrameworkCore - Lightweight and Extensible Infrastructure for Building High Quality Web Applications Based on ASP.NET Core. (Frameworks, Libraries and Tools / Application Frameworks)
- awesome-dotnet-core - DNTFrameworkCore - Lightweight and Extensible Infrastructure for Building High Quality Web Applications Based on ASP.NET Core. (Frameworks, Libraries and Tools / Application Frameworks)
- awesome-dotnet-core - DNTFrameworkCore - DNTFrameworkCore 是一个轻量级且可扩展的基础结构,用于基于ASP.NET Core构建高质量的Web应用程序 (框架, 库和工具 / 应用程序框架)
README
[![.NET](https://github.com/rabbal/DNTFrameworkCore/actions/workflows/dotnet.yml/badge.svg)](https://github.com/rabbal/DNTFrameworkCore/actions/workflows/dotnet.yml)
![Nuget](https://img.shields.io/nuget/v/DNTFrameworkCore)
[![build aspnet-core-api template](https://github.com/rabbal/DNTFrameworkCore/actions/workflows/aspnet-core-api-template.yml/badge.svg)](https://github.com/rabbal/DNTFrameworkCore/actions/workflows/aspnet-core-api-template.yml)
![Nuget](https://img.shields.io/nuget/v/DNTFrameworkCoreTemplateAPI?label=aspnet-core-api-template)
### What is DNTFrameworkCore?`DNTFrameworkCore` is a Lightweight and
Extensible Infrastructure for Building High-Quality Web Applications Based on ASP.NET Core and has the following goals:
* Common structures in various applications like Cross-Cutting Concerns, etc
* Follow DRY principle to focus on main business logic
* Reduce the development time
* Less bug and stop bug propagation
* Reduce the training time of the new developer with low knowledge about OOP and OODCRUD-based Thinking
----Application Service
```c#
public interface IBlogService : IEntityService
{
}public class BlogService : EntityService, IBlogService
{
private readonly IMapper _mapper;public BlogService(
IDbContext dbContext,
IEventBus bus,
IMapper mapper) : base(dbContext, bus)
{
_mapper = mapper ?? throw new ArgumentNullException(nameof(mapper));
}public override Task> FetchPagedListAsync(FilteredPagedRequest request,
CancellationToken cancellationToken = default)
{
return EntitySet.AsNoTracking()
.Select(b => new BlogModel
{
Id = b.Id,
Version = b.Version,
Url = b.Url,
Title = b.Title
}).ToPagedListAsync(request, cancellationToken);
}protected override void MapToEntity(BlogModel model, Blog blog)
{
_mapper.Map(model, blog);
}protected override BlogModel MapToModel(Blog blog)
{
return _mapper.Map(blog);
}
}
```
ASP.NET Core WebAPI
```c#
[Route("api/[controller]")]
public class BlogsController : EntityController
{
public BlogsController(IBlogService service) : base(service)
{
}protected override string CreatePermissionName => PermissionNames.Blogs_Create;
protected override string EditPermissionName => PermissionNames.Blogs_Edit;
protected override string ViewPermissionName => PermissionNames.Blogs_View;
protected override string DeletePermissionName => PermissionNames.Blogs_Delete;
}
```
ASP.NET Core MVC
```c#
public class BlogsController : EntityController
{
public BlogsController(IBlogService service) : base(service)
{
}protected override string CreatePermissionName => PermissionNames.Blogs_Create;
protected override string EditPermissionName => PermissionNames.Blogs_Edit;
protected override string ViewPermissionName => PermissionNames.Blogs_View;
protected override string DeletePermissionName => PermissionNames.Blogs_Delete;
protected override string ViewName => "_BlogPartial";
}
```
_BlogPartial.cshtml
```razor
@inherits EntityFormRazorPage
@{
Layout = "_EntityFormLayout";
EntityName = "Blog";
DeletePermission = PermissionNames.Blogs_Delete;
CreatePermission = PermissionNames.Blogs_Create;
EditPermission = PermissionNames.Blogs_Edit;
EntityDisplayName = "Blog";
}
```
![Role Modal MVC](https://github.com/rabbal/DNTFrameworkCore/blob/master/docs/role-modal-edit.JPG)
## InstallationTo create your first project based on DNTFrameworkCore you can install the following packages:
```
PM> Install-Package DNTFrameworkCore
PM> Install-Package DNTFrameworkCore.EFCore
PM> Install-Package DNTFrameworkCore.EFCore.SqlServer
PM> Install-Package DNTFrameworkCore.Web
PM> Install-Package DNTFrameworkCore.Web.Tenancy
PM> Install-Package DNTFrameworkCore.Web.EFCore
PM> Install-Package DNTFrameworkCore.Licensing
PM> Install-Package DNTFrameworkCore.FluentValidation```
OR
1- Run the following command to install boilerplate project template based on ASP.NET Core Web API and DNTFrameworkCore:
```dotnet new --install DNTFrameworkCoreTemplateAPI::*```
2- Create new project with installed template:
```dotnet new dntcore-api```
Now you have a solution like below that contains complete identity management feature includes user, role, and dynamic permission management and also integrated with persistent JWT authentication mechanism:
![Solution Structure](https://github.com/rabbal/DNTFrameworkCore/blob/master/docs/dnt-solution.jpg)
For more info about templates, you can watch [DNTFrameworkCoreTemplate repository](https://github.com/rabbal/DNTFrameworkCoreTemplate)
## Features
* Application Input Validation
* Transaction Management
* Eventing
* EntityGraph Tracking (Master-Detail)
* Numbering
* Functional Programming Error Handling
* Permission Authorization
* EntityService
* EntityController (API and MVC)
* DbLogger Provider based on EFCore
* ProtectionKey EFCore Store
* Hooks
* SoftDelete
* Tenancy
* Tracking mechanism (ICreationTracking, IModificationTracking)
* FluentValidation Integration
* BackgroundTaskQueue
* RowIntegrity
* StartupTask mechanism
* CQRS (coming soon)
* EntityHistory (coming soon)## Usage
[DNTFrameworkCore.TestAPI Complete ASP.NET Core Web API](https://github.com/rabbal/DNTFrameworkCore/tree/master/test/DNTFrameworkCore.TestAPI)**Create Entity**
```c#
public class Task : Entity, INumberedEntity, IHasRowVersion, IHasRowIntegrity, ICreationTracking, IModificationTracking
{
public const int MaxTitleLength = 256;
public const int MaxDescriptionLength = 1024;public string Title { get; set; }
public string NormalizedTitle { get; set; }
public string Number { get; set; }
public string Description { get; set; }
public TaskState State { get; set; } = TaskState.Todo;
public byte[] Version { get; set; }
}
```**Implement ProjectDbContext that inherited from DbContextCore**
```c#
public class ProjectDbContext : DbContextCore
{
public ProjectDbContext(DbContextOptions options, IEnumerable hooks) : base(options, hooks)
{
}protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.ApplyConfigurationsFromAssembly(Assembly.GetExecutingAssembly());modelBuilder.AddJsonFields();
modelBuilder.AddTrackingFields();
modelBuilder.AddIsDeletedField();
modelBuilder.AddRowVersionField();
modelBuilder.AddRowIntegrityField();
modelBuilder.NormalizeDateTime();
modelBuilder.NormalizeDecimalPrecision();
base.OnModelCreating(modelBuilder);
}
}
```
**Create Model/DTO**
```c#
[LocalizationResource(Name = "SharedResource", Location = "DNTFrameworkCore.TestAPI")]
public class TaskModel : MasterModel, IValidatableObject
{
public string Title { get; set; }[MaxLength(50, ErrorMessage = "Validation from DataAnnotations")]
public string Number { get; set; }public string Description { get; set; }
public TaskState State { get; set; } = TaskState.Todo;public IEnumerable Validate(ValidationContext validationContext)
{
if (Title == "IValidatableObject")
{
yield return new ValidationResult("Validation from IValidatableObject");
}
}
}
```Note: Based on validation infrastructure, you can validate Model/DTO with various approaches, using by DataAnnotation ValidateAttribute, Implementing IValidatableObject, or Implement IModelValidator that exist in DNTFrameworkCore package.
```c#
public class TaskValidator : ModelValidator
{
public override IEnumerable Validate(TaskModel model)
{
if (!Enum.IsDefined(typeof(TaskState), model.State))
{
yield return new ModelValidationResult(nameof(TaskModel.State), "Validation from IModelValidator");
}
}
}
```Also in most cases, one Model/DTO can be enough for your requirements about Create/Edit/View an entity. However, you can create ReadModel like below:
```c#
public class TaskReadModel : ReadModel
{
public string Title { get; set; }
public string Number { get; set; }
public TaskState State { get; set; } = TaskState.Todo;
}
```**Implement Service**
```c#
public interface ITaskService : IEntityService
{
}public class TaskService : EntityService,
ITaskService
{
private readonly IMapper _mapper;
public TaskService(IDbContext dbContext, IEventBus bus, IMapper mapper) :base(dbContext, bus)
{
_mapper = mapper ?? throw new ArgumentNullException(nameof(mapper);
}public override Task> FetchPagedListAsync(TaskFilteredPagedRequest request,
CancellationToken cancellationToken = default)
{
return EntitySet.AsNoTracking()
.WhereIf(model.State.HasValue, t => t.State == model.State)
.Select(t => new TaskReadModel
{
Id = t.Id,
Title = t.Title,
State = t.State,
Number = t.Number
}).ToPagedListAsync(request, cancellationToken);
}protected override void MapToEntity(TaskModel model, Task task)
{
_mapper.Map(model, task);
}protected override TaskModel MapToModel(Task task)
{
return _mapper.Map(task);
}
}
```In DNTFrameworkCore.EFCore [there is no dependency to AutoMapper](https://cezarypiatek.github.io/post/why-i-dont-use-automapper/) or other mapper libraries, then you can do mapping between Entity and Model manually by implementing MapToModel and MapToEntity abstract methods.
**Implement API Controller**
```c#
[Route("api/[controller]")]
public class
TasksController : EntityController
{
public TasksController(ITaskService service) : base(service)
{
}protected override string CreatePermissionName => PermissionNames.Tasks_Create;
protected override string EditPermissionName => PermissionNames.Tasks_Edit;
protected override string ViewPermissionName => PermissionNames.Tasks_View;
protected override string DeletePermissionName => PermissionNames.Tasks_Delete;
}
``````c#
[Route("api/[controller]")]
public class BlogsController : EntityController
{
public BlogsController(IBlogService service) : base(service)
{
}protected override string CreatePermissionName => PermissionNames.Blogs_Create;
protected override string EditPermissionName => PermissionNames.Blogs_Edit;
protected override string ViewPermissionName => PermissionNames.Blogs_View;
protected override string DeletePermissionName => PermissionNames.Blogs_Delete;
}```
Task-based Thinking
---------------
Rich Domain Model
```c#
public class PriceType : Entity, IAggregateRoot
{
private PriceType(Title title)
{
Title = title;
}
public PriceType(Title title, IPriceTypePolicy policy)
{
if (title == null) throw new ArgumentNullException(nameof(title));
if (policy == null) throw new ArgumentNullException(nameof(policy));Title = title;
if (!policy.IsUnique(this)) ThrowDomainException("PriceType Title Should Be Unique");
AddDomainEvent(new PriceTypeCreatedDomainEvent(this));
}public Title Title { get; private set; }
// public static Result New(Title title, IPriceTypePolicy policy)
// {
// if (title == null) throw new ArgumentNullException(nameof(title));
// if (policy == null) throw new ArgumentNullException(nameof(policy));
//
// var priceType = new PriceType(title);
// if (!policy.IsUnique(priceType)) return Fail("PriceType Title Should Be Unique");
//
// priceType.AddDomainEvent(new PriceTypeCreatedDomainEvent(priceType));
//
// return Ok(priceType);
// }
}
```
ValueObject
```c#
public class Title : ValueObject
{
private Title()
{
}public Title(string value)
{
value ??= string.Empty;switch (value.Length)
{
case 0:
ThrowDomainException("title should not be empty");
break;
case > 100:
ThrowDomainException("title is too long");
break;
}
}public string Value { get; private set; }
protected override IEnumerable EqualityValues
{
get { yield return Value; }
}// public static Result New(string value)
// {
// value ??= string.Empty;
//
// if (value.Length == 0) return Fail("title should not be empty");
//
// return value.Length > 100 ? Fail("title is too long") : Ok(new Title { Value = value });
// }public static implicit operator string(Title title)
{
return title.Value;
}public static explicit operator Title(string title)
{
return new(title);
}
}
```
DomainEvent```c#
public sealed class PriceTypeCreatedDomainEvent : DomainEvent
{
public PriceTypeCreatedDomainEvent(PriceType priceType)
{
PriceType = priceType ?? throw new ArgumentNullException(nameof(priceType));
}public PriceType PriceType { get; }
}
```CQRS (Command)
```c#
public sealed class CreatePriceTypeCommand : ICommand
{
public string Title { get; }
[JsonConstructor]
public CreatePriceTypeCommand(string title) => Title = title;
}
```
CQRS (CommandHandler)
```c#
public class PriceTypeCommandHandlers : ICommandHandler,
ICommandHandler
{
private readonly IUnitOfWork _uow;
private readonly IPriceTypeRepository _repository;
private readonly IPriceTypePolicy _policy;
private readonly IEventBus _bus;public PriceTypeCommandHandlers(
IUnitOfWork uow,
IPriceTypeRepository repository,
IPriceTypePolicy policy,
IEventBus bus)
{
_uow = uow ?? throw new ArgumentNullException(nameof(uow));
_repository = repository ?? throw new ArgumentNullException(nameof(repository));
_policy = policy ?? throw new ArgumentNullException(nameof(policy));
_bus = bus ?? throw new ArgumentNullException(nameof(bus));
}public async Task Handle(RemovePriceTypeCommand command, CancellationToken cancellationToken)
{
var priceType = await _repository.FindAsync(command.PriceTypeId, cancellationToken);
if (priceType is null) return Result.Fail($"PriceType with id:{command.PriceTypeId} not found");
//Alternative: _repository.Remove(priceType);
_uow.Set().Remove(priceType);
await _uow.SaveChanges(cancellationToken);
await _bus.DispatchDomainEvents(priceType, cancellationToken);
return Result.Ok();
}public async Task Handle(CreatePriceTypeCommand command, CancellationToken cancellationToken)
{
var title = new Title(command.Title);var priceType = new PriceType(title, _policy);
//Alternative: _repository.Add(priceType);
_uow.Set().Add(priceType);
await _uow.SaveChanges(cancellationToken);
await _bus.DispatchDomainEvents(priceType, cancellationToken);return Result.None;
}
}
```## TODO
- Write unit tests for packages
- Use nullable reference types
- Use HTTP problem details
- Implement IntegrationEvent mechanism
- Dispatching DomainEvents before/after save changes
- Complete CQRS and MediatR related behaviors
- Publish DNTFrameworkCore.Cqrs.* packages to nuget.org
- Provide sample codes for multi-tenant scenarios
- Provide sample codes for using Cqrs packages and Rich Domain Model
- Complete FilterExpression class with custom DSL support for filtering mechanism
- Complete DNTFrameworkCore.NHibernate packages
- Implement a mechanism to provide correlationId in distributed systems## ASP.NET Boilerplate
[DNTFrameworkCore (old versions) vs ABP Framework](https://medium.com/@rabbal/dntframeworkcore-vs-abp-framework-b48f5b7f8a24)A small part of this project like the following sections are taken from [ABP](https://github.com/aspnetboilerplate/aspnetboilerplate)
- Validation with refactoring to support functional programming error handling mechanism