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