using System.Collections; using System.ComponentModel.DataAnnotations; using System.Text; using HopFrame.Core.Config; using HopFrame.Core.Services; using HopFrame.Web.Components.Pages; using HopFrame.Web.Plugins.Annotations; using HopFrame.Web.Plugins.Events; using HopFrame.Web.Services; using Microsoft.EntityFrameworkCore; using Microsoft.FluentUI.AspNetCore.Components; namespace HopFrame.Web.Plugins.Internal; internal sealed class ExporterPlugin(IContextExplorer explorer, IToastService toasts, IFileService files) { public const char Separator = ';'; [EventHandler] public void OnInit(TableInitializedEvent e) { if (e.Sender.DialogData is not null) return; e.AddPageButton("Export", () => Export(e.Table), icon: new Microsoft.FluentUI.AspNetCore.Components.Icons.Regular.Size20.ArrowUpload()); e.AddPageButton("Import", () => Import(e.Table, e.Sender), icon: new Microsoft.FluentUI.AspNetCore.Components.Icons.Regular.Size20.ArrowDownload()); } private async Task Export(TableConfig table) { var manager = explorer.GetTableManager(table.PropertyName); if (manager is null) { toasts.ShowError("Data could not be exported!"); return; } var data = await manager .LoadPage(0, int.MaxValue); var properties = table.Properties.Where(prop => !prop.IsVirtualProperty).ToArray(); var csv = new StringBuilder(string.Join(Separator, properties.Select(prop => prop.Info.Name)) + '\n'); foreach (var entry in data) { var row = new List(); foreach (var property in properties) { row.Add(FormatProperty(property, entry)); } csv.Append(string.Join(Separator, row) + '\n'); } var result = csv.ToString(); await files.DownloadFile($"{table.DisplayName}.csv", Encoding.UTF8.GetBytes(result)); } private async Task Import(TableConfig table, HopFrameTablePage target) { var file = await files.UploadFile(); var stream = file.OpenReadStream(); var reader = new StreamReader(stream); var properties = table.Properties.Where(prop => !prop.IsVirtualProperty).ToArray(); var data = await reader.ReadToEndAsync(); var rows = data.Split('\n'); reader.Dispose(); await stream.DisposeAsync(); var headerProps = rows.First().Split(Separator); if (!headerProps.Any(h => properties.Any(prop => prop.Info.Name == h))) { toasts.ShowError("Table header in csv is not valid!"); return; } var elements = new List(); for (int rowIndex = 1; rowIndex < rows.Length; rowIndex++) { var row = rows[rowIndex]; if (string.IsNullOrWhiteSpace(row)) continue; var element = Activator.CreateInstance(table.TableType)!; var rowValues = row.Split(Separator); for (int i = 0; i < headerProps.Length; i++) { var property = properties.FirstOrDefault(prop => prop.Info.Name == headerProps[i]); if (property is null) continue; object? value = rowValues[i]; if (string.IsNullOrWhiteSpace((string)value)) continue; if (property.IsEnumerable) { if (!property.Info.PropertyType.IsGenericType) continue; var formattedEnumerable = (string)value; if (formattedEnumerable == "[]") continue; var values = formattedEnumerable .TrimStart('[') .TrimEnd(']') .Split(','); var addMethod = property.Info.PropertyType.GetMethod("Add"); if (addMethod is null) continue; var tableType = property.Info.PropertyType.GenericTypeArguments[0]; var relationManager = explorer.GetTableManager(tableType); var primaryKeyType = GetPrimaryKeyType(tableType); if (relationManager is null || primaryKeyType is null) continue; var enumerable = Activator.CreateInstance(property.Info.PropertyType); foreach (var key in values) { var entry = await relationManager.GetOne(ParseString(key, primaryKeyType)!); if (entry is null) continue; addMethod.Invoke(enumerable, [entry]); } property.Info.SetValue(element, enumerable); continue; } if (property.IsRelation) { var relationManager = explorer.GetTableManager(property.Info.PropertyType); var relationPrimaryKeyType = GetPrimaryKeyType(property.Info.PropertyType); if (relationManager is null || relationPrimaryKeyType is null) continue; value = await relationManager.GetOne(ParseString((string)value, relationPrimaryKeyType)!); } else if (property.Info.PropertyType == typeof(Guid)) { var success = Guid.TryParse((string)value, out var guid); if (success) value = guid; else toasts.ShowError($"'{value}' is not a valid guid"); } else { value = ParseString((string)value, property.Info.PropertyType); } property.Info.SetValue(element, value); } elements.Add(element); } var manager = explorer.GetTableManager(table.PropertyName); if (manager is null) { toasts.ShowError("Data could not be imported!"); return; } await manager.AddAll(elements); await target.Reload(); } private string FormatProperty(PropertyConfig property, object entity) { var value = property.Info.GetValue(entity); if (value is null) return string.Empty; if (property.IsEnumerable) { var enumerable = (IEnumerable)value; return '[' + string.Join(',', enumerable.OfType().Select(o => SelectPrimaryKey(o, property) ?? o.ToString())) + ']'; } return SelectPrimaryKey(value, property) ?? value.ToString() ?? string.Empty; } private string? SelectPrimaryKey(object entity, PropertyConfig config) { if (config.IsRelation) { var table = explorer.GetTable(entity.GetType()); if (table?.ContextConfig is RepositoryGroupConfig repoConfig) { return repoConfig.KeyProperty.GetValue(entity)?.ToString(); } } return entity .GetType() .GetProperties() .FirstOrDefault(prop => prop .GetCustomAttributes(true) .Any(attr => attr is KeyAttribute))? .GetValue(entity)? .ToString(); } private Type? GetPrimaryKeyType(Type tableType) { var table = explorer.GetTable(tableType); if (table?.ContextConfig is RepositoryGroupConfig repoConfig) { return repoConfig.KeyProperty.PropertyType; } return tableType .GetProperties() .FirstOrDefault(prop => prop .GetCustomAttributes(true) .Any(attr => attr is KeyAttribute))? .PropertyType; } private object? ParseString(string input, Type targetType) { try { var parseMethod = targetType .GetMethods() .Where(method => method.Name.StartsWith("Parse")) .FirstOrDefault(method => method.GetParameters().SingleOrDefault()?.ParameterType == typeof(string)); if (parseMethod is not null) return parseMethod.Invoke(null, [input]); return Convert.ChangeType(input, targetType); } catch (Exception) { return null; } } }