Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Lightweight and Extensible Infrastructure for Building Web Applications - Web Application Framework

License

NotificationsYou must be signed in to change notification settings

rabbal/DNTFrameworkCore

Repository files navigation

logo

.NETNugetbuild aspnet-core-api templateNuget

What is DNTFrameworkCore?

DNTFrameworkCore is a Lightweight andExtensible 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 OOD

CRUD-based Thinking

Application Service

publicinterfaceIBlogService:IEntityService<int,BlogModel>{}publicclassBlogService:EntityService<Blog,int,BlogModel>,IBlogService{privatereadonlyIMapper_mapper;publicBlogService(IDbContextdbContext,IEventBusbus,IMappermapper):base(dbContext,bus){_mapper=mapper??thrownewArgumentNullException(nameof(mapper));}publicoverrideTask<IPagedResult<BlogModel>>FetchPagedListAsync(FilteredPagedRequestrequest,CancellationTokencancellationToken=default){returnEntitySet.AsNoTracking().Select(b=>newBlogModel{Id=b.Id,Version=b.Version,Url=b.Url,Title=b.Title}).ToPagedListAsync(request,cancellationToken);}protectedoverridevoidMapToEntity(BlogModelmodel,Blogblog){_mapper.Map(model,blog);}protectedoverrideBlogModelMapToModel(Blogblog){return_mapper.Map<BlogModel>(blog);}}

ASP.NET Core WebAPI

[Route("api/[controller]")]publicclassBlogsController:EntityController<IBlogService,int,BlogModel>{publicBlogsController(IBlogServiceservice):base(service){}protectedoverridestringCreatePermissionName=>PermissionNames.Blogs_Create;protectedoverridestringEditPermissionName=>PermissionNames.Blogs_Edit;protectedoverridestringViewPermissionName=>PermissionNames.Blogs_View;protectedoverridestringDeletePermissionName=>PermissionNames.Blogs_Delete;}

ASP.NET Core MVC

publicclassBlogsController:EntityController<IBlogService,int,BlogModel>{publicBlogsController(IBlogServiceservice):base(service){}protectedoverridestringCreatePermissionName=>PermissionNames.Blogs_Create;protectedoverridestringEditPermissionName=>PermissionNames.Blogs_Edit;protectedoverridestringViewPermissionName=>PermissionNames.Blogs_View;protectedoverridestringDeletePermissionName=>PermissionNames.Blogs_Delete;protectedoverridestringViewName=>"_BlogPartial";}

_BlogPartial.cshtml

@inheritsEntityFormRazorPage<BlogModel>@{Layout="_EntityFormLayout";EntityName="Blog";DeletePermission=PermissionNames.Blogs_Delete;CreatePermission=PermissionNames.Blogs_Create;EditPermission=PermissionNames.Blogs_Edit;EntityDisplayName="Blog";}<divclass="form-group row">   <divclass="col col-md-8">       <labelasp-for="Title"class="col-form-label text-md-left"></label>       <inputasp-for="Title"autocomplete="off"class="form-control"/>       <spanasp-validation-for="Title"class="text-danger"></span>   </div></div><divclass="form-group row">   <divclass="col">       <labelasp-for="Url"class="col-form-label text-md-left"></label>       <inputasp-for="Url"class="form-control"type="url"/>       <spanasp-validation-for="Url"class="text-danger"></span>   </div></div>

Role Modal MVC

Installation

To create your first project based on DNTFrameworkCore you can install the following packages:

PM> Install-Package DNTFrameworkCorePM> Install-Package DNTFrameworkCore.EFCorePM> Install-Package DNTFrameworkCore.EFCore.SqlServerPM> Install-Package DNTFrameworkCore.WebPM> Install-Package DNTFrameworkCore.Web.TenancyPM> Install-Package DNTFrameworkCore.Web.EFCorePM> Install-Package DNTFrameworkCore.LicensingPM> 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

For more info about templates, you can watchDNTFrameworkCoreTemplate repository

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

Create Entity

publicclassTask:Entity<int>,INumberedEntity,IHasRowVersion,IHasRowIntegrity,ICreationTracking,IModificationTracking{publicconstintMaxTitleLength=256;publicconstintMaxDescriptionLength=1024;publicstringTitle{get;set;}publicstringNormalizedTitle{get;set;}publicstringNumber{get;set;}publicstringDescription{get;set;}publicTaskStateState{get;set;}=TaskState.Todo;publicbyte[]Version{get;set;}}

Implement ProjectDbContext that inherited from DbContextCore

publicclassProjectDbContext:DbContextCore{publicProjectDbContext(DbContextOptions<ProjectDbContext>options,IEnumerable<IHook>hooks):base(options,hooks){}protectedoverridevoidOnModelCreating(ModelBuildermodelBuilder){modelBuilder.ApplyConfigurationsFromAssembly(Assembly.GetExecutingAssembly());modelBuilder.AddJsonFields();modelBuilder.AddTrackingFields<long>();modelBuilder.AddIsDeletedField();modelBuilder.AddRowVersionField();modelBuilder.AddRowIntegrityField();modelBuilder.NormalizeDateTime();modelBuilder.NormalizeDecimalPrecision();base.OnModelCreating(modelBuilder);}}

Create Model/DTO

[LocalizationResource(Name="SharedResource",Location="DNTFrameworkCore.TestAPI")]publicclassTaskModel:MasterModel<int>,IValidatableObject{publicstringTitle{get;set;}[MaxLength(50,ErrorMessage="Validation from DataAnnotations")]publicstringNumber{get;set;}publicstringDescription{get;set;}publicTaskStateState{get;set;}=TaskState.Todo;publicIEnumerable<ValidationResult>Validate(ValidationContextvalidationContext){if(Title=="IValidatableObject"){yieldreturnnewValidationResult("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.

publicclassTaskValidator:ModelValidator<TaskModel>{publicoverrideIEnumerable<ModelValidationResult>Validate(TaskModelmodel){if(!Enum.IsDefined(typeof(TaskState),model.State)){yieldreturnnewModelValidationResult(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:

publicclassTaskReadModel:ReadModel<int>{publicstringTitle{get;set;}publicstringNumber{get;set;}publicTaskStateState{get;set;}=TaskState.Todo;}

Implement Service

publicinterfaceITaskService:IEntityService<int,TaskReadModel,TaskModel,TaskFilteredPagedRequest>{}publicclassTaskService:EntityService<Task,int,TaskReadModel,TaskModel,TaskFilteredPagedRequest>,ITaskService{privatereadonlyIMapper_mapper;publicTaskService(IDbContextdbContext,IEventBusbus,IMappermapper):base(dbContext,bus){_mapper=mapper??thrownewArgumentNullException(nameof(mapper);}publicoverrideTask<IPagedResult<TaskReadModel>>FetchPagedListAsync(TaskFilteredPagedRequestrequest,CancellationTokencancellationToken=default){returnEntitySet.AsNoTracking().WhereIf(model.State.HasValue, t=>t.State==model.State).Select(t=>newTaskReadModel{Id=t.Id,Title=t.Title,State=t.State,Number=t.Number}).ToPagedListAsync(request,cancellationToken);}protectedoverridevoidMapToEntity(TaskModelmodel,Tasktask){_mapper.Map(model,task);}protectedoverrideTaskModelMapToModel(Tasktask){return_mapper.Map<TaskModel>(task);}}

In DNTFrameworkCore.EFCorethere is no dependency to 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

[Route("api/[controller]")]publicclassTasksController:EntityController<ITaskService,int,TaskReadModel,TaskModel,TaskFilteredPagedRequest>{publicTasksController(ITaskServiceservice):base(service){}protectedoverridestringCreatePermissionName=>PermissionNames.Tasks_Create;protectedoverridestringEditPermissionName=>PermissionNames.Tasks_Edit;protectedoverridestringViewPermissionName=>PermissionNames.Tasks_View;protectedoverridestringDeletePermissionName=>PermissionNames.Tasks_Delete;}
[Route("api/[controller]")]publicclassBlogsController:EntityController<IBlogService,int,BlogModel>{publicBlogsController(IBlogServiceservice):base(service){}protectedoverridestringCreatePermissionName=>PermissionNames.Blogs_Create;protectedoverridestringEditPermissionName=>PermissionNames.Blogs_Edit;protectedoverridestringViewPermissionName=>PermissionNames.Blogs_View;protectedoverridestringDeletePermissionName=>PermissionNames.Blogs_Delete;}

Task-based Thinking

Rich Domain Model

publicclassPriceType:Entity<long>,IAggregateRoot{privatePriceType(Titletitle){Title=title;}publicPriceType(Titletitle,IPriceTypePolicypolicy){if(title==null)thrownewArgumentNullException(nameof(title));if(policy==null)thrownewArgumentNullException(nameof(policy));Title=title;if(!policy.IsUnique(this))ThrowDomainException("PriceType Title Should Be Unique");AddDomainEvent(newPriceTypeCreatedDomainEvent(this));}publicTitleTitle{get;privateset;}// public static Result<PriceType> 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>("PriceType Title Should Be Unique");////     priceType.AddDomainEvent(new PriceTypeCreatedDomainEvent(priceType));////     return Ok(priceType);// }}

ValueObject

publicclassTitle:ValueObject{privateTitle(){}publicTitle(stringvalue){value??=string.Empty;switch(value.Length){case0:ThrowDomainException("title should not be empty");break;case>100:ThrowDomainException("title is too long");break;}}publicstringValue{get;privateset;}protectedoverrideIEnumerable<object>EqualityValues{get{yieldreturnValue;}}// public static Result<Title> New(string value)// {//     value ??= string.Empty;////     if (value.Length == 0) return Fail<Title>("title should not be empty");////     return value.Length > 100 ? Fail<Title>("title is too long") : Ok(new Title { Value = value });// }publicstaticimplicitoperatorstring(Titletitle){returntitle.Value;}publicstaticexplicitoperatorTitle(stringtitle){returnnew(title);}}

DomainEvent

publicsealedclassPriceTypeCreatedDomainEvent:DomainEvent{publicPriceTypeCreatedDomainEvent(PriceTypepriceType){PriceType=priceType??thrownewArgumentNullException(nameof(priceType));}publicPriceTypePriceType{get;}}

CQRS (Command)

publicsealedclassCreatePriceTypeCommand:ICommand{publicstringTitle{get;}[JsonConstructor]publicCreatePriceTypeCommand(stringtitle)=>Title=title;}

CQRS (CommandHandler)

publicclassPriceTypeCommandHandlers:ICommandHandler<RemovePriceTypeCommand,Result>,ICommandHandler<CreatePriceTypeCommand,Result>{privatereadonlyIUnitOfWork_uow;privatereadonlyIPriceTypeRepository_repository;privatereadonlyIPriceTypePolicy_policy;privatereadonlyIEventBus_bus;publicPriceTypeCommandHandlers(IUnitOfWorkuow,IPriceTypeRepositoryrepository,IPriceTypePolicypolicy,IEventBusbus){_uow=uow??thrownewArgumentNullException(nameof(uow));_repository=repository??thrownewArgumentNullException(nameof(repository));_policy=policy??thrownewArgumentNullException(nameof(policy));_bus=bus??thrownewArgumentNullException(nameof(bus));}publicasyncTask<Result>Handle(RemovePriceTypeCommandcommand,CancellationTokencancellationToken){varpriceType=await_repository.FindAsync(command.PriceTypeId,cancellationToken);if(priceTypeisnull)returnResult.Fail($"PriceType with id:{command.PriceTypeId} not found");//Alternative: _repository.Remove(priceType);_uow.Set<PriceType>().Remove(priceType);await_uow.SaveChanges(cancellationToken);await_bus.DispatchDomainEvents(priceType,cancellationToken);returnResult.Ok();}publicasyncTask<Result>Handle(CreatePriceTypeCommandcommand,CancellationTokencancellationToken){vartitle=newTitle(command.Title);varpriceType=newPriceType(title,_policy);//Alternative: _repository.Add(priceType);_uow.Set<PriceType>().Add(priceType);await_uow.SaveChanges(cancellationToken);await_bus.DispatchDomainEvents(priceType,cancellationToken);returnResult.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

A small part of this project like the following sections are taken fromABP

  • Validation with refactoring to support functional programming error handling mechanism

About

Lightweight and Extensible Infrastructure for Building Web Applications - Web Application Framework

Topics

Resources

License

Stars

Watchers

Forks

Sponsor this project

    Packages

    No packages published

    Contributors2

    •  
    •  

    Languages


    [8]ページ先頭

    ©2009-2025 Movatter.jp