自定义应用模块: 重写服务

    注册到依赖注入的任何类,包括ABP框架的服务都可以被替换.

    你可以根据自己的需求使用不同的选项,下面的章节中将介绍这些选项.

    如果给定的服务定义了接口,像 类实现了 IIdentityUserAppService 接口,你可以为这个接口创建自己的实现并且替换当前的实现. 例如:

    MyIdentityUserAppService 通过命名约定替换了 IIdentityUserAppService 的当前实现. 如果你的类名不匹配,你需要手动公开服务接口:

    1. [ExposeServices(typeof(IIdentityUserAppService))]
    2. public class TestAppService : IIdentityUserAppService, ITransientDependency
    3. {
    4. //...
    5. }

    依赖注入系统允许为一个接口注册多个服务. 注入接口时会解析最后一个注入的服务. 显式的替换服务是一个好习惯.

    示例:

    1. [Dependency(ReplaceServices = true)]
    2. [ExposeServices(typeof(IIdentityUserAppService))]
    3. public class TestAppService : IIdentityUserAppService, ITransientDependency
    4. {
    5. //...
    6. }

    使用这种方法, IIdentityUserAppService 接口将只会有一个实现. 也可以使用以下方法替换服务:

    你可以在模块类的 ConfigureServices 方法编写替换服务代码.

    1. [Dependency(ReplaceServices = true)]
    2. [ExposeServices(typeof(IIdentityUserAppService), typeof(IdentityUserAppService), typeof(MyIdentityUserAppService))]
    3. public class MyIdentityUserAppService : IdentityUserAppService
    4. {
    5. //...
    6. public MyIdentityUserAppService(
    7. IdentityUserManager userManager,
    8. IIdentityUserRepository userRepository,
    9. IGuidGenerator guidGenerator
    10. ) : base(
    11. userManager,
    12. userRepository,
    13. guidGenerator)
    14. {
    15. }
    16. public async override Task<IdentityUserDto> CreateAsync(IdentityUserCreateDto input)
    17. {
    18. if (input.PhoneNumber.IsNullOrWhiteSpace())
    19. {
    20. throw new AbpValidationException(
    21. "Phone number is required for new users!",
    22. new List<ValidationResult>
    23. {
    24. new ValidationResult(
    25. "Phone number can not be empty!",
    26. )
    27. }
    28. ); }
    29. return await base.CreateAsync(input);
    30. }
    31. }

    示例中重写IdentityUserAppService CreateAsync 方法检查手机号码. 然后调用了基类方法继续基本业务逻辑. 通过这种方法你可以在基本业务逻辑之前之后执行其他业务逻辑.

    你也可以完全重写整个业务逻辑去创建用户,而不是调用基类方法.

    示例: 重写领域服务

    1. [Dependency(ReplaceServices = true)]
    2. public class MyIdentityUserManager : IdentityUserManager
    3. {
    4. public MyIdentityUserManager(
    5. IdentityUserStore store,
    6. IIdentityRoleRepository roleRepository,
    7. IIdentityUserRepository userRepository,
    8. IOptions<IdentityOptions> optionsAccessor,
    9. IPasswordHasher<IdentityUser> passwordHasher,
    10. IEnumerable<IUserValidator<IdentityUser>> userValidators,
    11. IEnumerable<IPasswordValidator<IdentityUser>> passwordValidators,
    12. ILookupNormalizer keyNormalizer,
    13. IdentityErrorDescriber errors,
    14. IServiceProvider services,
    15. ILogger<IdentityUserManager> logger,
    16. ICancellationTokenProvider cancellationTokenProvider) :
    17. base(store,
    18. roleRepository,
    19. userRepository,
    20. optionsAccessor,
    21. passwordHasher,
    22. userValidators,
    23. passwordValidators,
    24. keyNormalizer,
    25. errors,
    26. services,
    27. logger,
    28. cancellationTokenProvider)
    29. {
    30. }
    31. public async override Task<IdentityResult> CreateAsync(IdentityUser user)
    32. {
    33. if (user.PhoneNumber.IsNullOrWhiteSpace())
    34. {
    35. throw new AbpValidationException(
    36. "Phone number is required for new users!",
    37. new List<ValidationResult>
    38. {
    39. "Phone number can not be empty!",
    40. new []{"PhoneNumber"}
    41. )
    42. }
    43. );
    44. }
    45. return await base.CreateAsync(user);
    46. }
    47. }

    示例中类继承了 IdentityUserManager ,并且重写了 CreateAsync 方法进行了与之前相同的手机号码检查. 结果也是一样的,但是这次我们在领域服务实现了它,假设这是我们系统的核心领域逻辑.

    这里需要 [ExposeServices(typeof(IdentityUserManager))] attribute,因为 IdentityUserManager 没有定义接口 (像 ) ,依赖注入系统并不会按照约定公开继承类的服务(如已实现的接口).

    参阅本地化系统了解如何自定义错误消息.

    控制器,框架服务,视图组件类以及其他类型注册到依赖注入的类都可以像上面的示例那样被重写.

    你可以如所述扩展实体. 并使用上面介绍的重写相关服务使用自定义属性****执行其他业务逻辑.

    应用程序使用的数据传输对象(DTO)同样可扩展. 这样你可以使服务返回其他属性并在UI(或其他客户端)得到其他属性.

    示例

    假设你已经按照中的说明添加了 SocialSecurityNumber 并希望从 IdentityUserAppService的GetListAsync 方法获取用户列表时包括此属性.

    这段代码为 IdentityUserDto 类添加了 string 类型的 SocialSecurityNumber 属性. 现在你可以在RREST API客户端调用 /api/identity/users HTTP API(内部使用 IdentityUserAppService),你会在 extraProperties 部分看到 SocialSecurityNumber 值.

    1. {
    2. "totalCount": 1,
    3. "items": [{
    4. "tenantId": null,
    5. "userName": "admin",
    6. "name": "admin",
    7. "surname": null,
    8. "email": "[email protected]",
    9. "emailConfirmed": false,
    10. "phoneNumber": null,
    11. "phoneNumberConfirmed": false,
    12. "twoFactorEnabled": false,
    13. "lockoutEnabled": true,
    14. "lockoutEnd": null,
    15. "concurrencyStamp": "b4c371a0ab604de28af472fa79c3b70c",
    16. "isDeleted": false,
    17. "deleterId": null,
    18. "deletionTime": null,
    19. "lastModificationTime": "2020-04-09T21:25:47.0740706",
    20. "lastModifierId": null,
    21. "creationTime": "2020-04-09T21:25:46.8308744",
    22. "creatorId": null,
    23. "id": "8edecb8f-1894-a9b1-833b-39f4725db2a3",
    24. "extraProperties": {
    25. "SocialSecurityNumber": "123456789"
    26. }
    27. }]
    28. }

    手动添加了 123456789 值到数据库中.

    所有预构建的模块都在DTO中支持额外属性,你可以对其轻松的配置.

    当为实体定义额外的属性时,由于安全性它不会自动出现在所有相关的DTO中. 额外属性可能包含敏感数据并且你可能不想默认公开给客户端.

    因此如果要用于DTO,需要为相应的DTO显式定义相同的属性(如上所述). 如果要允许在用户创建时进行设置还需要为 IdentityUserCreateDto 定义.

    如果属性并不是安全敏感,这可能会很枯燥. 对象扩展系统允许你忽略检查定义的属性. 参阅示例:

    1. ObjectExtensionManager.Instance
    2. .AddOrUpdateProperty<IdentityUser, string>(
    3. "SocialSecurityNumber",
    4. options =>
    5. {
    6. options.MapEfCore(b => b.HasMaxLength(32));
    7. options.CheckPairDefinitionOnMapping = false;
    8. }
    9. );

    这是定义实体属性的另一种方法( 有关 ObjectExtensionManager 更多信息,请参阅). 这次我们设置了 CheckPairDefinitionOnMapping 为false,在将实体映射到DTO时会跳过定义检查.

    如果你不喜欢这种方法,但想简单的向多个对象(DTO)添加单个属, AddOrUpdateProperty 可以使用类型数组添加额外的属性:

    关于用户界面

    该系统允许你向实体和DTO添加额外的属性并执行自定义业务代码,但它与用户界面无关.

    包含了定义的主要服务列表. 另外 你也可以查看源码找到所有的服务.