Made AdminPageProperty generic + added dynamic display property selecting

This commit is contained in:
2024-10-26 11:54:02 +02:00
parent 599ce2bf43
commit ce15717c7d
16 changed files with 97 additions and 68 deletions

View File

@@ -1,4 +0,0 @@
namespace HopFrame.Database.Attributes;
[AttributeUsage(AttributeTargets.Property)]
public sealed class ListingPropertyAttribute : Attribute;

View File

@@ -1,7 +1,6 @@
using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema; using System.ComponentModel.DataAnnotations.Schema;
using System.Text.Json.Serialization; using System.Text.Json.Serialization;
using HopFrame.Database.Attributes;
namespace HopFrame.Database.Models; namespace HopFrame.Database.Models;
@@ -10,7 +9,7 @@ public class Permission {
[Key, Required, DatabaseGenerated(DatabaseGeneratedOption.Identity)] [Key, Required, DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public long Id { get; init; } public long Id { get; init; }
[Required, MaxLength(255), ListingProperty] [Required, MaxLength(255)]
public string PermissionName { get; set; } public string PermissionName { get; set; }
[Required] [Required]

View File

@@ -1,12 +1,11 @@
using System.ComponentModel; using System.ComponentModel;
using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations;
using HopFrame.Database.Attributes;
namespace HopFrame.Database.Models; namespace HopFrame.Database.Models;
public class PermissionGroup : IPermissionOwner { public class PermissionGroup : IPermissionOwner {
[Key, Required, MaxLength(50), ListingProperty] [Key, Required, MaxLength(50)]
public string Name { get; init; } public string Name { get; init; }
[Required, DefaultValue(false)] [Required, DefaultValue(false)]

View File

@@ -1,6 +1,5 @@
using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations;
using System.Text.Json.Serialization; using System.Text.Json.Serialization;
using HopFrame.Database.Attributes;
namespace HopFrame.Database.Models; namespace HopFrame.Database.Models;
@@ -9,7 +8,7 @@ public class User : IPermissionOwner {
[Key, Required, MinLength(36), MaxLength(36)] [Key, Required, MinLength(36), MaxLength(36)]
public Guid Id { get; init; } public Guid Id { get; init; }
[MaxLength(50), ListingProperty] [MaxLength(50)]
public string Username { get; set; } public string Username { get; set; }
[Required, MaxLength(50), EmailAddress] [Required, MaxLength(50), EmailAddress]

View File

@@ -0,0 +1,4 @@
namespace HopFrame.Web.Admin.Attributes.Members;
[AttributeUsage(AttributeTargets.Property)]
public sealed class ListingPropertyAttribute : Attribute;

View File

@@ -22,7 +22,7 @@ public interface IAdminPageGenerator<TModel> {
IAdminPageGenerator<TModel> ConfigureRepository<TRepository>() where TRepository : ModelRepository<TModel>; IAdminPageGenerator<TModel> ConfigureRepository<TRepository>() where TRepository : ModelRepository<TModel>;
IAdminPropertyGenerator Property<TProperty>(Expression<Func<TModel, TProperty>> propertyExpression); IAdminPropertyGenerator<TProperty> Property<TProperty>(Expression<Func<TModel, TProperty>> propertyExpression);
IAdminPageGenerator<TModel> ListingProperty<TProperty>(Expression<Func<TModel, TProperty>> propertyExpression); IAdminPageGenerator<TModel> ListingProperty<TProperty>(Expression<Func<TModel, TProperty>> propertyExpression);
} }

View File

@@ -1,19 +1,23 @@
using System.Linq.Expressions;
namespace HopFrame.Web.Admin.Generators; namespace HopFrame.Web.Admin.Generators;
public interface IAdminPropertyGenerator { public interface IAdminPropertyGenerator<TProperty> {
IAdminPropertyGenerator Sortable(bool sortable); IAdminPropertyGenerator<TProperty> Sortable(bool sortable);
IAdminPropertyGenerator Editable(bool editable); IAdminPropertyGenerator<TProperty> Editable(bool editable);
IAdminPropertyGenerator DisplayValueWhileEditing(bool display); IAdminPropertyGenerator<TProperty> DisplayValueWhileEditing(bool display);
IAdminPropertyGenerator DisplayInListing(bool display = true); IAdminPropertyGenerator<TProperty> DisplayInListing(bool display = true);
IAdminPropertyGenerator Ignore(bool ignore = true); IAdminPropertyGenerator<TProperty> Ignore(bool ignore = true);
IAdminPropertyGenerator Generated(bool generated = true); IAdminPropertyGenerator<TProperty> Generated(bool generated = true);
IAdminPropertyGenerator Bold(bool bold = true); IAdminPropertyGenerator<TProperty> Bold(bool bold = true);
IAdminPropertyGenerator DisplayName(string displayName); IAdminPropertyGenerator<TProperty> DisplayName(string displayName);
IAdminPropertyGenerator Description(string description); IAdminPropertyGenerator<TProperty> Description(string description);
IAdminPropertyGenerator Prefix(string prefix); IAdminPropertyGenerator<TProperty> Prefix(string prefix);
IAdminPropertyGenerator Validator(Func<object, bool> validator); IAdminPropertyGenerator<TProperty> Validator(Func<object, bool> validator);
IAdminPropertyGenerator IsSelector<TSelector>(); IAdminPropertyGenerator<TProperty> IsSelector<TSelector>();
IAdminPropertyGenerator<TProperty> DisplayProperty<TListingProperty>(Expression<Func<TProperty, TListingProperty>> propertyExpression);
IAdminPropertyGenerator<TProperty> DisplayPropertyForListType<TInnerProperty>(Expression<Func<TInnerProperty, object>> propertyExpression);
} }

View File

@@ -38,10 +38,13 @@ internal class AdminContextGenerator : IAdminContextGenerator {
foreach (var property in properties) { foreach (var property in properties) {
var propertyType = property.PropertyType.GenericTypeArguments[0]; var propertyType = property.PropertyType.GenericTypeArguments[0];
var pageGeneratorType = typeof(AdminPageGenerator<>).MakeGenericType(propertyType); 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<TContext>.ApplyConfigurationFromAttributes)); var titleMethod = pageGeneratorType.GetMethod(nameof(AdminPageGenerator<TContext>.Title));
populatorMethod?.Invoke(generatorInstance, [propertyType.GetCustomAttributes(false)]); titleMethod?.Invoke(generatorInstance, [property.Name]);
var populateMethod = pageGeneratorType.GetMethod(nameof(AdminPageGenerator<TContext>.ApplyConfigurationFromAttributes));
populateMethod?.Invoke(generatorInstance, [propertyType.GetCustomAttributes(false)]);
_adminPages.Add(propertyType, generatorInstance); _adminPages.Add(propertyType, generatorInstance);
} }

View File

@@ -10,32 +10,34 @@ namespace HopFrame.Web.Admin.Generators.Implementation;
internal sealed class AdminPageGenerator<TModel> : IAdminPageGenerator<TModel>, IGenerator<AdminPage<TModel>> { internal sealed class AdminPageGenerator<TModel> : IAdminPageGenerator<TModel>, IGenerator<AdminPage<TModel>> {
public readonly AdminPage<TModel> Page; public readonly AdminPage<TModel> Page;
private readonly IDictionary<string, AdminPropertyGenerator> _propertyGenerators; private readonly Dictionary<string, object> _propertyGenerators;
public AdminPageGenerator() { public AdminPageGenerator() {
Page = new AdminPage<TModel> { Page = new AdminPage<TModel> {
Permissions = new AdminPagePermissions(), Permissions = new AdminPagePermissions(),
ModelType = typeof(TModel) ModelType = typeof(TModel)
}; };
_propertyGenerators = new Dictionary<string, AdminPropertyGenerator>(); _propertyGenerators = new Dictionary<string, object>();
var type = typeof(TModel); var type = typeof(TModel);
var properties = type.GetProperties(); var properties = type.GetProperties();
var generatorType = typeof(AdminPropertyGenerator<>);
foreach (var property in properties) { foreach (var property in properties) {
var attributes = property.GetCustomAttributes(false); var attributes = property.GetCustomAttributes(false);
var genericType = generatorType.MakeGenericType(property.PropertyType);
var generator = Activator.CreateInstance(typeof(AdminPropertyGenerator), [property.Name, property.PropertyType]) as AdminPropertyGenerator; var generator = Activator.CreateInstance(genericType, [property.Name, property.PropertyType]);
generator?.ApplyConfigurationFromAttributes(this, attributes, property);
var method = genericType
.GetMethod(nameof(AdminPropertyGenerator<object>.ApplyConfigurationFromAttributes))?
.MakeGenericMethod(type);
method?.Invoke(generator, [this, attributes, property]);
_propertyGenerators.Add(property.Name, generator); _propertyGenerators.Add(property.Name, generator);
} }
} }
public AdminPageGenerator(string title) : this() {
Title(title);
}
public IAdminPageGenerator<TModel> Title(string title) { public IAdminPageGenerator<TModel> Title(string title) {
Page.Title = title; Page.Title = title;
Page.Url ??= title.ToLower(); Page.Url ??= title.ToLower();
@@ -100,13 +102,13 @@ internal sealed class AdminPageGenerator<TModel> : IAdminPageGenerator<TModel>,
return this; return this;
} }
public IAdminPropertyGenerator Property<TProperty>(Expression<Func<TModel, TProperty>> propertyExpression) { public IAdminPropertyGenerator<TProperty> Property<TProperty>(Expression<Func<TModel, TProperty>> propertyExpression) {
var property = GetPropertyInfo(propertyExpression); var property = GetPropertyInfo(propertyExpression);
if (_propertyGenerators.TryGetValue(property.Name, out var propertyGenerator)) if (_propertyGenerators.TryGetValue(property.Name, out var propertyGenerator))
return propertyGenerator; return propertyGenerator as AdminPropertyGenerator<TProperty>;
var generator = Activator.CreateInstance(typeof(AdminPropertyGenerator), new { property.Name, property.PropertyType }) as AdminPropertyGenerator; var generator = Activator.CreateInstance(typeof(AdminPropertyGenerator<TProperty>), new { property.Name, property.PropertyType }) as AdminPropertyGenerator<TProperty>;
generator?.ApplyConfigurationFromAttributes(this, property.GetCustomAttributes(false), property); generator?.ApplyConfigurationFromAttributes(this, property.GetCustomAttributes(false), property);
_propertyGenerators.Add(property.Name, generator); _propertyGenerators.Add(property.Name, generator);
@@ -122,8 +124,10 @@ internal sealed class AdminPageGenerator<TModel> : IAdminPageGenerator<TModel>,
public AdminPage<TModel> Compile() { public AdminPage<TModel> Compile() {
var properties = new List<AdminPageProperty>(); var properties = new List<AdminPageProperty>();
foreach (var generator in _propertyGenerators.Values){ foreach (var generator in _propertyGenerators.Values) {
properties.Add(generator.Compile()); var method = generator.GetType().GetMethod(nameof(AdminPropertyGenerator<object>.Compile));
var prop = method?.Invoke(generator, []) as AdminPageProperty;
properties.Add(prop);
} }
Page.Properties = properties; Page.Properties = properties;
@@ -131,7 +135,7 @@ internal sealed class AdminPageGenerator<TModel> : IAdminPageGenerator<TModel>,
return Page; return Page;
} }
private static PropertyInfo GetPropertyInfo<TSource, TProperty>(Expression<Func<TSource, TProperty>> propertyLambda) { public static PropertyInfo GetPropertyInfo<TSource, TProperty>(Expression<Func<TSource, TProperty>> propertyLambda) {
if (propertyLambda.Body is not MemberExpression member) { if (propertyLambda.Body is not MemberExpression member) {
throw new ArgumentException($"Expression '{propertyLambda}' refers to a method, not a property."); throw new ArgumentException($"Expression '{propertyLambda}' refers to a method, not a property.");
} }

View File

@@ -1,5 +1,6 @@
using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema; using System.ComponentModel.DataAnnotations.Schema;
using System.Linq.Expressions;
using System.Reflection; using System.Reflection;
using HopFrame.Web.Admin.Attributes; using HopFrame.Web.Admin.Attributes;
using HopFrame.Web.Admin.Attributes.Members; using HopFrame.Web.Admin.Attributes.Members;
@@ -7,74 +8,86 @@ using HopFrame.Web.Admin.Models;
namespace HopFrame.Web.Admin.Generators.Implementation; namespace HopFrame.Web.Admin.Generators.Implementation;
internal sealed class AdminPropertyGenerator(string name, Type type) : IAdminPropertyGenerator, IGenerator<AdminPageProperty> { internal sealed class AdminPropertyGenerator<TProperty>(string name, Type type) : IAdminPropertyGenerator<TProperty>, IGenerator<AdminPageProperty> {
private readonly AdminPageProperty _property = new() { private readonly AdminPageProperty _property = new() {
Name = name, Name = name,
Type = type Type = type
}; };
public IAdminPropertyGenerator Sortable(bool sortable) { public IAdminPropertyGenerator<TProperty> Sortable(bool sortable) {
_property.Sortable = sortable; _property.Sortable = sortable;
return this; return this;
} }
public IAdminPropertyGenerator Editable(bool editable) { public IAdminPropertyGenerator<TProperty> Editable(bool editable) {
_property.Editable = editable; _property.Editable = editable;
return this; return this;
} }
public IAdminPropertyGenerator DisplayValueWhileEditing(bool display) { public IAdminPropertyGenerator<TProperty> DisplayValueWhileEditing(bool display) {
_property.EditDisplayValue = display; _property.EditDisplayValue = display;
return this; return this;
} }
public IAdminPropertyGenerator DisplayInListing(bool display = true) { public IAdminPropertyGenerator<TProperty> DisplayInListing(bool display = true) {
_property.DisplayInListing = display; _property.DisplayInListing = display;
_property.Sortable = false; _property.Sortable = false;
return this; return this;
} }
public IAdminPropertyGenerator Ignore(bool ignore = false) { public IAdminPropertyGenerator<TProperty> Ignore(bool ignore = false) {
_property.Ignore = ignore; _property.Ignore = ignore;
return this; return this;
} }
public IAdminPropertyGenerator Generated(bool generated = true) { public IAdminPropertyGenerator<TProperty> Generated(bool generated = true) {
_property.Generated = generated; _property.Generated = generated;
return this; return this;
} }
public IAdminPropertyGenerator Bold(bool bold = true) { public IAdminPropertyGenerator<TProperty> Bold(bool bold = true) {
_property.Bold = bold; _property.Bold = bold;
return this; return this;
} }
public IAdminPropertyGenerator DisplayName(string displayName) { public IAdminPropertyGenerator<TProperty> DisplayName(string displayName) {
_property.DisplayName = displayName; _property.DisplayName = displayName;
return this; return this;
} }
public IAdminPropertyGenerator Description(string description) { public IAdminPropertyGenerator<TProperty> Description(string description) {
_property.Description = description; _property.Description = description;
return this; return this;
} }
public IAdminPropertyGenerator Prefix(string prefix) { public IAdminPropertyGenerator<TProperty> Prefix(string prefix) {
_property.Prefix = prefix; _property.Prefix = prefix;
return this; return this;
} }
public IAdminPropertyGenerator Validator(Func<object, bool> validator) { public IAdminPropertyGenerator<TProperty> Validator(Func<object, bool> validator) {
_property.Validator = validator; _property.Validator = validator;
return this; return this;
} }
public IAdminPropertyGenerator IsSelector<TSelector>() { public IAdminPropertyGenerator<TProperty> IsSelector<TSelector>() {
_property.SelectorType = typeof(TSelector); _property.SelectorType = typeof(TSelector);
return this; return this;
} }
public IAdminPropertyGenerator<TProperty> DisplayProperty<TListingProperty>(Expression<Func<TProperty, TListingProperty>> propertyExpression) {
var property = AdminPageGenerator<object>.GetPropertyInfo(propertyExpression);
_property.DisplayPropertyName = property.Name;
return this;
}
public IAdminPropertyGenerator<TProperty> DisplayPropertyForListType<TInnerProperty>(Expression<Func<TInnerProperty, object>> propertyExpression) {
var property = AdminPageGenerator<object>.GetPropertyInfo(propertyExpression);
_property.DisplayPropertyName = property.Name;
return this;
}
public AdminPageProperty Compile() { public AdminPageProperty Compile() {
_property.DisplayName ??= _property.Name; _property.DisplayName ??= _property.Name;
return _property; 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; var attribute = attributes.Single(a => a is AdminPrefixAttribute) as AdminPrefixAttribute;
Prefix(attribute?.Prefix); Prefix(attribute?.Prefix);
} }
if (attributes.Any(a => a is ListingPropertyAttribute)) {
var attribute = attributes.Single(a => a is ListingPropertyAttribute) as ListingPropertyAttribute;
_property.DisplayPropertyName = property.Name;
}
} }
} }

View File

@@ -7,6 +7,7 @@ public sealed class AdminPageProperty {
public string DisplayName { get; set; } public string DisplayName { get; set; }
public string Description { get; set; } public string Description { get; set; }
public string Prefix { get; set; } public string Prefix { get; set; }
public string DisplayPropertyName { get; set; }
public bool DisplayInListing { get; set; } = true; public bool DisplayInListing { get; set; } = true;
public bool Sortable { get; set; } = true; public bool Sortable { get; set; } = true;

View File

@@ -1,12 +1,10 @@
@rendermode InteractiveServer @rendermode InteractiveServer
@using System.Collections @using System.Collections
@using System.ComponentModel.DataAnnotations
@using BlazorStrap @using BlazorStrap
@using BlazorStrap.Shared.Components.Modal @using BlazorStrap.Shared.Components.Modal
@using static Microsoft.AspNetCore.Components.Web.RenderMode @using static Microsoft.AspNetCore.Components.Web.RenderMode
@using BlazorStrap.V5 @using BlazorStrap.V5
@using HopFrame.Database.Attributes
@using HopFrame.Web.Admin @using HopFrame.Web.Admin
@using HopFrame.Web.Admin.Models @using HopFrame.Web.Admin.Models
@using HopFrame.Web.Admin.Providers @using HopFrame.Web.Admin.Providers
@@ -166,7 +164,7 @@
return MapPropertyValue(property.GetValue(_entry), property); 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; if (value is null) return string.Empty;
var type = value.GetType(); 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() var prop = type.GetProperties()
.SingleOrDefault(p => p.GetCustomAttributes(false).Any(a => a is ListingPropertyAttribute)); .SingleOrDefault(p => p.GetCustomAttributes(false).Any(a => a is ListingPropertyAttribute));
return MapPropertyValue(prop?.GetValue(value), property); 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(); var stringValue = value.ToString();

View File

@@ -30,7 +30,8 @@ public class HopAdminContext : AdminPagesContext {
generator.Page<User>().Property(u => u.Permissions) generator.Page<User>().Property(u => u.Permissions)
.DisplayInListing(false) .DisplayInListing(false)
.IsSelector<PermissionGroup>(); .IsSelector<PermissionGroup>()
.DisplayPropertyForListType<Permission>(p => p.PermissionName);
generator.Page<User>().Property(u => u.Tokens) generator.Page<User>().Property(u => u.Tokens)
.Ignore(); .Ignore();
@@ -54,6 +55,7 @@ public class HopAdminContext : AdminPagesContext {
.Editable(false); .Editable(false);
generator.Page<PermissionGroup>().Property(g => g.Permissions) generator.Page<PermissionGroup>().Property(g => g.Permissions)
.DisplayInListing(false); .DisplayInListing(false)
.DisplayPropertyForListType<Permission>(p => p.PermissionName);
} }
} }

View File

@@ -178,12 +178,7 @@
return String.CompareOrdinal(propX, propY); return String.CompareOrdinal(propX, propY);
}); });
if (_currentSortDirection == ListSortDirection.Ascending) { _displayedModels = _currentSortDirection == ListSortDirection.Ascending ? _displayedModels.Order(comparer).ToList() : _displayedModels.OrderDescending(comparer).ToList();
_displayedModels = _displayedModels.Order(comparer).ToList();
}
else {
_displayedModels = _displayedModels.OrderDescending(comparer).ToList();
}
_currentSortProperty = property; _currentSortProperty = property;
} }

View File

@@ -7,6 +7,6 @@ public class DatabaseContext : HopDbContextBase {
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) { protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) {
base.OnConfiguring(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;");
} }
} }

View File

@@ -12,7 +12,7 @@ public class DatabaseContext : HopDbContextBase {
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) { protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) {
base.OnConfiguring(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) { protected override void OnModelCreating(ModelBuilder modelBuilder) {