ABP (Modern) ASP.NET Sample development framework) series 2. Introduction to ABP

Time:2021-4-3

ABP is“ ASP.NET Boilerplate Project ( ASP.NET Short for “model project”.

ASP.NET Boilerplate is a new starting point for developing modern web applications with best practices and popular technologies. It aims to become a general web application framework and project template.

ABP official website:http://www.aspnetboilerplate.com

ABP’s open source project on GitHub:https://github.com/aspnetboilerplate

The origin of ABP

“Dry – avoid duplicate code” is one of the most important ideas a good developer has when developing software. We have some similar requirements when developing enterprise web applications, such as login page, user / role management, permission verification, data validity verification, multilingual / localization and so on. A high-quality large-scale software will use some best practices, such as hierarchical architecture, domain driven design, dependency injection, etc. We may also use ORM, database migrations, logging and other tools.

Creating an enterprise application from scratch is cumbersome, because you need to do a lot of common basic work repeatedly. Many companies are developing their own application framework to reuse in different projects, and then develop some new functions based on the framework. But not every company has such strength. If we can share more, we may be able to avoid the repetition of writing similar code for each company or project. The reason why the author named the project“ ASP.NET Boilerplate “is a new starting point for developing general enterprise web applications, which directly uses ABP as a project template.

What is ABP?

ABP is a starting point for using best practices and the most popular tools for new modern web applications. It can be used as a basic framework or project template for general-purpose applications. Its functions include:

Server side:

  • Based on the latest. Net technology (currently ASP.NET MVC 5, web API 2, C # 5.0, in ASP.NET 5 will be upgraded after official release)
  • Implement Domain Driven Design (entity, warehouse, domain service, domain event, application service, data transmission object, work unit, etc.)
  • Implementing a layered architecture (domain layer, application layer, presentation layer and infrastructure layer) provides an infrastructure for developing reusable and configurable modules that integrate some of the most popular open source frameworks / libraries, perhaps some of which you are using.
  • Provides an infrastructure for us to easily use dependency injection (using Castle Windsor as the container of dependency injection)
  • Provide repository storage mode to support different ORM (implemented Entity Framework, NHibernate, mangodb and in memory database)
  • Support and implement the modular development of database migration (code first of EF) (each module has an independent EF dbcontext, and the database can be specified separately)
  • Includes a simple and flexible multilingual / localization system
  • Including an eventbus to realize the server-side global domain event unified exception handling (the application layer almost does not need to write exception handling code)
  • Data validation( Asp.NET MVC can only verify the parameters of the action method, while ABP can verify the validity of the parameters of the application layer method.)
  • Automatically create web API layer through application services (no need to write API controller layer)
  • Providing base class and help class makes it easy for us to implement some common tasks
  • Using “convention over configuration principle”

client:

  • Bootstrap, less, angularjs, jQuery, moderniser and other JS libraries: jQuery.validate 、 jQuery.form 、 jQuery.blockUI , json2, etc
  • Project templates are provided for single page applications (angularjs, durandaljs) and multi page applications (bootstrap + jQuery).
  • It is more convenient to use web API to encapsulate some JavaScript functions, AJAX, message box, notification component, busy state mask layer and so on

In addition to the ABP framework project, a module called “zero” is developed, which realizes the following functions:

  • Authentication and authorization management ASP.NET Identity Implementation)
  • User & role management system setting access management (system level, tenant level, user level, scope automatic management)
  • Audit log (automatically record the callers and parameters of each interface)

What is ABP not?

ABP provides an application development model for best practices. It has basic classes, interfaces and tools that make it easy to build large-scale applications that can be maintained.

However:

It’s not one of the rad tools, which are designed to create applications without coding. Instead, ABP provides a coding best practice.

It’s not a code generation tool. At run time, although it has some features to build dynamic code, it cannot generate code.

It is not an integrated framework. On the contrary, it uses popular tools / libraries to complete specific tasks (for example, EF is used for ORM, log4net is used for logging, Castle Windsor is used as a dependency injection container, and angularjs is used for spa framework).

According to my experience of using ABP for several months, although ABP is not rad, it is much faster than traditional three-tier architecture to develop projects.

Although ABP is not a code generation tool, it makes the code of our project more concise and standardized, which is conducive to the use of code generation tools.

I use the code generator developed by scaffolder + T4 of vs2013, which can automatically generate all front-end and back-end code and database according to the UML class diagram of domain objects. Simple curd module almost does not need to write code, and modules with complex business logic can mainly supplement domain layer code. In this way, we can spend more time on the design of domain model and reduce the time of writing code.

Next, through the original author’s “simple task system” example, demonstrate how to use ABP development project

Creating an empty web application from a template

ABP provides a startup template for new projects (although you can create projects manually and get ABP packages from nuget, the template is easier).

go towww.aspnetboilerplate.com/TemplatesCreate your application from a template.

You can choose Spa (angular JS or durandaljs) or MPa (classic multi page application) project. You can choose Entity Framework or NHibernate as the ORM framework.

Here, we select angularjs and Entity Framework, fill in the project name “simpletask system”, and click the “create my project” button to download a zip package. After decompression, we get the vs2013 solution, using the. Net version of 4.5.1.

ABP components and other third-party components are referenced in each project and need to be downloaded from nuget.

The yellow exclamation mark icon indicates that this component does not exist in the local folder and needs to be restored from nuget. The operation is as follows:

To get the project running, you need to create a database. This template assumes that you are using SQL 2008 or later. Of course, it can also be easily replaced by other relational databases.

open Web.Config File to view and configure the link string:


<add name="Default" connectionString="Server=localhost; Database=SimpleTaskSystemDb; Trusted_Connection=True;" />

(when code first data migration of EF is used later, a database named simpletask systemdb will be automatically created in SQL Server database.)

In this way, the project is ready to run! Open vs2013 and press F5:

The following will gradually implement this simple task system program

Create entity

Write entity classes in the core project because entities are part of the domain layer.

A simple application scenario: create some tasks and assign them to people. We need two entities: task and person.

The task entity has several properties: description, creation time, task state, and optional assigned person to refer to person.


public class Task : Entity<long>
{
 [ForeignKey("AssignedPersonId")]
 public virtual Person AssignedPerson { get; set; }

 public virtual int? AssignedPersonId { get; set; }

 public virtual string Description { get; set; }

 public virtual DateTime CreationTime { get; set; }

 public virtual TaskState State { get; set; }

 public Task()
 {
  CreationTime = DateTime.Now;
  State = TaskState.Active;
 }
}

The person entity is simpler, with only one name attribute defined


public class Person : Entity
{
 public virtual string Name { get; set; }
}

In the ABP framework, there is an entity base class, which has an ID attribute. Because the task class inherits from entity < long >, it has an ID of type long. The person class has an ID of type int, because the int type is the default type of the entity base class ID. if there is no specific type specified, the ID of the entity is of type int.

Create dbcontext

The dbcontext class needs to be defined before using the entityframework. The ABP template has created the dbcontext file. We only need to add the task and person classes to the idbset. Please see the code:


public class SimpleTaskSystemDbContext : AbpDbContext
{
 public virtual IDbSet<Task> Tasks { get; set; }

 public virtual IDbSet<Person> People { get; set; }

 public SimpleTaskSystemDbContext()
  : base("Default")
 {

 }

 public SimpleTaskSystemDbContext(string nameOrConnectionString)
  : base(nameOrConnectionString)
 {
   
 }
}

Creating database tables through database migrations

We use the code first schema of the Entity Framework to create the database schema. The project generated by ABP template has enabled the data migration function by default, so we need to modify it SimpleTaskSystem.EntityFramework In the migrations folder of the project Configuration.cs File:


internal sealed class Configuration : DbMigrationsConfiguration<SimpleTaskSystem.EntityFramework.SimpleTaskSystemDbContext>
{
 public Configuration()
 {
  AutomaticMigrationsEnabled = false;
 }

 protected override void Seed(SimpleTaskSystem.EntityFramework.SimpleTaskSystemDbContext context)
 {
  context.People.AddOrUpdate(
   p => p.Name,
   new Person {Name = "Isaac Asimov"},
   new Person {Name = "Thomas More"},
   new Person {Name = "George Orwell"},
   new Person {Name = "Douglas Adams"}
   );
 }
}

In the “package manager console” window at the bottom of vs2013, select the default project and execute the command “add migration initialcreate”

A XXXX will be generated under the migrations folder- InitialCreate.cs The contents are as follows:


public partial class InitialCreate : DbMigration
{
 public override void Up()
 {
  CreateTable(
   "dbo.StsPeople",
   c => new
    {
     Id = c.Int(nullable: false, identity: true),
     Name = c.String(),
    })
   .PrimaryKey(t => t.Id);
   
  CreateTable(
   "dbo.StsTasks",
   c => new
    {
     Id = c.Long(nullable: false, identity: true),
     AssignedPersonId = c.Int(),
     Description = c.String(),
     CreationTime = c.DateTime(nullable: false),
     State = c.Byte(nullable: false),
    })
   .PrimaryKey(t => t.Id)
   .ForeignKey("dbo.StsPeople", t => t.AssignedPersonId)
   .Index(t => t.AssignedPersonId);   
 }
  
 public override void Down()
 {
  DropForeignKey("dbo.StsTasks", "AssignedPersonId", "dbo.StsPeople");
  DropIndex("dbo.StsTasks", new[] { "AssignedPersonId" });
  DropTable("dbo.StsTasks");
  DropTable("dbo.StsPeople");
 }
}

Then continue to execute “update database” in the “package manager console”, and the corresponding data table will be automatically created in the database


PM> Update-Database

The database is as follows:

(after modifying the entity, you can execute add migration and update database again, so that you can easily synchronize the database structure with the entity class.)

Define warehouse interface

Through the warehousing mode, business code and database operation code can be better separated, and there are different implementation classes for different databases, while business code does not need to be modified.

The code that defines the warehousing interface is written to the core project because the warehousing interface is part of the domain layer.

Let’s define it firstWarehouse interface of task:


public interface ITaskRepository : IRepository<Task, long>
{

It inherits from the irepository generic interface in the ABP framework.

Common methods of adding, deleting, modifying and querying have been defined in irepository

thereforeItaskrepository has the above methods by default. It can be combined with its unique methodGetAllWithPeople(…)。

There is no need to create a repository class for the person class, because the default method is enough. ABP provides a way to inject a common repository, which will be seen in the taskappservice class in the section “create application service” later.

Implementation of warehousing

We will implement the itaskrepository repository interface defined above in the Entity Framework project.

A repository base class has been defined for the project created by the template: simpletask systemrepository base (this is a good practice because common methods can be added to this base class in the future).

public class TaskRepository : SimpleTaskSystemRepositoryBase<Task, long>, ITaskRepository
{
  public List<Task> GetAllWithPeople(int? assignedPersonId, TaskState? state)
  {
    //In the warehousing method, there is no need to deal with database connection, dbcontext and data transaction, but ABP framework will deal with it automatically.
      
    Var query = getall(); // getall() returns an iqueryable < T > interface type
      
    //Add some where conditions

    if (assignedPersonId.HasValue)
    {
      query = query.Where(task => task.AssignedPerson.Id == assignedPersonId.Value);
    }

    if (state.HasValue)
    {
      query = query.Where(task => task.State == state);
    }

    return query
      .OrderByDescending(task => task.CreationTime)
      .Include(task => task.AssignedPerson)
      .ToList();
  }
}

Taskrepository inherits from simpletasksystemrepository base and implements the itaskrepository interface defined above.

Create application services

stayApplication services are defined in the application project. First, define the application service layer interface of task


public interface ITaskAppService : IApplicationService
{
  GetTasksOutput GetTasks(GetTasksInput input);
  void UpdateTask(UpdateTaskInput input);
  void CreateTask(CreateTaskInput input);
}

Itaskappservice inherits from iapplicationservice. ABP automatically provides some functional features (such as dependency injection and parameter validation) for this class.

Then, we write the taskappservice class to implement the itaskappservice interface

public class TaskAppService : ApplicationService, ITaskAppService
{
  private readonly ITaskRepository _taskRepository;
  private readonly IRepository<Person> _personRepository;
    
  /// <summary>
  ///Constructors automatically inject the classes or interfaces we need
  /// </summary>
  public TaskAppService(ITaskRepository taskRepository, IRepository<Person> personRepository)
  {
    _taskRepository = taskRepository;
    _personRepository = personRepository;
  }
    
  public GetTasksOutput GetTasks(GetTasksInput input)
  {
    //Call the specific method getallwithpeople of task warehouse
    var tasks = _taskRepository.GetAllWithPeople(input.AssignedPersonId, input.State);

    //Automatically convert list < task > to list < taskdto > with automapper
    return new GetTasksOutput
        {
          Tasks = Mapper.Map<List<TaskDto>>(tasks)
        };
  }
    
  public void UpdateTask(UpdateTaskInput input)
  {
    //You can directly use the logger, which is defined in the Applicationservice base class
    Logger.Info("Updating a task for input: " + input);

    //Get the task entity object with the specified ID through the general method get of the warehouse base class
    var task = _taskRepository.Get(input.TaskId);

    //Modify the attribute value of task entity
    if (input.State.HasValue)
    {
      task.State = input.State.Value;
    }

    if (input.AssignedPersonId.HasValue)
    {
      task.AssignedPerson = _personRepository.Load(input.AssignedPersonId.Value);
    }

    //We don't need to call the update method
    //Because the method of application service layer turns on the unit of work mode by default
    //The ABP framework automatically saves all changes to the entity when the unit of work is completed, unless an exception is thrown. If there is an exception, it will be rolled back automatically, because the unit of work starts database transaction by default.
  }

  public void CreateTask(CreateTaskInput input)
  {
    Logger.Info("Creating a task for input: " + input);

    //Create a new task entity by inputting parameters
    var task = new Task { Description = input.Description };

    if (input.AssignedPersonId.HasValue)
    {
      task.AssignedPersonId = input.AssignedPersonId.Value;
    }

    //Call the insert method of the warehouse base class to save the entity to the database
    _taskRepository.Insert(task);
  }
}

Taskappservice uses repository for database operation, which injects references to repository objects into the constructor.

data validation

If the parameter object of application service method implements iinputdto or Ivalidate interface, ABP will automatically verify the validity of the parameter.

The createtask method has a createtaskinput parameter, which is defined as follows:


public class CreateTaskInput : IInputDto
{
  public int? AssignedPersonId { get; set; }

  [Required]
  public string Description { get; set; }
}

The description property is specified by a comment that it is required. You can also use other data annotation features.

If you want to use custom validation, you can implement the icustomvalidate interface:

public class UpdateTaskInput : IInputDto, ICustomValidate
{
  [Range(1, long.MaxValue)]
  public long TaskId { get; set; }

  public int? AssignedPersonId { get; set; }

  public TaskState? State { get; set; }

  public void AddValidationErrors(List<ValidationResult> results)
  {
    if (AssignedPersonId == null && State == null)
    {
      results.Add (New validationresult ("assignedpersonid and state cannot be empty at the same time!", new [] {"assignedpersonid", "state"}));
    }
  }
}

You can write custom validation code in the addvalidationerrors method.

Creating web API service

ABP can easily publish the public method of application service as web API interface, which can be called by client through Ajax.


DynamicApiControllerBuilder
  .ForAll<IApplicationService>(Assembly.GetAssembly(typeof (SimpleTaskSystemApplicationModule)), "tasksystem")
  .Build();

All classes in simpletasksystemapplicationmodule that inherit iapplicationservice interface will automatically create corresponding apicontroller, and the public methods in it will be converted into webapi interface methods.

Can pass http://xxx/api/services/tasksystem/Task/GetTasks Such a routing address.

Through the above cases, the usage of domain layer, infrastructure layer and application service layer is roughly introduced.

Now, you can ASP.NET MVC controller action method directly calls the application service method.

If you use spa single page programming, you can directly call the corresponding application service method in the client through Ajax (by creating a dynamic web API).

summary

The above is a small series to introduce ABP ASP.NET Sample development framework) series 2, ABP introductory tutorial detailed explanation, I hope to help you, if you have any questions, please leave me a message, Xiaobian will reply you in time. Thank you very much for your support to developer!