ABP框架使用(版本3.3.1)
1.Refers
EntityFramework
https://docs.microsoft.com/zh-cn/ef/core/modeling/entity-properties?tabs=data-annotations
https://entityframework.net/articles/carloscds-ef6-stored-procedure
abp sample
https://github.com/abpframework/abp-samples
2.DbMigrator
Remove-migration -force
add-migration initial
update-database
3.配置数据库表前缀
https://www.cnblogs.com/yiluomyt/p/10350524.html
https://www.bookstack.cn/read/abp-3.0-zh/dfae9fb778e87934.md#aasbho
基础模块(如身份, 租户管理 和 审计日志)使用 Abp 前缀, 其他的模块使用自己的前缀. 如Identity Server 模块使用前缀 IdentityServer.
修改Volo.Abp.IdentityServer.AbpIdentityServerDbProperties.DbTablePrefix 的方法如果用update-database命令是不起作用的
启动Acme.BookStore.DbMigrator可以生效
public class BookStoreDomainModule : AbpModule
{
public override void PreConfigureServices(ServiceConfigurationContext context)
{
Volo.Abp.AuditLogging.AbpAuditLoggingDbProperties.DbTablePrefix = "test_";
Volo.Abp.IdentityServer.AbpIdentityServerDbProperties.DbTablePrefix = "mid_";
BookStoreDomainObjectExtensions.Configure();
}
public override void ConfigureServices(ServiceConfigurationContext context)
{
Configure<AbpMultiTenancyOptions>(options =>
{
options.IsEnabled = MultiTenancyConsts.IsEnabled;
});
}
}
Option1 . 修改基础模块的表前缀,可以新起一个 BackgroundJobsDbContextModelCreatingExtensions
public static class BackgroundJobsDbContextModelCreatingExtensions
{
public static void ConfigureBackgroundJobs(
this ModelBuilder builder,
Action<BackgroundJobsModelBuilderConfigurationOptions> optionsAction = null)
{
var options = new BackgroundJobsModelBuilderConfigurationOptions(
BackgroundJobsDbProperties.DbTablePrefix,
BackgroundJobsDbProperties.DbSchema
);
optionsAction?.Invoke(options);
builder.Entity<BackgroundJobRecord>(b =>
{
b.ToTable("bg_" + "BackgroundJobs", options.Schema);
b.ConfigureCreationTime();
b.ConfigureExtraProperties();
b.Property(x => x.JobName)
.IsRequired()
.HasMaxLength(BackgroundJobRecordConsts.MaxJobNameLength);
//...
});
}
}
Optioin2 . 修改应用程序的模块更改数据库表前缀,在表的类前面加上表属性 [Table("Author")] ,在BookStoreDbContextModelCreatingExtensions.cs 类里
var entityTypes = builder.Model.GetEntityTypes().ToList();
// 设置自定义表前缀
foreach (var entityType in entityTypes)
{
if (entityType.ClrType
.GetCustomAttributes(typeof(TableAttribute), true)
.FirstOrDefault() is TableAttribute table)
{
// 如果你的表名就是实体类型名的话,可以修改为如下形式,就不必给出[table]的Name参数
// string tableName = tablePrefix + entityType.ClrType.Name;
// 如若有其他需求也可在此进行修改
string tableName = "TESTY" + table.Name;
builder.Entity(entityType.ClrType)
.ToTable(tableName);
}
}
Option3 . 最简单的方法是在 BookStoreMigrationsDbContext 指定表前缀
protected override void OnModelCreating(ModelBuilder builder)
{
base.OnModelCreating(builder);
/* Include modules to your migration db context */
builder.ConfigurePermissionManagement(options =>
{
options.TablePrefix = "cc";
});
builder.ConfigureSettingManagement(options =>
{
options.TablePrefix = "dd";
});
builder.ConfigureBackgroundJobs(options =>
{
options.TablePrefix = "ee";
});
builder.ConfigureAuditLogging(options =>
{
options.TablePrefix = "ff";
});
// builder.ConfigureIdentity();
builder.ConfigureIdentity(options =>
{
options.TablePrefix = "gg";
});
builder.ConfigureIdentityServer(options =>
{
options.TablePrefix = "hh";
});
builder.ConfigureFeatureManagement(options =>
{
options.TablePrefix = "aa";
});
builder.ConfigureTenantManagement(options =>
{
options.TablePrefix = "bb";
// options.Schema = "";
});
/* Configure your own tables/entities inside the ConfigureBookStore method */
builder.ConfigureBookStore();
}

改变了表的schema后,生成的sql会报错,在domain层加了类AbpIdentityServerDbProperties ,但不起作用,需在DomainModule上指定
public override void ConfigureServices(ServiceConfigurationContext context)
{
Configure<AbpMultiTenancyOptions>(options =>
{
options.IsEnabled = MultiTenancyConsts.IsEnabled;
});
#if DEBUG
context.Services.Replace(ServiceDescriptor.Singleton<IEmailSender, NullEmailSender>());
#endif
AbpIdentityServerDbProperties.DbSchema = "ids";
AbpIdentityDbProperties.DbSchema = "id";
}
4.Swagger小绿锁
Bearer
private void ConfigureSwaggerServices(IServiceCollection services)
{
services.AddSwaggerGen(
options =>
{
options.SwaggerDoc("v1", new OpenApiInfo { Title = "BookStore API", Version = "v1" });
options.DocInclusionPredicate((docName, description) => true);
options.CustomSchemaIds(type => type.FullName);
options.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme()
{
Name = "Authorization",
Scheme = "bearer",
Description = "Specify the authorization token.",
In = ParameterLocation.Header,
Type = SecuritySchemeType.Http,
});
options.AddSecurityRequirement(new OpenApiSecurityRequirement()
{
{
new OpenApiSecurityScheme
{
Reference = new OpenApiReference {Type = ReferenceType.SecurityScheme, Id = "Bearer"}
},
new string[] { }
},
});
}
);
}

token 值
{
"nbf": 1606551733,
"exp": 1638087733,
"iss": "https://localhost:44356",
"aud": "BookStore",
"client_id": "BookStore_App",
"scope": [
"BookStore"
]
}
bearerAuth
options.AddSecurityDefinition("bearerAuth", new Microsoft.OpenApi.Models.OpenApiSecurityScheme() { Description = "JWT Authorization header using the Bearer scheme. Example: \"Authorization: Bearer {token}\"", Name = "Authorization", In = Microsoft.OpenApi.Models.ParameterLocation.Header, Type = Microsoft.OpenApi.Models.SecuritySchemeType.OAuth2, Flows = new Microsoft.OpenApi.Models.OpenApiOAuthFlows() { Password = new Microsoft.OpenApi.Models.OpenApiOAuthFlow() { TokenUrl = new Uri("https://localhost:44356/connect/token") } } });

token 值
{
"nbf": 1606550841,
"exp": 1638086841,
"iss": "https://localhost:44356",
"aud": "BookStore",
"client_id": "BookStore_App",
"sub": "bfc83aa4-0278-4f4b-656e-39f912477096",
"auth_time": 1606550840,
"idp": "local",
"phone_number_verified": "False",
"email": "bookstore@test.com",
"email_verified": [
"False",
false
],
"name": "bookstore",
"scope": [
"address",
"email",
"openid",
"phone",
"profile",
"role",
"BookStore",
"offline_access"
],
"amr": [
"pwd"
]
}
5.自动API控制器
配置
基本配置很简单. 只需配置AbpAspNetCoreMvcOptions并使用ConventionalControllers.Create方法,如下所示:
此示例代码配置包含类BookStoreApplicationModule的程序集中的所有应用程序服务.下图显示了Swagger UI上的API内容.

例子
一些示例方法名称和按约定生成的相应路由:
| 服务方法名称 | HTTP Method | 路由 |
|---|---|---|
| GetAsync(Guid id) | GET | /api/app/book/{id} |
| GetListAsync() | GET | /api/app/book |
| CreateAsync(CreateBookDto input) | POST | /api/app/book |
| UpdateAsync(Guid id, UpdateBookDto input) | PUT | /api/app/book/{id} |
| DeleteAsync(Guid id) | DELETE | /api/app/book/{id} |
| GetEditorsAsync(Guid id) | GET | /api/app/book/{id}/editors |
| CreateEditorAsync(Guid id, BookEditorCreateDto input) | POST | /api/app/book/{id}/editor |
6. 扩展属性
Option1 :在类AppUser里面,扩展属性加在表AbpUsers
public class AppUser : FullAuditedAggregateRoot<Guid>, IUser
{
#region Base properties
/* These properties are shared with the IdentityUser entity of the Identity module.
* Do not change these properties through this class. Instead, use Identity module
* services (like IdentityUserManager) to change them.
* So, this properties are designed as read only!
*/
public virtual Guid? TenantId { get; private set; }
public virtual string UserName { get; private set; }
public virtual string Name { get; private set; }
public virtual string Surname { get; private set; }
public virtual string Email { get; private set; }
public virtual bool EmailConfirmed { get; private set; }
public virtual string PhoneNumber { get; private set; }
public virtual bool PhoneNumberConfirmed { get; private set; }
public string MyProperty { get; set; }
#endregion
/* Add your own properties here. Example:
*
* public string MyProperty { get; set; }
*
* If you add a property and using the EF Core, remember these;
*
* 1. Update BookStoreDbContext.OnModelCreating
* to configure the mapping for your new property
* 2. Update BookStoreEfCoreEntityExtensionMappings to extend the IdentityUser entity
* and add your new property to the migration.
* 3. Use the Add-Migration to add a new database migration.
* 4. Run the .DbMigrator project (or use the Update-Database command) to apply
* schema change to the database.
*/
private AppUser()
{
}
}
Option2 :
https://iter01.com/522920.html
BookStoreEfCoreEntityExtensionMappings
private static readonly OneTimeRunner OneTimeRunner = new OneTimeRunner();
public static void Configure()
{
BookStoreModulePropertyConfigurator.Configure();
OneTimeRunner.Run(() =>
{
/* You can configure entity extension properties for the
* entities defined in the used modules.
*
* The properties defined here becomes table fields.
* If you want to use the ExtraProperties dictionary of the entity
* instead of creating a new field, then define the property in the
* BookStoreDomainObjectExtensions class.
*
* Example:
*
* ObjectExtensionManager.Instance
* .MapEfCoreProperty<IdentityUser, string>(
* "MyProperty",
* b => b.HasMaxLength(128)
* );
*
* See the documentation for more:
* https://docs.abp.io/en/abp/latest/Customizing-Application-Modules-Extending-Entities
*/
ObjectExtensionManager.Instance
.MapEfCoreProperty<IdentityUser, string>(
"MyProperty",
(entityBuilder, propertyBuilder) =>
{
propertyBuilder.HasMaxLength(32);
}
);
});
}
}
Acme.BookStore.Application.Contracts
public static class BookStoreDtoExtensions
{
private static readonly OneTimeRunner OneTimeRunner = new OneTimeRunner();
public static void Configure()
{
OneTimeRunner.Run(() =>
{
/* You can add extension properties to DTOs
* defined in the depended modules.
*
* Example:
*
* ObjectExtensionManager.Instance
* .AddOrUpdateProperty<IdentityRoleDto, string>("Title");
*
* See the documentation for more:
* https://docs.abp.io/en/abp/latest/Object-Extensions
*/
ObjectExtensionManager.Instance
/* .AddOrUpdateProperty<string>(
new[]
{
typeof(IdentityUserDto),
typeof(IdentityUserCreateDto),
typeof(IdentityUserUpdateDto),
typeof(ProfileDto),
typeof(UpdateProfileDto)
},
"MyProperty"
)
*/
.AddOrUpdateProperty<string>(
new[]
{
typeof(IdentityRoleDto),
typeof(IdentityRoleCreateDto),
typeof(IdentityRoleUpdateDto)
},
"MyProperty"
);
});
}
}
7.Overriding Identity Services
https://github.com/abpframework/abp/blob/dev/docs/en/Customizing-Application-Modules-Overriding-Services.md
Example: Overriding a Domain Service
[Dependency(ReplaceServices = true)]
[ExposeServices(typeof(IdentityUserManager))]
public class MyIdentityUserManager : IdentityUserManager
{
public MyIdentityUserManager(
IdentityUserStore store,
IIdentityRoleRepository roleRepository,
IIdentityUserRepository userRepository,
IOptions<IdentityOptions> optionsAccessor,
IPasswordHasher<IdentityUser> passwordHasher,
IEnumerable<IUserValidator<IdentityUser>> userValidators,
IEnumerable<IPasswordValidator<IdentityUser>> passwordValidators,
ILookupNormalizer keyNormalizer,
IdentityErrorDescriber errors,
IServiceProvider services,
ILogger<IdentityUserManager> logger,
ICancellationTokenProvider cancellationTokenProvider) :
base(store,
roleRepository,
userRepository,
optionsAccessor,
passwordHasher,
userValidators,
passwordValidators,
keyNormalizer,
errors,
services,
logger,
cancellationTokenProvider)
{
}
public async override Task<IdentityResult> CreateAsync(IdentityUser user)
{
if (user.PhoneNumber.IsNullOrWhiteSpace())
{
throw new AbpValidationException(
"Phone number is required for new users!",
new List<ValidationResult>
{
new ValidationResult(
"Phone number can not be empty!",
new []{"PhoneNumber"}
)
}
);
}
return await base.CreateAsync(user);
}
}
/api/account/register
Volo.Abp.Account.AccountController
/api/account/login
Volo.Abp.Account.Web.Areas.Account.Controllers.AccountController
8.事件总线
https://www.cnblogs.com/whuanle/p/13679991.html
9.实体历史:ABP 提供了一个基础设施,可以自动的记录所有实体以及属性的变更历史。默认开启,一般应用不重要可以在预初始化PreInitialize 方法中禁用他Configuration.EntityHistory.IsEnabled = false;
Entity History : 会记录在表 AbpEntityChanges 和 AbpEntityPropertyChanges
[Audited]
public class MyEntity : Entity
{
public string MyProperty1 { get; set; }
[DisableAuditing]
public int MyProperty2 { get; set; }
public long MyProperty3 { get; set; }
}
10.调试
所有官方的 ABP nuget packages 都开启了Sourcelink。这就是说你可以在你的项目中很方便的调试 Abp. nuget packages。为了开启该功能,你需要像下面一样来设置你的 Visual Studio (2017+) 调试选项。

一旦你开启了该功能,你可以进入(F11)ABP的源代码。
11.AsyncCrudAppService?
12. SwaggerUI InjectJavaScript
.EnableSwaggerUi("apis/{*assetPath}", b =>
{
//对js进行了拓展
b.InjectJavaScript(Assembly.GetExecutingAssembly(), "YoYoCMS.PhoneBook.SwaggerUi.scripts.swagger.js");
});
13.禁用具体某个接口的审计功能
[DisableAuditing] //屏蔽这个AppService的审计功能
[AbpAuthorize(AppPermissions.Pages_Administration_AuditLogs)]
public class AuditLogAppService : GHITAssetAppServiceBase, IAuditLogAppService
14.复合主键
[Key, Column(Order = 2)]
public int CityId{ get; set; }
[Key, Column(Order = 3)]
public int companyId{ get; set; }
15. abphelper 的使用
%USERPROFILE%\.dotnet\tools\abphelper generate crud "Item" -d "D:\projects\github\csharp\_abp\DRS" --skip-db-migrations --skip-ui --skip-view-model --skip-localization --migration-project-name "DRS.DbMigrator.csproj"
当在Domain项目下创建了类Item后,abphelper 自动生成的crud代码如下

16. 定时任务AsyncPeriodicBackgroundWorkerBase 参照
abp-dev\abp\modules\identityserver\src\Volo.Abp.IdentityServer.Domain\Volo\Abp\IdentityServer\Tokens\TokenCleanupBackgroundWorker.cs
17. LocalEventBus : EntityChangedEventData 参照
abp-dev\abp\modules\identityserver\src\Volo.Abp.IdentityServer.Domain\Volo\Abp\IdentityServer\AllowedCorsOriginsCacheItemInvalidator.cs
18. 类继承Entity(复合主键类)需要实现Equals方法和GetKeys,在EFCore层用HasKey 参照
abp-dev\abp\modules\identityserver\src\Volo.Abp.IdentityServer.Domain\Volo\Abp\IdentityServer\ApiResources\ApiResourceProperty.cs
abp-dev\abp\modules\identityserver\src\Volo.Abp.IdentityServer.EntityFrameworkCore\Volo\Abp\IdentityServer\EntityFrameworkCore\IdentityServerDbContextModelCreatingExtensions.cs
19.同时需要用EfCore和MongoDB,connection string会被覆盖掉,需要实现 DefaultConnectionStringResolver
同一个entity只能定义EfCore或者MongoDB的DBContext,不能同时为EfCore和MongoDB定义不同的IRepositories,背后似乎是用entity name来做注入(看名字后面是否跟Interface名字匹配),对于EntityChangedEventData,也是基于Entity,而不是IRepositories
using Microsoft.Extensions.Options;
using System;
using System.Collections.Generic;
using System.Text;
using Volo.Abp.Data;
using Volo.Abp.DependencyInjection;
namespace MyAPP.MongoDb
{
[Dependency(ReplaceServices = true)]
public class MyAPPMongoDBConnectionStringResolver : DefaultConnectionStringResolver
{
public MyAPPMongoDBConnectionStringResolver(IOptionsSnapshot<AbpDbConnectionOptions> options) : base(options)
{
}
public override string Resolve(string connectionStringName = null)
{
var connStrName = "MyAPPMongoDb";
if (connectionStringName == connStrName
&& !string.IsNullOrWhiteSpace(Options.ConnectionStrings[connStrName]))
{
return Options.ConnectionStrings[connStrName];
}
//Get default value
return Options.ConnectionStrings["Default"];
}
}
}
20.UserManager
abp-dev\abp\modules\account\src\Volo.Abp.Account.Application\Volo\Abp\Account\AccountAppService.cs
public async Task<IActionResult> OnPostAsync(string returnUrl = null)
{
returnUrl = returnUrl ?? Url.Content("~/");
if (ModelState.IsValid)
{
var user = new IdentityUser { UserName = Input.Email, Email = Input.Email };
var result = await _userManager.CreateAsync(user, Input.Password); //创建账户 if (result.Succeeded)
{
_logger.LogInformation("User created a new account with password.");
var code = await _userManager.GenerateEmailConfirmationTokenAsync(user); //生成邮箱验证码
var callbackUrl = Url.Page( //生成验证的回调地址
"/Account/ConfirmEmail",
pageHandler: null,
values: new { userId = user.Id, code = code },
protocol: Request.Scheme);
await _emailSender.SendEmailAsync(Input.Email, "Confirm your email", //发送邮箱验证邮件
$"Please confirm your account by <a href='{HtmlEncoder.Default.Encode(callbackUrl)}'>clicking here</a>.");
await _signInManager.SignInAsync(user, isPersistent: false); //登录
return LocalRedirect(returnUrl);
}
foreach (var error in result.Errors)
{
ModelState.AddModelError(string.Empty, error.Description);
}
}
// If we got this far, something failed, redisplay form
return Page();
}
ExternalLoginInfo
if (!userLoginAlreadyExists)
{
(await UserManager.AddLoginAsync(user, new UserLoginInfo(
externalLoginInfo.LoginProvider,
externalLoginInfo.ProviderKey,
externalLoginInfo.ProviderDisplayName
))).CheckErrors();
}
21.[ApiExplorerSettings(IgnoreApi = true)]
abp-dev\abp\modules\account\src\Volo.Abp.Account.Web\Areas\Account\Controllers
22.Autofac.Core.Registration.ComponentNotRegisteredException: The requested service '...MyPermissionDataSeedContributor' has not been registered
can manually add to the DI system or implement the ITransientDependency interface.
context.Services.AddTransient<MyPermissionDataSeedContributor>();
23.项目分离
public override void Initialize()
{
IocManager.RegisterAssemblyByConvention(typeof(DMWebHostModule).GetAssembly());
var partManager = IocManager.Resolve<ApplicationPartManager>();
//分离类库里的任意类。
var type = typeof(BIApiController);
var assembly = type.Assembly;
//判断是否存在
if (!partManager.ApplicationParts.Any(o => o.Name == type.Namespace))
{
//添加分离类库的程序集
partManager.ApplicationParts.Add(new AssemblyPart(assembly));
}
}
}
24. WithDetails() 可以将sub entity也取出来
You can configure DefaultWithDetailsFunc for an entity in the ConfigureServices method of your module in your EntityFrameworkCore project.
Configure<AbpEntityOptions>(options =>
{
options.Entity<Order>(orderOptions =>
{
orderOptions.DefaultWithDetailsFunc = query => query.Include(o => o.Lines);
});
});
Then you can use the WithDetails without any parameter:
public async Task TestWithDetails()
{
var query = _orderRepository.WithDetails();
var orders = await AsyncExecuter.ToListAsync(query);
}
Get list of entities with details
var orders = await _orderRepository.GetListAsync(includeDetails: true);
// var list = _repository.WithDetails(i => i.ItemTiers, j => j.ItemClass).ToList();
如果sub entity里又有entity
options.Entity<TestEntity>(orderOptions =>
{
orderOptions.DefaultWithDetailsFunc = query => query.Include(o => o.SubTestEntity).ThenInclude(x=>x.SubSubTestEntity);
});
25.批量插入
public class TagRepository : EfCoreRepository<MeowvBlogDbContext, Tag, int>, ITagRepository
{
public TagRepository(IDbContextProvider<MeowvBlogDbContext> dbContextProvider) : base(dbContextProvider)
{
}
/// <summary>
/// 批量插入
/// </summary>
/// <param name="tags"></param>
/// <returns></returns>
public async Task BulkInsertAsync(IEnumerable<Tag> tags)
{
await DbContext.Set<Tag>().AddRangeAsync(tags);
await DbContext.SaveChangesAsync();
}
}
26.Override CreateFilteredQuery and GetEntityById in your AppService:
public class MyAppService : CrudAppService<ParentEntity, ParentEntityDto>, IMyAppService
{
public MyAppService(IRepository<ParentEntity> repository)
: base(repository)
{
}
protected override IQueryable<ParentEntity> CreateFilteredQuery(PagedAndSortedResultRequestDto input)
{
return Repository.GetAllIncluding(p => p.ChildEntity);
}
protected override ParentEntity GetEntityById(int id)
{
var entity = Repository.GetAllIncluding(p => p.ChildEntity).FirstOrDefault(p => p.Id == id);
if (entity == null)
{
throw new EntityNotFoundException(typeof(ParentEntity), id);
}
return entity;
}
}
Override Create API
public async override Task<ItemDto> CreateAsync(CreateUpdateItemDto input)
{
return await base.CreateAsync(input);
}
重写排序函数
protected override IQueryable<AuditLog> ApplySorting(IQueryable<AuditLog> query, AuditLogPagedDto input)
{
return base.ApplySorting(query, input).OrderByDescending(s => s.ExecutionTime);//时间降序
}
27.扩展 ICurrentUser,登录时增加Claim
[Dependency(ReplaceServices = true)]
[ExposeServices(typeof(AbpUserClaimsPrincipalFactory))] // 替换旧的AbpUserClaimsPrincipalFactory
public class MyUserClaimsPrincipalFactory : AbpUserClaimsPrincipalFactory, IScopedDependency
{
public MainUserClaimsPrincipalFactory(UserManager<IdentityUser> userManager,
RoleManager<IdentityRole> roleManager, IOptions<IdentityOptions> options) :
base(userManager, roleManager, options)
{
}
public override async Task<ClaimsPrincipal> CreateAsync(IdentityUser user)
{
var principal = await base.CreateAsync(user);
var identityPrincipal = principal.Identities.First();
/// add custom claim
/// identityPrincipal.AddClaim(new Claim(XXX, XXX));
return principal;
}
}
28 . Post时候总是报Bad Request 400错误,而Get没问题
[ERR] The required antiforgery cookie ".AspNetCore.Antiforgery.jc6jICwMZA8" is not present.
[INF] Authorization failed for the request at filter 'Volo.Abp.AspNetCore.Mvc.AntiForgery.AbpAutoValidateAntiforgeryTokenAuthorizationFilter'.
解决方法,在 ConfigureServices 禁止AbpAntiForgeryOptions
https://support.abp.io/QA/Questions/595/The-required-antiforgery-cookie-AspNetCoreAntiforgerydXGKnviEebk-is-not-present
https://github.com/abpframework/abp/pull/5864
Configure<AbpAntiForgeryOptions>(options =>
{
options.AutoValidate = false;
});
29.DynamicEntityPropertyValueManagerExtensions?
30.IAvoidDuplicateCrossCuttingConcerns ?
31. [RemoteService(false)] 和 [DisableAuditing] , 表 AbpAuditLogs 和 AbpAuditLogActions
不想公布某些特殊的接口访问,那么我们可以通过标记 [RemoteService(false)] 进行屏蔽,这样在Web API层就不会公布对应的接口了
默认的一般应用服务层和接口,都是会进行审计记录写入的,如果我们需要屏蔽某些应用服务层或者接口,不进行审计信息的记录,那么需要使用特性标记[DisableAuditing]来管理。
32.API返回创建实体的名字
Entity 和 EntityDto 继承 FullAuditedAggregateRootWithUser<Guid,AppUser>
public class Item : FullAuditedAggregateRootWithUser<Guid,AppUser>
{
public string Name { get; set; }
}
public class ItemDto : FullAuditedEntityWithUserDto<Guid, AppUserDto>
{
public string Name { get; set; }
}
增加 AutoMapperProfile
CreateMap<AppUserDto, AppUser>().ReverseMap();
默认返回 Creator
Configure<AbpEntityOptions>(options =>
{
options.Entity<Item>(orderOptions =>
{
orderOptions.DefaultWithDetailsFunc = query =>
query.Include(o=>o.Creator);
});
});
注意:这里千万不能改 AbpIdentityDbProperties.DbTablePrefix , 不然就linkage不到 AbpUsers
继承类的时候用 AppUser, 但用户数据是保存在 AbpUsers 里,两个entity share同样的properties
builder.Entity<AppUser>(b => { b.ToTable(AbpIdentityDbProperties.DbTablePrefix + "Users"); //Sharing the same table "AbpUsers" with the IdentityUser b.ConfigureByConvention(); b.ConfigureAbpUser(); /* Configure mappings for your additional properties * Also see the DRSEfCoreEntityExtensionMappings class */ });
33 . PermissionCheck & AuthorizationHandlerContext
abp\modules\blogging\src\Volo.Blogging.Application\Volo\Blogging\Comments\CommentAuthorizationHandler.cs
34. File upload IFileAppService
abp\modules\blogging\src\Volo.Blogging.Application\Volo\Blogging\Files\FileAppService.cs
35. AbpBlobStoringOptions
abp\modules\blob-storing-database\src\Volo.Abp.BlobStoring.Database.Domain\Volo\Abp\BlobStoring\Database\BlobStoringDatabaseDomainModule.cs
36.日志级别
public class MyException : Exception, IHasLogLevel
{
public LogLevel LogLevel { get; set; } = LogLevel.Warning;
//...
}
37. SoftDelete
var people1 = _personRepository.GetAllList();
using (_unitOfWorkManager.Current.DisableFilter(AbpDataFilters.SoftDelete))
{
var people2 = _personRepository.GetAllList();
}
var people3 = _personRepository.GetAllList();
设想这样的场景,当user删除后,entity对应的creator和lastModifier就找不到对应的record,只能返回空值。
需要单独为AppUser不启用SoftDelete的feature,可以override DbContext的CreateFilterExpression
protected override Expression<Func<TEntity, bool>> CreateFilterExpression<TEntity>() { //var expression = base.CreateFilterExpression<TEntity>(); Expression<Func<TEntity, bool>> expression = null; if (typeof(ISoftDelete).IsAssignableFrom(typeof(TEntity))) { if (typeof(TEntity).Name != typeof(AppUser).Name) { expression = e => !IsSoftDeleteFilterEnabled || !EF.Property<bool>(e, "IsDeleted"); } } if (typeof(IMultiTenant).IsAssignableFrom(typeof(TEntity))) { Expression<Func<TEntity, bool>> multiTenantFilter = e => !IsMultiTenantFilterEnabled || EF.Property<Guid>(e, "TenantId") == CurrentTenantId; expression = expression == null ? multiTenantFilter : CombineExpressions(expression, multiTenantFilter); } if (typeof(IActiveObject).IsAssignableFrom(typeof(TEntity))) { Expression<Func<TEntity, bool>> isActiveFilter = e => !IsActiveFilterEnabled || EF.Property<bool>(e, "Active"); expression = expression == null ? isActiveFilter : CombineExpressions(expression, isActiveFilter); } return expression; }
38.Setting Filter Parameters
CurrentUnitOfWork.SetFilterParameter("PersonFilter", "personId", 42);
39.ExtensibleObject
Dictionary<string, object> ExtraProperties { get; }
user.SetProperty("Title", "My Title");
40.Public Const
public static class IdentityPermissions
{
public const string GroupName = "AbpIdentity";
public static class Roles
{
public const string Default = GroupName + ".Roles";
public const string Create = Default + ".Create";
public const string Update = Default + ".Update";
public const string Delete = Default + ".Delete";
public const string ManagePermissions = Default + ".ManagePermissions";
}
public static string[] GetAll()
{
return ReflectionHelper.GetPublicConstantsRecursively(typeof(IdentityPermissions));
}
}
41.Timezone
Abp.Timing.TimeZone
42. 为了在create entity的时候,把LastModifierId也一起赋值,Override CrudAppService
将service 继承自 MyCrudAppServiceCrudAppService
[Dependency(ReplaceServices = true)] public abstract class MyCrudAppServiceCrudAppService<TEntity, TEntityDto, TKey, TGetListInput, TCreateInput, TUpdateInput> : CrudAppService<TEntity, TEntityDto, TEntityDto, TKey, TGetListInput, TCreateInput, TUpdateInput>, ITransientDependency where TEntity : class, IEntity<TKey> where TEntityDto : IEntityDto<TKey> { protected MyCrudAppServiceCrudAppService(IRepository<TEntity, TKey> repository) : base(repository) { } public override async Task<TEntityDto> CreateAsync(TCreateInput input) { await CheckCreatePolicyAsync(); var entity = await MapToEntityAsync(input); TryToSetTenantId(entity); await Repository.InsertAsync(entity, autoSave: true); TryToSetLastModifierId(entity); return await MapToGetOutputDtoAsync(entity); } protected virtual void TryToSetLastModifierId(TEntity entity) { var propertyInfo = entity.GetType().GetProperty("LastModifierId"); if (propertyInfo == null || propertyInfo.GetSetMethod(true) == null) { return; } propertyInfo.SetValue(entity, CurrentUser.Id); } }
43. Run Test Project的时候如果entity有继承 FullAuditedAggregateRootWithUser<Guid,AppUser> 就会报错
Microsoft.EntityFrameworkCore.DbUpdateException : An error occurred while updating the entries. See the inner exception for details.
---- Microsoft.Data.Sqlite.SqliteException : SQLite Error 19: 'FOREIGN KEY constraint failed'.
这是因为默认会加foreign key到表appuser,但由于实际用到的是abpusers(IdentityUser),所以外键约束导致出错
解决办法是在
namespace DRS.EntityFrameworkCore 里配置ignore Creator/LastModifier/Deleter
builder.Entity<Item>(b =>
{
b.ToTable(DRSConsts.DbTablePrefix + "Tests", DRSConsts.DbSchema);
b.ConfigureByConvention();
b.Ignore(i => i.Creator);
b.Ignore(i => i.LastModifier);
b.Ignore(i => i.Deleter);
/* Configure more properties here */
});
44.隐藏API
https://support.abp.io/QA/Questions/264/How-to-hide-an-endpoint-from-Swagger
private void ConfigureSwaggerServices(IServiceCollection services) { services.AddSwaggerGen( options => { options.SwaggerDoc("v1", new OpenApiInfo {Title = "MyProjectName API", Version = "v1"}); options.DocInclusionPredicate((docName, description) => true); options.CustomSchemaIds(type => type.FullName); options.DocumentFilter<HideOrganizationUnitsFilter>(); //<-------- added this ----------- } ); }
class HideOrganizationUnitsFilter : IDocumentFilter { private const string pathToHide = "/identity/organization-units"; public void Apply(OpenApiDocument swaggerDoc, DocumentFilterContext context) { var organizationUnitPaths = swaggerDoc .Paths .Where(pathItem => pathItem.Key.Contains(pathToHide, StringComparison.OrdinalIgnoreCase)) .ToList(); foreach (var item in organizationUnitPaths) { swaggerDoc.Paths.Remove(item.Key); } } }
45.string encrypt
abp-dev\abp\framework\test\Volo.Abp.Security.Tests\Volo\Abp\Security\Encryption\StringEncryptionService_Tests.cs
public void Should_Enrypt_And_Decrpyt_With_Default_Options(string plainText) { _stringEncryptionService .Decrypt(_stringEncryptionService.Encrypt(plainText)) .ShouldBe(plainText); }
46.定时清理AuditLog
public class DeleteOldAuditLogsWorker : AsyncPeriodicBackgroundWorkerBase, ISingletonDependency { private readonly IRepository<AuditLog> _auditLogRepository; public DeleteOldAuditLogsWorker(AbpTimer timer, IServiceScopeFactory serviceScopeFactory, IRepository<AuditLog> auditLogRepository) : base( timer, serviceScopeFactory) { _auditLogRepository = auditLogRepository; Timer.Period = 5000; } [UnitOfWork] protected async override Task DoWorkAsync(PeriodicBackgroundWorkerContext workerContext) { Logger.LogInformation("---------------- DeleteOldAuditLogsWorker 正在工作 ----------------"); var dt = new DateTime(2021,2,2); await _auditLogRepository.DeleteAsync(log => log.ExecutionTime <= dt); } }
public override void OnApplicationInitialization(ApplicationInitializationContext context) { context.ServiceProvider .GetRequiredService<IBackgroundWorkerManager>() .Add( context.ServiceProvider.GetRequiredService<DeleteOldAuditLogsWorker>() ); }
这里用到的 AsyncPeriodicBackgroundWorkerBase 跟abp的 BackgroundJobs 不是一回事
后台作业与后台工作者的区别是,前者主要用于某些耗时较长的任务,而不想阻塞用户的时候所使用。后者主要用于周期性的执行某些任务,从 “工作者” 的名字可以看出来,就是一个个工人,而且他们每个工人都拥有单独的后台线程。如果是多台机器,就会导致执行多次。
BackgroundJobs 的用法是继承 BackgroundJob<WriteToConsoleGreenJobArgs> , 可以用IBackgroundJobManager调用,也可以在数据库中插入一条数据调用
执行成功的话数据会清除,不成功就留在数据库中 IsAbandoned 为1
insert into [AbpBackgroundJobs](id,IsAbandoned,JobName,JobArgs,CreationTime,NextTryTime )
values(newid(),0,'GreenJob','{"Value":"test args"}',getdate(),'2021-03-06 15:03')
WriteToConsoleGreenJob
public class WriteToConsoleGreenJob : BackgroundJob<WriteToConsoleGreenJobArgs>, ITransientDependency { public override void Execute(WriteToConsoleGreenJobArgs args) { if (RandomHelper.GetRandom(0, 100) < 70) { //throw new ApplicationException("A sample exception from the WriteToConsoleGreenJob!"); } lock (Console.Out) { var oldColor = Console.ForegroundColor; Console.ForegroundColor = ConsoleColor.Green; Console.WriteLine(); Console.WriteLine($"############### WriteToConsoleGreenJob: {args.Value} - {args.Time:HH:mm:ss} ###############"); Console.WriteLine(); Logger.LogInformation("WriteToConsoleGreenJob"); Console.ForegroundColor = oldColor; } } }
WriteToConsoleGreenJobArgs
[BackgroundJobName("GreenJob")] public class WriteToConsoleGreenJobArgs { public string Value { get; set; } public DateTime Time { get; set; } public WriteToConsoleGreenJobArgs() { Time = DateTime.Now; } }
SampleJobCreator
public class SampleJobCreator : ITransientDependency { private readonly IBackgroundJobManager _backgroundJobManager; public SampleJobCreator(IBackgroundJobManager backgroundJobManager) { _backgroundJobManager = backgroundJobManager; } public void CreateJobs() { AsyncHelper.RunSync(CreateJobsAsync); } public async Task CreateJobsAsync() { await _backgroundJobManager.EnqueueAsync(new WriteToConsoleGreenJobArgs { Value = "test 1 (green)" }); await _backgroundJobManager.EnqueueAsync(new WriteToConsoleGreenJobArgs { Value = "test 2 (green)" }); await _backgroundJobManager.EnqueueAsync(new WriteToConsoleYellowJobArgs { Value = "test 1 (yellow)" }); await _backgroundJobManager.EnqueueAsync(new WriteToConsoleYellowJobArgs { Value = "test 2 (yellow)" }); } }
47.数据过滤
https://docs.abp.io/zh-Hans/abp/latest/Data-Filtering
- 添加
IsActiveFilterEnabled属性用于检查是否启用了IIsActive. 内部使用了之前介绍到的IDataFilter服务. - 重写
ShouldFilterEntity和CreateFilterExpression方法检查给定实体是否实现IIsActive接口,在必要时组合表达式.
48.获取DbContext的全部entity
// DbContextHelper public static IEnumerable<Type> GetEntityTypes(Type dbContextType) { return from property in dbContextType.GetTypeInfo().GetProperties(BindingFlags.Public | BindingFlags.Instance) where ReflectionHelper.IsAssignableToGenericType(property.PropertyType, typeof(DbSet<>)) && typeof(IEntity).IsAssignableFrom(property.PropertyType.GenericTypeArguments[0]) select property.PropertyType.GenericTypeArguments[0]; }
var entityTypes = GetEntityTypes(typeof(DRSDbContext)); foreach(Type entityType in entityTypes) { if (entityType.IsAssignableFrom(typeof(FullAuditedAggregateRoot<Guid>))) { //EfCoreRepositoryRegistrar.GetRepositoryType var repositoryType = typeof(EfCoreRepository<,,>).MakeGenericType(typeof(DRSDbContext), entityType, typeof(Guid)); //repositoryType.GetConstructor var method = repositoryType .MakeGenericType(entityType) .GetMethod( nameof(WithDetails) ); //var result = method.Invoke(repositoryObject,method); } }
49.数据过滤 Data Filtering
https://docs.abp.io/en/abp/2.3/Data-Filtering
按照文档加上Expression在DbContext后,可以在MyCrudAppService里手动enable给每个entity的AppService启动过滤功能
abp-dev\abp\framework\src\Volo.Abp.Data\Volo\Abp\Data\IDataFilter.cs
GetExpression()的写法无效,也不知为什么
//public static Expression<Func<TEntity, bool>> GetExpression() // //where TEntity : IEntity<TKey> //{ // var lambdaParam = Expression.Parameter(typeof(TEntity)); // var leftExpression = Expression.PropertyOrField(lambdaParam, "IsDeleted"); // // var idValue = Convert.ChangeType(id, typeof(TKey)); // Expression<Func<object>> closure = () => false; // idValue; // var rightExpression = Expression.Convert(closure.Body, leftExpression.Type); // var lambdaBody = Expression.Equal(leftExpression, rightExpression); // return Expression.Lambda<Func<TEntity, bool>>(lambdaBody, lambdaParam); //} public async Task PostActive() { // Expression<Func<TEntity, bool>> expression = GetExpression(); using (GetFilter<IActiveObject>().Enable()) { var entities = Repository.WithDetails(); } await Task.CompletedTask; } private IDataFilter<TFilter> GetFilter<TFilter>() where TFilter : class { var _filters = ServiceProvider.GetRequiredService<IDataFilter<IActiveObject>>() as IDataFilter<TFilter>; return _filters; }
50.并发锁
abp-dev\abp\framework\src\Volo.Abp.Caching\Volo\Abp\Caching\DistributedCache.cs
SyncSemaphore = new SemaphoreSlim(1, 1);
https://cloud.tencent.com/developer/article/1641967
51.BulkInsert
public async Task BulkInsertAsync(IEnumerable<Item> items)
{
await DbContext.Set<Item>().AddRangeAsync(items);
await DbContext.SaveChangesAsync();
}
52.加密
IStringEncryptionService


浙公网安备 33010602011771号