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方法,如下所示:

[DependsOn(BookStoreApplicationModule)]
public class BookStoreWebModule : AbpModule
{
    public override void ConfigureServices(ServiceConfigurationContext context)
    {
        Configure<AbpAspNetCoreMvcOptions>(options =>
        {
            options.ConventionalControllers.Create(typeof(BookStoreApplicationModule).Assembly);
        });
    }
}
C#
 

此示例代码配置包含类BookStoreApplicationModule的程序集中的所有应用程序服务.下图显示了Swagger UI上的API内容.

Abp vNext框架 ASP.NET Core API 自动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+) 调试选项。

调试 - 图1

一旦你开启了该功能,你可以进入(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 oolsabphelper generate crud "Item" -d "D:projectsgithubcsharp\_abpDRS" --skip-db-migrations --skip-ui --skip-view-model --skip-localization --migration-project-name "DRS.DbMigrator.csproj"

当在Domain项目下创建了类Item后,abphelper 自动生成的crud代码如下

 16. 定时任务AsyncPeriodicBackgroundWorkerBase 参照

abp-devabpmodulesidentityserversrcVolo.Abp.IdentityServer.DomainVoloAbpIdentityServerTokensTokenCleanupBackgroundWorker.cs

 17. LocalEventBus : EntityChangedEventData 参照

abp-devabpmodulesidentityserversrcVolo.Abp.IdentityServer.DomainVoloAbpIdentityServerAllowedCorsOriginsCacheItemInvalidator.cs

18. 类继承Entity(复合主键类)需要实现Equals方法和GetKeys,在EFCore层用HasKey 参照

abp-devabpmodulesidentityserversrcVolo.Abp.IdentityServer.DomainVoloAbpIdentityServerApiResourcesApiResourceProperty.cs

abp-devabpmodulesidentityserversrcVolo.Abp.IdentityServer.EntityFrameworkCoreVoloAbpIdentityServerEntityFrameworkCoreIdentityServerDbContextModelCreatingExtensions.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-devabpmodulesaccountsrcVolo.Abp.Account.ApplicationVoloAbpAccountAccountAppService.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-devabpmodulesaccountsrcVolo.Abp.Account.WebAreasAccountControllers

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

abpmodulesloggingsrcVolo.Blogging.ApplicationVoloBloggingCommentsCommentAuthorizationHandler.cs

34. File upload IFileAppService

abpmodulesloggingsrcVolo.Blogging.ApplicationVoloBloggingFilesFileAppService.cs

35. AbpBlobStoringOptions

abpmoduleslob-storing-databasesrcVolo.Abp.BlobStoring.Database.DomainVoloAbpBlobStoringDatabaseBlobStoringDatabaseDomainModule.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-devabpframework estVolo.Abp.Security.TestsVoloAbpSecurityEncryptionStringEncryptionService_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-devabpframeworksrcVolo.Abp.DataVoloAbpDataIDataFilter.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-devabpframeworksrcVolo.Abp.CachingVoloAbpCachingDistributedCache.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  

原文地址:https://www.cnblogs.com/sui84/p/14013614.html