ABP框架创建一套CRUD的API

315 阅读4分钟

创建一套CRUD的API

1、创建实体

领域层

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Volo.Abp.Domain.Entities.Auditing;

namespace Acme.BookStore.Books {
    //AuditedAggregateRoot是聚合根实体的标识
    public class Book : AuditedAggregateRoot<Guid> {
        public string Name { get; set; }

        public BookType Type { get; set; }

        public DateTime PublishDate { get; set; }

        public float Price { get; set; }
    }
}

2、创建枚举

领域层的Shared层的数据

namespace Acme.BookStore.Books
{
    public enum BookType
    {
        Undefined,
        Adventure,
        Biography,
        Dystopia,
        Fantastic,
        Horror,
        Science,
        ScienceFiction,
        Poetry
    }
}

3、将实体添加到DbContext中

基础设施层,添加DbSet,添加映射代码

public class BookStoreDbContext : AbpDbContext<BookStoreDbContext>
{
    public DbSet<Book> Books { get; set; }
    //...

    protected override void OnModelCreating(ModelBuilder builder)
    {
        base.OnModelCreating(builder);

        /* Include modules to your migration db context */

        builder.ConfigurePermissionManagement();
        ...

        /* Configure your own tables/entities inside here */
    	//在这里添加映射代码
        builder.Entity<Book>(b =>
        {
            b.ToTable(BookStoreConsts.DbTablePrefix + "Books",
                BookStoreConsts.DbSchema);
        	//配置/映射继承的属性,应对所有的实体使用它.
            b.ConfigureByConvention(); //auto configure for the base class props
            b.Property(x => x.Name).IsRequired().HasMaxLength(128);
        });
    }
}

4、添加数据迁移

在 Acme.BookStore.EntityFrameworkCore 目录打开命令行终端(右键选择 open in terminal)输入以下命令

dotnet ef migrations add Created_HeatScheduleException_Entity

可以看到迁移成功

取消迁移

dotnet ef migrations remove

更新到数据库

dotnet ef database update

若报错

原因,需要单独安装迁移工具

dotnet tool install --global dotnet-ef

5、添加种子数据

在 *.Domain 项目下创建 IDataSeedContributor 的派生类,并且拷贝以下代码:

using System;
using System.Threading.Tasks;
using Acme.BookStore.Books;
using Volo.Abp.Data;
using Volo.Abp.DependencyInjection;
using Volo.Abp.Domain.Repositories;

namespace Acme.BookStore
{
    public class BookStoreDataSeederContributor
        : IDataSeedContributor, ITransientDependency
    {
        private readonly IRepository<Book, Guid> _bookRepository;

        public BookStoreDataSeederContributor(IRepository<Book, Guid> bookRepository)
        {
            _bookRepository = bookRepository;
        }

        public async Task SeedAsync(DataSeedContext context)
        {
            if (await _bookRepository.GetCountAsync() <= 0)
            {
                await _bookRepository.InsertAsync(
                    new Book
                    {
                        Name = "1984",
                        Type = BookType.Dystopia,
                        PublishDate = new DateTime(1949, 6, 8),
                        Price = 19.84f
                    },
                    autoSave: true
                );

                await _bookRepository.InsertAsync(
                    new Book
                    {
                        Name = "The Hitchhiker's Guide to the Galaxy",
                        Type = BookType.ScienceFiction,
                        PublishDate = new DateTime(1995, 9, 27),
                        Price = 42.0f
                    },
                    autoSave: true
                );
            }
        }
    }
}

6、更新数据库

运行 Acme.BookStore.DbMigrator 应用程序来更新数据库:

7、创建Dto

在Application.Contracts 项目中,创建两个Dto类

namespace Acme.BookStore.Books {
    //BookDto继承自 AuditedEntityDto<Guid>.与上面定义的 Book 实体一样具有一些审计属性.
    public class BookDto : AuditedEntityDto<Guid> {
        public string Name { get; set; }

        public BookType Type { get; set; }

        public DateTime PublishDate { get; set; }

        public float Price { get; set; }
    }
}
namespace Acme.BookStore.Books {
    //用于在创建或更新书籍的时候从用户界面获取图书信息.
    public class CreateUpdateBookDto {
        [Required]
        [StringLength(128)]
        public string Name { get; set; }

        [Required]
        public BookType Type { get; set; } = BookType.Undefined;

        [Required]
        [DataType(DataType.Date)]
        public DateTime PublishDate { get; set; } = DateTime.Now;

        [Required]
        public float Price { get; set; }
    }
}

并配置AutoMapper映射关系

public class BookStoreApplicationAutoMapperProfile : Profile
{
    public BookStoreApplicationAutoMapperProfile()
    {
        /* You can configure your AutoMapper mapping configuration here.
         * Alternatively, you can split your mapping configurations
         * into multiple profile classes for a better organization. */

        //启动模板配置了AutoMapper,只需要在这里定义映射
        CreateMap<Book, BookDto>();
        CreateMap<CreateUpdateBookDto, Book>();
    }
}

8、创建增删改查接口

在Acme.BookStore.Application.Contracts项目

namespace Acme.BookStore.Books {
    public interface IBookAppService :
        ICrudAppService< //Defines CRUD methods
            BookDto, //Used to show books
            Guid, //Primary key of the book entity
            PagedAndSortedResultRequestDto, //Used for paging/sorting
            CreateUpdateBookDto> //Used to create/update a book
    {

    }
}

//可以只有查询,没有更新、新增
public interface IIronRequiredAppService : 
	ICrudAppService<IronRequiredPlanDto, Guid, GetIronRequiredQueryDto>
        , IApplicationService

9、创建增删改查接口的实现类

在Acme.BookStore.Application项目中

namespace Acme.BookStore.Books {
    public class BookAppService :
        CrudAppService<
            Book, //The Book entity
            BookDto, //Used to show books
            Guid, //Primary key of the book entity
            PagedAndSortedResultRequestDto, //Used for paging/sorting
            CreateUpdateBookDto>, //Used to create/update a book
        IBookAppService //implement the IBookAppService
    {
        public BookAppService(IRepository<Book, Guid> repository)
            : base(repository) {

        }
    }
}

运行项目

常见问题

问题:The required antiforgery cookie ".AspNetCore.Antiforgery.-UtNyN_JVjA" is not present

现象:PUT方法失败,别的方法成功

解决方法

在ConfigureServices方法中增加如下配置

Configure<AbpAntiForgeryOptions>(options =>
{
    options.TokenCookie.SameSite = Microsoft.AspNetCore.Http.SameSiteMode.Lax;
    options.TokenCookie.Expiration = TimeSpan.FromDays(365);
    options.AutoValidateIgnoredHttpMethods.Add("POST");
    options.AutoValidateIgnoredHttpMethods.Add("PUT");
    options.AutoValidateIgnoredHttpMethods.Add("DELETE");
});

问题:swagger认证时,弹出一个这样的框框

解决方案:在AppService中注释掉下面的代码

public class HeatScheduleExceptionAppService : CrudAppService<HeatScheduleException, HeatScheduleExceptionDto, Guid, HeatScheduleExceptionGetListInput, CreateHeatScheduleExceptionDto, UpdateHeatScheduleExceptionDto>,
    IHeatScheduleExceptionAppService
{
    //下面这几个注释掉可以本地调试
    //protected override string GetPolicyName { get; set; } = SteelServicePermissions.HeatScheduleException.Default;
    //protected override string GetListPolicyName { get; set; } = SteelServicePermissions.HeatScheduleException.Default;
    //protected override string CreatePolicyName { get; set; } = SteelServicePermissions.HeatScheduleException.Create;
    //protected override string UpdatePolicyName { get; set; } = SteelServicePermissions.HeatScheduleException.Update;
    //protected override string DeletePolicyName { get; set; } = SteelServicePermissions.HeatScheduleException.Delete;

使用AbpHelper生成控制器的步骤

可以用这个gui界面

image.png

使用AbpHelper生成CRUD步骤

地址如下

easyabp.io/abphelper/A…

1、安装abpHelper

dotnet tool install EasyAbp.AbpHelper -g

更新命令

dotnet tool update EasyAbp.AbpHelper -g

2、创建abp项目

这里也可以不用创建,直接使用现有的项目

abp new MyToDo

3、创建实体类

public class Todo : FullAuditedEntity<Guid>
{
    public string Content { get; set; }
    public bool Done { get; set; }
}

4、在外面运行命令

abphelper generate crud Todo

如果不在项目文件夹,也可以自己指定

abphelper generate crud Todo -d C:\MyTodo

5、修改app.setting配置连接字符串,运行Migrator项目

生成的代码解读

没有生成种子数据

这里面的删除是软删除,由于实体类继承自FullAuditedEntity

1、应用服务层接口及实现

public interface ITodoAppService :
    ICrudAppService< 
        TodoDto, 
        Guid, 
        TodoGetListInput,
        CreateUpdateTodoDto,
        CreateUpdateTodoDto>
{

}

public class TodoAppService : CrudAppService<Todo, TodoDto, Guid, TodoGetListInput, CreateUpdateTodoDto, CreateUpdateTodoDto>,
    ITodoAppService
{
    protected override string GetPolicyName { get; set; } = MyToDoPermissions.Todo.Default;
    protected override string GetListPolicyName { get; set; } = MyToDoPermissions.Todo.Default;
    protected override string CreatePolicyName { get; set; } = MyToDoPermissions.Todo.Create;
    protected override string UpdatePolicyName { get; set; } = MyToDoPermissions.Todo.Update;
    protected override string DeletePolicyName { get; set; } = MyToDoPermissions.Todo.Delete;

    private readonly ITodoRepository _repository;

    public TodoAppService(ITodoRepository repository) : base(repository)
    {
        _repository = repository;
    }

    protected override async Task<IQueryable<Todo>> CreateFilteredQueryAsync(TodoGetListInput input)
    {
        // TODO: AbpHelper generated
        return (await base.CreateFilteredQueryAsync(input))
            .WhereIf(!input.Content.IsNullOrWhiteSpace(), x => x.Content.Contains(input.Content))
            .WhereIf(input.Done != null, x => x.Done == input.Done)
            ;
    }
}

2、DTO及AutoMapper

[Serializable]
public class CreateUpdateTodoDto
{
    public string Content { get; set; }

    public bool Done { get; set; }
}
[Serializable]
public class TodoDto : FullAuditedEntityDto<Guid>
{
    public string Content { get; set; }

    public bool Done { get; set; }
}
[Serializable]
public class TodoGetListInput : PagedAndSortedResultRequestDto
{
    public string Content { get; set; }

    public bool? Done { get; set; }
}

AutoMapper

这两个位置的Dto是有区别的

public class MyToDoApplicationAutoMapperProfile : Profile
{
    public MyToDoApplicationAutoMapperProfile()
    {
        /* You can configure your AutoMapper mapping configuration here.
         * Alternatively, you can split your mapping configurations
         * into multiple profile classes for a better organization. */
        CreateMap<Todo, TodoDto>();
        CreateMap<CreateUpdateTodoDto, Todo>(MemberList.Source);
    }
}

下面这个是ViewModel,如果只是接口的话,应该不需要管

public class MyToDoWebAutoMapperProfile : Profile
{
    public MyToDoWebAutoMapperProfile()
    {
        //Define your AutoMapper configuration here for the Web project.
        CreateMap<TodoDto, CreateEditTodoViewModel>();
        CreateMap<CreateEditTodoViewModel, CreateUpdateTodoDto>();
    }
}

3、仓储层接口及实现

public interface ITodoRepository : IRepository<Todo, Guid>
{
}

public class TodoRepository : EfCoreRepository<MyToDoDbContext, Todo, Guid>, ITodoRepository
{
    public TodoRepository(IDbContextProvider<MyToDoDbContext> dbContextProvider) : base(dbContextProvider)
    {
    }

    public override async Task<IQueryable<Todo>> WithDetailsAsync()
    {
        return (await GetQueryableAsync()).IncludeDetails();
    }
}

4、查询扩展类

public static class TodoEfCoreQueryableExtensions
{
    public static IQueryable<Todo> IncludeDetails(this IQueryable<Todo> queryable, bool include = true)
    {
        if (!include)
        {
            return queryable;
        }

        return queryable
            // .Include(x => x.xxx) // TODO: AbpHelper generated
            ;
    }
}

5、DbContext中的配置

public DbSet<Todo> Todos { get; set; }

//...

builder.Entity<Todo>(b =>
{
    b.ToTable(MyToDoConsts.DbTablePrefix + "Todos", MyToDoConsts.DbSchema);
    b.ConfigureByConvention(); 
    

    /* Configure more properties here */
});