31 changed files with 966 additions and 460 deletions
@ -0,0 +1,59 @@ |
|||
using Microsoft.Extensions.Logging; |
|||
using Microsoft.Extensions.Logging.Abstractions; |
|||
using System.Linq; |
|||
using System.Threading.Tasks; |
|||
using Volo.Abp.Authorization.Permissions; |
|||
using Volo.Abp.Data; |
|||
using Volo.Abp.DependencyInjection; |
|||
using Volo.Abp.MultiTenancy; |
|||
using Volo.Abp.PermissionManagement; |
|||
|
|||
namespace LINGYUN.Abp.MicroService.AdminService; |
|||
public class AdminServiceDataSeeder : ITransientDependency |
|||
{ |
|||
protected ILogger<AdminServiceDataSeeder> Logger { get; } |
|||
protected ICurrentTenant CurrentTenant { get; } |
|||
protected IPermissionDefinitionManager PermissionDefinitionManager { get; } |
|||
protected IPermissionDataSeeder PermissionDataSeeder { get; } |
|||
|
|||
public AdminServiceDataSeeder( |
|||
IPermissionDefinitionManager permissionDefinitionManager, |
|||
IPermissionDataSeeder permissionDataSeeder, |
|||
ICurrentTenant currentTenant) |
|||
{ |
|||
PermissionDefinitionManager = permissionDefinitionManager; |
|||
PermissionDataSeeder = permissionDataSeeder; |
|||
CurrentTenant = currentTenant; |
|||
|
|||
Logger = NullLogger<AdminServiceDataSeeder>.Instance; |
|||
} |
|||
|
|||
public virtual async Task SeedAsync(DataSeedContext context) |
|||
{ |
|||
using (CurrentTenant.Change(context.TenantId)) |
|||
{ |
|||
await SeedAdminRolePermissionsAsync(context); |
|||
} |
|||
} |
|||
|
|||
private async Task SeedAdminRolePermissionsAsync(DataSeedContext context) |
|||
{ |
|||
Logger.LogInformation("Seeding the default role permissions..."); |
|||
|
|||
var multiTenancySide = CurrentTenant.GetMultiTenancySide(); |
|||
var permissionNames = (await PermissionDefinitionManager.GetPermissionsAsync()) |
|||
.Where(p => p.MultiTenancySide.HasFlag(multiTenancySide)) |
|||
.Where(p => !p.Providers.Any() || p.Providers.Contains(RolePermissionValueProvider.ProviderName)) |
|||
.Select(p => p.Name) |
|||
.ToArray(); |
|||
|
|||
await PermissionDataSeeder.SeedAsync( |
|||
RolePermissionValueProvider.ProviderName, |
|||
"admin", |
|||
permissionNames, |
|||
context?.TenantId |
|||
); |
|||
|
|||
Logger.LogInformation("Seed default role permissions completed."); |
|||
} |
|||
} |
|||
@ -1,5 +1,34 @@ |
|||
{ |
|||
"ConnectionStrings": { |
|||
"Default": "Host=127.0.0.1;Database=abp;Username=postgres;Password=123456" |
|||
}, |
|||
"OpenIddict": { |
|||
"Applications": { |
|||
"VueAdmin": { |
|||
"ClientId": "vue-admin-client", |
|||
"ClientSecret": "1q2w3e*", |
|||
"RootUrls": [ "http://localhost:5666" ] |
|||
}, |
|||
"InternalService": { |
|||
"ClientId": "InternalServiceClient", |
|||
"ClientSecret": "1q2w3e*" |
|||
}, |
|||
"VueOAuthClient": { |
|||
"ClientId": "vue-oauth-client", |
|||
"RootUrls": [ |
|||
"http://localhost:5666", |
|||
"http://localhost:30000", |
|||
"http://localhost:30010", |
|||
"http://localhost:30015", |
|||
"http://localhost:30020", |
|||
"http://localhost:30025", |
|||
"http://localhost:30030", |
|||
"http://localhost:30040", |
|||
"http://localhost:30045", |
|||
"http://localhost:30050", |
|||
"http://localhost:30060" |
|||
] |
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
|||
@ -0,0 +1,35 @@ |
|||
using LINGYUN.Abp.MicroService.AuthServer.DataSeeds; |
|||
using System.Threading.Tasks; |
|||
using Volo.Abp.Data; |
|||
using Volo.Abp.DependencyInjection; |
|||
using Volo.Abp.MultiTenancy; |
|||
|
|||
namespace LINGYUN.Abp.MicroService.AuthServer; |
|||
public class AuthServerDataSeeder : ITransientDependency |
|||
{ |
|||
protected ICurrentTenant CurrentTenant { get; } |
|||
protected OpenIddictDataSeeder OpenIddictDataSeeder { get; } |
|||
protected IdentityClaimTypeDataSeeder IdentityClaimTypeDataSeeder { get; } |
|||
protected IdentityUserRoleDataSeeder IdentityUserRoleDataSeeder { get; } |
|||
public AuthServerDataSeeder( |
|||
ICurrentTenant currentTenant, |
|||
OpenIddictDataSeeder openIddictDataSeeder, |
|||
IdentityClaimTypeDataSeeder identityClaimTypeDataSeeder, |
|||
IdentityUserRoleDataSeeder identityUserRoleDataSeeder) |
|||
{ |
|||
CurrentTenant = currentTenant; |
|||
OpenIddictDataSeeder = openIddictDataSeeder; |
|||
IdentityClaimTypeDataSeeder = identityClaimTypeDataSeeder; |
|||
IdentityUserRoleDataSeeder = identityUserRoleDataSeeder; |
|||
} |
|||
|
|||
public virtual async Task SeedAsync(DataSeedContext context) |
|||
{ |
|||
using (CurrentTenant.Change(context.TenantId)) |
|||
{ |
|||
await OpenIddictDataSeeder.SeedAsync(context); |
|||
await IdentityClaimTypeDataSeeder.SeedAsync(context); |
|||
await IdentityUserRoleDataSeeder.SeedAsync(context); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,89 @@ |
|||
using JetBrains.Annotations; |
|||
using Microsoft.Extensions.Logging; |
|||
using OpenIddict.Abstractions; |
|||
using System.Threading.Tasks; |
|||
using Volo.Abp.Data; |
|||
using Volo.Abp.DependencyInjection; |
|||
using Volo.Abp.Guids; |
|||
using Volo.Abp.Identity; |
|||
|
|||
namespace LINGYUN.Abp.MicroService.AuthServer.DataSeeds; |
|||
public class IdentityClaimTypeDataSeeder : ITransientDependency |
|||
{ |
|||
public ILogger<IdentityClaimTypeDataSeeder> Logger { protected get; set; } |
|||
|
|||
protected IGuidGenerator GuidGenerator { get; } |
|||
protected IdentityClaimTypeManager IdentityClaimTypeManager { get; } |
|||
protected IIdentityClaimTypeRepository IdentityClaimTypeRepository { get; } |
|||
public IdentityClaimTypeDataSeeder( |
|||
IGuidGenerator guidGenerator, |
|||
IdentityClaimTypeManager identityClaimTypeManager, |
|||
IIdentityClaimTypeRepository identityClaimTypeRepository) |
|||
{ |
|||
GuidGenerator = guidGenerator; |
|||
IdentityClaimTypeManager = identityClaimTypeManager; |
|||
IdentityClaimTypeRepository = identityClaimTypeRepository; |
|||
} |
|||
|
|||
public async virtual Task SeedAsync(DataSeedContext context) |
|||
{ |
|||
if (context.TenantId.HasValue) |
|||
{ |
|||
return; |
|||
} |
|||
|
|||
Logger.LogInformation("Seeding the default identity claim types..."); |
|||
|
|||
await CreateIdentityClaimTypeAsync(OpenIddictConstants.Claims.Address); |
|||
await CreateIdentityClaimTypeAsync(OpenIddictConstants.Claims.Birthdate, valueType: IdentityClaimValueType.DateTime); |
|||
await CreateIdentityClaimTypeAsync(OpenIddictConstants.Claims.Country); |
|||
await CreateIdentityClaimTypeAsync(OpenIddictConstants.Claims.Email); |
|||
await CreateIdentityClaimTypeAsync(OpenIddictConstants.Claims.EmailVerified, valueType: IdentityClaimValueType.Boolean); |
|||
await CreateIdentityClaimTypeAsync(OpenIddictConstants.Claims.FamilyName); |
|||
await CreateIdentityClaimTypeAsync(OpenIddictConstants.Claims.Gender); |
|||
await CreateIdentityClaimTypeAsync(OpenIddictConstants.Claims.GivenName); |
|||
await CreateIdentityClaimTypeAsync(OpenIddictConstants.Claims.Locale); |
|||
await CreateIdentityClaimTypeAsync(OpenIddictConstants.Claims.Locality); |
|||
await CreateIdentityClaimTypeAsync(OpenIddictConstants.Claims.MiddleName); |
|||
await CreateIdentityClaimTypeAsync(OpenIddictConstants.Claims.Name); |
|||
await CreateIdentityClaimTypeAsync(OpenIddictConstants.Claims.Nickname); |
|||
await CreateIdentityClaimTypeAsync(OpenIddictConstants.Claims.PhoneNumber); |
|||
await CreateIdentityClaimTypeAsync(OpenIddictConstants.Claims.PhoneNumberVerified, valueType: IdentityClaimValueType.Boolean); |
|||
await CreateIdentityClaimTypeAsync(OpenIddictConstants.Claims.Picture); |
|||
await CreateIdentityClaimTypeAsync(OpenIddictConstants.Claims.PostalCode); |
|||
await CreateIdentityClaimTypeAsync(OpenIddictConstants.Claims.PreferredUsername); |
|||
await CreateIdentityClaimTypeAsync(OpenIddictConstants.Claims.Profile); |
|||
await CreateIdentityClaimTypeAsync(OpenIddictConstants.Claims.Region); |
|||
await CreateIdentityClaimTypeAsync(OpenIddictConstants.Claims.Role); |
|||
await CreateIdentityClaimTypeAsync(OpenIddictConstants.Claims.StreetAddress); |
|||
await CreateIdentityClaimTypeAsync(OpenIddictConstants.Claims.Username); |
|||
await CreateIdentityClaimTypeAsync(OpenIddictConstants.Claims.Website); |
|||
await CreateIdentityClaimTypeAsync(OpenIddictConstants.Claims.Zoneinfo); |
|||
|
|||
Logger.LogInformation("Seeding default identity claim types completed."); |
|||
} |
|||
|
|||
private async Task CreateIdentityClaimTypeAsync( |
|||
[NotNull] string name, |
|||
bool required = false, |
|||
bool isStatic = false, |
|||
[CanBeNull] string regex = null, |
|||
[CanBeNull] string regexDescription = null, |
|||
[CanBeNull] string description = null, |
|||
IdentityClaimValueType valueType = IdentityClaimValueType.String) |
|||
{ |
|||
if (!await IdentityClaimTypeRepository.AnyAsync(name)) |
|||
{ |
|||
await IdentityClaimTypeManager.CreateAsync( |
|||
new IdentityClaimType( |
|||
GuidGenerator.Create(), |
|||
name, |
|||
required, |
|||
isStatic, |
|||
regex, |
|||
regexDescription, |
|||
description, |
|||
valueType)); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,124 @@ |
|||
using Microsoft.AspNetCore.Identity; |
|||
using Microsoft.Extensions.Options; |
|||
using System; |
|||
using System.Threading.Tasks; |
|||
using Volo.Abp.Data; |
|||
using Volo.Abp.DependencyInjection; |
|||
using Volo.Abp.Guids; |
|||
using Volo.Abp.Identity; |
|||
|
|||
namespace LINGYUN.Abp.MicroService.AuthServer.DataSeeds; |
|||
public class IdentityUserRoleDataSeeder : ITransientDependency |
|||
{ |
|||
public const string AdminUserIdPropertyName = "AdminUserId"; |
|||
public const string AdminEmailPropertyName = "AdminEmail"; |
|||
public const string AdminEmailDefaultValue = "admin@abp.io"; |
|||
public const string AdminUserNamePropertyName = "AdminUserName"; |
|||
public const string AdminUserNameDefaultValue = "admin"; |
|||
public const string AdminPasswordPropertyName = "AdminPassword"; |
|||
public const string AdminPasswordDefaultValue = "1q2w3E*"; |
|||
|
|||
protected IGuidGenerator GuidGenerator { get; } |
|||
protected IIdentityRoleRepository RoleRepository { get; } |
|||
protected IIdentityUserRepository UserRepository { get; } |
|||
protected ILookupNormalizer LookupNormalizer { get; } |
|||
protected IdentityUserManager UserManager { get; } |
|||
protected IdentityRoleManager RoleManager { get; } |
|||
protected IOptions<IdentityOptions> IdentityOptions { get; } |
|||
|
|||
public IdentityUserRoleDataSeeder( |
|||
IGuidGenerator guidGenerator, |
|||
IIdentityRoleRepository roleRepository, |
|||
IIdentityUserRepository userRepository, |
|||
ILookupNormalizer lookupNormalizer, |
|||
IdentityUserManager userManager, |
|||
IdentityRoleManager roleManager, |
|||
IOptions<IdentityOptions> identityOptions) |
|||
{ |
|||
GuidGenerator = guidGenerator; |
|||
RoleRepository = roleRepository; |
|||
UserRepository = userRepository; |
|||
LookupNormalizer = lookupNormalizer; |
|||
UserManager = userManager; |
|||
RoleManager = roleManager; |
|||
IdentityOptions = identityOptions; |
|||
} |
|||
|
|||
public virtual async Task SeedAsync(DataSeedContext context) |
|||
{ |
|||
await SeedAdminUserAsync(context); |
|||
await SeedDefaultRoleAsync(context); |
|||
} |
|||
|
|||
private async Task SeedAdminUserAsync(DataSeedContext context) |
|||
{ |
|||
await IdentityOptions.SetAsync(); |
|||
|
|||
const string adminRoleName = "admin"; |
|||
var adminUserName = context?[AdminUserNamePropertyName] as string ?? AdminUserNameDefaultValue; |
|||
|
|||
Guid adminRoleId; |
|||
if (!await RoleManager.RoleExistsAsync(adminRoleName)) |
|||
{ |
|||
adminRoleId = GuidGenerator.Create(); |
|||
var adminRole = new IdentityRole( |
|||
adminRoleId, |
|||
adminRoleName, |
|||
context.TenantId) |
|||
{ |
|||
IsStatic = true, |
|||
IsPublic = true |
|||
}; |
|||
(await RoleManager.CreateAsync(adminRole)).CheckErrors(); |
|||
} |
|||
else |
|||
{ |
|||
var adminRole = await RoleManager.FindByNameAsync(adminRoleName); |
|||
adminRoleId = adminRole.Id; |
|||
} |
|||
|
|||
var adminUserId = GuidGenerator.Create(); |
|||
if (context.Properties.TryGetValue(AdminUserIdPropertyName, out var userIdString) && |
|||
Guid.TryParse(userIdString?.ToString(), out var adminUserGuid)) |
|||
{ |
|||
adminUserId = adminUserGuid; |
|||
} |
|||
var adminEmailAddress = context?[AdminEmailPropertyName] as string ?? AdminEmailDefaultValue; |
|||
var adminPassword = context?[AdminPasswordPropertyName] as string ?? AdminPasswordDefaultValue; |
|||
|
|||
var adminUser = await UserManager.FindByNameAsync(adminUserName); |
|||
if (adminUser == null) |
|||
{ |
|||
adminUser = new IdentityUser( |
|||
adminUserId, |
|||
adminUserName, |
|||
adminEmailAddress, |
|||
context.TenantId); |
|||
|
|||
adminUser.AddRole(adminRoleId); |
|||
|
|||
// 创建租户管理用户
|
|||
(await UserManager.CreateAsync(adminUser)).CheckErrors(); |
|||
(await UserManager.AddPasswordAsync(adminUser, adminPassword)).CheckErrors(); |
|||
} |
|||
} |
|||
|
|||
private async Task SeedDefaultRoleAsync(DataSeedContext context) |
|||
{ |
|||
const string defaultRoleName = "Users"; |
|||
if (await RoleManager.FindByNameAsync(defaultRoleName) != null) |
|||
{ |
|||
var roleId = GuidGenerator.Create(); |
|||
var defaultRole = new IdentityRole( |
|||
roleId, |
|||
defaultRoleName, |
|||
context.TenantId) |
|||
{ |
|||
IsStatic = true, |
|||
IsPublic = true, |
|||
IsDefault = true, |
|||
}; |
|||
(await RoleManager.CreateAsync(defaultRole)).CheckErrors(); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,47 @@ |
|||
using LINGYUN.Abp.UI.Navigation; |
|||
using System.Collections.Generic; |
|||
using System.Threading.Tasks; |
|||
using Volo.Abp.Data; |
|||
using Volo.Abp.DependencyInjection; |
|||
using Volo.Abp.MultiTenancy; |
|||
|
|||
namespace LINGYUN.Abp.MicroService.PlatformService; |
|||
public class PlatformServiceDataSeeder : ITransientDependency |
|||
{ |
|||
protected IEnumerable<INavigationSeedContributor> NavigationSeedContributors { get; } |
|||
protected INavigationProvider NavigationProvider { get; } |
|||
protected ICurrentTenant CurrentTenant { get; } |
|||
public PlatformServiceDataSeeder( |
|||
IEnumerable<INavigationSeedContributor> navigationSeedContributors, |
|||
INavigationProvider navigationProvider, |
|||
ICurrentTenant currentTenant) |
|||
{ |
|||
NavigationSeedContributors = navigationSeedContributors; |
|||
NavigationProvider = navigationProvider; |
|||
CurrentTenant = currentTenant; |
|||
} |
|||
|
|||
public async virtual Task SeedAsync(DataSeedContext context) |
|||
{ |
|||
using (CurrentTenant.Change(context.TenantId)) |
|||
{ |
|||
await SeedNavigationAsync(); |
|||
} |
|||
} |
|||
|
|||
private async Task SeedNavigationAsync() |
|||
{ |
|||
var menus = await NavigationProvider.GetAllAsync(); |
|||
|
|||
var multiTenancySides = CurrentTenant.IsAvailable |
|||
? MultiTenancySides.Tenant |
|||
: MultiTenancySides.Host; |
|||
|
|||
var seedContext = new NavigationSeedContext(menus, multiTenancySides); |
|||
|
|||
foreach (var contributor in NavigationSeedContributors) |
|||
{ |
|||
await contributor.SeedAsync(seedContext); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,139 @@ |
|||
using LINGYUN.Abp.BackgroundTasks; |
|||
using LINGYUN.Abp.BackgroundTasks.Internal; |
|||
using Microsoft.Extensions.Options; |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Threading.Tasks; |
|||
using Volo.Abp.Data; |
|||
using Volo.Abp.DependencyInjection; |
|||
using Volo.Abp.MultiTenancy; |
|||
|
|||
namespace LINGYUN.Abp.MicroService.TaskService; |
|||
public class TaskServiceDataSeeder : ITransientDependency |
|||
{ |
|||
protected AbpBackgroundTasksOptions Options { get; } |
|||
protected IJobStore JobStore { get; } |
|||
protected IJobScheduler JobScheduler { get; } |
|||
protected ICurrentTenant CurrentTenant { get; } |
|||
|
|||
public TaskServiceDataSeeder( |
|||
IOptions<AbpBackgroundTasksOptions> options, |
|||
IJobStore jobStore, |
|||
IJobScheduler jobScheduler, |
|||
ICurrentTenant currentTenant) |
|||
{ |
|||
Options = options.Value; |
|||
JobStore = jobStore; |
|||
JobScheduler = jobScheduler; |
|||
CurrentTenant = currentTenant; |
|||
} |
|||
|
|||
public async virtual Task SeedAsync(DataSeedContext context) |
|||
{ |
|||
if (context.TenantId.HasValue) |
|||
{ |
|||
using (CurrentTenant.Change(context.TenantId)) |
|||
{ |
|||
await QueueBackgroundJobAsync(context.TenantId.Value); |
|||
} |
|||
} |
|||
} |
|||
|
|||
public async virtual Task RemoveSeedAsync(Guid tenantId) |
|||
{ |
|||
using (CurrentTenant.Change(tenantId)) |
|||
{ |
|||
var pollingJob = BuildPollingJobInfo(tenantId); |
|||
await JobScheduler.RemoveAsync(pollingJob); |
|||
await JobStore.RemoveAsync(pollingJob.Id); |
|||
|
|||
var cleaningJob = BuildCleaningJobInfo(tenantId); |
|||
await JobScheduler.RemoveAsync(cleaningJob); |
|||
await JobStore.RemoveAsync(cleaningJob.Id); |
|||
|
|||
var checkingJob = BuildCheckingJobInfo(tenantId); |
|||
await JobScheduler.RemoveAsync(checkingJob); |
|||
await JobStore.RemoveAsync(checkingJob.Id); |
|||
} |
|||
} |
|||
|
|||
protected async virtual Task QueueBackgroundJobAsync(Guid tenantId) |
|||
{ |
|||
var pollingJob = BuildPollingJobInfo(tenantId); |
|||
await JobStore.StoreAsync(pollingJob); |
|||
await JobScheduler.QueueAsync(pollingJob); |
|||
|
|||
var cleaningJob = BuildCleaningJobInfo(tenantId); |
|||
await JobStore.StoreAsync(cleaningJob); |
|||
await JobScheduler.QueueAsync(cleaningJob); |
|||
|
|||
var checkingJob = BuildCheckingJobInfo(tenantId); |
|||
await JobStore.StoreAsync(checkingJob); |
|||
await JobScheduler.QueueAsync(checkingJob); |
|||
} |
|||
|
|||
protected virtual JobInfo BuildPollingJobInfo(Guid tenantId) |
|||
{ |
|||
return new JobInfo |
|||
{ |
|||
Id = tenantId.ToString() + "_Polling", |
|||
Name = nameof(BackgroundPollingJob), |
|||
Group = "Polling", |
|||
Description = "Polling tasks to be executed", |
|||
Args = new Dictionary<string, object>() { { nameof(JobInfo.TenantId), tenantId } }, |
|||
Status = JobStatus.Running, |
|||
BeginTime = DateTime.Now, |
|||
CreationTime = DateTime.Now, |
|||
Cron = Options.JobFetchCronExpression, |
|||
JobType = JobType.Period, |
|||
Priority = JobPriority.High, |
|||
Source = JobSource.System, |
|||
LockTimeOut = Options.JobFetchLockTimeOut, |
|||
TenantId = tenantId, |
|||
Type = typeof(BackgroundPollingJob).AssemblyQualifiedName, |
|||
}; |
|||
} |
|||
|
|||
protected virtual JobInfo BuildCleaningJobInfo(Guid tenantId) |
|||
{ |
|||
return new JobInfo |
|||
{ |
|||
Id = tenantId.ToString() + "_Cleaning", |
|||
Name = nameof(BackgroundCleaningJob), |
|||
Group = "Cleaning", |
|||
Description = "Cleaning tasks to be executed", |
|||
Args = new Dictionary<string, object>() { { nameof(JobInfo.TenantId), tenantId } }, |
|||
Status = JobStatus.Running, |
|||
BeginTime = DateTime.Now, |
|||
CreationTime = DateTime.Now, |
|||
Cron = Options.JobCleanCronExpression, |
|||
JobType = JobType.Period, |
|||
Priority = JobPriority.High, |
|||
Source = JobSource.System, |
|||
TenantId = tenantId, |
|||
Type = typeof(BackgroundCleaningJob).AssemblyQualifiedName, |
|||
}; |
|||
} |
|||
|
|||
protected virtual JobInfo BuildCheckingJobInfo(Guid tenantId) |
|||
{ |
|||
return new JobInfo |
|||
{ |
|||
Id = tenantId.ToString() + "_Checking", |
|||
Name = nameof(BackgroundCheckingJob), |
|||
Group = "Checking", |
|||
Description = "Checking tasks to be executed", |
|||
Args = new Dictionary<string, object>() { { nameof(JobInfo.TenantId), tenantId } }, |
|||
Status = JobStatus.Running, |
|||
BeginTime = DateTime.Now, |
|||
CreationTime = DateTime.Now, |
|||
Cron = Options.JobCheckCronExpression, |
|||
LockTimeOut = Options.JobCheckLockTimeOut, |
|||
JobType = JobType.Period, |
|||
Priority = JobPriority.High, |
|||
Source = JobSource.System, |
|||
TenantId = tenantId, |
|||
Type = typeof(BackgroundCheckingJob).AssemblyQualifiedName, |
|||
}; |
|||
} |
|||
} |
|||
Loading…
Reference in new issue