Abstract
Software projects require constant changes and updates. If the structure develops in the wrong way, it will prevent changes and extensions, and most of the time will lead to task duplication or rewriting of the project from scratch. To get rid of the complexity and task duplication that most programmers and developers face, which is also caused by the inconsistency of code at different levels of the program, we need a simple consistent structure for writing software projects so that we can hide some of the complexity and focus on business of the task. For example, the Bootstrap framework is a very useful framework for Front End, but few people would prefer to use frameworks like Bootstrap for design, and write all of their design with CSS from the beginning. For the Back End section, however, a simple, general-purpose framework can save time and cost and produce high-quality code and a uniform architecture. This framework allows developers to develop their projects based on an appropriate integrated pattern. The framework must be flexible enough to allow the programmer to apply any changes needed, relying on its robust structure.
Why Framework?
One of the problems of software companies is the lack of the right structure for developing their projects. As a result, they have often produced such complex and nested code that creating changes in one part of the project severely affects or disrupts other parts. Therefore, the lack of the right structure for development makes it impossible to update the previous code and reduces the efficiency of the team to almost zero. The reason for this is the difference in coding and the lack of structure and architecture. The development team must first agree on a set of rules and structures. Architectural patterns are not the result of a programmer's experiences; they have resulted from the experiences of hundreds of programmers and design professionals over years. These frameworks are not innovations or inventions, but are feedbacks on redesign and recoding that programmers have been involved with in order to achieve the highest levels of flexibility, extensibility, and reusability. Their use makes the produced codes more simple, flexible, and extensible. The use of a framework can help us save time and cost and make it easier to document and maintain the system.
Asp.Net Unique Architecture(AUA) Framework
Using the AUA ( Asp.Net Unique Architecture ) Framework, you can easily have better, faster, and more orderly and focused coding. This framework is based on new and up-to-date concepts, structures, and architectures, including Clean Architecture, Clean Code, Domain-driven design (DDD), Lmax Architecture, SOLID Principle, Code Refactoring, GRASP (object-oriented design principle)AUA is a simple, lightweight framework for producing projects of any size(small and large). It can be applied in all architectures (Microservice, CQRS, etc.) due to its transparency in structure. It is also full of different design patterns, thus a great source for software architects and developers.
- Domain Driven Design (DDD)
- EF 6 and EF Core 3.0,3.1
- The ability to develop software in a simple and fast way
- Based on SOLID Principles
- Modular design
- Layered architecture
AUA Framework'sVersions
- Asp.Net MVC(.net framework and ef6)
- Asp.Net MVCCore 3.0,3.1
- Asp.Net Web API Core 3.0,3.1
- Asp.Net 5
AUA Framework's Overall Structure
The different layers of the AUA framework are as follows,
Layer's Name |
Use |
Common Layer |
This layer contains common items used in other layers, such as Enums, Consts, Extensions,… ،Tools |
Data Layer |
This layer contains items associated with the data source, including Entity Framework Context, Db Extensions, Search Filters, Unit of Work Pattern, Configuration Tools, and Dapper Context |
Domain EntityLayer |
This layer contains the entities and their configuration. |
ModelsLayer |
This layer contains DTOs, View Models, and Config mapping: EntitiesDto, ReportModels, View Models,… |
Service Infrastructure Layer |
The overall infrastructure of Services and Repository is written and becomes ready for use in this layer. |
Service Layer |
This layer includes all the business services of your project, including BaseServices, BusinessService, EntitiesService, ReportService, etc. |
WebApi or Ui Mvc Layer |
This is an interface user layer that can be written with General MVC- WebApi- GraphQl- Grapc. |
Test Layer |
This layer is designed for writing Unit Tests (ToDo) |
External web service Layer |
This layer is for calling external services. (ToDo) |
Adding New Entity
Entity is the main concept, and indeed at the heart of the architecture of, the AUA framework. Each entity is defined by a class that contains its features and its relation to other entities. Each entity has an identifier that can be of any Data Type allowed in .NET, or it can be a combination of two or more Data Types allowed therein (combination key).
Entity Class
Each entity inherits from the DomainEntity class, to which a primary key field called Id and one or more monitoring fields (depending on the setting type) are added.
public class Student: DomainEntity {
public string FirstName {
get;
set;
}
public string LastName {
get;
set;
}
public int Age {
get;
set;
}
}
It should be specified if the primary key has a data type other than the int data type (e.g. the Long data type is considered under the primary key)
public class Student: DomainEntity < long > {
public string FirstName {
get;
set;
}
public string LastName {
get;
set;
}
public int Age {
get;
set;
}
}
By default, the following fields are added to each entity.
The Id key of the primary key and its data type can be specified when defining an entity.
The IsActive field shows whether the entity is active or inactive and it has a bool data type.
The RegDate displays the date and time the entity is created (automatically created inside SQL Server) and does not need to be filled in and sent.
public class DomainEntity < TPrimaryKey > : BaseDomainEntity < TPrimaryKey > , IAuditInfo {
public DateTime RegDate {
get;
set;
}
}
public class BaseDomainEntity < TPrimaryKey > : IDomainEntity < TPrimaryKey > {
public TPrimaryKey Id {
get;
set;
}
public bool IsActive {
get;
set;
}
}
Entity Configs
There is a configuration class for each entity that can specify the length of field settings for it.
public class StudentConfig: IEntityTypeConfiguration < Student > {
public void Configure(EntityTypeBuilder < Student > builder) {
builder.Property(p => p.FirstName).HasMaxLength(LengthConsts.MaxStringLen50);
builder.Property(p => p.LastName).HasMaxLength(LengthConsts.MaxStringLen50);
}
}
Naming Pattern
Every company or project should have its own naming pattern to help us easily understand the previous concepts and add new concepts to the project. Good and consistent naming creates a good flow in your project, thereby increasing code readability and durability. An important feature of a good framework is having a good naming pattern, which all programmers should follow. In this framework, all components are named, such as: folder, file, class, function, variable, etc.
"You should name a variable with the same care we do in naming a first-born child" (Uncle Bob in Clean Code).
Each team must use a specific naming pattern, such that if any file is separated from the project, all programmers can specify the exact address of the file from the file name. For the AUA framework, we have suggested the following naming pattern.
Services
All business is implemented in the form of services and created in the service layer. The service layer uses the Service Infrastructure layer and automatically connects to each service in its own Repository. The advantage of this approach is that the developer is not involved in the two concepts of repository and service and focuses only on the service itself. The service has its own built-in Repository, which is one of the most important features of the AUA framework architecture. For example, if we want to write a service for StudentEntity, we must first create an interface for Student Entity which inherits from the IGenericEntityService class.
public interface IStudentService: IGenericEntityService < Student, StudentDto > {}
Service Types
Service |
Description |
Entities Service |
These types of services are for working with entities and have a repository inside, Moreover, the business related to each entity is written in each service. |
Business Service |
This type of service is intended for the implementation of businesses. |
General Service |
General services that are used throughout the application, such as the login service - JWT Token service. |
List Service |
Quick Report Service - To write quick operational reports. |
Report Service |
A service used for getting Enterprise reports. |
SQL Function Service |
A service used for access to SQL Function and Stored procedure. |
Validation Service |
Services to validate the view models sent by clients. |
Log Service |
Logging service. |
InMemory Service |
Services used for working with in-memory data. |
Views Service |
Services used for working with SQL Service views. |
External Service |
Services used for working with the external service. |
External Service Provider |
Services to cover and match external services with the Core Domain.
|
Reporting
Reporting is the end product of operations and is especially important for users and managers of organizations and companies to use reports to meet their needs and users, faster and more dynamically.
This type of report is a quick and simple report that can be used for quick reporting and applying various filters to any entity. Creating this type of report is very simple and has a simple structure, and it is easy to apply various and professional filters. This type of report has one input model view and one output model view. Its Input model view includes filters that must be applied, but the report can have no filters and inputs. You can easily report from different tables and have a variety of reports. These types of operational reports are known as ListService in the structure of the AUA framework. In each report, if a non-zero value is sent to TotalCount from the input. The framework will not perform the TotalCount computations and will return the value with no change.
First: Create SearchVm
public class AppUserSearchVm: BaseSearchVm {
public string FirstName {
get;
set;
}
public string LastName {
get;
set;
}
public string UserName {
get;
set;
}
public bool ? IsActive {
get;
set;
}
}
Second: Create ListDto (to return the output)
public class AppUserListDto: IHaveCustomMappings {
public string FirstName {
get;
set;
}
public string LastName {
get;
set;
}
public string UserName {
get;
set;
}
public string FullName => $ "{FirstName} {LastName}";
public string Password {
get;
set;
}
public string Phone {
get;
set;
}
public string Email {
get;
set;
}
public DateTime RegistrationDate {
get;
set;
}
public string RegistrationDatePersian => RegistrationDate.ToPersianDate();
public ICollection < RoleDto > UserRoleDtos {
get;
set;
}
public void ConfigureMapping(Profile configuration) {
configuration.CreateMap < AppUser, AppUserListDto > ().ForMember(p => p.UserRoleDtos, p => p.MapFrom(q => q.UserRoles.Select(r => r.Role)));
}
}
Third: Create interface (To Injection)
public interface IAppUserListService : IBaseListService<AppUser, AppUserListDto>
{
Task<ListResultVm<AppUserListDto>> ListAsync(AppUserSearchVm appUserSearchVm);
}
Fourth: Create ListService (To apply filters)
public sealed class AppUserListService: BaseListService < AppUser, AppUserListDto, AppUserSearchVm > , IAppUserListService {
public AppUserListService(IUnitOfWork unitOfWork, IMapper mapperInstance): base(unitOfWork, mapperInstance) {}
public async Task < ListResultVm < AppUserListDto >> ListAsync(AppUserSearchVm appUserSearchVm) {
SetSearchVm(appUserSearchVm);
ApplyUserNameFilter();
ApplyLastNameFilter();
ApplyFirstNameFilter();
ApplyIsActiveFilters();
return await CreateListVmResultAsync();
}
private void ApplyUserNameFilter() {
if (string.IsNullOrWhiteSpace(SearchVm.UserName)) return;
Query = Query.Where(p => p.UserName.Contains(SearchVm.UserName));
}
private void ApplyLastNameFilter() {
if (string.IsNullOrWhiteSpace(SearchVm.LastName)) return;
Query = Query.Where(p => p.LastName.Contains(SearchVm.LastName));
}
private void ApplyFirstNameFilter() {
if (string.IsNullOrWhiteSpace(SearchVm.FirstName)) return;
Query = Query.Where(p => p.FirstName.Contains(SearchVm.FirstName));
}
private void ApplyIsActiveFilters() {
if (!SearchVm.IsActive.HasValue) return;
Query = Query.Where(p => p.IsActive == SearchVm.IsActive);
}
}
You can inject reporting and use it.