zoukankan      html  css  js  c++  java
  • 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  

  • 相关阅读:
    JavaScript Patterns 5.7 Object Constants
    JavaScript Patterns 5.6 Static Members
    JavaScript Patterns 5.5 Sandbox Pattern
    JavaScript Patterns 5.4 Module Pattern
    JavaScript Patterns 5.3 Private Properties and Methods
    JavaScript Patterns 5.2 Declaring Dependencies
    JavaScript Patterns 5.1 Namespace Pattern
    JavaScript Patterns 4.10 Curry
    【Android】如何快速构建Android Demo
    【Android】如何实现ButterKnife
  • 原文地址:https://www.cnblogs.com/sui84/p/14013614.html
Copyright © 2011-2022 走看看