From fae8e614dc22e83427f660e26aa4cfa76737ad87 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?SAL=C4=B0H=20=C3=96ZKARA?= Date: Wed, 18 Feb 2026 10:18:16 +0300 Subject: [PATCH] low-code: add initializer and hot-reload docs Update Low-Code docs to introduce a Low-Code Initializer pattern and model.json hot-reload support. Shows using DynamicEntityAssemblyInfo with rootNamespace and projectRootPath, adds a parameter table, and replaces simple assembly registration with an AsyncOneTimeRunner-based InitializeAsync that registers reference entities, assemblies, optional fluent API configs, and calls DynamicModelManager.Instance.InitializeAsync(). Adds a ResolveDomainSourcePath helper, guidance to call the initializer from Program.cs (and DbMigrator/other entry points) before building the app, and notes fallback to embedded resources when projectRootPath is empty. --- docs/en/low-code/fluent-api.md | 44 +++++++++++++---- docs/en/low-code/index.md | 89 +++++++++++++++++++++++++++++++--- 2 files changed, 116 insertions(+), 17 deletions(-) diff --git a/docs/en/low-code/fluent-api.md b/docs/en/low-code/fluent-api.md index ff384ce294..5e93fa01da 100644 --- a/docs/en/low-code/fluent-api.md +++ b/docs/en/low-code/fluent-api.md @@ -376,14 +376,24 @@ entity.Interceptors.Add(new CommandInterceptorDescriptor ## Assembly Registration -Register assemblies containing `[DynamicEntity]` classes: +Register assemblies containing `[DynamicEntity]` classes in your [Low-Code Initializer](index.md#1-create-a-low-code-initializer): ````csharp AbpDynamicEntityConfig.SourceAssemblies.Add( - new DynamicEntityAssemblyInfo(typeof(MyDomainModule).Assembly) + new DynamicEntityAssemblyInfo( + typeof(MyDomainModule).Assembly, + rootNamespace: "MyApp", + projectRootPath: sourcePath // For model.json hot-reload + ) ); ```` +| Parameter | Description | +|-----------|-------------| +| `assembly` | The assembly containing `[DynamicEntity]` classes and/or `model.json` | +| `rootNamespace` | Root namespace for the assembly (used for embedded resource lookup) | +| `projectRootPath` | Path to the Domain project source folder (enables `model.json` hot-reload in development) | + You can also register entity types directly: ````csharp @@ -485,20 +495,34 @@ public class OrderLine } ```` -Register everything in your Domain module: +Register everything in your [Low-Code Initializer](index.md#1-create-a-low-code-initializer): ````csharp -public override void ConfigureServices(ServiceConfigurationContext context) +public static class MyAppLowCodeInitializer { - AbpDynamicEntityConfig.SourceAssemblies.Add( - new DynamicEntityAssemblyInfo(typeof(MyDomainModule).Assembly) - ); - - // Reference existing ABP entities - AbpDynamicEntityConfig.ReferencedEntityList.Add("UserName"); + private static readonly AsyncOneTimeRunner Runner = new(); + + public static async Task InitializeAsync() + { + await Runner.RunAsync(async () => + { + // Reference existing ABP entities + AbpDynamicEntityConfig.ReferencedEntityList.Add("UserName"); + + // Register assembly + AbpDynamicEntityConfig.SourceAssemblies.Add( + new DynamicEntityAssemblyInfo(typeof(MyDomainModule).Assembly) + ); + + // Initialize + await DynamicModelManager.Instance.InitializeAsync(); + }); + } } ```` +Then call `await MyAppLowCodeInitializer.InitializeAsync();` in your `Program.cs` before building the application. + This gives you four auto-generated pages (Customers, Products, Orders with nested OrderLines), complete with permissions, menu items, foreign key lookups, and interceptor-based business rules. ## See Also diff --git a/docs/en/low-code/index.md b/docs/en/low-code/index.md index c019e5289a..8e98bb0b01 100644 --- a/docs/en/low-code/index.md +++ b/docs/en/low-code/index.md @@ -53,20 +53,95 @@ Run `dotnet ef migrations add Added_Product` and start your application. You get ## Getting Started -### 1. Register Your Assembly +### 1. Create a Low-Code Initializer -In your Domain module, register the assembly that contains your `[DynamicEntity]` classes: +Create a static initializer class in your Domain project's `_Dynamic` folder that registers your assembly and calls `DynamicModelManager.Instance.InitializeAsync()`: ````csharp -public override void ConfigureServices(ServiceConfigurationContext context) +using Volo.Abp.Identity; +using Volo.Abp.LowCode.Configuration; +using Volo.Abp.LowCode.Modeling; +using Volo.Abp.Threading; + +namespace MyApp._Dynamic; + +public static class MyAppLowCodeInitializer { - AbpDynamicEntityConfig.SourceAssemblies.Add( - new DynamicEntityAssemblyInfo(typeof(YourDomainModule).Assembly) - ); + private static readonly AsyncOneTimeRunner Runner = new(); + + public static async Task InitializeAsync() + { + await Runner.RunAsync(async () => + { + // Register reference entities (optional — for linking to existing C# entities) + AbpDynamicEntityConfig.ReferencedEntityList.Add( + nameof(IdentityUser.UserName), + nameof(IdentityUser.Email) + ); + + // Register assemblies containing [DynamicEntity] classes and model.json + var sourcePath = ResolveDomainSourcePath(); + AbpDynamicEntityConfig.SourceAssemblies.Add( + new DynamicEntityAssemblyInfo( + typeof(MyAppDomainModule).Assembly, + rootNamespace: "MyApp", + projectRootPath: sourcePath // Required for model.json hot-reload in development + ) + ); + + // Fluent API configurations (optional — highest priority) + AbpDynamicEntityConfig.EntityConfigurations.Configure("MyApp.Products.Product", entity => + { + entity.AddOrGetProperty("InternalNotes").AsServerOnly(); + }); + + // Initialize the dynamic model manager + await DynamicModelManager.Instance.InitializeAsync(); + }); + } + + private static string ResolveDomainSourcePath() + { + // Traverse up from bin folder to find the Domain project source + var baseDir = AppContext.BaseDirectory; + var current = new DirectoryInfo(baseDir); + + for (int i = 0; i < 10 && current != null; i++) + { + var candidate = Path.Combine(current.FullName, "src", "MyApp.Domain"); + if (Directory.Exists(Path.Combine(candidate, "_Dynamic"))) + { + return candidate; + } + current = current.Parent; + } + + // Fallback for production (embedded resource will be used instead) + return string.Empty; + } } ```` -### 2. Configure DbContext +> The `projectRootPath` parameter enables hot-reload of `model.json` during development. When the path is empty or the file doesn't exist, the module falls back to reading `model.json` as an embedded resource. + +### 2. Call the Initializer in Program.cs + +The initializer must be called **before** the application starts. Add it to `Program.cs`: + +````csharp +public static async Task Main(string[] args) +{ + // Initialize Low-Code before building the application + await MyAppLowCodeInitializer.InitializeAsync(); + + var builder = WebApplication.CreateBuilder(args); + // ... rest of your startup code +} +```` + +> **Important:** The initializer must also be called in your `DbMigrator` project and any other entry points (AuthServer, HttpApi.Host, etc.) that use dynamic entities. This ensures EF Core migrations can discover the entity schema. + +### 3. Configure DbContext Call `ConfigureDynamicEntities()` in your `DbContext`: