Продолжаем работать с билдером
This commit is contained in:
@@ -1,247 +0,0 @@
|
||||
using System.Collections;
|
||||
using System.ComponentModel;
|
||||
using Pf2e.Database.Model;
|
||||
using Pf2e.Database.Model.Base.Keywords;
|
||||
using Pf2e.Database.Model.Base.Source;
|
||||
using Pf2e.Database.Model.Base.Targets;
|
||||
using Pf2e.Database.Model.Common.I18n;
|
||||
using Pf2e.Database.Model.Enum;
|
||||
|
||||
namespace Pf2e.Web.Helpers;
|
||||
|
||||
public class EntityFieldFormatter
|
||||
{
|
||||
/// <summary>
|
||||
/// Форматировать значение для отображения.
|
||||
/// </summary>
|
||||
public string? FormatValue(object? value)
|
||||
{
|
||||
if (value == null)
|
||||
return null;
|
||||
|
||||
Type type = value.GetType();
|
||||
|
||||
foreach (ValueFormatter formatter in _formatters)
|
||||
{
|
||||
if (formatter.CanFormat(type))
|
||||
{
|
||||
return formatter.Format(value);
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Абстрактный класс форматтера значений.
|
||||
/// </summary>
|
||||
private abstract class ValueFormatter
|
||||
{
|
||||
/// <summary>
|
||||
/// Проверяет, может ли форматтер обработать указанный тип.
|
||||
/// </summary>
|
||||
/// <param name="type">Тип значения.</param>
|
||||
/// <returns><c>true</c>, если форматтер может обработать тип, иначе <c>false</c>.</returns>
|
||||
public abstract bool CanFormat(Type type);
|
||||
|
||||
/// <summary>
|
||||
/// Форматирует значение для отображения.
|
||||
/// </summary>
|
||||
/// <param name="value">Значение для форматирования.</param>
|
||||
/// <returns>Строковое представление отформатированного значения.</returns>
|
||||
public abstract string Format(object value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Форматтер для конкретного типа.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">Тип значения.</typeparam>
|
||||
/// <param name="formatter">Функция форматирования значения.</param>
|
||||
private class TypeValueFormatter<T>(Func<object, string> formatter) : ValueFormatter
|
||||
{
|
||||
public override bool CanFormat(Type type) => type == typeof(T);
|
||||
|
||||
public override string Format(object value) => formatter(value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Форматтер с пользовательскими функциями проверки и форматирования.
|
||||
/// </summary>
|
||||
/// <param name="checker">Функция проверки типа.</param>
|
||||
/// <param name="formatter">Функция форматирования значения.</param>
|
||||
private class CustomValueFormatter(Func<Type, bool> checker, Func<object, string> formatter)
|
||||
: ValueFormatter
|
||||
{
|
||||
public override bool CanFormat(Type type) => checker(type);
|
||||
|
||||
public override string Format(object value) => formatter(value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Форматтер для коллекций.
|
||||
/// </summary>
|
||||
private class CollectionFormatter(EntityFieldFormatter fieldFormatter) : ValueFormatter
|
||||
{
|
||||
public override bool CanFormat(Type type) =>
|
||||
typeof(IEnumerable).IsAssignableFrom(type) && type != typeof(string);
|
||||
|
||||
public override string Format(object value)
|
||||
{
|
||||
IEnumerable collection = (IEnumerable)value;
|
||||
List<string> items = [];
|
||||
|
||||
foreach (object? item in collection)
|
||||
{
|
||||
string? itemDisplay = fieldFormatter.FormatValue(item);
|
||||
items.Add(itemDisplay ?? "null");
|
||||
}
|
||||
|
||||
return items.Count == 0 ? "(empty)" : string.Join("<br/>", items);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Коллекция форматтеров.
|
||||
/// </summary>
|
||||
private readonly List<ValueFormatter> _formatters;
|
||||
|
||||
/// <summary>
|
||||
/// Получить описание элемента перечисления из DescriptionAttribute.
|
||||
/// </summary>
|
||||
/// <param name="value">Элемент перечисления.</param>
|
||||
/// <returns>Описание или имя элемента, если атрибут не найден.</returns>
|
||||
private static string GetEnumDescription(Enum value)
|
||||
{
|
||||
Type type = value.GetType();
|
||||
string name = Enum.GetName(type, value) ?? value.ToString();
|
||||
|
||||
System.Reflection.FieldInfo? field = type.GetField(name);
|
||||
if (field == null)
|
||||
return value.ToString();
|
||||
|
||||
if (
|
||||
Attribute.GetCustomAttribute(field, typeof(DescriptionAttribute))
|
||||
is DescriptionAttribute attr
|
||||
)
|
||||
{
|
||||
return attr.Description;
|
||||
}
|
||||
|
||||
return value.ToString();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Конструктор EntitySerializer.
|
||||
/// </summary>
|
||||
public EntityFieldFormatter()
|
||||
{
|
||||
_formatters =
|
||||
[
|
||||
// Форматтеры для перечислений
|
||||
new CustomValueFormatter(
|
||||
(type) => type.IsEnum,
|
||||
(value) =>
|
||||
{
|
||||
Enum enumValue = (Enum)value;
|
||||
string description = GetEnumDescription(enumValue);
|
||||
return $"{description} ({enumValue})";
|
||||
}
|
||||
),
|
||||
// Примитивные типы
|
||||
new CustomValueFormatter(
|
||||
(type) => type.IsPrimitive || type == typeof(string) || type == typeof(decimal),
|
||||
(value) => value.ToString() ?? "null"
|
||||
),
|
||||
// LocaleString
|
||||
new TypeValueFormatter<LocaleString>(
|
||||
(value) =>
|
||||
{
|
||||
LocaleString localeString = (LocaleString)value;
|
||||
List<string> items = [];
|
||||
foreach (LocaleItem item in localeString)
|
||||
{
|
||||
items.Add($"{item.Locale}: {item.Value}");
|
||||
}
|
||||
return string.Join("<br/>", items);
|
||||
}
|
||||
),
|
||||
// IEntity
|
||||
new CustomValueFormatter(
|
||||
(type) => typeof(IEntity).IsAssignableFrom(type),
|
||||
(value) =>
|
||||
{
|
||||
IEntity entity = (IEntity)value;
|
||||
EntityType entityType = entity.Entity.Type;
|
||||
string typeName = PgNameHelper.GetPgName(entityType);
|
||||
string slug = entity.Entity.Slug;
|
||||
string name = entity.Entity.Name[LocaleType.English];
|
||||
return $"<a href=\"/{typeName}/{slug}\">{name}</a>";
|
||||
}
|
||||
),
|
||||
// EntitySource
|
||||
new TypeValueFormatter<EntitySource>(
|
||||
(value) =>
|
||||
{
|
||||
EntitySource entitySource = (EntitySource)value;
|
||||
string sourceName = entitySource.Source.Name;
|
||||
string suffix = !string.IsNullOrEmpty(entitySource.Suffix)
|
||||
? $" ({entitySource.Suffix})"
|
||||
: "";
|
||||
string isRemaster = entitySource.IsRemaster ? " [Remaster]" : "";
|
||||
return $"{sourceName}{suffix}{isRemaster}";
|
||||
}
|
||||
),
|
||||
// Keyword
|
||||
new TypeValueFormatter<Keyword>(
|
||||
(value) =>
|
||||
{
|
||||
Keyword keyword = (Keyword)value;
|
||||
string name = keyword.Name[LocaleType.English];
|
||||
return $"{name}";
|
||||
}
|
||||
),
|
||||
// KeywordAlias
|
||||
new TypeValueFormatter<KeywordAlias>(
|
||||
(value) =>
|
||||
{
|
||||
KeywordAlias alias = (KeywordAlias)value;
|
||||
return $"{alias.Value}";
|
||||
}
|
||||
),
|
||||
// ExternalTarget
|
||||
new TypeValueFormatter<ExternalTarget>(
|
||||
(value) =>
|
||||
{
|
||||
ExternalTarget target = (ExternalTarget)value;
|
||||
return $"{target.Name} (Icon: {target.Icon}, Prefix: {target.Prefix})";
|
||||
}
|
||||
),
|
||||
// EntityExternal
|
||||
new TypeValueFormatter<EntityExternal>(
|
||||
(value) =>
|
||||
{
|
||||
EntityExternal entityExternal = (EntityExternal)value;
|
||||
string targetName = entityExternal.Target?.Name ?? "Unknown";
|
||||
string externalId = !string.IsNullOrEmpty(entityExternal.ExternalId)
|
||||
? $" ID: {entityExternal.ExternalId}"
|
||||
: "";
|
||||
string suffix = !string.IsNullOrEmpty(entityExternal.Suffix)
|
||||
? $" Suffix: {entityExternal.Suffix}"
|
||||
: "";
|
||||
return $"{targetName}{externalId}{suffix}";
|
||||
}
|
||||
),
|
||||
// Source
|
||||
new TypeValueFormatter<Source>(
|
||||
(value) =>
|
||||
{
|
||||
Source source = (Source)value;
|
||||
string releaseDate = source.ReleaseDate?.ToString("yyyy-MM-dd") ?? "Unknown";
|
||||
return $"{source.Name} (Category: {source.Category}, Release: {releaseDate})";
|
||||
}
|
||||
),
|
||||
// Коллекции
|
||||
new CollectionFormatter(this),
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,395 @@
|
||||
using Pf2e.Common;
|
||||
using Pf2e.Database.Model.Base;
|
||||
using Pf2e.Database.Model.Structure;
|
||||
using Pf2e.Web.Helpers.Serialization;
|
||||
using Action = Pf2e.Database.Model.Structure.Action;
|
||||
|
||||
namespace Pf2e.Web.Helpers;
|
||||
|
||||
public class EntitySerialization
|
||||
{
|
||||
public EntitySerialization()
|
||||
{
|
||||
if (_builders.Count == 0)
|
||||
{
|
||||
RegisterBuilders();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Сериализует объект в результат сериализации.
|
||||
/// </summary>
|
||||
/// <param name="value">Объект для сериализации.</param>
|
||||
/// <returns>Результат сериализации.</returns>
|
||||
public SerializationResult Serialize(object value)
|
||||
{
|
||||
SerializationResult result = new();
|
||||
|
||||
// Находим подходящий построитель для типа объекта
|
||||
EntityBuilder? builder = _builders.FirstOrDefault(b => b.EntityType == value.GetType());
|
||||
if (builder != null)
|
||||
{
|
||||
result.Groups = builder.Compile(value);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Если построитель не найден, создаём пустой результат
|
||||
result.Groups = [];
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private static List<EntityBuilder> _builders = [];
|
||||
|
||||
private static EntityBuilder<TEntity> NewBuilder<TEntity>()
|
||||
where TEntity : class
|
||||
{
|
||||
EntityBuilder<TEntity> builder = new();
|
||||
_builders.Add(builder);
|
||||
return builder;
|
||||
}
|
||||
|
||||
private static void RegisterBuilders()
|
||||
{
|
||||
// Добавляем построитель для FullEntity
|
||||
NewBuilder<FullEntity>()
|
||||
.AddGroup(
|
||||
"Основная сущность",
|
||||
builder =>
|
||||
{
|
||||
builder.AddField(fe => fe.Entity).AddField(fe => fe.SubEntity);
|
||||
}
|
||||
);
|
||||
|
||||
NewBuilder<Entity>()
|
||||
.AddGroup(
|
||||
"Сущность",
|
||||
builder =>
|
||||
{
|
||||
builder
|
||||
.AddField(entity => entity.Id)
|
||||
.AddField(entity => entity.Name)
|
||||
.AddField(entity => entity.Type)
|
||||
.AddField(entity => entity.Slug)
|
||||
.AddField(entity => entity.Traits)
|
||||
.AddField(entity => entity.Description)
|
||||
.AddField(entity => entity.IsTranslated)
|
||||
.SetValueFormatter(v => (v as bool? == true) ? "Да" : "Нет")
|
||||
.AddField(entity => entity.IsChecked)
|
||||
.SetValueFormatter(v => (v as bool? == true) ? "Да" : "Нет")
|
||||
.AddField(entity => entity.IsLegacy)
|
||||
.SetValueFormatter(v => (v as bool? == true) ? "Да" : "Нет")
|
||||
.AddField(entity => entity.Keywords)
|
||||
.AddField(entity => entity.Sources)
|
||||
.AddField(entity => entity.LegacyEntities)
|
||||
.AddField(entity => entity.RemasterEntities);
|
||||
}
|
||||
);
|
||||
|
||||
NewBuilder<Action>()
|
||||
.AddGroup(
|
||||
"Действие",
|
||||
builder =>
|
||||
{
|
||||
builder
|
||||
.AddField(a => a.Id)
|
||||
.AddField(a => a.Entity)
|
||||
.AddField(a => a.Actions)
|
||||
.AddField(a => a.Category)
|
||||
.AddField(a => a.Extra);
|
||||
}
|
||||
);
|
||||
|
||||
NewBuilder<Ancestry>()
|
||||
.AddGroup(
|
||||
"Родословная",
|
||||
builder =>
|
||||
{
|
||||
builder
|
||||
.AddField(a => a.Id)
|
||||
.AddField(a => a.Entity)
|
||||
.AddField(a => a.Size)
|
||||
.AddField(a => a.Hp)
|
||||
.AddField(a => a.Speed)
|
||||
.AddField(a => a.VisionEnum)
|
||||
.AddField(a => a.AncestryBoosts)
|
||||
.AddField(a => a.AncestryItems)
|
||||
.AddField(a => a.AncestryLanguages)
|
||||
.AddField(a => a.Heritages);
|
||||
}
|
||||
);
|
||||
|
||||
NewBuilder<Archetype>()
|
||||
.AddGroup(
|
||||
"Архетип",
|
||||
builder =>
|
||||
{
|
||||
builder
|
||||
.AddField(a => a.Id)
|
||||
.AddField(a => a.Entity)
|
||||
.AddField(a => a.Level)
|
||||
.AddField(a => a.Extra)
|
||||
.AddField(a => a.DedicationFeat)
|
||||
.UseCommentFrom(a => a.DedicationFeatId)
|
||||
.AddField(a => a.Feats);
|
||||
}
|
||||
);
|
||||
|
||||
NewBuilder<Background>()
|
||||
.AddGroup(
|
||||
"Предыстория",
|
||||
builder =>
|
||||
{
|
||||
builder
|
||||
.AddField(b => b.Id)
|
||||
.AddField(b => b.Entity)
|
||||
.AddField(b => b.BackgroundBoosts)
|
||||
.AddField(b => b.BackgroundItems)
|
||||
.AddField(b => b.BackgroundSkills);
|
||||
}
|
||||
);
|
||||
|
||||
NewBuilder<Class>()
|
||||
.AddGroup(
|
||||
"Класс",
|
||||
builder =>
|
||||
{
|
||||
builder
|
||||
.AddField(c => c.Id)
|
||||
.AddField(c => c.Entity)
|
||||
.AddField(c => c.Hp)
|
||||
.AddField(c => c.Spellcasting)
|
||||
.AddField(c => c.Tradition)
|
||||
.AddField(c => c.AdditionalTrainedSkills)
|
||||
.AddField(c => c.ClassKeyAbilities)
|
||||
.AddField(c => c.ClassProgressions)
|
||||
.AddField(c => c.ClassTrainings)
|
||||
.AddField(c => c.SubclassGroups);
|
||||
}
|
||||
);
|
||||
|
||||
NewBuilder<Condition>()
|
||||
.AddGroup(
|
||||
"Состояние",
|
||||
builder =>
|
||||
{
|
||||
builder
|
||||
.AddField(c => c.Id)
|
||||
.AddField(c => c.Entity)
|
||||
.AddField(c => c.Group)
|
||||
.AddField(c => c.Extra);
|
||||
}
|
||||
);
|
||||
|
||||
NewBuilder<Deity>()
|
||||
.AddGroup(
|
||||
"Божество",
|
||||
builder =>
|
||||
{
|
||||
builder
|
||||
.AddField(d => d.Id)
|
||||
.AddField(d => d.Entity)
|
||||
.AddField(d => d.Type)
|
||||
.AddField(d => d.Category)
|
||||
.AddField(d => d.Fonts)
|
||||
.AddField(d => d.Sanctification)
|
||||
.AddField(d => d.SanctificationMust)
|
||||
.AddField(d => d.Edicts)
|
||||
.AddField(d => d.Anathema)
|
||||
.AddField(d => d.ReligiousSymbol)
|
||||
.AddField(d => d.SacredAnimal)
|
||||
.AddField(d => d.AreaOfConcern)
|
||||
.AddField(d => d.DeityAttributes)
|
||||
.AddField(d => d.DeityDomains)
|
||||
.AddField(d => d.DeitySpells)
|
||||
.AddField(d => d.DeityWeapons)
|
||||
.AddField(d => d.CategoryEntity)
|
||||
.UseCommentFrom(d => d.CategoryId)
|
||||
.AddField(d => d.Skills);
|
||||
}
|
||||
);
|
||||
|
||||
NewBuilder<DeityCategory>()
|
||||
.AddGroup(
|
||||
"Категория божества",
|
||||
builder =>
|
||||
{
|
||||
builder
|
||||
.AddField(dc => dc.Id)
|
||||
.AddField(dc => dc.Entity)
|
||||
.AddField(dc => dc.Description);
|
||||
}
|
||||
);
|
||||
|
||||
NewBuilder<Domain>()
|
||||
.AddGroup(
|
||||
"Домен",
|
||||
builder =>
|
||||
{
|
||||
builder
|
||||
.AddField(d => d.Id)
|
||||
.AddField(d => d.Entity)
|
||||
.AddField(d => d.DomainSpell)
|
||||
.UseCommentFrom(d => d.DomainSpellId)
|
||||
.AddField(d => d.AdvancedDomainSpell)
|
||||
.UseCommentFrom(d => d.AdvancedDomainSpellId)
|
||||
.AddField(d => d.ApocryphalSpell)
|
||||
.UseCommentFrom(d => d.ApocryphalSpellId)
|
||||
.AddField(d => d.AdvancedApocryphalSpell)
|
||||
.UseCommentFrom(d => d.AdvancedApocryphalSpellId)
|
||||
.AddField(d => d.DeityDomains);
|
||||
}
|
||||
);
|
||||
|
||||
NewBuilder<Feat>()
|
||||
.AddGroup(
|
||||
"Способность",
|
||||
builder =>
|
||||
{
|
||||
builder
|
||||
.AddField(f => f.Id)
|
||||
.AddField(f => f.Entity)
|
||||
.AddField(f => f.Category)
|
||||
.AddField(f => f.Level)
|
||||
.AddField(f => f.Actions)
|
||||
.AddField(f => f.Extra)
|
||||
.AddField(f => f.Archetypes)
|
||||
.AddField(f => f.FeatPrerequisites);
|
||||
}
|
||||
);
|
||||
|
||||
NewBuilder<Heritage>()
|
||||
.AddGroup(
|
||||
"Наследие",
|
||||
builder =>
|
||||
{
|
||||
builder
|
||||
.AddField(h => h.Id)
|
||||
.AddField(h => h.Entity)
|
||||
.AddField(h => h.Ancestry)
|
||||
.UseCommentFrom(h => h.AncestryId);
|
||||
}
|
||||
);
|
||||
|
||||
NewBuilder<Language>()
|
||||
.AddGroup(
|
||||
"Язык",
|
||||
builder =>
|
||||
{
|
||||
builder.AddField(l => l.Id).AddField(l => l.Entity);
|
||||
}
|
||||
);
|
||||
|
||||
NewBuilder<Rule>()
|
||||
.AddGroup(
|
||||
"Правило",
|
||||
builder =>
|
||||
{
|
||||
builder
|
||||
.AddField(r => r.Id)
|
||||
.AddField(r => r.Entity)
|
||||
.AddField(r => r.Order)
|
||||
.AddField(r => r.Parent)
|
||||
.UseCommentFrom(r => r.ParentId)
|
||||
.AddField(r => r.Children)
|
||||
.AddField(r => r.Breadcrumbs);
|
||||
}
|
||||
);
|
||||
|
||||
NewBuilder<Ritual>()
|
||||
.AddGroup(
|
||||
"Ритуал",
|
||||
builder =>
|
||||
{
|
||||
builder
|
||||
.AddField(r => r.Id)
|
||||
.AddField(r => r.Entity)
|
||||
.AddField(r => r.Level)
|
||||
.AddField(r => r.CastTime)
|
||||
.AddField(r => r.Duration)
|
||||
.AddField(r => r.Elements)
|
||||
.AddField(r => r.Range)
|
||||
.AddField(r => r.PrimaryCheckRank)
|
||||
.AddField(r => r.PrimaryCheckSuffix)
|
||||
.AddField(r => r.SecondaryCasters)
|
||||
.AddField(r => r.SecondaryCastersMax)
|
||||
.AddField(r => r.SecondaryCastersSuffix)
|
||||
.AddField(r => r.RangeString)
|
||||
.AddField(r => r.Target)
|
||||
.AddField(r => r.Cost)
|
||||
.AddField(r => r.Requirement)
|
||||
.AddField(r => r.Area)
|
||||
.AddField(r => r.Checks);
|
||||
}
|
||||
);
|
||||
|
||||
NewBuilder<Spell>()
|
||||
.AddGroup(
|
||||
"Заклинание",
|
||||
builder =>
|
||||
{
|
||||
builder
|
||||
.AddField(s => s.Id)
|
||||
.AddField(s => s.Entity)
|
||||
.AddField(s => s.Level)
|
||||
.AddField(s => s.IsFocus)
|
||||
.AddField(s => s.Actions)
|
||||
.AddField(s => s.Duration)
|
||||
.AddField(s => s.Range)
|
||||
.AddField(s => s.Traditions)
|
||||
.AddField(s => s.Elements)
|
||||
.AddField(s => s.Trigger)
|
||||
.AddField(s => s.Target)
|
||||
.AddField(s => s.Cost)
|
||||
.AddField(s => s.Requirement)
|
||||
.AddField(s => s.SpellAreas)
|
||||
.AddField(s => s.SpellDefenses);
|
||||
}
|
||||
);
|
||||
|
||||
NewBuilder<Subclass>()
|
||||
.AddGroup(
|
||||
"Подкласс",
|
||||
builder =>
|
||||
{
|
||||
builder
|
||||
.AddField(s => s.Id)
|
||||
.AddField(s => s.Entity)
|
||||
.AddField(s => s.Group)
|
||||
.UseCommentFrom(s => s.GroupId)
|
||||
.AddField(s => s.Additional)
|
||||
.AddField(s => s.Extra)
|
||||
.AddField(s => s.Entities)
|
||||
.AddField(s => s.Trainings);
|
||||
}
|
||||
);
|
||||
|
||||
NewBuilder<SubclassGroup>()
|
||||
.AddGroup(
|
||||
"Группа подклассов",
|
||||
builder =>
|
||||
{
|
||||
builder
|
||||
.AddField(sg => sg.Id)
|
||||
.AddField(sg => sg.Entity)
|
||||
.AddField(sg => sg.Class)
|
||||
.UseCommentFrom(sg => sg.ClassId)
|
||||
.AddField(sg => sg.Subclasses);
|
||||
}
|
||||
);
|
||||
|
||||
NewBuilder<Trait>()
|
||||
.AddGroup(
|
||||
"Признак",
|
||||
builder =>
|
||||
{
|
||||
builder
|
||||
.AddField(t => t.Id)
|
||||
.AddField(t => t.Entity)
|
||||
.AddField(t => t.Type)
|
||||
.AddField(t => t.Entities);
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,101 +0,0 @@
|
||||
using System.Reflection;
|
||||
using Pf2e.Common;
|
||||
|
||||
namespace Pf2e.Web.Helpers;
|
||||
|
||||
/// <summary>
|
||||
/// Сериализатор сущности для отображения на веб-странице.
|
||||
/// </summary>
|
||||
public class EntitySerializer
|
||||
{
|
||||
/// <summary>
|
||||
/// Результат сериализации сущности.
|
||||
/// </summary>
|
||||
public class SerializationResult
|
||||
{
|
||||
public List<FieldGroup> Groups { get; set; } = [];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Информация о поле.
|
||||
/// </summary>
|
||||
public class FieldInfo
|
||||
{
|
||||
public string Name { get; set; } = string.Empty;
|
||||
public string Type { get; set; } = string.Empty;
|
||||
public object? Value { get; set; }
|
||||
public string DisplayValue { get; set; } = string.Empty;
|
||||
public bool IsImplemented { get; set; } = true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Форматтер полей сущности.
|
||||
/// </summary>
|
||||
public EntityFieldFormatter FieldFormatter { get; } = new();
|
||||
|
||||
/// <summary>
|
||||
/// Сериализовать FullEntity.
|
||||
/// </summary>
|
||||
public SerializationResult Serialize(FullEntity fullEntity)
|
||||
{
|
||||
SerializationResult result = new();
|
||||
|
||||
//// Группа для основных полей Entity
|
||||
//FieldGroup entityGroup = new() { Name = "Основные поля" };
|
||||
//AddFieldsToGroup(entityGroup, fullEntity.Entity);
|
||||
//result.Groups.Add(entityGroup);
|
||||
|
||||
//// Группа для полей IEntity (SubEntity)
|
||||
//if (fullEntity.SubEntity != null)
|
||||
//{
|
||||
// FieldGroup subEntityGroup = new()
|
||||
// {
|
||||
// Name = "Поля " + fullEntity.SubEntity.GetType().Name,
|
||||
// };
|
||||
// AddFieldsToGroup(subEntityGroup, fullEntity.SubEntity);
|
||||
// result.Groups.Add(subEntityGroup);
|
||||
//}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Добавить поля объекта в группу.
|
||||
/// </summary>
|
||||
//private void AddFieldsToGroup(FieldGroup group, object obj)
|
||||
//{
|
||||
// Type type = obj.GetType();
|
||||
// PropertyInfo[] properties = type.GetProperties(BindingFlags.Public | BindingFlags.Instance);
|
||||
|
||||
// foreach (PropertyInfo property in properties)
|
||||
// {
|
||||
// // Пропускаем навигационные свойства Entity
|
||||
// if (property.Name == "Entity")
|
||||
// continue;
|
||||
|
||||
// object? value = property.GetValue(obj);
|
||||
// FieldBuilderDefinition fieldInfo = CreateFieldInfo(property, value);
|
||||
// group.Fields.Add(fieldInfo);
|
||||
// }
|
||||
//}
|
||||
|
||||
/// <summary>
|
||||
/// Создать FieldBuilderDefinition для свойства.
|
||||
/// </summary>
|
||||
/// <param name="property">Поле класса.</param>
|
||||
/// <param name="value">Значение поля.</param>
|
||||
/// <returns>Информация о поле.</returns>
|
||||
private FieldInfo CreateFieldInfo(PropertyInfo property, object? value)
|
||||
{
|
||||
string? displayValue = FieldFormatter.FormatValue(value);
|
||||
|
||||
return new()
|
||||
{
|
||||
Name = property.Name,
|
||||
Type = property.PropertyType.Name,
|
||||
Value = value,
|
||||
DisplayValue = displayValue ?? "null",
|
||||
IsImplemented = displayValue is not null,
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,196 @@
|
||||
using System.ComponentModel;
|
||||
using Pf2e.Database.Model;
|
||||
using Pf2e.Database.Model.Base.Keywords;
|
||||
using Pf2e.Database.Model.Base.Source;
|
||||
using Pf2e.Database.Model.Base.Targets;
|
||||
using Pf2e.Database.Model.Common.I18n;
|
||||
using Pf2e.Database.Model.Enum;
|
||||
|
||||
namespace Pf2e.Web.Helpers.Format;
|
||||
|
||||
public static class Formatters
|
||||
{
|
||||
/// <summary>
|
||||
/// Форматтер для перечислений (enum).
|
||||
/// </summary>
|
||||
public static readonly ValueFormatter EnumFormatter = new CustomValueFormatter(
|
||||
(type) => type.IsEnum,
|
||||
(value) =>
|
||||
{
|
||||
Enum enumValue = (Enum)value;
|
||||
string description = GetEnumDescription(enumValue);
|
||||
return $"{description} ({enumValue})";
|
||||
|
||||
string GetEnumDescription(Enum v)
|
||||
{
|
||||
Type type = v.GetType();
|
||||
string name = Enum.GetName(type, v) ?? v.ToString();
|
||||
|
||||
System.Reflection.FieldInfo? field = type.GetField(name);
|
||||
if (field == null)
|
||||
return v.ToString();
|
||||
|
||||
if (
|
||||
Attribute.GetCustomAttribute(field, typeof(DescriptionAttribute))
|
||||
is DescriptionAttribute attr
|
||||
)
|
||||
{
|
||||
return attr.Description;
|
||||
}
|
||||
|
||||
return v.ToString();
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
/// <summary>
|
||||
/// Форматтер для примитивных типов и строк.
|
||||
/// </summary>
|
||||
public static readonly ValueFormatter PrimitiveFormatter = new CustomValueFormatter(
|
||||
(type) => type.IsPrimitive || type == typeof(string) || type == typeof(decimal),
|
||||
(value) => value.ToString() ?? "null"
|
||||
);
|
||||
|
||||
/// <summary>
|
||||
/// Форматтер для коллекций.
|
||||
/// </summary>
|
||||
public static readonly ValueFormatter CollectionFormatter = new CollectionFormatter();
|
||||
|
||||
/// <summary>
|
||||
/// Форматтеры для стандартных типов из базы данных.
|
||||
/// </summary>
|
||||
public static readonly List<ValueFormatter> DefaultTypeFormatters =
|
||||
[
|
||||
new TypeValueFormatter<LocaleString>(
|
||||
(value) =>
|
||||
{
|
||||
LocaleString localeString = (LocaleString)value;
|
||||
List<string> items = [];
|
||||
foreach (LocaleItem item in localeString)
|
||||
{
|
||||
items.Add($"{item.Locale}: {item.Value}");
|
||||
}
|
||||
return string.Join("<br/>", items);
|
||||
}
|
||||
),
|
||||
// IEntity
|
||||
new CustomValueFormatter(
|
||||
(type) => typeof(IEntity).IsAssignableFrom(type),
|
||||
(value) =>
|
||||
{
|
||||
IEntity entity = (IEntity)value;
|
||||
EntityType entityType = entity.Entity.Type;
|
||||
string typeName = PgNameHelper.GetPgName(entityType);
|
||||
string slug = entity.Entity.Slug;
|
||||
string name = entity.Entity.Name[LocaleType.English];
|
||||
return $"<a href=\"/{typeName}/{slug}\">{name}</a>";
|
||||
}
|
||||
),
|
||||
// EntitySource
|
||||
new TypeValueFormatter<EntitySource>(
|
||||
(value) =>
|
||||
{
|
||||
EntitySource entitySource = (EntitySource)value;
|
||||
string sourceName = entitySource.Source.Name;
|
||||
string suffix = !string.IsNullOrEmpty(entitySource.Suffix)
|
||||
? $" ({entitySource.Suffix})"
|
||||
: "";
|
||||
string isRemaster = entitySource.IsRemaster ? " [Remaster]" : "";
|
||||
return $"{sourceName}{suffix}{isRemaster}";
|
||||
}
|
||||
),
|
||||
// Keyword
|
||||
new TypeValueFormatter<Keyword>(
|
||||
(value) =>
|
||||
{
|
||||
Keyword keyword = (Keyword)value;
|
||||
string name = keyword.Name[LocaleType.English];
|
||||
return $"{name}";
|
||||
}
|
||||
),
|
||||
// KeywordAlias
|
||||
new TypeValueFormatter<KeywordAlias>(
|
||||
(value) =>
|
||||
{
|
||||
KeywordAlias alias = (KeywordAlias)value;
|
||||
return $"{alias.Value}";
|
||||
}
|
||||
),
|
||||
// ExternalTarget
|
||||
new TypeValueFormatter<ExternalTarget>(
|
||||
(value) =>
|
||||
{
|
||||
ExternalTarget target = (ExternalTarget)value;
|
||||
return $"{target.Name} (Icon: {target.Icon}, Prefix: {target.Prefix})";
|
||||
}
|
||||
),
|
||||
// EntityExternal
|
||||
new TypeValueFormatter<EntityExternal>(
|
||||
(value) =>
|
||||
{
|
||||
EntityExternal entityExternal = (EntityExternal)value;
|
||||
string targetName = entityExternal.Target?.Name ?? "Unknown";
|
||||
string externalId = !string.IsNullOrEmpty(entityExternal.ExternalId)
|
||||
? $" ID: {entityExternal.ExternalId}"
|
||||
: "";
|
||||
string suffix = !string.IsNullOrEmpty(entityExternal.Suffix)
|
||||
? $" Suffix: {entityExternal.Suffix}"
|
||||
: "";
|
||||
return $"{targetName}{externalId}{suffix}";
|
||||
}
|
||||
),
|
||||
// Source
|
||||
new TypeValueFormatter<Source>(
|
||||
(value) =>
|
||||
{
|
||||
Source source = (Source)value;
|
||||
string releaseDate = source.ReleaseDate?.ToString("yyyy-MM-dd") ?? "Unknown";
|
||||
return $"{source.Name} (Category: {source.Category}, Release: {releaseDate})";
|
||||
}
|
||||
),
|
||||
];
|
||||
|
||||
/// <summary>
|
||||
/// Набор всех стандартных форматтеров.
|
||||
/// </summary>
|
||||
public static readonly List<ValueFormatter> AllFormatters =
|
||||
[
|
||||
.. DefaultTypeFormatters,
|
||||
EnumFormatter,
|
||||
PrimitiveFormatter,
|
||||
CollectionFormatter,
|
||||
];
|
||||
|
||||
/// <summary>
|
||||
/// Добавить пользовательский форматтер в набор всех форматтеров.
|
||||
/// </summary>
|
||||
/// <param name="formatter">Добавляемый форматтер.</param>
|
||||
public static void AddFormatter(ValueFormatter formatter)
|
||||
{
|
||||
AllFormatters.Insert(0, formatter);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Выбрать подходящий форматтер для типа.
|
||||
/// </summary>
|
||||
/// <param name="type">Тип значения.</param>
|
||||
/// <returns>Форматтер для типа или <c>null</c>, если подходящего не найдено.</returns>
|
||||
public static ValueFormatter? SelectFormatter(Type type) =>
|
||||
AllFormatters.FirstOrDefault(f => f.CanFormat(type)) ?? null;
|
||||
|
||||
/// <summary>
|
||||
/// Выбрать подходящий форматтер для значения.
|
||||
/// </summary>
|
||||
/// <param name="value">Значение.</param>
|
||||
/// <returns>Форматтер для значения или <c>null</c>, если значение равно <c>null</c> или подходящего не найдено.</returns>
|
||||
public static ValueFormatter? SelectFormatter(object? value) =>
|
||||
value == null ? null : SelectFormatter(value.GetType());
|
||||
|
||||
/// <summary>
|
||||
/// Форматировать значение в строку.
|
||||
/// </summary>
|
||||
/// <param name="value">Форматируемое значение.</param>
|
||||
/// <returns>Строковое представление отформатированного значения.</returns>
|
||||
public static string Format(object? value) =>
|
||||
SelectFormatter(value)?.Format(value!) ?? "[not implemented]";
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
using System.Collections;
|
||||
using static LinqToDB.Reflection.Methods.LinqToDB.Insert;
|
||||
|
||||
namespace Pf2e.Web.Helpers.Format;
|
||||
|
||||
/// <summary>
|
||||
/// Абстрактный класс форматтера значений.
|
||||
/// </summary>
|
||||
public abstract class ValueFormatter
|
||||
{
|
||||
/// <summary>
|
||||
/// Проверяет, может ли форматтер обработать указанный тип.
|
||||
/// </summary>
|
||||
/// <param name="type">Тип значения.</param>
|
||||
/// <returns><c>true</c>, если форматтер может обработать тип, иначе <c>false</c>.</returns>
|
||||
public abstract bool CanFormat(Type type);
|
||||
|
||||
/// <summary>
|
||||
/// Форматирует значение для отображения.
|
||||
/// </summary>
|
||||
/// <param name="value">Значение для форматирования.</param>
|
||||
/// <returns>Строковое представление отформатированного значения.</returns>
|
||||
public abstract string Format(object value);
|
||||
|
||||
/// <summary>
|
||||
/// Форматирует имя типа для отображения.
|
||||
/// </summary>
|
||||
/// <param name="type">Тип значения.</param>
|
||||
/// <returns>Строковое представление имени типа.</returns>
|
||||
public virtual string TypeName(Type type) => type.Name;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Форматтер для конкретного типа.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">Тип значения.</typeparam>
|
||||
/// <param name="formatter">Функция форматирования значения.</param>
|
||||
public class TypeValueFormatter<T>(Func<object, string> formatter) : ValueFormatter
|
||||
{
|
||||
public override bool CanFormat(Type type) => type == typeof(T);
|
||||
|
||||
public override string Format(object value) => formatter(value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Форматтер с пользовательскими функциями проверки и форматирования.
|
||||
/// </summary>
|
||||
/// <param name="checker">Функция проверки типа.</param>
|
||||
/// <param name="formatter">Функция форматирования значения.</param>
|
||||
public class CustomValueFormatter(
|
||||
Func<Type, bool> checker,
|
||||
Func<object, string> formatter,
|
||||
Func<Type, string>? typeName = null
|
||||
) : ValueFormatter
|
||||
{
|
||||
public override bool CanFormat(Type type) => checker(type);
|
||||
|
||||
public override string Format(object value) => formatter(value);
|
||||
|
||||
public override string TypeName(Type type) => typeName?.Invoke(type) ?? base.TypeName(type);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Форматтер для коллекций.
|
||||
/// </summary>
|
||||
public class CollectionFormatter() : ValueFormatter
|
||||
{
|
||||
public override bool CanFormat(Type type) =>
|
||||
typeof(IEnumerable).IsAssignableFrom(type) && type != typeof(string);
|
||||
|
||||
public override string Format(object value)
|
||||
{
|
||||
IEnumerable collection = (IEnumerable)value;
|
||||
List<string> items = [];
|
||||
|
||||
foreach (object? item in collection)
|
||||
{
|
||||
items.Add(Formatters.Format(item));
|
||||
}
|
||||
|
||||
return items.Count == 0 ? "(empty)" : string.Join("<br/>", items);
|
||||
}
|
||||
|
||||
public override string TypeName(Type type)
|
||||
{
|
||||
return $"{type.Name[..^2]}<{string.Join(", ", type.GetGenericArguments().Select(t => t.Name))}>";
|
||||
}
|
||||
}
|
||||
@@ -1,19 +1,19 @@
|
||||
using System.Reflection;
|
||||
using System.Reflection;
|
||||
using NpgsqlTypes;
|
||||
using Pf2e.Database.Model.Enum;
|
||||
|
||||
namespace Pf2e.Web.Helpers
|
||||
{
|
||||
/// <summary>
|
||||
/// Âñïîìîãàòåëüíûé êëàññ äëÿ ïîëó÷åíèÿ èìåí PostgresSQL èç ïåðå÷èñëåíèé.
|
||||
/// Вспомогательный класс для получения имен PostgresSQL из перечислений.
|
||||
/// </summary>
|
||||
public static class PgNameHelper
|
||||
{
|
||||
/// <summary>
|
||||
/// Ïîëó÷àåò èìÿ PostgresSQL äëÿ çàäàííîãî òèïà ñóùíîñòè.
|
||||
/// Получает имя PostgresSQL для заданного типа сущности.
|
||||
/// </summary>
|
||||
/// <param name="entityType">Òèï ñóùíîñòè.</param>
|
||||
/// <returns>Ñòðîêîâîå ïðåäñòàâëåíèå èìåíè PostgresSQL.</returns>
|
||||
/// <param name="entityType">Тип сущности.</param>
|
||||
/// <returns>Строковое представление имени PostgresSQL.</returns>
|
||||
public static string GetPgName(EntityType entityType)
|
||||
{
|
||||
FieldInfo? fieldInfo = entityType.GetType().GetField(entityType.ToString());
|
||||
|
||||
@@ -0,0 +1,144 @@
|
||||
using System.Reflection;
|
||||
using Pf2e.Web.Helpers.Format;
|
||||
|
||||
namespace Pf2e.Web.Helpers.Serialization;
|
||||
|
||||
/// <summary>
|
||||
/// Построитель сериализации для сущности.
|
||||
/// </summary>
|
||||
/// <param name="type">Тип сущности для сериализации.</param>
|
||||
public abstract class EntityBuilder(Type type)
|
||||
{
|
||||
/// <summary>
|
||||
/// Тип сущности для сериализации.
|
||||
/// </summary>
|
||||
public Type EntityType { get; } = type;
|
||||
|
||||
/// <summary>
|
||||
/// Сериализует объект в список групп полей.
|
||||
/// </summary>
|
||||
/// <param name="value"></param>
|
||||
/// <returns></returns>
|
||||
public abstract List<SerializationGroup> Compile(object value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Построитель сериализации для типа T.
|
||||
/// </summary>
|
||||
/// <typeparam name="TEntity">Тип сущности для сериализации.</typeparam>
|
||||
public class EntityBuilder<TEntity>() : EntityBuilder(typeof(TEntity))
|
||||
where TEntity : class
|
||||
{
|
||||
/// <summary>
|
||||
/// Список групп полей.
|
||||
/// </summary>
|
||||
public List<FieldGroupDefinition> Groups { get; set; } = [];
|
||||
|
||||
/// <summary>
|
||||
/// Добавляет группу полей в построитель сериализации.
|
||||
/// </summary>
|
||||
/// <param name="name">Название группы.</param>
|
||||
/// <param name="configure">Делегат для настройки группы полей.</param>
|
||||
/// <returns>Текущий экземпляр построителя.</returns>
|
||||
public EntityBuilder<TEntity> AddGroup(
|
||||
string name,
|
||||
Action<FieldGroupBuilder<TEntity>> configure
|
||||
)
|
||||
{
|
||||
FieldGroupBuilder<TEntity> groupBuilder = new(name);
|
||||
configure(groupBuilder);
|
||||
Groups.Add(groupBuilder.Build());
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Добавляет группу полей в построитель сериализации с названием типа.
|
||||
/// </summary>
|
||||
/// <param name="configure">Делегат для настройки группы полей.</param>
|
||||
/// <returns>Текущий экземпляр построителя.</returns>
|
||||
public EntityBuilder<TEntity> AddGroup(Action<FieldGroupBuilder<TEntity>> configure)
|
||||
{
|
||||
return AddGroup(typeof(TEntity).Name, configure);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override List<SerializationGroup> Compile(object value)
|
||||
{
|
||||
return Groups.Select(group => CompileGroup(group, value)).ToList();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Строит группу полей.
|
||||
/// </summary>
|
||||
/// <returns>Готовая группа полей.</returns>
|
||||
public SerializationGroup CompileGroup(FieldGroupDefinition groupDefinition, object value)
|
||||
{
|
||||
SerializationGroup result = new() { Name = groupDefinition.Name };
|
||||
|
||||
foreach (FieldDefinition field in groupDefinition.Fields)
|
||||
{
|
||||
PropertyInfo? propertyInfo = groupDefinition.GroupType.GetProperty(field.Name);
|
||||
|
||||
if (propertyInfo == null)
|
||||
continue;
|
||||
|
||||
object? fieldValue = propertyInfo.GetValue(value);
|
||||
|
||||
result.Fields.Add(CompileField(field, fieldValue));
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Построение поля сериализации.
|
||||
/// </summary>
|
||||
/// <param name="field">Определение поля.</param>
|
||||
/// <param name="value">Значение поля.</param>
|
||||
/// <returns>Построенное поле сериализации.</returns>
|
||||
private SerializationField CompileField(FieldDefinition field, object? value)
|
||||
{
|
||||
if (value is null)
|
||||
{
|
||||
return new()
|
||||
{
|
||||
Name = field.Name,
|
||||
Type = field.Type.Name,
|
||||
Value = "null",
|
||||
IsImplemented = true,
|
||||
};
|
||||
}
|
||||
|
||||
if (field.ValueFormatter is not null)
|
||||
{
|
||||
return new()
|
||||
{
|
||||
Name = field.Name,
|
||||
Type = field.Type.Name,
|
||||
Value = field.ValueFormatter(value),
|
||||
IsImplemented = true,
|
||||
};
|
||||
}
|
||||
|
||||
ValueFormatter? formatter = Formatters.SelectFormatter(value);
|
||||
|
||||
if (formatter is not null)
|
||||
{
|
||||
return new()
|
||||
{
|
||||
Name = field.Name,
|
||||
Type = field.Type.Name,
|
||||
Value = formatter.Format(value),
|
||||
IsImplemented = true,
|
||||
};
|
||||
}
|
||||
|
||||
return new()
|
||||
{
|
||||
Name = field.Name,
|
||||
Type = field.Type.Name,
|
||||
Value = value.ToString() ?? "null",
|
||||
IsImplemented = false,
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
namespace Pf2e.Web.Helpers.Serialization;
|
||||
|
||||
/// <summary>
|
||||
/// Информация о поле.
|
||||
/// </summary>
|
||||
public class FieldDefinition
|
||||
{
|
||||
/// <summary>
|
||||
/// Название поля.
|
||||
/// </summary>
|
||||
public string Name { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Тип поля.
|
||||
/// </summary>
|
||||
public required Type Type { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Форматтер значения поля.
|
||||
/// </summary>
|
||||
public Func<object, string>? ValueFormatter { get; set; } = null;
|
||||
}
|
||||
@@ -0,0 +1,116 @@
|
||||
using System.Linq.Expressions;
|
||||
using System.Reflection;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Pf2e.Web.Helpers.Format;
|
||||
|
||||
namespace Pf2e.Web.Helpers.Serialization;
|
||||
|
||||
/// <summary>
|
||||
/// Построитель группы полей для типа TField.
|
||||
/// </summary>
|
||||
/// <typeparam name="TField">Тип сущности для группы полей.</typeparam>
|
||||
public class FieldGroupBuilder<TField>(string name)
|
||||
where TField : class
|
||||
{
|
||||
private readonly FieldGroupDefinition _groupDefinition = new()
|
||||
{
|
||||
Name = name,
|
||||
GroupType = typeof(TField),
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Добавляет поле в группу.
|
||||
/// </summary>
|
||||
/// <param name="fieldExpression">Лямбда-выражение для получения поля.</param>
|
||||
/// <returns>Текущий экземпляр построителя группы полей.</returns>
|
||||
public FieldGroupBuilder<TField> AddField(Expression<Func<TField, object>> fieldExpression)
|
||||
{
|
||||
// Получаем информацию о свойстве из выражения
|
||||
MemberExpression? memberExpression = GetMemberExpression(fieldExpression.Body);
|
||||
if (memberExpression == null)
|
||||
{
|
||||
throw new ArgumentException(
|
||||
"Выражение должно быть доступом к свойству",
|
||||
nameof(fieldExpression)
|
||||
);
|
||||
}
|
||||
|
||||
PropertyInfo propertyInfo = (PropertyInfo)memberExpression.Member;
|
||||
|
||||
// Получаем атрибут CommentAttribute, если он есть
|
||||
CommentAttribute? commentAttribute = propertyInfo.GetCustomAttribute<CommentAttribute>();
|
||||
string fieldName = commentAttribute?.Comment ?? propertyInfo.Name;
|
||||
Type fieldType = propertyInfo.PropertyType;
|
||||
|
||||
_groupDefinition.Fields.Add(new() { Name = fieldName, Type = fieldType });
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Устанавливает форматтер значения для последнего добавленного поля.
|
||||
/// </summary>
|
||||
/// <param name="valueFormatter">Функция форматирования значения.</param>
|
||||
/// <returns>Текущий экземпляр построителя группы полей.</returns>
|
||||
public FieldGroupBuilder<TField> SetValueFormatter(Func<object, string> valueFormatter)
|
||||
{
|
||||
_groupDefinition.Fields.Last().ValueFormatter = valueFormatter;
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Использует комментарий из другого поля для установки имени последнего добавленного поля.
|
||||
/// </summary>
|
||||
/// <param name="fieldExpression">Лямбда-выражение для получения поля с комментарием.</param>
|
||||
/// <returns>Текущий экземпляр построителя группы полей.</returns>
|
||||
public FieldGroupBuilder<TField> UseCommentFrom(
|
||||
Expression<Func<TField, object>> fieldExpression
|
||||
)
|
||||
{
|
||||
// Получаем информацию о свойстве из выражения
|
||||
MemberExpression? memberExpression = GetMemberExpression(fieldExpression.Body);
|
||||
if (memberExpression == null)
|
||||
{
|
||||
throw new ArgumentException(
|
||||
"Выражение должно быть доступом к свойству",
|
||||
nameof(fieldExpression)
|
||||
);
|
||||
}
|
||||
|
||||
PropertyInfo propertyInfo = (PropertyInfo)memberExpression.Member;
|
||||
|
||||
// Получаем атрибут CommentAttribute, если он есть
|
||||
CommentAttribute? commentAttribute = propertyInfo.GetCustomAttribute<CommentAttribute>();
|
||||
if (commentAttribute != null)
|
||||
{
|
||||
// Устанавливаем имя последнего добавленного поля
|
||||
if (_groupDefinition.Fields.Count > 0)
|
||||
{
|
||||
_groupDefinition.Fields.Last().Name = commentAttribute.Comment;
|
||||
}
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Завершает построение группы полей и возвращает определение группы.
|
||||
/// </summary>
|
||||
/// <returns>Итоговое определение группы полей.</returns>
|
||||
public FieldGroupDefinition Build() => _groupDefinition;
|
||||
|
||||
/// <summary>
|
||||
/// Получает MemberExpression из тела лямбда-выражения.
|
||||
/// </summary>
|
||||
/// <param name="expression">Тело выражения.</param>
|
||||
/// <returns>MemberExpression или null, если не удалось получить.</returns>
|
||||
private static MemberExpression? GetMemberExpression(Expression expression)
|
||||
{
|
||||
return expression switch
|
||||
{
|
||||
MemberExpression memberExpression => memberExpression,
|
||||
UnaryExpression { NodeType: ExpressionType.Convert } unaryExpression =>
|
||||
unaryExpression.Operand as MemberExpression,
|
||||
_ => null,
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
namespace Pf2e.Web.Helpers.Serialization;
|
||||
|
||||
/// <summary>
|
||||
/// Группа полей.
|
||||
/// </summary>
|
||||
public class FieldGroupDefinition
|
||||
{
|
||||
/// <summary>
|
||||
/// Название группы.
|
||||
/// </summary>
|
||||
public string Name { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Тип класса, к которому относится поля группы.
|
||||
/// </summary>
|
||||
public Type GroupType { get; set; } = null!;
|
||||
|
||||
/// <summary>
|
||||
/// Список полей в группе.
|
||||
/// </summary>
|
||||
public List<FieldDefinition> Fields { get; set; } = [];
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
namespace Pf2e.Web.Helpers.Serialization;
|
||||
|
||||
/// <summary>
|
||||
/// Результат сериализации сущности.
|
||||
/// </summary>
|
||||
public class SerializationResult
|
||||
{
|
||||
/// <summary>
|
||||
/// Группы полей.
|
||||
/// </summary>
|
||||
public List<SerializationGroup> Groups { get; set; } = [];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Группа полей для сериализации.
|
||||
/// </summary>
|
||||
public class SerializationGroup
|
||||
{
|
||||
/// <summary>
|
||||
/// Название группы.
|
||||
/// </summary>
|
||||
public string Name { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Список полей в группе.
|
||||
/// </summary>
|
||||
public List<SerializationField> Fields { get; set; } = [];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Поле для сериализации.
|
||||
/// </summary>
|
||||
public class SerializationField
|
||||
{
|
||||
/// <summary>
|
||||
/// Название поля.
|
||||
/// </summary>
|
||||
public string Name { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Значение поля в строковом представлении.
|
||||
/// </summary>
|
||||
public string Type { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Значение поля в строковом представлении.
|
||||
/// </summary>
|
||||
public string Value { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Флаг, указывающий, реализовано ли поле (true) или нет (false).
|
||||
/// </summary>
|
||||
public bool IsImplemented { get; set; } = true;
|
||||
}
|
||||
@@ -1,151 +0,0 @@
|
||||
using System.Linq.Expressions;
|
||||
using System.Reflection;
|
||||
|
||||
namespace Pf2e.Web.Helpers;
|
||||
|
||||
/// <summary>
|
||||
/// Построитель сериализации для типа T.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">Тип сущности для сериализации.</typeparam>
|
||||
public class SerializationBuilder<T>
|
||||
where T : class
|
||||
{
|
||||
/// <summary>
|
||||
/// Список групп полей.
|
||||
/// </summary>
|
||||
public List<FieldGroup> Groups { get; set; } = [];
|
||||
|
||||
/// <summary>
|
||||
/// Форматтер полей сущности.
|
||||
/// </summary>
|
||||
protected EntityFieldFormatter FieldFormatter { get; } = new();
|
||||
|
||||
/// <summary>
|
||||
/// Добавляет группу полей в построитель сериализации.
|
||||
/// </summary>
|
||||
/// <param name="name">Название группы.</param>
|
||||
/// <param name="configure">Делегат для настройки группы полей.</param>
|
||||
/// <returns>Текущий экземпляр построителя.</returns>
|
||||
public SerializationBuilder<T> AddGroup(string name, Action<FieldGroupBuilder<T>> configure)
|
||||
{
|
||||
FieldGroupBuilder<T> groupBuilder = new(name);
|
||||
configure(groupBuilder);
|
||||
Groups.Add(groupBuilder.Build());
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Добавляет группу полей в построитель сериализации с названием типа.
|
||||
/// </summary>
|
||||
/// <param name="configure">Делегат для настройки группы полей.</param>
|
||||
/// <returns>Текущий экземпляр построителя.</returns>
|
||||
public SerializationBuilder<T> AddGroup(Action<FieldGroupBuilder<T>> configure)
|
||||
{
|
||||
return AddGroup(typeof(T).Name, configure);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Построитель группы полей для типа TField.
|
||||
/// </summary>
|
||||
/// <typeparam name="TField">Тип сущности для группы полей.</typeparam>
|
||||
public class FieldGroupBuilder<TField>(string name)
|
||||
where TField : class
|
||||
{
|
||||
private readonly FieldGroup _group = new() { Name = name };
|
||||
|
||||
/// <summary>
|
||||
/// Добавляет поле в группу.
|
||||
/// </summary>
|
||||
/// <param name="fieldExpression">Лямбда-выражение для получения поля.</param>
|
||||
/// <returns>Текущий экземпляр построителя группы полей.</returns>
|
||||
public FieldGroupBuilder<TField> AddField(Expression<Func<TField, object>> fieldExpression)
|
||||
{
|
||||
// Получаем информацию о свойстве из выражения
|
||||
MemberExpression? memberExpression = GetMemberExpression(fieldExpression.Body);
|
||||
if (memberExpression == null)
|
||||
{
|
||||
throw new ArgumentException(
|
||||
"Выражение должно быть доступом к свойству",
|
||||
nameof(fieldExpression)
|
||||
);
|
||||
}
|
||||
|
||||
PropertyInfo propertyInfo = (PropertyInfo)memberExpression.Member;
|
||||
string fieldName = propertyInfo.Name;
|
||||
Type fieldType = propertyInfo.PropertyType;
|
||||
|
||||
_group.Fields.Add(new() { Name = fieldName, Type = fieldType });
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Устанавливает форматтер значения для последнего добавленного поля.
|
||||
/// </summary>
|
||||
/// <param name="valueFormatter">Функция форматирования значения.</param>
|
||||
/// <returns>Текущий экземпляр построителя группы полей.</returns>
|
||||
public FieldGroupBuilder<TField> SetValueFormatter(Func<object, string> valueFormatter)
|
||||
{
|
||||
_group.Fields.Last().ValueFormatter = valueFormatter;
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Строит группу полей.
|
||||
/// </summary>
|
||||
/// <returns>Готовая группа полей.</returns>
|
||||
public FieldGroup Build() => _group;
|
||||
|
||||
/// <summary>
|
||||
/// Получает MemberExpression из тела лямбда-выражения.
|
||||
/// </summary>
|
||||
/// <param name="expression">Тело выражения.</param>
|
||||
/// <returns>MemberExpression или null, если не удалось получить.</returns>
|
||||
private static MemberExpression? GetMemberExpression(Expression expression)
|
||||
{
|
||||
return expression switch
|
||||
{
|
||||
MemberExpression memberExpression => memberExpression,
|
||||
UnaryExpression { NodeType: ExpressionType.Convert } unaryExpression =>
|
||||
unaryExpression.Operand as MemberExpression,
|
||||
_ => null,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Группа полей.
|
||||
/// </summary>
|
||||
public class FieldGroup
|
||||
{
|
||||
/// <summary>
|
||||
/// Название группы.
|
||||
/// </summary>
|
||||
public string Name { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Список полей в группе.
|
||||
/// </summary>
|
||||
public List<FieldBuilderDefinition> Fields { get; set; } = [];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Информация о поле.
|
||||
/// </summary>
|
||||
public class FieldBuilderDefinition
|
||||
{
|
||||
/// <summary>
|
||||
/// Название поля.
|
||||
/// </summary>
|
||||
public string Name { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Тип поля.
|
||||
/// </summary>
|
||||
public required Type Type { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Форматтер значения поля.
|
||||
/// </summary>
|
||||
public Func<object, string>? ValueFormatter { get; set; } = null;
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
@page "/{type}/{slug}"
|
||||
@model Pf2e.Web.Pages.EntityModel
|
||||
@model Pf2e.Web.Pages.Entity
|
||||
|
||||
@{
|
||||
ViewData["Title"] = "Entity";
|
||||
@@ -26,10 +26,10 @@
|
||||
{
|
||||
<span class="badge bg-warning text-dark">НЕ РЕАЛИЗОВАНО:</span>
|
||||
}
|
||||
@Html.Raw(field.DisplayValue)
|
||||
@Html.Raw(field.Value)
|
||||
</dd>
|
||||
}
|
||||
</dl>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
@@ -16,22 +16,22 @@ using Pf2e.Web.Helpers;
|
||||
namespace Pf2e.Web.Pages
|
||||
{
|
||||
[ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)]
|
||||
public class EntityModel(Pf2eDbContext context) : PageModel
|
||||
public class Entity(Pf2eDbContext context) : PageModel
|
||||
{
|
||||
private readonly Pf2eDbContext _context = context;
|
||||
|
||||
public FullEntity FullEntity { get; set; } = null!;
|
||||
public EntitySerializer.SerializationResult SerializationResult { get; set; } = null!;
|
||||
public Pf2e.Web.Helpers.Serialization.SerializationResult SerializationResult { get; set; } = null!;
|
||||
|
||||
public IActionResult OnGet(string type, string slug)
|
||||
{
|
||||
// Добавим отладочную информацию
|
||||
System.Diagnostics.Debug.WriteLine(
|
||||
$"EntityModel.OnGet called with type: {type}, slug: {slug}"
|
||||
$"Entity.OnGet called with type: {type}, slug: {slug}"
|
||||
);
|
||||
|
||||
// Логируем в консоль для веб-сервера
|
||||
Console.WriteLine($"EntityModel.OnGet called with type: {type}, slug: {slug}");
|
||||
Console.WriteLine($"Entity.OnGet called with type: {type}, slug: {slug}");
|
||||
|
||||
try
|
||||
{
|
||||
@@ -41,7 +41,7 @@ namespace Pf2e.Web.Pages
|
||||
Console.WriteLine($"EntityType resolved to: {entityType}");
|
||||
|
||||
// Находим сущность по slug и типу
|
||||
Entity? entity = _context
|
||||
Database.Model.Base.Entity? entity = _context
|
||||
.Entities.AsNoTracking()
|
||||
.FirstOrDefault(e =>
|
||||
e.Slug.ToLower() == slug.ToLower() && e.Type == entityType
|
||||
@@ -67,7 +67,7 @@ namespace Pf2e.Web.Pages
|
||||
FullEntity = new(_context, entityType, entity.Id);
|
||||
|
||||
// Сериализуем для отображения
|
||||
EntitySerializer serializer = new();
|
||||
EntitySerialization serializer = new();
|
||||
SerializationResult = serializer.Serialize(FullEntity);
|
||||
|
||||
// Set breadcrumbs
|
||||
@@ -76,15 +76,15 @@ namespace Pf2e.Web.Pages
|
||||
{
|
||||
("Home", "/"),
|
||||
(entityType.ToString(), $"/{typeName}"),
|
||||
(entity.Name[Pf2e.Database.Model.Common.I18n.LocaleType.English], "")
|
||||
(entity.Name[Pf2e.Database.Model.Common.I18n.LocaleType.English], ""),
|
||||
};
|
||||
|
||||
return Page();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
System.Diagnostics.Debug.WriteLine($"Error in EntityModel.OnGet: {ex.Message}");
|
||||
Console.WriteLine($"Error in EntityModel.OnGet: {ex.Message}");
|
||||
System.Diagnostics.Debug.WriteLine($"Error in Entity.OnGet: {ex.Message}");
|
||||
Console.WriteLine($"Error in Entity.OnGet: {ex.Message}");
|
||||
return NotFound();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,7 +18,7 @@ namespace Pf2e.Web.Pages
|
||||
[BindProperty(SupportsGet = true)]
|
||||
public int? PageNum { get; set; } = 1;
|
||||
|
||||
public IList<Entity> Entity { get; set; } = null!;
|
||||
public IList<Database.Model.Base.Entity> Entity { get; set; } = null!;
|
||||
|
||||
public EntityType CurrentType;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user