• 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  

  • 相关阅读:
    解决docx4j 变量替换 由于变量存在样式式或空白字符 导致替换失败问题
    redis批量删除key 远程批量删除key
    idea 集成sonarLint检查代码bugs
    mac jmeter 的使用
    tomcat配置管理员-走后门
    终端mysql Operation not permitted错误解决方案
    update使用inner join
    hibernate 三种状态的转换
    数据库中间表插入乱序
    解决https证书验证不通过的问题
  • 原文地址:https://www.cnblogs.com/sui84/p/14013614.html
Copyright © 2020-2023  润新知