chore 优化代码
This commit is contained in:
@ -1,11 +1,10 @@
|
||||
using Volo.Abp.DependencyInjection;
|
||||
using Volo.Abp.Ui.Branding;
|
||||
|
||||
namespace KonSoft.Shared.Hosting.AspNetCore
|
||||
namespace KonSoft.Shared.Hosting.AspNetCore;
|
||||
|
||||
[Dependency(ReplaceServices = true)]
|
||||
public class KonSoftBrandingProvider : DefaultBrandingProvider
|
||||
{
|
||||
[Dependency(ReplaceServices = true)]
|
||||
public class KonSoftBrandingProvider : DefaultBrandingProvider
|
||||
{
|
||||
public override string AppName => "KonSoft";
|
||||
}
|
||||
}
|
||||
public override string AppName => "KonSoft";
|
||||
}
|
||||
@ -5,28 +5,24 @@ using Volo.Abp.MultiTenancy;
|
||||
using Volo.Abp.Swashbuckle;
|
||||
using Volo.Abp.VirtualFileSystem;
|
||||
|
||||
namespace KonSoft.Shared.Hosting.AspNetCore
|
||||
{
|
||||
[DependsOn(
|
||||
typeof(KonSoftSharedLocalizationModule),
|
||||
typeof(KonSoftSharedHostingModule),
|
||||
typeof(AbpAspNetCoreSerilogModule),
|
||||
typeof(AbpSwashbuckleModule),
|
||||
typeof(AbpMultiTenancyModule)
|
||||
)]
|
||||
public class KonSoftSharedHostingAspNetCoreModule : AbpModule
|
||||
{
|
||||
public override void ConfigureServices(ServiceConfigurationContext context)
|
||||
{
|
||||
Configure<AbpVirtualFileSystemOptions>(options =>
|
||||
{
|
||||
options.FileSets.AddEmbedded<KonSoftSharedHostingAspNetCoreModule>("KonSoft.Shared.Hosting.AspNetCore");
|
||||
});
|
||||
namespace KonSoft.Shared.Hosting.AspNetCore;
|
||||
|
||||
Configure<AbpMultiTenancyOptions>(options =>
|
||||
{
|
||||
options.IsEnabled = KonSoftConsts.MultiTenancyEnabled;
|
||||
});
|
||||
}
|
||||
[DependsOn(
|
||||
typeof(KonSoftSharedLocalizationModule),
|
||||
typeof(KonSoftSharedHostingModule),
|
||||
typeof(AbpAspNetCoreSerilogModule),
|
||||
typeof(AbpSwashbuckleModule),
|
||||
typeof(AbpMultiTenancyModule)
|
||||
)]
|
||||
public class KonSoftSharedHostingAspNetCoreModule : AbpModule
|
||||
{
|
||||
public override void ConfigureServices(ServiceConfigurationContext context)
|
||||
{
|
||||
Configure<AbpVirtualFileSystemOptions>(options =>
|
||||
{
|
||||
options.FileSets.AddEmbedded<KonSoftSharedHostingAspNetCoreModule>("KonSoft.Shared.Hosting.AspNetCore");
|
||||
});
|
||||
|
||||
Configure<AbpMultiTenancyOptions>(options => { options.IsEnabled = KonSoftConsts.MultiTenancyEnabled; });
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -18,10 +18,10 @@ public static class SwaggerConfigurationHelper
|
||||
)
|
||||
{
|
||||
context.Services.AddAbpSwaggerGenWithOidc(
|
||||
authority: authority,
|
||||
scopes: scopes,
|
||||
flows: flows,
|
||||
discoveryEndpoint: discoveryEndpoint,
|
||||
authority,
|
||||
scopes,
|
||||
flows,
|
||||
discoveryEndpoint,
|
||||
options =>
|
||||
{
|
||||
options.SwaggerDoc(apiName, new OpenApiInfo { Title = apiTitle, Version = apiVersion });
|
||||
|
||||
@ -1,7 +1,5 @@
|
||||
namespace KonSoft.Shared.Hosting.Gateways
|
||||
{
|
||||
public class Class1
|
||||
{
|
||||
namespace KonSoft.Shared.Hosting.Gateways;
|
||||
|
||||
}
|
||||
}
|
||||
public class Class1
|
||||
{
|
||||
}
|
||||
@ -1,9 +1,9 @@
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Serilog;
|
||||
using System;
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Serilog;
|
||||
using Volo.Abp;
|
||||
using Volo.Abp.Data;
|
||||
using Volo.Abp.DependencyInjection;
|
||||
@ -12,98 +12,99 @@ using Volo.Abp.EventBus.Distributed;
|
||||
using Volo.Abp.MultiTenancy;
|
||||
using Volo.Abp.Uow;
|
||||
|
||||
namespace KonSoft.Shared.Hosting.Microservices.DbMigrations.EfCore
|
||||
namespace KonSoft.Shared.Hosting.Microservices.DbMigrations.EfCore;
|
||||
|
||||
public abstract class PendingEfCoreMigrationsChecker<TDbContext> : ITransientDependency
|
||||
where TDbContext : DbContext
|
||||
{
|
||||
public abstract class PendingEfCoreMigrationsChecker<TDbContext> : ITransientDependency
|
||||
where TDbContext : DbContext
|
||||
protected PendingEfCoreMigrationsChecker(
|
||||
IUnitOfWorkManager unitOfWorkManager,
|
||||
IServiceProvider serviceProvider,
|
||||
ICurrentTenant currentTenant,
|
||||
IDistributedEventBus distributedEventBus,
|
||||
IAbpDistributedLock abpDistributedLock,
|
||||
string databaseName)
|
||||
{
|
||||
protected IUnitOfWorkManager UnitOfWorkManager { get; }
|
||||
protected IServiceProvider ServiceProvider { get; }
|
||||
protected ICurrentTenant CurrentTenant { get; }
|
||||
protected IDistributedEventBus DistributedEventBus { get; }
|
||||
protected IAbpDistributedLock DistributedLockProvider { get; }
|
||||
protected string DatabaseName { get; }
|
||||
UnitOfWorkManager = unitOfWorkManager;
|
||||
ServiceProvider = serviceProvider;
|
||||
CurrentTenant = currentTenant;
|
||||
DistributedEventBus = distributedEventBus;
|
||||
DistributedLockProvider = abpDistributedLock;
|
||||
DatabaseName = databaseName;
|
||||
}
|
||||
|
||||
protected PendingEfCoreMigrationsChecker(
|
||||
IUnitOfWorkManager unitOfWorkManager,
|
||||
IServiceProvider serviceProvider,
|
||||
ICurrentTenant currentTenant,
|
||||
IDistributedEventBus distributedEventBus,
|
||||
IAbpDistributedLock abpDistributedLock,
|
||||
string databaseName)
|
||||
protected IUnitOfWorkManager UnitOfWorkManager { get; }
|
||||
protected IServiceProvider ServiceProvider { get; }
|
||||
protected ICurrentTenant CurrentTenant { get; }
|
||||
protected IDistributedEventBus DistributedEventBus { get; }
|
||||
protected IAbpDistributedLock DistributedLockProvider { get; }
|
||||
protected string DatabaseName { get; }
|
||||
|
||||
public virtual async Task CheckAndApplyDatabaseMigrationsAsync()
|
||||
{
|
||||
await TryAsync(LockAndApplyDatabaseMigrationsAsync);
|
||||
}
|
||||
|
||||
protected async Task TryAsync(Func<Task> task, int retryCount = 3)
|
||||
{
|
||||
try
|
||||
{
|
||||
UnitOfWorkManager = unitOfWorkManager;
|
||||
ServiceProvider = serviceProvider;
|
||||
CurrentTenant = currentTenant;
|
||||
DistributedEventBus = distributedEventBus;
|
||||
DistributedLockProvider = abpDistributedLock;
|
||||
DatabaseName = databaseName;
|
||||
await task();
|
||||
}
|
||||
|
||||
public virtual async Task CheckAndApplyDatabaseMigrationsAsync()
|
||||
catch (Exception ex)
|
||||
{
|
||||
await TryAsync(LockAndApplyDatabaseMigrationsAsync);
|
||||
}
|
||||
retryCount--;
|
||||
|
||||
protected async Task TryAsync(Func<Task> task, int retryCount = 3)
|
||||
{
|
||||
try
|
||||
if (retryCount <= 0)
|
||||
{
|
||||
await task();
|
||||
throw;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
retryCount--;
|
||||
|
||||
if (retryCount <= 0)
|
||||
{
|
||||
throw;
|
||||
}
|
||||
Log.Warning(
|
||||
$"{ex.GetType().Name} has been thrown. The operation will be tried {retryCount} times more. Exception:\n{ex.Message}");
|
||||
|
||||
Log.Warning($"{ex.GetType().Name} has been thrown. The operation will be tried {retryCount} times more. Exception:\n{ex.Message}");
|
||||
await Task.Delay(RandomHelper.GetRandom(5000, 15000));
|
||||
|
||||
await Task.Delay(RandomHelper.GetRandom(5000, 15000));
|
||||
|
||||
await TryAsync(task, retryCount);
|
||||
}
|
||||
}
|
||||
|
||||
protected virtual async Task LockAndApplyDatabaseMigrationsAsync()
|
||||
{
|
||||
await using (var handle = await DistributedLockProvider.TryAcquireAsync("Migration_" + DatabaseName))
|
||||
{
|
||||
Log.Information($"Lock is acquired for db migration and seeding on database named: {DatabaseName}...");
|
||||
|
||||
if (handle is null)
|
||||
{
|
||||
Log.Information($"Handle is null because of the locking for : {DatabaseName}");
|
||||
return;
|
||||
}
|
||||
|
||||
using (CurrentTenant.Change(null))
|
||||
{
|
||||
// Create database tables if needed
|
||||
using (var uow = UnitOfWorkManager.Begin(requiresNew: true, isTransactional: false))
|
||||
{
|
||||
var dbContext = ServiceProvider.GetRequiredService<TDbContext>();
|
||||
|
||||
var pendingMigrations = await dbContext
|
||||
.Database
|
||||
.GetPendingMigrationsAsync();
|
||||
|
||||
if (pendingMigrations.Any())
|
||||
{
|
||||
await dbContext.Database.MigrateAsync();
|
||||
}
|
||||
|
||||
await uow.CompleteAsync();
|
||||
}
|
||||
|
||||
await ServiceProvider.GetRequiredService<IDataSeeder>()
|
||||
.SeedAsync();
|
||||
}
|
||||
Log.Information($"Lock is released for db migration and seeding on database named: {DatabaseName}...");
|
||||
}
|
||||
await TryAsync(task, retryCount);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected virtual async Task LockAndApplyDatabaseMigrationsAsync()
|
||||
{
|
||||
await using (var handle = await DistributedLockProvider.TryAcquireAsync("Migration_" + DatabaseName))
|
||||
{
|
||||
Log.Information($"Lock is acquired for db migration and seeding on database named: {DatabaseName}...");
|
||||
|
||||
if (handle is null)
|
||||
{
|
||||
Log.Information($"Handle is null because of the locking for : {DatabaseName}");
|
||||
return;
|
||||
}
|
||||
|
||||
using (CurrentTenant.Change(null))
|
||||
{
|
||||
// Create database tables if needed
|
||||
using (var uow = UnitOfWorkManager.Begin(true, false))
|
||||
{
|
||||
var dbContext = ServiceProvider.GetRequiredService<TDbContext>();
|
||||
|
||||
var pendingMigrations = await dbContext
|
||||
.Database
|
||||
.GetPendingMigrationsAsync();
|
||||
|
||||
if (pendingMigrations.Any())
|
||||
{
|
||||
await dbContext.Database.MigrateAsync();
|
||||
}
|
||||
|
||||
await uow.CompleteAsync();
|
||||
}
|
||||
|
||||
await ServiceProvider.GetRequiredService<IDataSeeder>()
|
||||
.SeedAsync();
|
||||
}
|
||||
|
||||
Log.Information($"Lock is released for db migration and seeding on database named: {DatabaseName}...");
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,4 +1,6 @@
|
||||
using KonSoft.Shared.Hosting.AspNetCore;
|
||||
using System;
|
||||
using System.Linq;
|
||||
using KonSoft.Shared.Hosting.AspNetCore;
|
||||
using Medallion.Threading;
|
||||
using Medallion.Threading.Redis;
|
||||
using Microsoft.AspNetCore.Authentication.JwtBearer;
|
||||
@ -7,8 +9,6 @@ using Microsoft.AspNetCore.DataProtection;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using StackExchange.Redis;
|
||||
using System;
|
||||
using System.Linq;
|
||||
using Volo.Abp.AspNetCore.Authentication.JwtBearer;
|
||||
using Volo.Abp.AspNetCore.Mvc.UI.MultiTenancy;
|
||||
using Volo.Abp.BackgroundJobs.RabbitMQ;
|
||||
@ -21,84 +21,83 @@ using Volo.Abp.Modularity;
|
||||
using Volo.Abp.Security.Claims;
|
||||
using Volo.Abp.Swashbuckle;
|
||||
|
||||
namespace KonSoft.Shared.Hosting.Microservices
|
||||
namespace KonSoft.Shared.Hosting.Microservices;
|
||||
|
||||
[DependsOn(
|
||||
typeof(KonSoftSharedHostingAspNetCoreModule),
|
||||
typeof(AbpBackgroundJobsRabbitMqModule),
|
||||
typeof(AbpAspNetCoreAuthenticationJwtBearerModule),
|
||||
typeof(AbpEventBusRabbitMqModule),
|
||||
typeof(AbpCachingStackExchangeRedisModule),
|
||||
typeof(AbpDistributedLockingModule),
|
||||
typeof(AbpAspNetCoreMvcUiMultiTenancyModule),
|
||||
typeof(AbpEntityFrameworkCoreModule),
|
||||
typeof(AbpSwashbuckleModule)
|
||||
)]
|
||||
public class KonSoftSharedHostingMicroservicesModule : AbpModule
|
||||
{
|
||||
[DependsOn(
|
||||
typeof(KonSoftSharedHostingAspNetCoreModule),
|
||||
typeof(AbpBackgroundJobsRabbitMqModule),
|
||||
typeof(AbpAspNetCoreAuthenticationJwtBearerModule),
|
||||
typeof(AbpEventBusRabbitMqModule),
|
||||
typeof(AbpCachingStackExchangeRedisModule),
|
||||
typeof(AbpDistributedLockingModule),
|
||||
typeof(AbpAspNetCoreMvcUiMultiTenancyModule),
|
||||
typeof(AbpEntityFrameworkCoreModule),
|
||||
typeof(AbpSwashbuckleModule)
|
||||
)]
|
||||
public class KonSoftSharedHostingMicroservicesModule : AbpModule
|
||||
public override void ConfigureServices(ServiceConfigurationContext context)
|
||||
{
|
||||
public override void ConfigureServices(ServiceConfigurationContext context)
|
||||
{
|
||||
var configuration = context.Services.GetConfiguration();
|
||||
var hostingEnvironment = context.Services.GetHostingEnvironment();
|
||||
var configuration = context.Services.GetConfiguration();
|
||||
var hostingEnvironment = context.Services.GetHostingEnvironment();
|
||||
|
||||
ConfigureCache();
|
||||
ConfigureDataProtection(context, configuration);
|
||||
ConfigureAuthentication(context, configuration);
|
||||
ConfigureCors(context, configuration);
|
||||
}
|
||||
|
||||
private void ConfigureCache()
|
||||
{
|
||||
Configure<AbpDistributedCacheOptions>(options => { options.KeyPrefix = "KonSoft:"; });
|
||||
}
|
||||
|
||||
private void ConfigureDataProtection(
|
||||
ServiceConfigurationContext context,
|
||||
IConfiguration configuration)
|
||||
{
|
||||
context.Services.AddDataProtection().SetApplicationName("KonSoft")
|
||||
.PersistKeysToStackExchangeRedis(ConnectionMultiplexer.Connect(configuration["Redis:Configuration"]!),
|
||||
"KonSoft-Protection-Keys");
|
||||
|
||||
context.Services.AddSingleton<IDistributedLockProvider>(_ =>
|
||||
new RedisDistributedSynchronizationProvider(ConnectionMultiplexer
|
||||
.Connect(configuration["Redis:Configuration"]!).GetDatabase()));
|
||||
}
|
||||
|
||||
private void ConfigureAuthentication(ServiceConfigurationContext context, IConfiguration configuration)
|
||||
{
|
||||
context.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
|
||||
.AddAbpJwtBearer(options =>
|
||||
{
|
||||
options.Authority = configuration["AuthServer:Authority"];
|
||||
options.RequireHttpsMetadata = configuration.GetValue<bool>("AuthServer:RequireHttpsMetadata");
|
||||
options.Audience = KonSoftConsts.AuthServerAudience;
|
||||
});
|
||||
|
||||
context.Services.Configure<AbpClaimsPrincipalFactoryOptions>(options =>
|
||||
{
|
||||
options.IsDynamicClaimsEnabled = true;
|
||||
});
|
||||
}
|
||||
|
||||
private void ConfigureCors(ServiceConfigurationContext context, IConfiguration configuration)
|
||||
{
|
||||
context.Services.AddCors(options =>
|
||||
{
|
||||
options.AddDefaultPolicy(builder =>
|
||||
{
|
||||
builder
|
||||
.WithOrigins(configuration["App:CorsOrigins"]?
|
||||
.Split(",", StringSplitOptions.RemoveEmptyEntries)
|
||||
.Select(o => o.RemovePostFix("/"))
|
||||
.ToArray() ?? [])
|
||||
.WithAbpExposedHeaders()
|
||||
.SetIsOriginAllowedToAllowWildcardSubdomains()
|
||||
.AllowAnyHeader()
|
||||
.AllowAnyMethod()
|
||||
.AllowCredentials();
|
||||
});
|
||||
});
|
||||
}
|
||||
ConfigureCache();
|
||||
ConfigureDataProtection(context, configuration);
|
||||
ConfigureAuthentication(context, configuration);
|
||||
ConfigureCors(context, configuration);
|
||||
}
|
||||
}
|
||||
|
||||
private void ConfigureCache()
|
||||
{
|
||||
Configure<AbpDistributedCacheOptions>(options => { options.KeyPrefix = "KonSoft:"; });
|
||||
}
|
||||
|
||||
private void ConfigureDataProtection(
|
||||
ServiceConfigurationContext context,
|
||||
IConfiguration configuration)
|
||||
{
|
||||
context.Services.AddDataProtection().SetApplicationName("KonSoft")
|
||||
.PersistKeysToStackExchangeRedis(ConnectionMultiplexer.Connect(configuration["Redis:Configuration"]!),
|
||||
"KonSoft-Protection-Keys");
|
||||
|
||||
context.Services.AddSingleton<IDistributedLockProvider>(_ =>
|
||||
new RedisDistributedSynchronizationProvider(ConnectionMultiplexer
|
||||
.Connect(configuration["Redis:Configuration"]!).GetDatabase()));
|
||||
}
|
||||
|
||||
private void ConfigureAuthentication(ServiceConfigurationContext context, IConfiguration configuration)
|
||||
{
|
||||
context.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
|
||||
.AddAbpJwtBearer(options =>
|
||||
{
|
||||
options.Authority = configuration["AuthServer:Authority"];
|
||||
options.RequireHttpsMetadata = configuration.GetValue<bool>("AuthServer:RequireHttpsMetadata");
|
||||
options.Audience = KonSoftConsts.AuthServerAudience;
|
||||
});
|
||||
|
||||
context.Services.Configure<AbpClaimsPrincipalFactoryOptions>(options =>
|
||||
{
|
||||
options.IsDynamicClaimsEnabled = true;
|
||||
});
|
||||
}
|
||||
|
||||
private void ConfigureCors(ServiceConfigurationContext context, IConfiguration configuration)
|
||||
{
|
||||
context.Services.AddCors(options =>
|
||||
{
|
||||
options.AddDefaultPolicy(builder =>
|
||||
{
|
||||
builder
|
||||
.WithOrigins(configuration["App:CorsOrigins"]?
|
||||
.Split(",", StringSplitOptions.RemoveEmptyEntries)
|
||||
.Select(o => o.RemovePostFix("/"))
|
||||
.ToArray() ?? [])
|
||||
.WithAbpExposedHeaders()
|
||||
.SetIsOriginAllowedToAllowWildcardSubdomains()
|
||||
.AllowAnyHeader()
|
||||
.AllowAnyMethod()
|
||||
.AllowCredentials();
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -2,20 +2,19 @@
|
||||
using Volo.Abp.Data;
|
||||
using Volo.Abp.Modularity;
|
||||
|
||||
namespace KonSoft.Shared.Hosting
|
||||
namespace KonSoft.Shared.Hosting;
|
||||
|
||||
[DependsOn(
|
||||
typeof(AbpAutofacModule),
|
||||
typeof(AbpDataModule)
|
||||
)]
|
||||
public class KonSoftSharedHostingModule : AbpModule
|
||||
{
|
||||
[DependsOn(
|
||||
typeof(AbpAutofacModule),
|
||||
typeof(AbpDataModule)
|
||||
)]
|
||||
public class KonSoftSharedHostingModule : AbpModule
|
||||
public override void ConfigureServices(ServiceConfigurationContext context)
|
||||
{
|
||||
public override void ConfigureServices(ServiceConfigurationContext context)
|
||||
Configure<AbpDbConnectionOptions>(options =>
|
||||
{
|
||||
Configure<AbpDbConnectionOptions>(options =>
|
||||
{
|
||||
// TODO Mapping DbConnections
|
||||
});
|
||||
}
|
||||
// TODO Mapping DbConnections
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -5,31 +5,29 @@ using Volo.Abp.Validation;
|
||||
using Volo.Abp.Validation.Localization;
|
||||
using Volo.Abp.VirtualFileSystem;
|
||||
|
||||
namespace KonSoft.Shared.Localization
|
||||
namespace KonSoft.Shared.Localization;
|
||||
|
||||
[DependsOn(
|
||||
typeof(AbpValidationModule)
|
||||
)]
|
||||
public class KonSoftSharedLocalizationModule : AbpModule
|
||||
{
|
||||
|
||||
[DependsOn(
|
||||
typeof(AbpValidationModule)
|
||||
)]
|
||||
public class KonSoftSharedLocalizationModule : AbpModule
|
||||
public override void ConfigureServices(ServiceConfigurationContext context)
|
||||
{
|
||||
public override void ConfigureServices(ServiceConfigurationContext context)
|
||||
Configure<AbpVirtualFileSystemOptions>(options =>
|
||||
{
|
||||
Configure<AbpVirtualFileSystemOptions>(options =>
|
||||
{
|
||||
options.FileSets.AddEmbedded<KonSoftSharedLocalizationModule>();
|
||||
});
|
||||
options.FileSets.AddEmbedded<KonSoftSharedLocalizationModule>();
|
||||
});
|
||||
|
||||
Configure<AbpLocalizationOptions>(options =>
|
||||
{
|
||||
options.Resources
|
||||
.Add<KonSoftResource>("en")
|
||||
.AddBaseTypes(
|
||||
typeof(AbpValidationResource)
|
||||
).AddVirtualJson("/Localization/KonSoft");
|
||||
Configure<AbpLocalizationOptions>(options =>
|
||||
{
|
||||
options.Resources
|
||||
.Add<KonSoftResource>("en")
|
||||
.AddBaseTypes(
|
||||
typeof(AbpValidationResource)
|
||||
).AddVirtualJson("/Localization/KonSoft");
|
||||
|
||||
options.DefaultResourceType = typeof(KonSoftResource);
|
||||
});
|
||||
}
|
||||
options.DefaultResourceType = typeof(KonSoftResource);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,9 +1,8 @@
|
||||
using Volo.Abp.Localization;
|
||||
|
||||
namespace KonSoft.Shared.Localization.Localization
|
||||
namespace KonSoft.Shared.Localization.Localization;
|
||||
|
||||
[LocalizationResourceName("KonSoft")]
|
||||
public class KonSoftResource
|
||||
{
|
||||
[LocalizationResourceName("KonSoft")]
|
||||
public class KonSoftResource
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user