diff --git a/src/HopFrame.Database/Attributes/ListingPropertyAttribute.cs b/src/HopFrame.Database/Attributes/ListingPropertyAttribute.cs deleted file mode 100644 index b5fc86a..0000000 --- a/src/HopFrame.Database/Attributes/ListingPropertyAttribute.cs +++ /dev/null @@ -1,4 +0,0 @@ -namespace HopFrame.Database.Attributes; - -[AttributeUsage(AttributeTargets.Property)] -public sealed class ListingPropertyAttribute : Attribute; diff --git a/src/HopFrame.Database/Models/Permission.cs b/src/HopFrame.Database/Models/Permission.cs index 475632e..db111ba 100644 --- a/src/HopFrame.Database/Models/Permission.cs +++ b/src/HopFrame.Database/Models/Permission.cs @@ -1,7 +1,6 @@ using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; using System.Text.Json.Serialization; -using HopFrame.Database.Attributes; namespace HopFrame.Database.Models; @@ -10,7 +9,7 @@ public class Permission { [Key, Required, DatabaseGenerated(DatabaseGeneratedOption.Identity)] public long Id { get; init; } - [Required, MaxLength(255), ListingProperty] + [Required, MaxLength(255)] public string PermissionName { get; set; } [Required] diff --git a/src/HopFrame.Database/Models/PermissionGroup.cs b/src/HopFrame.Database/Models/PermissionGroup.cs index b6613bb..7a70ebd 100644 --- a/src/HopFrame.Database/Models/PermissionGroup.cs +++ b/src/HopFrame.Database/Models/PermissionGroup.cs @@ -1,12 +1,11 @@ using System.ComponentModel; using System.ComponentModel.DataAnnotations; -using HopFrame.Database.Attributes; namespace HopFrame.Database.Models; public class PermissionGroup : IPermissionOwner { - [Key, Required, MaxLength(50), ListingProperty] + [Key, Required, MaxLength(50)] public string Name { get; init; } [Required, DefaultValue(false)] diff --git a/src/HopFrame.Database/Models/User.cs b/src/HopFrame.Database/Models/User.cs index 2fe6c8e..971d899 100644 --- a/src/HopFrame.Database/Models/User.cs +++ b/src/HopFrame.Database/Models/User.cs @@ -1,6 +1,5 @@ using System.ComponentModel.DataAnnotations; using System.Text.Json.Serialization; -using HopFrame.Database.Attributes; namespace HopFrame.Database.Models; @@ -9,7 +8,7 @@ public class User : IPermissionOwner { [Key, Required, MinLength(36), MaxLength(36)] public Guid Id { get; init; } - [MaxLength(50), ListingProperty] + [MaxLength(50)] public string Username { get; set; } [Required, MaxLength(50), EmailAddress] diff --git a/src/HopFrame.Web.Admin/Attributes/Members/ListingPropertyAttribute.cs b/src/HopFrame.Web.Admin/Attributes/Members/ListingPropertyAttribute.cs new file mode 100644 index 0000000..eeb10f8 --- /dev/null +++ b/src/HopFrame.Web.Admin/Attributes/Members/ListingPropertyAttribute.cs @@ -0,0 +1,4 @@ +namespace HopFrame.Web.Admin.Attributes.Members; + +[AttributeUsage(AttributeTargets.Property)] +public sealed class ListingPropertyAttribute : Attribute; \ No newline at end of file diff --git a/src/HopFrame.Web.Admin/Generators/IAdminPageGenerator.cs b/src/HopFrame.Web.Admin/Generators/IAdminPageGenerator.cs index 0e7a72c..dc09056 100644 --- a/src/HopFrame.Web.Admin/Generators/IAdminPageGenerator.cs +++ b/src/HopFrame.Web.Admin/Generators/IAdminPageGenerator.cs @@ -22,7 +22,7 @@ public interface IAdminPageGenerator { IAdminPageGenerator ConfigureRepository() where TRepository : ModelRepository; - IAdminPropertyGenerator Property(Expression> propertyExpression); + IAdminPropertyGenerator Property(Expression> propertyExpression); IAdminPageGenerator ListingProperty(Expression> propertyExpression); } diff --git a/src/HopFrame.Web.Admin/Generators/IAdminPropertyGenerator.cs b/src/HopFrame.Web.Admin/Generators/IAdminPropertyGenerator.cs index 2d75abe..745dbb5 100644 --- a/src/HopFrame.Web.Admin/Generators/IAdminPropertyGenerator.cs +++ b/src/HopFrame.Web.Admin/Generators/IAdminPropertyGenerator.cs @@ -1,19 +1,23 @@ +using System.Linq.Expressions; + namespace HopFrame.Web.Admin.Generators; -public interface IAdminPropertyGenerator { +public interface IAdminPropertyGenerator { - IAdminPropertyGenerator Sortable(bool sortable); - IAdminPropertyGenerator Editable(bool editable); - IAdminPropertyGenerator DisplayValueWhileEditing(bool display); - IAdminPropertyGenerator DisplayInListing(bool display = true); - IAdminPropertyGenerator Ignore(bool ignore = true); - IAdminPropertyGenerator Generated(bool generated = true); - IAdminPropertyGenerator Bold(bool bold = true); + IAdminPropertyGenerator Sortable(bool sortable); + IAdminPropertyGenerator Editable(bool editable); + IAdminPropertyGenerator DisplayValueWhileEditing(bool display); + IAdminPropertyGenerator DisplayInListing(bool display = true); + IAdminPropertyGenerator Ignore(bool ignore = true); + IAdminPropertyGenerator Generated(bool generated = true); + IAdminPropertyGenerator Bold(bool bold = true); - IAdminPropertyGenerator DisplayName(string displayName); - IAdminPropertyGenerator Description(string description); - IAdminPropertyGenerator Prefix(string prefix); - IAdminPropertyGenerator Validator(Func validator); - IAdminPropertyGenerator IsSelector(); + IAdminPropertyGenerator DisplayName(string displayName); + IAdminPropertyGenerator Description(string description); + IAdminPropertyGenerator Prefix(string prefix); + IAdminPropertyGenerator Validator(Func validator); + IAdminPropertyGenerator IsSelector(); + IAdminPropertyGenerator DisplayProperty(Expression> propertyExpression); + IAdminPropertyGenerator DisplayPropertyForListType(Expression> propertyExpression); } \ No newline at end of file diff --git a/src/HopFrame.Web.Admin/Generators/Implementation/AdminContextGenerator.cs b/src/HopFrame.Web.Admin/Generators/Implementation/AdminContextGenerator.cs index aa90737..ae62af6 100644 --- a/src/HopFrame.Web.Admin/Generators/Implementation/AdminContextGenerator.cs +++ b/src/HopFrame.Web.Admin/Generators/Implementation/AdminContextGenerator.cs @@ -38,10 +38,13 @@ internal class AdminContextGenerator : IAdminContextGenerator { foreach (var property in properties) { var propertyType = property.PropertyType.GenericTypeArguments[0]; var pageGeneratorType = typeof(AdminPageGenerator<>).MakeGenericType(propertyType); - var generatorInstance = Activator.CreateInstance(pageGeneratorType, [property.Name]); // Calls constructor with title attribute + var generatorInstance = Activator.CreateInstance(pageGeneratorType); - var populatorMethod = pageGeneratorType.GetMethod(nameof(AdminPageGenerator.ApplyConfigurationFromAttributes)); - populatorMethod?.Invoke(generatorInstance, [propertyType.GetCustomAttributes(false)]); + var titleMethod = pageGeneratorType.GetMethod(nameof(AdminPageGenerator.Title)); + titleMethod?.Invoke(generatorInstance, [property.Name]); + + var populateMethod = pageGeneratorType.GetMethod(nameof(AdminPageGenerator.ApplyConfigurationFromAttributes)); + populateMethod?.Invoke(generatorInstance, [propertyType.GetCustomAttributes(false)]); _adminPages.Add(propertyType, generatorInstance); } diff --git a/src/HopFrame.Web.Admin/Generators/Implementation/AdminPageGenerator.cs b/src/HopFrame.Web.Admin/Generators/Implementation/AdminPageGenerator.cs index 4ea22e5..f61225b 100644 --- a/src/HopFrame.Web.Admin/Generators/Implementation/AdminPageGenerator.cs +++ b/src/HopFrame.Web.Admin/Generators/Implementation/AdminPageGenerator.cs @@ -10,32 +10,34 @@ namespace HopFrame.Web.Admin.Generators.Implementation; internal sealed class AdminPageGenerator : IAdminPageGenerator, IGenerator> { public readonly AdminPage Page; - private readonly IDictionary _propertyGenerators; + private readonly Dictionary _propertyGenerators; public AdminPageGenerator() { Page = new AdminPage { Permissions = new AdminPagePermissions(), ModelType = typeof(TModel) }; - _propertyGenerators = new Dictionary(); + _propertyGenerators = new Dictionary(); var type = typeof(TModel); var properties = type.GetProperties(); + var generatorType = typeof(AdminPropertyGenerator<>); foreach (var property in properties) { var attributes = property.GetCustomAttributes(false); + var genericType = generatorType.MakeGenericType(property.PropertyType); - var generator = Activator.CreateInstance(typeof(AdminPropertyGenerator), [property.Name, property.PropertyType]) as AdminPropertyGenerator; - generator?.ApplyConfigurationFromAttributes(this, attributes, property); + var generator = Activator.CreateInstance(genericType, [property.Name, property.PropertyType]); + + var method = genericType + .GetMethod(nameof(AdminPropertyGenerator.ApplyConfigurationFromAttributes))? + .MakeGenericMethod(type); + method?.Invoke(generator, [this, attributes, property]); _propertyGenerators.Add(property.Name, generator); } } - public AdminPageGenerator(string title) : this() { - Title(title); - } - public IAdminPageGenerator Title(string title) { Page.Title = title; Page.Url ??= title.ToLower(); @@ -100,13 +102,13 @@ internal sealed class AdminPageGenerator : IAdminPageGenerator, return this; } - public IAdminPropertyGenerator Property(Expression> propertyExpression) { + public IAdminPropertyGenerator Property(Expression> propertyExpression) { var property = GetPropertyInfo(propertyExpression); if (_propertyGenerators.TryGetValue(property.Name, out var propertyGenerator)) - return propertyGenerator; + return propertyGenerator as AdminPropertyGenerator; - var generator = Activator.CreateInstance(typeof(AdminPropertyGenerator), new { property.Name, property.PropertyType }) as AdminPropertyGenerator; + var generator = Activator.CreateInstance(typeof(AdminPropertyGenerator), new { property.Name, property.PropertyType }) as AdminPropertyGenerator; generator?.ApplyConfigurationFromAttributes(this, property.GetCustomAttributes(false), property); _propertyGenerators.Add(property.Name, generator); @@ -122,8 +124,10 @@ internal sealed class AdminPageGenerator : IAdminPageGenerator, public AdminPage Compile() { var properties = new List(); - foreach (var generator in _propertyGenerators.Values){ - properties.Add(generator.Compile()); + foreach (var generator in _propertyGenerators.Values) { + var method = generator.GetType().GetMethod(nameof(AdminPropertyGenerator.Compile)); + var prop = method?.Invoke(generator, []) as AdminPageProperty; + properties.Add(prop); } Page.Properties = properties; @@ -131,7 +135,7 @@ internal sealed class AdminPageGenerator : IAdminPageGenerator, return Page; } - private static PropertyInfo GetPropertyInfo(Expression> propertyLambda) { + public static PropertyInfo GetPropertyInfo(Expression> propertyLambda) { if (propertyLambda.Body is not MemberExpression member) { throw new ArgumentException($"Expression '{propertyLambda}' refers to a method, not a property."); } diff --git a/src/HopFrame.Web.Admin/Generators/Implementation/AdminPropertyGenerator.cs b/src/HopFrame.Web.Admin/Generators/Implementation/AdminPropertyGenerator.cs index 92df826..b626463 100644 --- a/src/HopFrame.Web.Admin/Generators/Implementation/AdminPropertyGenerator.cs +++ b/src/HopFrame.Web.Admin/Generators/Implementation/AdminPropertyGenerator.cs @@ -1,5 +1,6 @@ using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; +using System.Linq.Expressions; using System.Reflection; using HopFrame.Web.Admin.Attributes; using HopFrame.Web.Admin.Attributes.Members; @@ -7,74 +8,86 @@ using HopFrame.Web.Admin.Models; namespace HopFrame.Web.Admin.Generators.Implementation; -internal sealed class AdminPropertyGenerator(string name, Type type) : IAdminPropertyGenerator, IGenerator { +internal sealed class AdminPropertyGenerator(string name, Type type) : IAdminPropertyGenerator, IGenerator { private readonly AdminPageProperty _property = new() { Name = name, Type = type }; - public IAdminPropertyGenerator Sortable(bool sortable) { + public IAdminPropertyGenerator Sortable(bool sortable) { _property.Sortable = sortable; return this; } - public IAdminPropertyGenerator Editable(bool editable) { + public IAdminPropertyGenerator Editable(bool editable) { _property.Editable = editable; return this; } - public IAdminPropertyGenerator DisplayValueWhileEditing(bool display) { + public IAdminPropertyGenerator DisplayValueWhileEditing(bool display) { _property.EditDisplayValue = display; return this; } - public IAdminPropertyGenerator DisplayInListing(bool display = true) { + public IAdminPropertyGenerator DisplayInListing(bool display = true) { _property.DisplayInListing = display; _property.Sortable = false; return this; } - public IAdminPropertyGenerator Ignore(bool ignore = false) { + public IAdminPropertyGenerator Ignore(bool ignore = false) { _property.Ignore = ignore; return this; } - public IAdminPropertyGenerator Generated(bool generated = true) { + public IAdminPropertyGenerator Generated(bool generated = true) { _property.Generated = generated; return this; } - public IAdminPropertyGenerator Bold(bool bold = true) { + public IAdminPropertyGenerator Bold(bool bold = true) { _property.Bold = bold; return this; } - public IAdminPropertyGenerator DisplayName(string displayName) { + public IAdminPropertyGenerator DisplayName(string displayName) { _property.DisplayName = displayName; return this; } - public IAdminPropertyGenerator Description(string description) { + public IAdminPropertyGenerator Description(string description) { _property.Description = description; return this; } - public IAdminPropertyGenerator Prefix(string prefix) { + public IAdminPropertyGenerator Prefix(string prefix) { _property.Prefix = prefix; return this; } - public IAdminPropertyGenerator Validator(Func validator) { + public IAdminPropertyGenerator Validator(Func validator) { _property.Validator = validator; return this; } - public IAdminPropertyGenerator IsSelector() { + public IAdminPropertyGenerator IsSelector() { _property.SelectorType = typeof(TSelector); return this; } + public IAdminPropertyGenerator DisplayProperty(Expression> propertyExpression) { + var property = AdminPageGenerator.GetPropertyInfo(propertyExpression); + _property.DisplayPropertyName = property.Name; + return this; + } + + public IAdminPropertyGenerator DisplayPropertyForListType(Expression> propertyExpression) { + var property = AdminPageGenerator.GetPropertyInfo(propertyExpression); + _property.DisplayPropertyName = property.Name; + return this; + } + public AdminPageProperty Compile() { _property.DisplayName ??= _property.Name; return _property; @@ -129,5 +142,10 @@ internal sealed class AdminPropertyGenerator(string name, Type type) : IAdminPro var attribute = attributes.Single(a => a is AdminPrefixAttribute) as AdminPrefixAttribute; Prefix(attribute?.Prefix); } + + if (attributes.Any(a => a is ListingPropertyAttribute)) { + var attribute = attributes.Single(a => a is ListingPropertyAttribute) as ListingPropertyAttribute; + _property.DisplayPropertyName = property.Name; + } } } \ No newline at end of file diff --git a/src/HopFrame.Web.Admin/Models/AdminPageProperty.cs b/src/HopFrame.Web.Admin/Models/AdminPageProperty.cs index 6707458..f1b1ddd 100644 --- a/src/HopFrame.Web.Admin/Models/AdminPageProperty.cs +++ b/src/HopFrame.Web.Admin/Models/AdminPageProperty.cs @@ -7,6 +7,7 @@ public sealed class AdminPageProperty { public string DisplayName { get; set; } public string Description { get; set; } public string Prefix { get; set; } + public string DisplayPropertyName { get; set; } public bool DisplayInListing { get; set; } = true; public bool Sortable { get; set; } = true; diff --git a/src/HopFrame.Web/Components/Administration/AdminPageModal.razor b/src/HopFrame.Web/Components/Administration/AdminPageModal.razor index 5fa3ad8..0141634 100644 --- a/src/HopFrame.Web/Components/Administration/AdminPageModal.razor +++ b/src/HopFrame.Web/Components/Administration/AdminPageModal.razor @@ -1,12 +1,10 @@ @rendermode InteractiveServer @using System.Collections -@using System.ComponentModel.DataAnnotations @using BlazorStrap @using BlazorStrap.Shared.Components.Modal @using static Microsoft.AspNetCore.Components.Web.RenderMode @using BlazorStrap.V5 -@using HopFrame.Database.Attributes @using HopFrame.Web.Admin @using HopFrame.Web.Admin.Models @using HopFrame.Web.Admin.Providers @@ -166,7 +164,7 @@ return MapPropertyValue(property.GetValue(_entry), property); } - public string MapPropertyValue(object value, AdminPageProperty property) { + public string MapPropertyValue(object value, AdminPageProperty property, bool isSubProperty = false) { if (value is null) return string.Empty; var type = value.GetType(); @@ -180,11 +178,18 @@ } } - if (type.GetProperties().Any(p => p.GetCustomAttributes(false).Any(a => a is ListingPropertyAttribute))) { + /*if (type.GetProperties().Any(p => p.GetCustomAttributes(false).Any(a => a is ListingPropertyAttribute))) { var prop = type.GetProperties() .SingleOrDefault(p => p.GetCustomAttributes(false).Any(a => a is ListingPropertyAttribute)); return MapPropertyValue(prop?.GetValue(value), property); + }*/ + + if (!string.IsNullOrEmpty(property.DisplayPropertyName) && !isSubProperty) { + var prop = type.GetProperties() + .SingleOrDefault(p => p.Name == property.DisplayPropertyName); + + return MapPropertyValue(prop?.GetValue(value), property, true); } var stringValue = value.ToString(); diff --git a/src/HopFrame.Web/HopAdminContext.cs b/src/HopFrame.Web/HopAdminContext.cs index 54be7d4..6da2927 100644 --- a/src/HopFrame.Web/HopAdminContext.cs +++ b/src/HopFrame.Web/HopAdminContext.cs @@ -30,7 +30,8 @@ public class HopAdminContext : AdminPagesContext { generator.Page().Property(u => u.Permissions) .DisplayInListing(false) - .IsSelector(); + .IsSelector() + .DisplayPropertyForListType(p => p.PermissionName); generator.Page().Property(u => u.Tokens) .Ignore(); @@ -54,6 +55,7 @@ public class HopAdminContext : AdminPagesContext { .Editable(false); generator.Page().Property(g => g.Permissions) - .DisplayInListing(false); + .DisplayInListing(false) + .DisplayPropertyForListType(p => p.PermissionName); } } \ No newline at end of file diff --git a/src/HopFrame.Web/Pages/Administration/AdminPageList.razor b/src/HopFrame.Web/Pages/Administration/AdminPageList.razor index 17c5f64..8932a56 100644 --- a/src/HopFrame.Web/Pages/Administration/AdminPageList.razor +++ b/src/HopFrame.Web/Pages/Administration/AdminPageList.razor @@ -178,12 +178,7 @@ return String.CompareOrdinal(propX, propY); }); - if (_currentSortDirection == ListSortDirection.Ascending) { - _displayedModels = _displayedModels.Order(comparer).ToList(); - } - else { - _displayedModels = _displayedModels.OrderDescending(comparer).ToList(); - } + _displayedModels = _currentSortDirection == ListSortDirection.Ascending ? _displayedModels.Order(comparer).ToList() : _displayedModels.OrderDescending(comparer).ToList(); _currentSortProperty = property; } diff --git a/test/FrontendTest/DatabaseContext.cs b/test/FrontendTest/DatabaseContext.cs index 5da7d59..9a89c5d 100644 --- a/test/FrontendTest/DatabaseContext.cs +++ b/test/FrontendTest/DatabaseContext.cs @@ -7,6 +7,6 @@ public class DatabaseContext : HopDbContextBase { protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) { base.OnConfiguring(optionsBuilder); - optionsBuilder.UseSqlite("Data Source=C:\\Users\\Remote\\Documents\\Projekte\\HopFrame\\test\\RestApiTest\\bin\\Debug\\net8.0\\test.db;Mode=ReadWrite;"); + optionsBuilder.UseSqlite(@"Data Source=C:\Users\leon\Documents\Projekte\HopFrame\test\RestApiTest\bin\Debug\net8.0\test.db;Mode=ReadWrite;"); } } \ No newline at end of file diff --git a/test/RestApiTest/DatabaseContext.cs b/test/RestApiTest/DatabaseContext.cs index 1167277..ef370c7 100644 --- a/test/RestApiTest/DatabaseContext.cs +++ b/test/RestApiTest/DatabaseContext.cs @@ -12,7 +12,7 @@ public class DatabaseContext : HopDbContextBase { protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) { base.OnConfiguring(optionsBuilder); - optionsBuilder.UseSqlite("Data Source=C:\\Users\\Remote\\Documents\\Projekte\\HopFrame\\test\\RestApiTest\\bin\\Debug\\net8.0\\test.db;Mode=ReadWrite;"); + optionsBuilder.UseSqlite(@"Data Source=C:\Users\leon\Documents\Projekte\HopFrame\test\RestApiTest\bin\Debug\net8.0\test.db;Mode=ReadWrite;"); } protected override void OnModelCreating(ModelBuilder modelBuilder) {