diff --git a/docs/blazor/admin.md b/docs/blazor/admin.md
index feec095..e89a8f4 100644
--- a/docs/blazor/admin.md
+++ b/docs/blazor/admin.md
@@ -25,6 +25,8 @@ simply by reading the structure of the provided model and optionally some additi
}
```
+ > **Hint:** you can specify the url of the admin page by adding the `AdminPageUrl` Attribute
+
3. **Optionally** you can further configure your pages in the `OnModelCreating` method
```csharp
@@ -61,6 +63,15 @@ simply by reading the structure of the provided model and optionally some additi
```
4. **Optionally** you can also add some of the following attributes to your classes / properties to further configure the admin pages:\
\
+ Attributes for classes and properties:
+
+ ```csharp
+ [AttributeUsage(AttributeTargets.Class | AttributeTargets.Property)]
+ public sealed class AdminNameAttribute(string name) : Attribute {
+ public string Name { get; set; } = name;
+ }
+ ```
+
Attributes for classes:
```csharp
@@ -86,8 +97,8 @@ simply by reading the structure of the provided model and optionally some additi
```csharp
[AttributeUsage(AttributeTargets.Class)]
- public class AdminUrlAttribute(string url) : Attribute {
- public string Url { get; set; } = url;
+ public sealed class AdminDescriptionAttribute(string description) : Attribute {
+ public string Description { get; set; } = description;
}
```
diff --git a/src/HopFrame.Web.Admin/Attributes/AdminPageUrlAttribute.cs b/src/HopFrame.Web.Admin/Attributes/AdminPageUrlAttribute.cs
new file mode 100644
index 0000000..ddc49df
--- /dev/null
+++ b/src/HopFrame.Web.Admin/Attributes/AdminPageUrlAttribute.cs
@@ -0,0 +1,10 @@
+namespace HopFrame.Web.Admin.Attributes;
+
+///
+/// This attribute specifies the url of the admin page and needs to be applied on the AdminPage property in the AdminContext directly
+///
+/// The page url: '/administration/{url}'
+[AttributeUsage(AttributeTargets.Property)]
+public sealed class AdminPageUrlAttribute(string url) : Attribute {
+ public string Url { get; set; } = url;
+}
\ No newline at end of file
diff --git a/src/HopFrame.Web.Admin/Attributes/AdminDescriptionAttribute.cs b/src/HopFrame.Web.Admin/Attributes/Classes/AdminDescriptionAttribute.cs
similarity index 72%
rename from src/HopFrame.Web.Admin/Attributes/AdminDescriptionAttribute.cs
rename to src/HopFrame.Web.Admin/Attributes/Classes/AdminDescriptionAttribute.cs
index 92e3fe3..cc23438 100644
--- a/src/HopFrame.Web.Admin/Attributes/AdminDescriptionAttribute.cs
+++ b/src/HopFrame.Web.Admin/Attributes/Classes/AdminDescriptionAttribute.cs
@@ -1,6 +1,6 @@
namespace HopFrame.Web.Admin.Attributes;
-[AttributeUsage(AttributeTargets.Class | AttributeTargets.Property)]
+[AttributeUsage(AttributeTargets.Class)]
public sealed class AdminDescriptionAttribute(string description) : Attribute {
public string Description { get; set; } = description;
}
\ No newline at end of file
diff --git a/src/HopFrame.Web.Admin/Attributes/Classes/AdminUrlAttribute.cs b/src/HopFrame.Web.Admin/Attributes/Classes/AdminUrlAttribute.cs
deleted file mode 100644
index ab87a2e..0000000
--- a/src/HopFrame.Web.Admin/Attributes/Classes/AdminUrlAttribute.cs
+++ /dev/null
@@ -1,6 +0,0 @@
-namespace HopFrame.Web.Admin.Attributes.Classes;
-
-[AttributeUsage(AttributeTargets.Class)]
-public class AdminUrlAttribute(string url) : Attribute {
- public string Url { get; set; } = url;
-}
\ 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 2a71a75..30a6ef5 100644
--- a/src/HopFrame.Web.Admin/Generators/IAdminPageGenerator.cs
+++ b/src/HopFrame.Web.Admin/Generators/IAdminPageGenerator.cs
@@ -18,13 +18,6 @@ public interface IAdminPageGenerator {
/// the specified description
///
IAdminPageGenerator Description(string description);
-
- ///
- /// Sets the url for the Admin Page
- ///
- /// the specified url (administration/{url})
- ///
- IAdminPageGenerator Url(string url);
///
/// Sets the permission needed to view the Admin Page
diff --git a/src/HopFrame.Web.Admin/Generators/Implementation/AdminContextGenerator.cs b/src/HopFrame.Web.Admin/Generators/Implementation/AdminContextGenerator.cs
index ae62af6..54137d4 100644
--- a/src/HopFrame.Web.Admin/Generators/Implementation/AdminContextGenerator.cs
+++ b/src/HopFrame.Web.Admin/Generators/Implementation/AdminContextGenerator.cs
@@ -1,6 +1,5 @@
+using HopFrame.Web.Admin.Attributes;
using HopFrame.Web.Admin.Models;
-using HopFrame.Web.Admin.Providers;
-using Microsoft.Extensions.DependencyInjection;
namespace HopFrame.Web.Admin.Generators.Implementation;
@@ -27,13 +26,14 @@ internal class AdminContextGenerator : IAdminContextGenerator {
return (generator as AdminPageGenerator)?.Compile();
}
- public TContext CompileContext() where TContext : AdminPagesContext {
+ public TContext CompileContext(IServiceProvider provider) where TContext : AdminPagesContext {
var type = typeof(TContext);
var compileMethod = typeof(AdminContextGenerator).GetMethod(nameof(CompilePage));
var properties = type.GetProperties();
- var context = Activator.CreateInstance();
+ var dependencies = ResolveDependencies(provider);
+ var context = Activator.CreateInstance(type, dependencies) as TContext;
foreach (var property in properties) {
var propertyType = property.PropertyType.GenericTypeArguments[0];
@@ -49,31 +49,48 @@ internal class AdminContextGenerator : IAdminContextGenerator {
_adminPages.Add(propertyType, generatorInstance);
}
- context.OnModelCreating(this);
+ context?.OnModelCreating(this);
foreach (var property in properties) {
var modelType = property.PropertyType.GenericTypeArguments[0];
var method = compileMethod?.MakeGenericMethod(modelType);
- property.SetValue(context, method?.Invoke(this, []));
+ var compiledPage = method?.Invoke(this, []) as AdminPage;
+
+ var url = property.Name;
+ if (property.GetCustomAttributes(false).Any(a => a is AdminPageUrlAttribute)) {
+ var attribute = property.GetCustomAttributes(false)
+ .Single(a => a is AdminPageUrlAttribute) as AdminPageUrlAttribute;
+
+ url = attribute?.Url;
+ }
+ compiledPage!.Url = url;
+
+ property.SetValue(context, compiledPage);
}
return context;
}
+ private object[] ResolveDependencies(IServiceProvider provider) {
+ return ResolveDependencies(typeof(TContext), provider);
+ }
+
+ public static object[] ResolveDependencies(Type type, IServiceProvider provider) {
+ var ctors = type.GetConstructors();
+ if (ctors.Length == 0) return [];
+ if (ctors.Length > 1)
+ throw new ArgumentException($"Dependencies of {type.Name} could not be resolved (multiple constructors)!");
- public static void RegisterPages(AdminPagesContext context, IAdminPagesProvider provider, IServiceCollection services) {
- var properties = context.GetType().GetProperties();
-
- foreach (var property in properties) {
- var page = property.GetValue(context) as AdminPage;
- if (page is null) continue;
-
- provider.RegisterAdminPage(page.Title.ToLower(), page);
-
- if (page.RepositoryProvider is not null)
- services.AddScoped(page.RepositoryProvider);
+ var ctor = ctors[0];
+ var depTypes = ctor.GetParameters();
+ var dependencies = new object[depTypes.Length];
+
+ for (var i = 0; i < depTypes.Length; i++) {
+ dependencies[i] = provider.GetService(depTypes[i].ParameterType);
}
+
+ return dependencies;
}
}
\ No newline at end of file
diff --git a/src/HopFrame.Web.Admin/Generators/Implementation/AdminPageGenerator.cs b/src/HopFrame.Web.Admin/Generators/Implementation/AdminPageGenerator.cs
index 2718e15..d2fb6ec 100644
--- a/src/HopFrame.Web.Admin/Generators/Implementation/AdminPageGenerator.cs
+++ b/src/HopFrame.Web.Admin/Generators/Implementation/AdminPageGenerator.cs
@@ -40,7 +40,6 @@ internal sealed class AdminPageGenerator : IAdminPageGenerator,
public IAdminPageGenerator Title(string title) {
Page.Title = title;
- Page.Url ??= title.ToLower();
return this;
}
@@ -49,11 +48,6 @@ internal sealed class AdminPageGenerator : IAdminPageGenerator,
return this;
}
- public IAdminPageGenerator Url(string url) {
- Page.Url = url;
- return this;
- }
-
public IAdminPageGenerator ViewPermission(string permission) {
Page.Permissions.View = permission;
return this;
@@ -167,11 +161,6 @@ internal sealed class AdminPageGenerator : IAdminPageGenerator,
Description(attribute?.Description);
}
- if (attributes.Any(a => a is AdminUrlAttribute)) {
- var attribute = attributes.Single(a => a is AdminUrlAttribute) as AdminUrlAttribute;
- Url(attribute?.Url);
- }
-
if (attributes.Any(a => a is AdminPermissionsAttribute)) {
var attribute = attributes.Single(a => a is AdminPermissionsAttribute) as AdminPermissionsAttribute;
CreatePermission(attribute?.Permissions.Create);
diff --git a/src/HopFrame.Web.Admin/Models/AdminPage.cs b/src/HopFrame.Web.Admin/Models/AdminPage.cs
index 748eb00..1eb43db 100644
--- a/src/HopFrame.Web.Admin/Models/AdminPage.cs
+++ b/src/HopFrame.Web.Admin/Models/AdminPage.cs
@@ -1,5 +1,5 @@
using System.ComponentModel;
-using System.Text.Json.Serialization;
+using HopFrame.Web.Admin.Generators.Implementation;
namespace HopFrame.Web.Admin.Models;
@@ -23,4 +23,11 @@ public class AdminPage {
public bool ShowCreateButton { get; set; } = true;
public bool ShowDeleteButton { get; set; } = true;
public bool ShowUpdateButton { get; set; } = true;
+
+ public IModelRepository LoadModelRepository(IServiceProvider provider) {
+ if (RepositoryProvider is null) return null;
+
+ var dependencies = AdminContextGenerator.ResolveDependencies(RepositoryProvider, provider);
+ return Activator.CreateInstance(RepositoryProvider, dependencies) as IModelRepository;
+ }
}
diff --git a/src/HopFrame.Web.Admin/Providers/IAdminPagesProvider.cs b/src/HopFrame.Web.Admin/Providers/IAdminPagesProvider.cs
index 564f52a..34b5a4d 100644
--- a/src/HopFrame.Web.Admin/Providers/IAdminPagesProvider.cs
+++ b/src/HopFrame.Web.Admin/Providers/IAdminPagesProvider.cs
@@ -4,7 +4,6 @@ namespace HopFrame.Web.Admin.Providers;
public interface IAdminPagesProvider {
- internal void RegisterAdminPage(string url, AdminPage page);
AdminPage LoadAdminPage(string url);
IList LoadRegisteredAdminPages();
AdminPage HasPageFor(Type type);
diff --git a/src/HopFrame.Web.Admin/Providers/Implementation/AdminPagesProvider.cs b/src/HopFrame.Web.Admin/Providers/Implementation/AdminPagesProvider.cs
index b2e38b4..223e5e2 100644
--- a/src/HopFrame.Web.Admin/Providers/Implementation/AdminPagesProvider.cs
+++ b/src/HopFrame.Web.Admin/Providers/Implementation/AdminPagesProvider.cs
@@ -2,25 +2,44 @@ using HopFrame.Web.Admin.Models;
namespace HopFrame.Web.Admin.Providers.Implementation;
-public class AdminPagesProvider : IAdminPagesProvider {
- private readonly IDictionary _pages = new Dictionary();
-
- public void RegisterAdminPage(string url, AdminPage page) {
- _pages.Add(url, page);
+public class AdminPagesProvider(IServiceProvider provider) : IAdminPagesProvider {
+ private static readonly IDictionary Pages = new Dictionary();
+
+ public static void RegisterAdminPage(string url, Type pageType) where TContext : AdminPagesContext {
+ Pages.Add(url, new PageDataStore {
+ ContextType = typeof(TContext),
+ PageType = pageType
+ });
}
public AdminPage LoadAdminPage(string url) {
- return _pages.TryGetValue(url, out var page) ? page : null;
+ if (!Pages.TryGetValue(url, out var data)) return null;
+
+ var context = provider.GetService(data.ContextType);
+ var property = data.ContextType.GetProperties()
+ .SingleOrDefault(prop => prop.PropertyType == data.PageType);
+
+ return property?.GetValue(context) as AdminPage;
}
public IList LoadRegisteredAdminPages() {
- return _pages.Values.ToList();
+ return Pages
+ .Select(pair => LoadAdminPage(pair.Key))
+ .ToList();
}
public AdminPage HasPageFor(Type type) {
- return _pages
- .Where(p => p.Value.ModelType == type)
- .Select(p => p.Value)
- .SingleOrDefault();
+ foreach (var (url, data) in Pages) {
+ var innerType = data.PageType.GenericTypeArguments[0];
+ if (innerType != type) continue;
+ return LoadAdminPage(url);
+ }
+
+ return null;
}
-}
\ No newline at end of file
+}
+
+internal struct PageDataStore {
+ public Type PageType { get; set; }
+ public Type ContextType { get; set; }
+}
diff --git a/src/HopFrame.Web.Admin/ServiceCollectionExtensions.cs b/src/HopFrame.Web.Admin/ServiceCollectionExtensions.cs
index f3e8370..87aaf32 100644
--- a/src/HopFrame.Web.Admin/ServiceCollectionExtensions.cs
+++ b/src/HopFrame.Web.Admin/ServiceCollectionExtensions.cs
@@ -1,3 +1,4 @@
+using HopFrame.Web.Admin.Attributes;
using HopFrame.Web.Admin.Generators.Implementation;
using HopFrame.Web.Admin.Providers;
using HopFrame.Web.Admin.Providers.Implementation;
@@ -8,22 +9,36 @@ namespace HopFrame.Web.Admin;
public static class ServiceCollectionExtensions {
- private static IAdminPagesProvider _provider;
-
public static IServiceCollection AddAdminContext(this IServiceCollection services) where TContext : AdminPagesContext {
- var provider = GetProvider();
- services.TryAddSingleton(provider);
+ services.TryAddSingleton();
- var generator = new AdminContextGenerator();
- var context = generator.CompileContext();
- AdminContextGenerator.RegisterPages(context, provider, services);
- services.AddSingleton(context);
+ services.AddSingleton(provider => {
+ var generator = new AdminContextGenerator();
+ var context = generator.CompileContext(provider);
+ return context;
+ });
+
+ PreregisterPages();
return services;
}
- private static IAdminPagesProvider GetProvider() {
- return _provider ??= new AdminPagesProvider();
+ private static void PreregisterPages() where TContext : AdminPagesContext {
+ var contextType = typeof(TContext);
+ var props = contextType.GetProperties();
+
+ foreach (var property in props) {
+ var url = property.Name;
+
+ if (property.GetCustomAttributes(false).Any(a => a is AdminPageUrlAttribute)) {
+ var attribute = property.GetCustomAttributes(false)
+ .Single(a => a is AdminPageUrlAttribute) as AdminPageUrlAttribute;
+
+ url = attribute?.Url;
+ }
+
+ AdminPagesProvider.RegisterAdminPage(url, property.PropertyType);
+ }
}
}
\ No newline at end of file
diff --git a/src/HopFrame.Web/Components/Administration/AdminPageModal.razor b/src/HopFrame.Web/Components/Administration/AdminPageModal.razor
index f410739..8e04608 100644
--- a/src/HopFrame.Web/Components/Administration/AdminPageModal.razor
+++ b/src/HopFrame.Web/Components/Administration/AdminPageModal.razor
@@ -1,7 +1,6 @@
@rendermode InteractiveServer
@using System.Collections
-@using System.Globalization
@using BlazorStrap
@using BlazorStrap.Shared.Components.Modal
@using static Microsoft.AspNetCore.Components.Web.RenderMode
@@ -144,7 +143,7 @@
_currentPage = page;
_entry = entryToEdit;
_isEdit = entryToEdit is not null;
- _repository = Provider.GetService(_currentPage.RepositoryProvider) as IModelRepository;
+ _repository = _currentPage.LoadModelRepository(Provider);
_entry ??= Activator.CreateInstance(_currentPage.ModelType);
_context = new EditContext(_entry);
@@ -247,8 +246,7 @@
foreach (var value in _values) {
if (value.Key.Unique) {
if (value.Value == value.Key.GetValue(_entry)) continue;
- var repo = Provider.GetService(_currentPage.RepositoryProvider) as IModelRepository;
- var data = repo!.ReadAllO().GetAwaiter().GetResult();
+ var data = _repository!.ReadAllO().GetAwaiter().GetResult();
foreach (var entry in data) {
var other = value.Key.GetValue(entry);
if (!other.Equals(value.Value)) continue;
@@ -295,7 +293,7 @@
throw new ArgumentException($"'{property.Name}' cannot be a selector because a admin page for '{type.Name}' does not exist!");
}
- var repo = Provider.GetService(page.RepositoryProvider) as IModelRepository;
+ var repo = page.LoadModelRepository(Provider);
var objects = (await repo!.ReadAllO()).ToArray();
_selectorValues[property] = objects;
diff --git a/src/HopFrame.Web/HopAdminContext.cs b/src/HopFrame.Web/HopAdminContext.cs
index 22aac7b..6d89e76 100644
--- a/src/HopFrame.Web/HopAdminContext.cs
+++ b/src/HopFrame.Web/HopAdminContext.cs
@@ -2,6 +2,7 @@ using System.Text.RegularExpressions;
using HopFrame.Database.Models;
using HopFrame.Security;
using HopFrame.Web.Admin;
+using HopFrame.Web.Admin.Attributes;
using HopFrame.Web.Admin.Generators;
using HopFrame.Web.Admin.Models;
using HopFrame.Web.Repositories;
@@ -10,7 +11,10 @@ namespace HopFrame.Web;
internal class HopAdminContext : AdminPagesContext {
+ [AdminPageUrl("users")]
public AdminPage Users { get; set; }
+
+ [AdminPageUrl("groups")]
public AdminPage Groups { get; set; }
public override void OnModelCreating(IAdminContextGenerator generator) {
diff --git a/src/HopFrame.Web/Pages/Administration/AdminPageList.razor b/src/HopFrame.Web/Pages/Administration/AdminPageList.razor
index cba8f4f..2f2519c 100644
--- a/src/HopFrame.Web/Pages/Administration/AdminPageList.razor
+++ b/src/HopFrame.Web/Pages/Administration/AdminPageList.razor
@@ -106,6 +106,7 @@
@inject ITokenContext Auth
@inject IPermissionRepository Permissions
@inject SweetAlertService Alerts
+@inject NavigationManager Navigator
@code {
[Parameter]
@@ -127,12 +128,17 @@
protected override async Task OnInitializedAsync() {
_pageData = Pages.LoadAdminPage(Url);
+ if (_pageData is null) {
+ Navigator.NavigateTo("/administration", true);
+ return;
+ }
+
_currentSortProperty = _pageData.DefaultSortPropertyName;
_currentSortDirection = _pageData.DefaultSortDirection;
if (_pageData.RepositoryProvider is null)
throw new ArgumentException($"AdminPage '{_pageData.Title}' does not specify a model repository!'");
- _modelRepository = Provider.GetService(_pageData.RepositoryProvider) as IModelRepository;
+ _modelRepository = _pageData.LoadModelRepository(Provider);
_hasEditPermission = _pageData.Permissions.Update is null || await Permissions.HasPermission(Auth.User, _pageData.Permissions.Update);
_hasDeletePermission = _pageData.Permissions.Delete is null || await Permissions.HasPermission(Auth.User, _pageData.Permissions.Delete);