diff --git a/.idea/.idea.HopFrame/.idea/workspace.xml b/.idea/.idea.HopFrame/.idea/workspace.xml
index fe26cba..1ccd691 100644
--- a/.idea/.idea.HopFrame/.idea/workspace.xml
+++ b/.idea/.idea.HopFrame/.idea/workspace.xml
@@ -11,10 +11,7 @@
-
-
-
-
+
@@ -33,7 +30,7 @@
@@ -62,6 +59,7 @@
+
@@ -79,6 +77,7 @@
+
@@ -129,7 +128,7 @@
"RunOnceActivity.git.unshallow": "true",
"b5f11219-dfc4-47a1-b02c-90ab603034fb.executor": "Debug",
"dcdf1689-dc07-47e4-8824-2e60a4fbf301.executor": "Debug",
- "git-widget-placeholder": "dev",
+ "git-widget-placeholder": "!34 on feature/repositories",
"list.type.of.created.stylesheet": "CSS",
"node.js.detected.package.eslint": "true",
"node.js.detected.package.tslint": "true",
@@ -258,15 +257,8 @@
-
-
-
-
- 1736850899254
-
-
-
- 1736850899254
+
+
@@ -644,7 +636,23 @@
1740742749325
-
+
+
+ 1740743139064
+
+
+
+ 1740743139064
+
+
+
+ 1741985203179
+
+
+
+ 1741985203179
+
+
@@ -695,8 +703,6 @@
-
-
@@ -720,6 +726,8 @@
-
+
+
+
\ No newline at end of file
diff --git a/docs/Writerside/hopframe.tree b/docs/Writerside/hopframe.tree
index 3afb72e..46bb555 100644
--- a/docs/Writerside/hopframe.tree
+++ b/docs/Writerside/hopframe.tree
@@ -17,6 +17,7 @@
+
@@ -27,6 +28,10 @@
+
+
+
+
\ No newline at end of file
diff --git a/docs/Writerside/topics/Custom-Repositories.md b/docs/Writerside/topics/Custom-Repositories.md
new file mode 100644
index 0000000..e0c9c09
--- /dev/null
+++ b/docs/Writerside/topics/Custom-Repositories.md
@@ -0,0 +1,132 @@
+# Custom Repositories
+
+Custom repositories in HopFrame allow you to define and integrate custom logic for managing database entities. By implementing the `IHopFrameRepository` interface, you can gain full control over how data is retrieved, modified, and managed. This feature is ideal for scenarios where the default behavior does not meet specific business requirements.
+
+## IHopFrameRepository Interface
+
+The `IHopFrameRepository` interface defines a contract for a repository that works with a specific model (`TModel`) and its primary key (`TKey`). The interface provides the following methods:
+
+- **LoadPage**
+ Loads a paginated set of items.
+
+ ```c#
+ Task> LoadPage(int page, int perPage);
+ ```
+
+ - **Parameters:**
+ - `page`: The page number to load.
+ - `perPage`: The number of items per page.
+ - **Returns:** A collection of items for the specified page.
+
+- **Search**
+ Performs a search query on the repository.
+
+ ```c#
+ Task> Search(string searchTerm, int page, int perPage);
+ ```
+
+ - **Parameters:**
+ - `searchTerm`: The term to search for.
+ - `page`: The page number to load.
+ - `perPage`: The number of items per page.
+ - **Returns:** A `SearchResult` containing matching items and the total number of pages.
+
+- **GetTotalPageCount**
+ Retrieves the total number of pages based on the items per page.
+
+ ```c#
+ Task GetTotalPageCount(int perPage);
+ ```
+
+ - **Parameters:**
+ - `perPage`: The number of items per page.
+ - **Returns:** The total number of pages.
+
+- **CreateItem**
+ Adds a new item to the repository.
+
+ ```c#
+ Task CreateItem(TModel item);
+ ```
+
+ - **Parameters:**
+ - `item`: The item to create.
+
+- **EditItem**
+ Updates an existing item in the repository.
+
+ ```c#
+ Task EditItem(TModel item);
+ ```
+
+ - **Parameters:**
+ - `item`: The item to update.
+
+- **DeleteItem**
+ Removes an item from the repository.
+
+ ```c#
+ Task DeleteItem(TModel item);
+ ```
+
+ - **Parameters:**
+ - `item`: The item to delete.
+
+- **GetOne**
+ Retrieves a single item based on its primary key.
+
+ ```c#
+ Task GetOne(TKey key);
+ ```
+
+ - **Parameters:**
+ - `key`: The primary key of the item to retrieve.
+ - **Returns:** The item if found, or `null` if not.
+
+## `SearchResult` Struct
+
+The `SearchResult` struct is used to encapsulate the results of a search query.
+
+- **Properties:**
+ - `Items`: The items retrieved from the search query.
+ - `PageCount`: The total number of pages based on the search results.
+
+```c#
+public readonly struct SearchResult(IEnumerable items, int pageCount) {
+ public IEnumerable Items { get; init; }
+ public int PageCount { get; init; }
+}
+```
+
+## Adding Custom Repositories
+
+To add and configure a custom repository in HopFrame, use the `AddCustomRepository` methods. These methods allow you to specify a repository class (`TRepository`) implementing `IHopFrameRepository` and define configurations for the associated table.
+
+- **With Configurator**
+
+ ```c#
+ HopFrameConfigurator AddCustomRepository(
+ Expression> keyExpression,
+ Action> configurator
+ )
+ where TRepository : IHopFrameRepository;
+ ```
+
+ - **Parameters:**
+ - `keyExpression`: The key of the model.
+ - `configurator`: Configures the table page.
+
+- **Without Configurator**
+
+ ```c#
+ TableConfigurator AddCustomRepository(
+ Expression> keyExpression
+ )
+ where TRepository : IHopFrameRepository;
+ ```
+
+ - **Parameters:**
+ - `keyExpression`: The key of the model.
+ - **Returns:** A `TableConfigurator` to configure the table.
+
+By implementing custom repositories and using these methods, you can fully leverage the flexibility of HopFrame for your data management needs. Let me know if you'd like further elaboration!
\ No newline at end of file
diff --git a/docs/Writerside/topics/Exporter-Plugin.md b/docs/Writerside/topics/Exporter-Plugin.md
new file mode 100644
index 0000000..e2492c3
--- /dev/null
+++ b/docs/Writerside/topics/Exporter-Plugin.md
@@ -0,0 +1,51 @@
+# Exporter Plugin
+
+The Exporter Plugin is a tool for managing the import and export of data from the HopFrame UI. It provides functionality for exporting table data into a CSV file and importing data back into the system, making data manipulation and backups more seamless.
+
+## What the Exporter Plugin Does
+
+1. **Export Table Data to CSV**
+ - The plugin allows users to export all data from a table as a CSV file.
+ - The exported file includes all non-virtual properties as table headers.
+ - The export process dynamically constructs rows for each entry in the table.
+
+2. **Import Data from CSV**
+ - Users can import a CSV file to populate or update a table.
+ - The import process reads the file, validates the headers, and creates new entries or updates existing ones.
+ - Relationships and enumerable properties are also resolved using the appropriate managers.
+
+3. **User Interface Integration**
+ - Adds two buttons, "Export" and "Import," to the page header of each table.
+ - **Export Button:** Initiates the export functionality.
+ - **Import Button:** Allows users to upload a CSV file for import.
+
+4. **Error Handling**
+ - Ensures errors during import or export (e.g., invalid file format, missing data, or system issues) are shown to the user as toast messages.
+
+## Adding the Exporter Plugin
+
+To include the Exporter Plugin in your HopFrame setup, use the `AddExporters` method provided by the `HopFrameConfiguratorExtensions`.
+
+Here’s how to register the Exporter Plugin in your application configuration:
+
+```c#
+builder.Services.AddHopFrame(options => {
+ options.AddExporters();
+});
+```
+
+The `AddExporters` method internally registers the `ExporterPlugin` and attaches its functionality to the HopFrame.
+
+## Key Features of the Export Process
+
+- **Dynamic Header Creation:** Automatically generates headers based on the table's non-virtual properties.
+- **Data Transformation:** Transforms property values into CSV-compatible formats.
+- **File Download:** Saves the generated CSV file with the table’s display name.
+
+## Key Features of the Import Process
+
+- **Header Validation:** Validates that the CSV file headers match the table's properties.
+- **Type Conversion:** Converts values in the CSV file to their respective data types.
+- **Relationship Management:** Resolves relationships and enumerable properties during import.
+
+This plugin streamlines data operations, reducing manual effort and enabling quick data migration or updates. Let me know if you’d like to dive deeper into any specific aspect!
\ No newline at end of file
diff --git a/docs/Writerside/topics/HopFrameConfig.md b/docs/Writerside/topics/HopFrameConfig.md
index f6e9ff9..a4329d3 100644
--- a/docs/Writerside/topics/HopFrameConfig.md
+++ b/docs/Writerside/topics/HopFrameConfig.md
@@ -118,6 +118,50 @@ DbContextConfigurator? GetDbContext() where TDbContext :
- **Returns:** The configurator of the context if it already was defined, `null` if not.
+### AddCustomRepository (With configurator)
+
+Adds a table of the desired type and configures it to use a custom repository.
+
+```c#
+HopFrameConfigurator AddCustomRepository(
+ Expression> keyExpression,
+ Action> configurator
+)
+ where TRepository : IHopFrameRepository
+```
+
+- **Type Parameters:**
+ - `TRepository`: The repository class that inherits from `IHopFrameRepository` (needs to be registered as a service).
+ - `TModel`: The model of the table.
+ - `TKey`: The type of the primary key.
+
+- **Parameters:**
+ - `keyExpression`: The key of the model.
+ - `configurator`: The configurator used for configuring the table page.
+
+- **Returns:** `HopFrameConfigurator`
+
+### AddCustomRepository (Without configurator)
+
+Adds a table of the desired type and configures it to use a custom repository.
+
+```c#
+TableConfigurator AddCustomRepository(
+ Expression> keyExpression
+)
+ where TRepository : IHopFrameRepository
+```
+
+- **Type Parameters:**
+ - `TRepository`: The repository class that inherits from `IHopFrameRepository` (needs to be registered as a service).
+ - `TModel`: The model of the table.
+ - `TKey`: The type of the primary key.
+
+- **Parameters:**
+ - `keyExpression`: The key of the model.
+
+- **Returns:** The configurator used for configuring the table page: `TableConfigurator`.
+
### DisplayUserInfo
Determines if the name of the currently logged-in user should be displayed in the top right corner of the admin UI.
diff --git a/docs/Writerside/topics/IFileService.md b/docs/Writerside/topics/IFileService.md
new file mode 100644
index 0000000..6ee41fe
--- /dev/null
+++ b/docs/Writerside/topics/IFileService.md
@@ -0,0 +1,41 @@
+# IFileService
+
+The `IFileService` interface provides methods for handling file operations, such as downloading and uploading files within the HopFrame web application. It abstracts file-related operations to ensure a smooth and consistent user experience.
+
+## Methods
+
+1. **DownloadFile**
+ - Initiates the download of a file with the given name and data.
+ - Suitable for dynamically generating and offering files to the user, such as CSV exports or reports.
+
+ ```c#
+ Task DownloadFile(string name, byte[] data);
+ ```
+
+ - **Parameters:**
+ - `name`: The name of the file to be downloaded (including the extension, e.g., "example.csv").
+ - `data`: The byte array representing the content of the file.
+ - **Usage Example:** Exporting table data as a CSV file for download.
+
+2. **UploadFile**
+ - Allows the user to upload a file through the web interface and returns the uploaded file for further processing.
+ - This method provides integration with Blazor's `IBrowserFile` for easy file handling.
+
+ ```c#
+ Task UploadFile();
+ ```
+
+ - **Returns:** An `IBrowserFile` instance representing the uploaded file.
+ - **Usage Example:** Importing data from a CSV file to populate or update a table.
+
+## Integration
+
+The `IFileService` is commonly used in conjunction with plugins or components that require file operations, such as the Exporter Plugin, which leverages this service to enable data export and import functionality.
+
+## Key Features
+
+- Streamlines file handling for web applications.
+- Simplifies both download and upload processes with minimal code.
+- Ensures compatibility with Blazor's file-handling capabilities.
+
+By implementing or extending the `IFileService`, developers can customize the file-handling behavior to suit specific application needs. Let me know if you'd like more examples or details!
\ No newline at end of file
diff --git a/src/HopFrame.Core/Config/DbContextConfig.cs b/src/HopFrame.Core/Config/DbContextConfig.cs
index 8dba413..a8dd407 100644
--- a/src/HopFrame.Core/Config/DbContextConfig.cs
+++ b/src/HopFrame.Core/Config/DbContextConfig.cs
@@ -2,7 +2,13 @@
namespace HopFrame.Core.Config;
-public class DbContextConfig {
+public interface ITableGroupConfig {
+ public Type ContextType { get; }
+ public List Tables { get; }
+ public HopFrameConfig ParentConfig { get; }
+}
+
+public class DbContextConfig : ITableGroupConfig {
public Type ContextType { get; }
public List Tables { get; } = new();
public HopFrameConfig ParentConfig { get; }
diff --git a/src/HopFrame.Core/Config/HopFrameConfig.cs b/src/HopFrame.Core/Config/HopFrameConfig.cs
index e04fd07..57aabde 100644
--- a/src/HopFrame.Core/Config/HopFrameConfig.cs
+++ b/src/HopFrame.Core/Config/HopFrameConfig.cs
@@ -1,11 +1,13 @@
-using HopFrame.Core.Callbacks;
+using System.Linq.Expressions;
+using HopFrame.Core.Callbacks;
+using HopFrame.Core.Repositories;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
namespace HopFrame.Core.Config;
public class HopFrameConfig {
- public List Contexts { get; } = new();
+ public List Contexts { get; } = new();
public bool DisplayUserInfo { get; set; } = true;
public string? BasePolicy { get; set; }
public string? LoginPageRewrite { get; set; }
@@ -48,6 +50,36 @@ public sealed class HopFrameConfigurator(HopFrameConfig config, IServiceCollecti
return new DbContextConfigurator(context);
}
+ ///
+ /// Adds a table of the desired type and configures it to use a custom repository
+ ///
+ /// The key of the model
+ /// The configurator used for configuring the table page
+ /// The repository class that inherits from the (needs to be registered as a service)
+ /// The model of the table
+ /// The type of the primary key
+ public HopFrameConfigurator AddCustomRepository(Expression> keyExpression, Action> configurator) {
+ var context = AddCustomRepository(keyExpression);
+ configurator.Invoke(context);
+ return this;
+ }
+
+ ///
+ /// Adds a table of the desired type and configures it to use a custom repository
+ ///
+ /// The key of the model
+ /// The repository class that inherits from the (needs to be registered as a service)
+ /// The model of the table
+ /// The type of the primary key
+ /// The configurator used for configuring the table page
+ public TableConfigurator AddCustomRepository(Expression> keyExpression) {
+ var keyProperty = TableConfigurator.GetPropertyInfo(keyExpression);
+ var context = new RepositoryGroupConfig(typeof(TRepository), keyProperty, InnerConfig);
+ context.Tables.Add(new TableConfig(context, typeof(TModel), typeof(TRepository).Name, 0));
+ InnerConfig.Contexts.Add(context);
+ return new TableConfigurator(context.Tables[0]);
+ }
+
///
/// Check if a context is already registered in the HopFrame
///
@@ -64,6 +96,7 @@ public sealed class HopFrameConfigurator(HopFrameConfig config, IServiceCollecti
/// The configurator of the context if it already was defined, null if not
public DbContextConfigurator? GetDbContext() where TDbContext : DbContext {
var config = InnerConfig.Contexts
+ .OfType()
.SingleOrDefault(context => context.ContextType == typeof(TDbContext));
if (config is null) return null;
diff --git a/src/HopFrame.Core/Config/RepositoryGroupConfig.cs b/src/HopFrame.Core/Config/RepositoryGroupConfig.cs
new file mode 100644
index 0000000..40b10dc
--- /dev/null
+++ b/src/HopFrame.Core/Config/RepositoryGroupConfig.cs
@@ -0,0 +1,13 @@
+using System.Reflection;
+
+namespace HopFrame.Core.Config;
+
+public class RepositoryGroupConfig(Type repoType, PropertyInfo keyProperty, HopFrameConfig config) : ITableGroupConfig {
+ public Type ContextType { get; } = repoType;
+
+ public List Tables { get; } = new();
+
+ public HopFrameConfig ParentConfig { get; } = config;
+
+ public PropertyInfo KeyProperty { get; } = keyProperty;
+}
\ No newline at end of file
diff --git a/src/HopFrame.Core/Config/TableConfig.cs b/src/HopFrame.Core/Config/TableConfig.cs
index 455ca48..2de5648 100644
--- a/src/HopFrame.Core/Config/TableConfig.cs
+++ b/src/HopFrame.Core/Config/TableConfig.cs
@@ -11,7 +11,7 @@ public class TableConfig {
public string PropertyName { get; }
public string DisplayName { get; set; }
public string? Description { get; set; }
- public DbContextConfig ContextConfig { get; }
+ public ITableGroupConfig ContextConfig { get; }
public bool Ignored { get; set; }
public int Order { get; set; }
internal bool Seeded { get; set; }
@@ -23,7 +23,7 @@ public class TableConfig {
public List Properties { get; } = new();
- public TableConfig(DbContextConfig config, Type tableType, string propertyName, int nthTable) {
+ public TableConfig(ITableGroupConfig config, Type tableType, string propertyName, int nthTable) {
TableType = tableType;
PropertyName = propertyName;
ContextConfig = config;
diff --git a/src/HopFrame.Core/Repositories/IHopFrameRepository.cs b/src/HopFrame.Core/Repositories/IHopFrameRepository.cs
new file mode 100644
index 0000000..c4f09e2
--- /dev/null
+++ b/src/HopFrame.Core/Repositories/IHopFrameRepository.cs
@@ -0,0 +1,24 @@
+namespace HopFrame.Core.Repositories;
+
+public interface IHopFrameRepository where TModel : class {
+
+ Task> LoadPage(int page, int perPage);
+
+ Task> Search(string searchTerm, int page, int perPage);
+
+ Task GetTotalPageCount(int perPage);
+
+ Task CreateItem(TModel item);
+
+ Task EditItem(TModel item);
+
+ Task DeleteItem(TModel item);
+
+ Task GetOne(TKey key);
+
+}
+
+public readonly struct SearchResult(IEnumerable items, int pageCount) {
+ public IEnumerable Items { get; init; } = items;
+ public int PageCount { get; init; } = pageCount;
+}
diff --git a/src/HopFrame.Core/Services/ITableManager.cs b/src/HopFrame.Core/Services/ITableManager.cs
index 2078d10..0f8fac7 100644
--- a/src/HopFrame.Core/Services/ITableManager.cs
+++ b/src/HopFrame.Core/Services/ITableManager.cs
@@ -4,7 +4,7 @@ using HopFrame.Core.Config;
namespace HopFrame.Core.Services;
public interface ITableManager {
- public IQueryable