When we do something, we generally need to understand its characteristics and internal logical relationship in detail. Once we understand the whole thing in detail, we can improve our efficiency through some auxiliary means. This essay introduces the rules of each layered project of ABP VNext framework, and realizes the rapid generation of project class code and project files combined with the code generation tool Database2Sharp.
When the ABP VNext framework officially downloads a project, it will generate a standard blank project framework. This code tool is not to replace the project code generation, but to meet the requirements of incremental development module based on data table (after all, the official does not generate project code for data table). Finally, all sub modules can be integrated on the main module, Form a complete system.
1. Project relationship of ABP VNext framework
At present, the framework code generation includes: Application service layer: Application Contracts and Application project, Domain layer: Domain Shared and Domain projects, infrastructure layer: EntityFrameworkCore project, HTTP layer: HttpApi and HttpApi Client project. Generate code integration related base class code and simplify the class code of project files.
Application service layer:
Application.Contracts, including application service interfaces and related data transfer objects (DTOs).
Application, including application service implementation, depends on Domain package and application Contracts package.
Domain layer:
Domain.Shared, including constants, enumerations and other types
Domain includes entity, warehouse interface, domain service interface and its implementation and other domain objects, which depend on domain Shared package
Infrastructure layer:
EntityFrameworkCore, including ORM processing of EF, uses warehousing mode to realize data storage function.
HTTP layer
HttpApi project to develop REST style HTTP API s for modules.
HttpApi.Client project, which will apply the service interface to realize the client call of remote endpoint and provide the function of dynamic proxy HTTP C client.
The dependencies of each layer are shown in the figure below.
2. Project code of each layer of ABP VNext framework
I wrote in the first essay< The controller of HttpApi module is encapsulated in the ABP VNext framework >In order to simplify the repetition of some complex codes of subclasses, the user-defined base class is used to encapsulate some common functions. Through the way of generic parameters, various processing of strongly typed interfaces can be perfectly realized.
For the content of ABP VNext project, we continue to deduce it to its project organization. For simplicity, we use a simple customer table T_Customer table to introduce the hierarchy and relationship of framework projects.
For this additional added table, let's first look at the application of the application service layer Contracts project file, as shown below.
The mapped DTO is placed in the DTO directory, while the Interface definition of the application service is placed in the Interface directory. The advantage of using the directory is to facilitate viewing and management, especially when there are many business tables.
The DTO class is defined as follows.
The base class objects EntityDto, CreationAuditedEntityDto, AuditEntityDto and FullAuditedEntityDto are used. The specific base class DTO depends on which system fields our table contains. If only CreationTime and creatiorid are included, CreationAuditedEntityDto is adopted, and so on.
The entity class relationship of the domain layer is similar to the previous DTO relationship, as shown below.
In this way, when using the code generation tool to generate code, we need to judge the system fields of the table to use different system DTO base classes.
The interface definition file of the application service layer is shown below, which uses the user-defined base class or interface described in our previous essay.
By passing in generic types, we can build strongly typed interface definitions.
The Application project of Application service layer includes DTO mapping file and Application service layer interface implementation class, as shown below.
The Automapper files that map DTO and domain entity relationships are placed in the MapProfile folder, while the interface implementation class files are placed in the Service directory, which is also convenient for management.
The mapping class file mainly defines the relationship between DTO and domain entity, as shown below.
In this way, the file is defined separately, and the mapping file of the whole assembly will be uniformly loaded in the module, which is more convenient.
public class TestProjectApplicationModule : AbpModule { public override void ConfigureServices(ServiceConfigurationContext context) { Configure<AbpAutoMapperOptions>(options => { options.AddMaps<TestProjectApplicationModule>(); }); } }
The interface implementation of the application service layer is defined as follows.
/// <summary> /// Customer,Application layer service interface implementation /// </summary> public class CustomerAppService : MyCrudAppService<Customer, CustomerDto, string, CustomerPagedDto, CreateCustomerDto, CustomerDto>, ICustomerAppService
By inheriting related user-defined base classes, you can uniformly encapsulate some common interface implementations, pass in the corresponding generic types, and build strongly typed interface implementations.
In addition, the implementation class also needs to contain some method overloads to rewrite some rules, such as sorting, query processing, and some general information escape. The detailed implementation code of the application service layer interface is as follows.
/// <summary> /// Customer,Application layer service interface implementation /// </summary> public class CustomerAppService : MyCrudAppService<Customer, CustomerDto, string, CustomerPagedDto, CreateCustomerDto, CustomerDto>, ICustomerAppService { private readonly IRepository<Customer, string> _repository;//Business object warehouse object public CustomerAppService(IRepository<Customer, string> repository) : base(repository) { _repository = repository; } /// <summary> /// Custom condition processing /// </summary> /// <param name="input">query criteria Dto</param> /// <returns></returns> protected override async Task<IQueryable<Customer>> CreateFilteredQueryAsync(CustomerPagedDto input) { var query = await base.CreateFilteredQueryAsync(input); query = query .WhereIf(!input.ExcludeId.IsNullOrWhiteSpace(), t=>t.Id != input.ExcludeId) //Exclude ID .WhereIf(!input.Name.IsNullOrWhiteSpace(), t => t.Name.Contains(input.Name)) //If exact matching is required, use Equals //Interval query .WhereIf(input.AgeStart.HasValue, s => s.Age >= input.AgeStart.Value) .WhereIf(input.AgeEnd.HasValue, s => s.Age <= input.AgeEnd.Value) //Create date range query .WhereIf(input.CreationTimeStart.HasValue, s => s.CreationTime >= input.CreationTimeStart.Value) .WhereIf(input.CreationTimeEnd.HasValue, s => s.CreationTime <= input.CreationTimeEnd.Value) ; return query; } /// <summary> /// Custom sort processing /// </summary> /// <param name="query">Searchable LINQ</param> /// <param name="input">query criteria Dto</param> /// <returns></returns> protected override IQueryable<Customer> ApplySorting(IQueryable<Customer> query, CustomerPagedDto input) { //Sort by creation time in reverse order return base.ApplySorting(query, input).OrderByDescending(s => s.CreationTime);//Time descending order //Sort by first field and then by second field //return base.ApplySorting(query, input).OrderBy(s=>s.DictType_ID).ThenBy(s => s.Seq); } /// <summary> /// Gets the dictionary collection of field Chinese aliases (used for interface display) /// </summary> /// <returns></returns> public override Task<Dictionary<string, string>> GetColumnNameAlias() { Dictionary<string, string> dict = new Dictionary<string, string>(); #region Add alias resolution //System part field dict.Add("Id", "number"); dict.Add("UserName", "user name"); dict.Add("Creator", "Creator"); dict.Add("CreatorUserName", "Creator"); dict.Add("CreationTime", "Creation time"); //Other fields dict.Add("Name", "full name"); dict.Add("Age", ""); #endregion return Task.FromResult(dict); } /// <summary> /// Escape records /// </summary> /// <param name="item">dto data object</param> /// <returns></returns> protected override void ConvertDto(CustomerDto item) { //Override if escape is required #region Reference code //User name escape //if (item.Creator.HasValue) //{ // //Need in CustomerDto Medium increase CreatorUserName attribute // var user = _userRepository.FirstOrDefault(item.Creator.Value); // if (user != null) // { // item.CreatorUserName = user.UserName; // } //} //if (item.UserId.HasValue) //{ // item.UserName = _userRepository.Get(item.UserId.Value).UserName; //} //IP Address escape //if (!string.IsNullOrEmpty(item.ClientIpAddress)) //{ // item.ClientIpAddress = item.ClientIpAddress.Replace("::1", "127.0.0.1"); //} #endregion } /// <summary> /// Additional interfaces for testing /// </summary> public Task<bool> TestExtra() { return Task.FromResult(true); } }
These are related to T_Customer table related information, such as table information and field information, can be processed uniformly through code generation tool metadata.
Content of Domain layer, including Domain and Domain Share two projects, content and applicaiton Similar to the contracts project, it mainly defines some entity related content, which is also generated according to the table and table fields. Some of these classes can be built based on named controls and project names.
The Customer Domain entity definition code in the Domain project is as follows.
The base class relationship between domain entities and aggregation roots is as follows.
Specifically, use * * * entity (such as FullAuditedEntity base class) or aggregation root * * * aggregateroot (such as FullAuditedAggregateRoot) as the base class of domain entities. When generating, we need to judge the field relationship of the table. If the table contains ExtraProperties and ConcurrencyStamp, use the base class related to aggregation root.
Our T_Customer contains the fields required by the aggregation root base class. When generating code, the base class should use the fullauditedaggregateroot < T > base class.
For the EntityFrameworkCore project file, it is mainly to generate the DbSet of the corresponding table and then use it for operation.
The DbContext file is as follows:
namespace WHC.TestProject.EntityFrameworkCore { [ConnectionStringName("Default")] public class TestProjectDbContext : AbpDbContext<TestProjectDbContext> { /// <summary> /// T_Customer,Data table object /// </summary> public virtual DbSet<Customer> Customers { get; set; } public TestProjectDbContext(DbContextOptions<TestProjectDbContext> options) : base(options) { } protected override void OnModelCreating(ModelBuilder builder) { base.OnModelCreating(builder); builder.ConfigureTestProject(); } } }
It can be generated according to the rules, and others can be ignored.
Note that for content processing in EntityFrameworkCoreModule, if the aggregation root is not used as the base class of domain entities, but the * * Entity standard Entity (such as FullAuditedEntity base class) is used as the base class, it needs to be set to true by default in this file, because ABP VNext framework only processes domain entities added to the aggregation root by default.
namespace WHC.TestProject.EntityFrameworkCore { [DependsOn( typeof(TestProjectDomainModule), typeof(AbpEntityFrameworkCoreModule) )] public class TestProjectEntityFrameworkCoreModule : AbpModule { public override void ConfigureServices(ServiceConfigurationContext context) { context.Services.AddAbpDbContext<TestProjectDbContext>(options => { //options.AddDefaultRepositories(); //By default, this will be for each aggregate root entity (class derived from) AggregateRoot)Create a repository. //If you also want to create a repository for other entities, set includeAllEntities by true: //reference resources https://docs.abp.io/en/abp/latest/Entity-Framework-Core#add-default-repositories options.AddDefaultRepositories(includeAllEntities: true); }); } } }
In the above processing, even if the * * Entity standard Entity (such as the FullAuditedEntity base class) is used as the base class, there is no problem, and the Repository object can be reflected smoothly.
For Http project stratification, it includes HttpApi project and HttpApi Client, the former focuses on the service interface of the application service layer and knows the custom API rules. Although the automatic API publishing of the application service layer ApplicationService can be used by default, it is recommended to rewrite (also an official practice) for stronger control rules. The two project files are shown below.
The HttpApi project controller also adopts the user-defined base class introduced earlier, which can reduce the processing of duplicate code.
namespace WHC.TestProject.Controllers { /// <summary> /// Customer,Controller object /// </summary> //[RemoteService] //[Area("crm")] [ControllerName("Customer")] [Route("api/Customer")] public class CustomerController : MyAbpControllerBase<CustomerDto, string, CustomerPagedDto,CreateCustomerDto, CustomerDto>, ICustomerAppService { private readonly ICustomerAppService _appService; public CustomerController(ICustomerAppService appService) : base(appService) { _appService = appService; } } }
The MyAbpControllerBase controller base class encapsulates many common CRUD methods (Create/Update/Delete/GetList/Get), as well as some basic methods such as BatchDelete, Count and GetColumnNameAlias.
For the generation of project files for ABP VNext projects, by the way, in fact, this file is very simple without much content, including namespace, project name and some common references. It is also an XML file. Just fill in the relevant information generation file.
For the solution, it contains different project files and each project file has an independent GUID. Therefore, we can dynamically build the corresponding GUID value and bind it to the template.
In the code tool, the back-end provides data binding to the front-end template to realize the dynamic content.
3. Use the code generation tool Database2Sharp to generate ABP VNext framework project
The above describes the code generation of each project layer of ABP VNext framework and some rules for code generation processing. How is the actual code generation tool generated?
Code generation tool download address: http://www.iqidi.com/database2sharp.htm.
First, click the node on the left to expand the database of ABP VNext project to read the metadata of the database for later code generation.
Then select [code generation] [ABP VNext framework code generation] from the right-click menu or select quick entry in the toolbar, which has the same effect.
In the pop-up dialog box, select the relevant data table to generate the framework code. Note to modify the appropriate main namespace, which can be TestProject or WHC Project, etc.
Finally, the next step is to generate confirmation to generate relevant solution code.
After generation, all project relationships have been improved. You can directly open the solution to view the whole project, as shown below.
The solution generated in this way can be compiled into a separate module. When necessary, you can directly reference and add dependencies in the main project.
For example, we are in a standard project of ABP VNext, microbookstore The module generated by the code generation tool just now is introduced into the web, then in the microbookstorewebmodule CS, as shown below.
Because we use the DLL reference method, the corresponding reference relationship can be added in the project.
At the same time, add the TestProject dependency of the project in the EFCore project. The EF dependency of the project is as follows.
In this way, the running project can have the Swagger interface to view and call uniformly.
The above is the introduction to the analysis and project relationship of ABP VNext framework project, and it is important to use code generation tools to assist the operation and processing of incremental modular development, so that we can develop ABP VNext project more conveniently and efficiently.