创建一套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界面
使用AbpHelper生成CRUD步骤
地址如下
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 */
});