462 lines
22 KiB
C#
462 lines
22 KiB
C#
using System;
|
||
using System.Collections.Generic;
|
||
using System.Data;
|
||
using System.IO;
|
||
using System.Linq;
|
||
using System.Text.Json;
|
||
using System.Threading.Tasks;
|
||
using Microsoft.Extensions.Logging;
|
||
using Sozsoft.Languages.Entities;
|
||
using Sozsoft.Languages.Languages;
|
||
using Sozsoft.Platform.Entities;
|
||
using Sozsoft.Platform.Enums;
|
||
using Sozsoft.Platform.ListForms;
|
||
using Volo.Abp.Data;
|
||
using Volo.Abp.DependencyInjection;
|
||
using Volo.Abp.Domain.Repositories;
|
||
using Volo.Abp.MultiTenancy;
|
||
using Volo.Abp.PermissionManagement;
|
||
using static Sozsoft.Platform.PlatformConsts;
|
||
|
||
namespace Sozsoft.Platform.Data.Seeds;
|
||
|
||
/// <summary>
|
||
/// Wizard ile oluşturulan konfigürasyonları Seeds/WizardData/*.json dosyalarından okuyarak veritabanına aktarır.
|
||
/// Wizard çalıştıktan sonra oluşturulan JSON dosyaları projeye commit edildikten sonra,
|
||
/// veritabanı silinip yeniden oluşturulduğunda bu seeder tüm wizard konfigürasyonlarını geri yükler.
|
||
/// </summary>
|
||
public class WizardDataSeeder : IDataSeedContributor, ITransientDependency
|
||
{
|
||
private readonly IRepository<LanguageKey, Guid> _repoLangKey;
|
||
private readonly IRepository<LanguageText, Guid> _repoLangText;
|
||
private readonly IRepository<PermissionGroupDefinitionRecord, Guid> _repoPermGroup;
|
||
private readonly IRepository<PermissionDefinitionRecord, Guid> _repoPerm;
|
||
private readonly IPermissionGrantRepository _permissionGrantRepository;
|
||
private readonly IRepository<Menu, Guid> _repoMenu;
|
||
private readonly IRepository<DataSource, Guid> _repoDataSource;
|
||
private readonly IRepository<ListForm, Guid> _repoListForm;
|
||
private readonly IRepository<ListFormField, Guid> _repoListFormField;
|
||
private readonly ILogger<WizardDataSeeder> _logger;
|
||
|
||
private readonly string _cultureNameDefault = PlatformConsts.DefaultLanguage;
|
||
private readonly string _appName = PlatformConsts.AppName;
|
||
|
||
public WizardDataSeeder(
|
||
IRepository<LanguageKey, Guid> repoLangKey,
|
||
IRepository<LanguageText, Guid> repoLangText,
|
||
IRepository<PermissionGroupDefinitionRecord, Guid> repoPermGroup,
|
||
IRepository<PermissionDefinitionRecord, Guid> repoPerm,
|
||
IPermissionGrantRepository permissionGrantRepository,
|
||
IRepository<Menu, Guid> repoMenu,
|
||
IRepository<DataSource, Guid> repoDataSource,
|
||
IRepository<ListForm, Guid> repoListForm,
|
||
IRepository<ListFormField, Guid> repoListFormField,
|
||
ILogger<WizardDataSeeder> logger)
|
||
{
|
||
_repoLangKey = repoLangKey;
|
||
_repoLangText = repoLangText;
|
||
_repoPermGroup = repoPermGroup;
|
||
_repoPerm = repoPerm;
|
||
_permissionGrantRepository = permissionGrantRepository;
|
||
_repoMenu = repoMenu;
|
||
_repoDataSource = repoDataSource;
|
||
_repoListForm = repoListForm;
|
||
_repoListFormField = repoListFormField;
|
||
_logger = logger;
|
||
}
|
||
|
||
public async Task SeedAsync(DataSeedContext context)
|
||
{
|
||
var wizardDataPath = Path.Combine(Directory.GetCurrentDirectory(), "Seeds", "WizardData");
|
||
if (!Directory.Exists(wizardDataPath))
|
||
{
|
||
_logger.LogInformation("WizardData directory not found, skipping.");
|
||
return;
|
||
}
|
||
|
||
var jsonFiles = Directory.GetFiles(wizardDataPath, "*.json").OrderBy(f => Path.GetFileName(f)).ToArray();
|
||
if (jsonFiles.Length == 0)
|
||
{
|
||
_logger.LogInformation("No JSON files found in WizardData directory, skipping.");
|
||
return;
|
||
}
|
||
|
||
_logger.LogInformation("WizardDataSeeder started. {Count} files to be processed.", jsonFiles.Length);
|
||
|
||
var options = new JsonSerializerOptions { PropertyNameCaseInsensitive = true };
|
||
|
||
foreach (var filePath in jsonFiles)
|
||
{
|
||
try
|
||
{
|
||
var json = await File.ReadAllTextAsync(filePath);
|
||
var seedFile = JsonSerializer.Deserialize<WizardSeedFileDto>(json, options);
|
||
|
||
if (seedFile?.Wizard == null)
|
||
{
|
||
_logger.LogWarning("Invalid file skipped: {FilePath}", filePath);
|
||
continue;
|
||
}
|
||
|
||
var wizardName = seedFile.Wizard.WizardName?.Trim();
|
||
if (string.IsNullOrWhiteSpace(wizardName))
|
||
{
|
||
_logger.LogWarning("WizardName is empty, skipped: {FilePath}", filePath);
|
||
continue;
|
||
}
|
||
|
||
var fileName = Path.GetFileName(filePath);
|
||
|
||
// Zaten seeded mi kontrol et (ListForm var mı?)
|
||
if (await _repoListForm.AnyAsync(a => a.ListFormCode == seedFile.Wizard.ListFormCode))
|
||
{
|
||
_logger.LogInformation("[{File}] '{WizardName}' already exists, skipped.", fileName, wizardName);
|
||
continue;
|
||
}
|
||
|
||
_logger.LogInformation("[{File}] '{WizardName}' is being applied...", fileName, wizardName);
|
||
await ApplyWizardSeedAsync(seedFile);
|
||
_logger.LogInformation("[{File}] '{WizardName}' has been successfully applied.", fileName, wizardName);
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
_logger.LogError(ex, $"Error - {filePath}: {ex.Message}");
|
||
}
|
||
}
|
||
}
|
||
|
||
private async Task ApplyWizardSeedAsync(WizardSeedFileDto seedFile)
|
||
{
|
||
var input = seedFile.Wizard;
|
||
var isDeleted = seedFile.IsDeletedField;
|
||
var isCreated = seedFile.IsCreatedField;
|
||
input.SubForms ??= new List<SubFormDto>();
|
||
input.Widgets ??= new List<WidgetEditDto>();
|
||
input.Workflow ??= new WorkflowDto();
|
||
input.WorkflowCriteria ??= new List<ListFormWorkflowCriteriaDto>();
|
||
input.Workflow.Criteria = input.WorkflowCriteria;
|
||
|
||
var wizardName = input.WizardName.Trim();
|
||
var code = string.IsNullOrWhiteSpace(input.MenuCode)
|
||
? WizardConsts.WizardKey(wizardName)
|
||
: input.MenuCode.Trim();
|
||
var listFormCode = string.IsNullOrWhiteSpace(input.ListFormCode)
|
||
? code
|
||
: input.ListFormCode.Trim();
|
||
var titleLangKey = $"{listFormCode}.Title";
|
||
var nameLangKey = code;
|
||
var descLangKey = $"{listFormCode}.Desc";
|
||
var permCreateName = $"{code}.Create";
|
||
var permUpdateName = $"{code}.Update";
|
||
var permDeleteName = $"{code}.Delete";
|
||
var permExportName = $"{code}.Export";
|
||
var permImportName = $"{code}.Import";
|
||
var permNoteName = $"{code}.Note";
|
||
|
||
// Dil - Language Keys
|
||
await CreateLangKeyAsync(nameLangKey, input.LanguageTextMenuEn, input.LanguageTextMenuTr);
|
||
await CreateLangKeyAsync(titleLangKey, input.LanguageTextTitleEn, input.LanguageTextTitleTr);
|
||
await CreateLangKeyAsync(descLangKey, input.LanguageTextDescEn, input.LanguageTextDescTr);
|
||
|
||
// Permission Group
|
||
var groupName = input.PermissionGroupName ?? AppName;
|
||
if (!await _repoPermGroup.AnyAsync(a => a.Name == groupName))
|
||
{
|
||
await _repoPermGroup.InsertAsync(
|
||
new PermissionGroupDefinitionRecord(Guid.NewGuid(), groupName, groupName), autoSave: true);
|
||
if (string.Equals(groupName, input.MenuParentCode, StringComparison.OrdinalIgnoreCase))
|
||
await EnsureLangKeyAsync(groupName);
|
||
else
|
||
await CreateLangKeyAsync(groupName, groupName, groupName);
|
||
}
|
||
|
||
// Permissions - tek seferde mevcut permission'ları çek, sonra her birini kontrol et
|
||
var permQueryable = await _repoPerm.GetQueryableAsync();
|
||
var existingPerms = permQueryable.Where(a => a.GroupName == groupName).ToList();
|
||
|
||
var permRead = existingPerms.FirstOrDefault(a => a.Name == code);
|
||
if (permRead == null)
|
||
permRead = await _repoPerm.InsertAsync(new PermissionDefinitionRecord(
|
||
Guid.NewGuid(), groupName, code, null, nameLangKey, true, MultiTenancySides.Both), autoSave: true);
|
||
|
||
var permCreate = existingPerms.FirstOrDefault(a => a.Name == permCreateName);
|
||
if (permCreate == null)
|
||
permCreate = await _repoPerm.InsertAsync(new PermissionDefinitionRecord(
|
||
Guid.NewGuid(), groupName, permCreateName, permRead.Name, WizardConsts.LangKeyCreate, true, MultiTenancySides.Both), autoSave: true);
|
||
|
||
var permUpdate = existingPerms.FirstOrDefault(a => a.Name == permUpdateName);
|
||
if (permUpdate == null)
|
||
permUpdate = await _repoPerm.InsertAsync(new PermissionDefinitionRecord(
|
||
Guid.NewGuid(), groupName, permUpdateName, permRead.Name, WizardConsts.LangKeyUpdate, true, MultiTenancySides.Both), autoSave: true);
|
||
|
||
var permDelete = existingPerms.FirstOrDefault(a => a.Name == permDeleteName);
|
||
if (permDelete == null)
|
||
permDelete = await _repoPerm.InsertAsync(new PermissionDefinitionRecord(
|
||
Guid.NewGuid(), groupName, permDeleteName, permRead.Name, WizardConsts.LangKeyDelete, true, MultiTenancySides.Both), autoSave: true);
|
||
|
||
var permExport = existingPerms.FirstOrDefault(a => a.Name == permExportName);
|
||
if (permExport == null)
|
||
permExport = await _repoPerm.InsertAsync(new PermissionDefinitionRecord(
|
||
Guid.NewGuid(), groupName, permExportName, permRead.Name, WizardConsts.LangKeyExport, true, MultiTenancySides.Both), autoSave: true);
|
||
|
||
var permImport = existingPerms.FirstOrDefault(a => a.Name == permImportName);
|
||
if (permImport == null)
|
||
permImport = await _repoPerm.InsertAsync(new PermissionDefinitionRecord(
|
||
Guid.NewGuid(), groupName, permImportName, permRead.Name, WizardConsts.LangKeyImport, true, MultiTenancySides.Both), autoSave: true);
|
||
|
||
var permNote = existingPerms.FirstOrDefault(a => a.Name == permNoteName);
|
||
if (permNote == null)
|
||
permNote = await _repoPerm.InsertAsync(new PermissionDefinitionRecord(
|
||
Guid.NewGuid(), groupName, permNoteName, permRead.Name, WizardConsts.LangKeyNote, true, MultiTenancySides.Both), autoSave: true);
|
||
|
||
// // Permission Grants - Admin role için, sadece eksik olanları ekle
|
||
// var existingGrants = await _permissionGrantRepository.GetListAsync("R", PlatformConsts.AbpIdentity.User.AdminRoleName);
|
||
// var existingGrantNames = new HashSet<string>(existingGrants.Select(g => g.Name));
|
||
|
||
// var grantsToInsert = new[]
|
||
// {
|
||
// permRead.Name, permCreate.Name, permUpdate.Name,
|
||
// permDelete.Name, permExport.Name, permImport.Name, permNote.Name
|
||
// }
|
||
// .Where(name => !existingGrantNames.Contains(name))
|
||
// .Select(name => new PermissionGrant(Guid.NewGuid(), name, "R", PlatformConsts.AbpIdentity.User.AdminRoleName))
|
||
// .ToList();
|
||
|
||
// if (grantsToInsert.Count > 0)
|
||
// {
|
||
// await _permissionGrantRepository.InsertManyAsync(grantsToInsert, autoSave: true);
|
||
// }
|
||
|
||
// Menu Parent
|
||
var menuQueryable = await _repoMenu.GetQueryableAsync();
|
||
var menuParent = await _repoMenu.FirstOrDefaultAsync(a => a.Code == input.MenuParentCode);
|
||
if (menuParent == null)
|
||
{
|
||
var maxRootOrder = menuQueryable.Where(a => a.ParentCode == null || a.ParentCode == "").Select(a => (int?)a.Order).Max() ?? 0;
|
||
var menuParentIcon = !string.IsNullOrWhiteSpace(input.MenuParentIcon)
|
||
? input.MenuParentIcon
|
||
: !string.IsNullOrWhiteSpace(input.MenuIcon)
|
||
? input.MenuIcon
|
||
: WizardConsts.MenuIcon;
|
||
await CreateLangKeyAsync(input.MenuParentCode, input.LanguageTextMenuParentEn, input.LanguageTextMenuParentTr);
|
||
menuParent = await _repoMenu.InsertAsync(new Menu
|
||
{
|
||
Code = input.MenuParentCode,
|
||
DisplayName = input.MenuParentCode,
|
||
IsDisabled = false,
|
||
Icon = menuParentIcon,
|
||
Order = maxRootOrder + 1,
|
||
}, autoSave: true);
|
||
}
|
||
|
||
// Menu
|
||
var maxChildOrder = menuQueryable.Where(a => a.ParentCode == menuParent.Code).Select(a => (int?)a.Order).Max() ?? 0;
|
||
var existingMenu = await _repoMenu.FirstOrDefaultAsync(a => a.Code == code);
|
||
if (existingMenu == null)
|
||
{
|
||
await _repoMenu.InsertAsync(new Menu
|
||
{
|
||
Code = code,
|
||
DisplayName = nameLangKey,
|
||
IsDisabled = false,
|
||
ParentCode = menuParent.Code,
|
||
Icon = input.MenuIcon ?? WizardConsts.MenuIcon,
|
||
Target = null,
|
||
ElementId = null,
|
||
CssClass = null,
|
||
Url = WizardConsts.MenuUrl(code),
|
||
RequiredPermissionName = permRead.Name,
|
||
Order = maxChildOrder + 1,
|
||
}, autoSave: true);
|
||
}
|
||
|
||
// DataSource
|
||
var existingDataSource = await _repoDataSource.FirstOrDefaultAsync(a => a.Code == input.DataSourceCode);
|
||
if (existingDataSource == null)
|
||
{
|
||
await _repoDataSource.InsertAsync(new DataSource
|
||
{
|
||
Code = input.DataSourceCode,
|
||
DataSourceType = input.DataSourceConnectionString != null &&
|
||
input.DataSourceConnectionString.IndexOf("Server", StringComparison.OrdinalIgnoreCase) >= 0
|
||
? DataSourceTypeEnum.Mssql
|
||
: DataSourceTypeEnum.Postgresql,
|
||
ConnectionString = input.DataSourceConnectionString
|
||
}, autoSave: true);
|
||
}
|
||
|
||
|
||
// EditingFormJson
|
||
var editingFormDtos = input.Groups
|
||
.Select((g, gi) => new EditingFormDto
|
||
{
|
||
Order = gi + 1,
|
||
Caption = g.Caption,
|
||
ColCount = g.ColCount,
|
||
ColSpan = g.ColCount,
|
||
ItemType = "group",
|
||
Items = g.Items
|
||
.Where(i => i.DataField != input.KeyFieldName)
|
||
.Select((it, ii) => new EditingFormItemDto
|
||
{
|
||
Order = ii + 1,
|
||
DataField = it.DataField,
|
||
EditorType2 = it.EditorType,
|
||
ColSpan = it.ColSpan,
|
||
EditorOptions = string.IsNullOrWhiteSpace(it.EditorOptions) ? null : it.EditorOptions,
|
||
EditorScript = string.IsNullOrWhiteSpace(it.EditorScript) ? null : it.EditorScript,
|
||
IsRequired = it.IsRequired,
|
||
})
|
||
.ToArray()
|
||
})
|
||
.ToList();
|
||
|
||
// ListForm - varsa sil, yeniden ekle
|
||
var existingListForm = await _repoListForm.FirstOrDefaultAsync(a => a.ListFormCode == input.ListFormCode);
|
||
if (existingListForm != null)
|
||
{
|
||
await _repoListForm.DeleteAsync(existingListForm, autoSave: true);
|
||
}
|
||
|
||
var existingListFormFields = await _repoListFormField.GetListAsync(a => a.ListFormCode == input.ListFormCode);
|
||
if (existingListFormFields.Count > 0)
|
||
{
|
||
await _repoListFormField.DeleteManyAsync(existingListFormFields, autoSave: true);
|
||
}
|
||
|
||
// ListForm
|
||
await _repoListForm.InsertAsync(new ListForm
|
||
{
|
||
ListFormType = ListFormTypeEnum.List,
|
||
PageSize = 10,
|
||
ExportJson = WizardConsts.DefaultExportJson,
|
||
IsSubForm = false,
|
||
ShowNote = input.SubForms.Count > 0,
|
||
LayoutJson = WizardConsts.DefaultLayoutJson(input.DefaultLayout, input.Grid, input.Pivot, input.Tree, input.Chart, input.Gantt, input.Scheduler),
|
||
CultureName = LanguageCodes.En,
|
||
ListFormCode = input.ListFormCode,
|
||
Name = nameLangKey,
|
||
Title = titleLangKey,
|
||
Description = descLangKey,
|
||
DataSourceCode = input.DataSourceCode,
|
||
IsTenant = input.IsTenant,
|
||
IsBranch = input.IsBranch,
|
||
IsOrganizationUnit = input.IsOrganizationUnit,
|
||
SelectCommandType = input.SelectCommandType,
|
||
SelectCommand = input.SelectCommand,
|
||
KeyFieldName = input.KeyFieldName,
|
||
KeyFieldDbSourceType = input.KeyFieldDbSourceType,
|
||
DefaultFilter = isDeleted ? WizardConsts.DefaultFilterJson : null,
|
||
SortMode = GridOptions.SortModeSingle,
|
||
FilterRowJson = WizardConsts.DefaultFilterRowJson,
|
||
HeaderFilterJson = WizardConsts.DefaultHeaderFilterJson,
|
||
SearchPanelJson = WizardConsts.DefaultSearchPanelJson,
|
||
GroupPanelJson = JsonSerializer.Serialize(new { Visible = false }),
|
||
SelectionJson = WizardConsts.DefaultSelectionSingleJson,
|
||
ColumnOptionJson = WizardConsts.DefaultColumnOptionJson(),
|
||
PermissionJson = WizardConsts.DefaultPermissionJson(code),
|
||
DeleteCommand = isDeleted ? WizardConsts.DefaultDeleteCommand(input.SelectCommand) : null,
|
||
DeleteFieldsDefaultValueJson = isDeleted
|
||
? WizardConsts.DefaultDeleteFieldsDefaultValueJson(input.KeyFieldDbSourceType)
|
||
: WizardConsts.DefaultFieldsJsonOnlyId(input.KeyFieldDbSourceType),
|
||
InsertFieldsDefaultValueJson = isCreated
|
||
? WizardConsts.DefaultInsertFieldsDefaultValueJson(input.KeyFieldDbSourceType)
|
||
: WizardConsts.DefaultFieldsJsonOnlyId(input.KeyFieldDbSourceType),
|
||
PagerOptionJson = WizardConsts.DefaultPagerOptionJson,
|
||
EditingOptionJson = WizardConsts.DefaultEditingOptionJson(titleLangKey, 600, 500, input.AllowDeleting, input.AllowAdding, input.AllowUpdating, input.ConfirmDelete, false, input.AllowDetail),
|
||
EditingFormJson = editingFormDtos.Count > 0 ? JsonSerializer.Serialize(editingFormDtos) : null,
|
||
SubFormsJson = input.SubForms.Count > 0 ? JsonSerializer.Serialize(input.SubForms) : null,
|
||
WidgetsJson = input.Widgets.Count > 0 ? JsonSerializer.Serialize(input.Widgets) : null,
|
||
WorkflowJson = HasWorkflow(input.Workflow, input.WorkflowCriteria) ? JsonSerializer.Serialize(input.Workflow) : null,
|
||
}, autoSave: true);
|
||
|
||
// ListFormFields
|
||
var fieldOrder = 0;
|
||
foreach (var group in input.Groups)
|
||
{
|
||
foreach (var item in group.Items)
|
||
{
|
||
fieldOrder++;
|
||
await _repoListFormField.InsertAsync(new ListFormField
|
||
{
|
||
ListFormCode = input.ListFormCode,
|
||
FieldName = item.DataField,
|
||
CaptionName = item.CaptionName,
|
||
Visible = item.DataField != input.KeyFieldName,
|
||
IsActive = true,
|
||
Width = 0, //Fit columns için hepsine sıfır veriyorum.
|
||
AllowSearch = true,
|
||
ListOrderNo = fieldOrder,
|
||
SourceDbType = item.DbSourceType,
|
||
CultureName = PlatformConsts.DefaultLanguage,
|
||
PermissionJson = WizardConsts.DefaultFieldPermissionJson(code),
|
||
ColumnCustomizationJson = WizardConsts.DefaultColumnCustomizationJson,
|
||
ColumnFilterJson = WizardConsts.DefaultColumnFilteringJson,
|
||
PivotSettingsJson = WizardConsts.DefaultPivotSettingsJson,
|
||
LookupJson = !string.IsNullOrWhiteSpace(item.LookupQuery)
|
||
? WizardConsts.DefaultLookupJson(item.LookupDataSourceType, item.DisplayExpr, item.ValueExpr, item.LookupQuery)
|
||
: null,
|
||
}, autoSave: true);
|
||
|
||
await CreateLangKeyAsync(item.CaptionName, item.EnglishCaption, item.TurkishCaption);
|
||
}
|
||
}
|
||
}
|
||
|
||
private static bool HasWorkflow(WorkflowDto workflow, List<ListFormWorkflowCriteriaDto> criteria)
|
||
{
|
||
return workflow != null && (
|
||
!string.IsNullOrWhiteSpace(workflow.ApprovalUserFieldName) ||
|
||
!string.IsNullOrWhiteSpace(workflow.ApprovalDateFieldName) ||
|
||
!string.IsNullOrWhiteSpace(workflow.ApprovalStatusFieldName) ||
|
||
!string.IsNullOrWhiteSpace(workflow.ApprovalDescriptionFieldName) ||
|
||
(criteria?.Count ?? 0) > 0
|
||
);
|
||
}
|
||
|
||
private async Task CreateLangKeyAsync(string key, string textEn, string textTr)
|
||
{
|
||
if (string.IsNullOrWhiteSpace(key)) return;
|
||
|
||
var langKey = await _repoLangKey.FirstOrDefaultAsync(a => a.ResourceName == _appName && a.Key == key)
|
||
?? await _repoLangKey.InsertAsync(new LanguageKey { ResourceName = _appName, Key = key }, autoSave: true);
|
||
|
||
var existingTexts = await _repoLangText.GetListAsync(a => a.ResourceName == _appName && a.Key == langKey.Key);
|
||
|
||
var existingEn = existingTexts.FirstOrDefault(a => a.CultureName == _cultureNameDefault);
|
||
if (existingEn == null)
|
||
{
|
||
await _repoLangText.InsertAsync(new LanguageText
|
||
{
|
||
ResourceName = _appName,
|
||
Key = langKey.Key,
|
||
CultureName = _cultureNameDefault,
|
||
Value = textEn ?? key
|
||
}, autoSave: true);
|
||
}
|
||
|
||
var existingTr = existingTexts.FirstOrDefault(a => a.CultureName == LanguageCodes.Tr);
|
||
if (existingTr == null)
|
||
{
|
||
await _repoLangText.InsertAsync(new LanguageText
|
||
{
|
||
ResourceName = _appName,
|
||
Key = langKey.Key,
|
||
CultureName = LanguageCodes.Tr,
|
||
Value = textTr ?? key
|
||
}, autoSave: true);
|
||
}
|
||
}
|
||
|
||
private async Task EnsureLangKeyAsync(string key)
|
||
{
|
||
if (string.IsNullOrWhiteSpace(key)) return;
|
||
|
||
if (!await _repoLangKey.AnyAsync(a => a.ResourceName == _appName && a.Key == key))
|
||
{
|
||
await _repoLangKey.InsertAsync(new LanguageKey { ResourceName = _appName, Key = key }, autoSave: true);
|
||
}
|
||
}
|
||
}
|
||
|
||
|