├── BlazorDynamicForm ├── _Imports.razor ├── Attributes │ ├── FileData.cs │ ├── ReadonlyFormAttribute.cs │ ├── TextAreaAttribute.cs │ ├── NameAttribute.cs │ ├── PlaceholderAttribute.cs │ ├── DefaultValueFormAttribute.cs │ ├── MultipleSelectAttribute.cs │ ├── CodeEditorAttribute.cs │ ├── ValidationRule.cs │ ├── RangeAttribute.cs │ ├── RequiredRule.cs │ └── Grid.cs ├── Components │ ├── BooleanComponent.razor │ ├── IntComponent.razor │ ├── FloatComponent.razor │ ├── DecimalComponent.razor │ ├── TextAreaComponent.razor │ ├── MultipleOptionsComponent.razor │ ├── StringComponent.razor │ ├── DynamicForm.razor │ ├── EnumComponent.razor │ ├── CodeEditorComponent.razor │ ├── ObjectComponent.razor │ ├── FileComponent.razor │ ├── DictionaryComponent.razor │ └── ListComponent.razor ├── Core │ ├── FormComponentBase.cs │ └── DynamicFormConfiguration.cs ├── AttributesComponents │ ├── LabelComponent.razor │ └── BoxComponent.razor ├── BlazorDynamicForm.csproj ├── Utility.cs ├── BlazorDynamicForm.sln └── README.md ├── BlazorDynamicFormTest ├── package.json ├── Client │ ├── wwwroot │ │ ├── favicon.ico │ │ ├── icon-192.png │ │ ├── css │ │ │ ├── open-iconic │ │ │ │ ├── font │ │ │ │ │ ├── fonts │ │ │ │ │ │ ├── open-iconic.eot │ │ │ │ │ │ ├── open-iconic.otf │ │ │ │ │ │ ├── open-iconic.ttf │ │ │ │ │ │ └── open-iconic.woff │ │ │ │ │ └── css │ │ │ │ │ │ └── open-iconic-bootstrap.min.css │ │ │ │ ├── ICON-LICENSE │ │ │ │ ├── README.md │ │ │ │ └── FONT-LICENSE │ │ │ └── app.css │ │ └── index.html │ ├── Shared │ │ ├── MainLayout.razor │ │ ├── SurveyPrompt.razor │ │ ├── NavMenu.razor.css │ │ ├── NavMenu.razor │ │ └── MainLayout.razor.css │ ├── App.razor │ ├── _Imports.razor │ ├── BlazorDynamicFormTest.Client.csproj │ ├── Program.cs │ └── Properties │ │ ├── launchSettings.json │ │ └── Pages │ │ └── Form.razor ├── package-lock.json └── BlazorDynamicFormTest.sln ├── TypeAnnotationParser ├── Annotation.cs ├── ParserConfiguration.cs ├── PropertyType.cs ├── SchemeModel.cs ├── Generator │ ├── ObjectGeneratorOptions.cs │ └── ObjectGenerator.cs ├── SchemeProperty.cs ├── TypeAnnotationParser.csproj ├── Serialization │ └── Scheme.cs ├── TypeAnnotationParser.cs └── README.md ├── .github └── ISSUE_TEMPLATE │ ├── custom.md │ ├── feature_request.md │ └── bug_report.md ├── LICENSE ├── SECURITY.md ├── TypeAnnotationParser.Test ├── TypeAnnotationParser.Test.csproj └── UnitTest1.cs ├── CONTRIBUTING.md ├── README.md ├── CODE_OF_CONDUCT.md └── .gitignore /BlazorDynamicForm/_Imports.razor: -------------------------------------------------------------------------------- 1 | @using Microsoft.AspNetCore.Components.Web -------------------------------------------------------------------------------- /BlazorDynamicFormTest/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "dependencies": { 3 | "@syncfusion/blazor-themes": "^21.2.10" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /TypeAnnotationParser/Annotation.cs: -------------------------------------------------------------------------------- 1 | namespace TypeAnnotationParser; 2 | 3 | public record Annotation(Type Type, bool Inherit); -------------------------------------------------------------------------------- /BlazorDynamicFormTest/Client/wwwroot/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheSmallPixel/BlazorDynamicForm/HEAD/BlazorDynamicFormTest/Client/wwwroot/favicon.ico -------------------------------------------------------------------------------- /BlazorDynamicFormTest/Client/wwwroot/icon-192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheSmallPixel/BlazorDynamicForm/HEAD/BlazorDynamicFormTest/Client/wwwroot/icon-192.png -------------------------------------------------------------------------------- /TypeAnnotationParser/ParserConfiguration.cs: -------------------------------------------------------------------------------- 1 | namespace TypeAnnotationParser; 2 | 3 | public record ParserConfiguration 4 | { 5 | public List Attributes { get; set; } = new(); 6 | } -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/custom.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Custom issue template 3 | about: Describe this issue template's purpose here. 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | 11 | -------------------------------------------------------------------------------- /BlazorDynamicFormTest/Client/wwwroot/css/open-iconic/font/fonts/open-iconic.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheSmallPixel/BlazorDynamicForm/HEAD/BlazorDynamicFormTest/Client/wwwroot/css/open-iconic/font/fonts/open-iconic.eot -------------------------------------------------------------------------------- /BlazorDynamicFormTest/Client/wwwroot/css/open-iconic/font/fonts/open-iconic.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheSmallPixel/BlazorDynamicForm/HEAD/BlazorDynamicFormTest/Client/wwwroot/css/open-iconic/font/fonts/open-iconic.otf -------------------------------------------------------------------------------- /BlazorDynamicFormTest/Client/wwwroot/css/open-iconic/font/fonts/open-iconic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheSmallPixel/BlazorDynamicForm/HEAD/BlazorDynamicFormTest/Client/wwwroot/css/open-iconic/font/fonts/open-iconic.ttf -------------------------------------------------------------------------------- /BlazorDynamicFormTest/Client/wwwroot/css/open-iconic/font/fonts/open-iconic.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheSmallPixel/BlazorDynamicForm/HEAD/BlazorDynamicFormTest/Client/wwwroot/css/open-iconic/font/fonts/open-iconic.woff -------------------------------------------------------------------------------- /BlazorDynamicForm/Attributes/FileData.cs: -------------------------------------------------------------------------------- 1 | namespace BlazorDynamicForm.Attributes; 2 | 3 | public record FileData 4 | { 5 | public string Name { get; set; } 6 | public string ContentType { get; set; } 7 | public string Data { get; set; } 8 | } -------------------------------------------------------------------------------- /BlazorDynamicForm/Attributes/ReadonlyFormAttribute.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel.DataAnnotations; 2 | using TypeAnnotationParser; 3 | 4 | namespace BlazorDynamicForm.Attributes; 5 | 6 | public class ReadonlyFormAttribute : AttributeScheme 7 | { 8 | } -------------------------------------------------------------------------------- /BlazorDynamicForm/Attributes/TextAreaAttribute.cs: -------------------------------------------------------------------------------- 1 | using BlazorDynamicForm.Core; 2 | using TypeAnnotationParser; 3 | 4 | namespace BlazorDynamicForm.Attributes 5 | { 6 | public class TextAreaAttribute : DynamicRendererComponent 7 | { 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /TypeAnnotationParser/PropertyType.cs: -------------------------------------------------------------------------------- 1 | namespace TypeAnnotationParser; 2 | 3 | public enum PropertyType 4 | { 5 | Integer, 6 | String, 7 | Decimal, 8 | Float, 9 | Double, 10 | Object, 11 | Array, 12 | File, 13 | Dictionary, 14 | Enum, 15 | Boolean 16 | } -------------------------------------------------------------------------------- /TypeAnnotationParser/SchemeModel.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | using Newtonsoft.Json.Converters; 3 | using System.Dynamic; 4 | 5 | namespace TypeAnnotationParser; 6 | 7 | public record SchemeModel : SchemeProperty 8 | { 9 | public Dictionary References { get; set; } = new(); 10 | 11 | } -------------------------------------------------------------------------------- /BlazorDynamicForm/Attributes/NameAttribute.cs: -------------------------------------------------------------------------------- 1 | using TypeAnnotationParser; 2 | 3 | namespace BlazorDynamicForm.Attributes; 4 | 5 | public class NameAttribute : AttributeScheme 6 | { 7 | public NameAttribute() { } 8 | 9 | public NameAttribute(string name) 10 | { 11 | Name = name; 12 | } 13 | 14 | public string Name { get; set; } 15 | } -------------------------------------------------------------------------------- /BlazorDynamicForm/Attributes/PlaceholderAttribute.cs: -------------------------------------------------------------------------------- 1 | using TypeAnnotationParser; 2 | 3 | namespace BlazorDynamicForm.Attributes; 4 | 5 | public class PlaceholderAttribute : AttributeScheme 6 | { 7 | public PlaceholderAttribute(string placeholder) 8 | { 9 | Placeholder = placeholder; 10 | } 11 | 12 | public string Placeholder { get; set; } 13 | } -------------------------------------------------------------------------------- /BlazorDynamicForm/Attributes/DefaultValueFormAttribute.cs: -------------------------------------------------------------------------------- 1 | using TypeAnnotationParser; 2 | 3 | namespace BlazorDynamicForm.Attributes; 4 | 5 | public class DefaultValueFormAttribute : AttributeScheme 6 | { 7 | public DefaultValueFormAttribute(object defaultValue) 8 | { 9 | DefaultValue = defaultValue; 10 | } 11 | 12 | public object DefaultValue { get; set; } 13 | } -------------------------------------------------------------------------------- /TypeAnnotationParser/Generator/ObjectGeneratorOptions.cs: -------------------------------------------------------------------------------- 1 | namespace BlazorDynamicForm; 2 | 3 | public record ObjectGeneratorOptions 4 | { 5 | public int MaxRecursiveDepth { get; set; } = 10; 6 | public bool InitStringsEmpty { get; set; } = true; 7 | 8 | public bool CreateCollectionElement { get; set; } = false; 9 | public bool CreateDictionaryElement { get; set; } = false; 10 | public bool CreateObjectElement { get; set; } = false; 11 | } -------------------------------------------------------------------------------- /BlazorDynamicForm/Attributes/MultipleSelectAttribute.cs: -------------------------------------------------------------------------------- 1 | using BlazorDynamicForm.Core; 2 | using TypeAnnotationParser; 3 | 4 | namespace BlazorDynamicForm.Attributes; 5 | 6 | public class MultipleSelectAttribute : DynamicRendererComponent 7 | { 8 | public string[] Options { get; set; } 9 | public MultipleSelectAttribute() { } 10 | 11 | public MultipleSelectAttribute(params string[] options) 12 | { 13 | Options = options; 14 | } 15 | } -------------------------------------------------------------------------------- /BlazorDynamicFormTest/Client/Shared/MainLayout.razor: -------------------------------------------------------------------------------- 1 | @inherits LayoutComponentBase 2 | 3 |
4 | 7 | 8 |
9 |
10 | About 11 |
12 | 13 |
14 | @Body 15 |
16 |
17 |
18 | -------------------------------------------------------------------------------- /BlazorDynamicFormTest/Client/App.razor: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Not found 8 | 9 |

Sorry, there's nothing at this address.

10 |
11 |
12 |
13 | -------------------------------------------------------------------------------- /BlazorDynamicFormTest/Client/_Imports.razor: -------------------------------------------------------------------------------- 1 | @using System.Net.Http 2 | @using System.Net.Http.Json 3 | @using Microsoft.AspNetCore.Components.Forms 4 | @using Microsoft.AspNetCore.Components.Routing 5 | @using Microsoft.AspNetCore.Components.Web 6 | @using Microsoft.AspNetCore.Components.Web.Virtualization 7 | @using Microsoft.AspNetCore.Components.WebAssembly.Http 8 | @using Microsoft.JSInterop 9 | @using BlazorDynamicFormTest.Client 10 | @using BlazorDynamicFormTest.Client.Shared 11 | @using BlazorMonaco 12 | @using BlazorMonaco.Editor 13 | @using BlazorMonaco.Languages -------------------------------------------------------------------------------- /BlazorDynamicFormTest/Client/Shared/SurveyPrompt.razor: -------------------------------------------------------------------------------- 1 |
2 | 3 | @Title 4 | 5 | 6 | Please take our 7 | brief survey 8 | 9 | and tell us what you think. 10 |
11 | 12 | @code { 13 | // Demonstrates how a parent component can supply parameters 14 | [Parameter] 15 | public string? Title { get; set; } 16 | } 17 | -------------------------------------------------------------------------------- /BlazorDynamicForm/Attributes/CodeEditorAttribute.cs: -------------------------------------------------------------------------------- 1 | using BlazorDynamicForm.Core; 2 | using TypeAnnotationParser; 3 | 4 | namespace BlazorDynamicForm.Attributes; 5 | 6 | /// 7 | /// Monaco: 8 | /// 9 | public class CodeEditorAttribute : DynamicRendererComponent 10 | { 11 | public CodeEditorAttribute(string language) 12 | { 13 | Language = language; 14 | } 15 | 16 | public CodeEditorAttribute(){} 17 | public string Language { get; set; } 18 | public string Theme { get; set; } 19 | public string Example { get; set; } 20 | 21 | 22 | } -------------------------------------------------------------------------------- /BlazorDynamicForm/Attributes/ValidationRule.cs: -------------------------------------------------------------------------------- 1 | using TypeAnnotationParser; 2 | 3 | namespace BlazorDynamicForm.Attributes; 4 | 5 | public abstract class ValidationRule : Attribute 6 | { 7 | public ValidationRule() 8 | { 9 | } 10 | 11 | public ValidationRule(string propertyName) 12 | { 13 | PropertyName = propertyName; 14 | } 15 | 16 | public string PropertyName { get; set; } 17 | 18 | public virtual bool IsValid(SchemeModel map, object? value) 19 | { 20 | return true; 21 | } 22 | 23 | public virtual string FormatErrorMessage(string name) 24 | { 25 | return string.Empty; 26 | } 27 | } -------------------------------------------------------------------------------- /BlazorDynamicForm/Components/BooleanComponent.razor: -------------------------------------------------------------------------------- 1 | @using Microsoft.AspNetCore.Components.Forms 2 | @using BlazorDynamicForm.Attributes 3 | @inherits BlazorDynamicForm.Core.FormComponentBase 4 | 5 |
6 |
7 |
8 | 9 | 10 |
11 |
12 |
13 | @code { 14 | 15 | private bool TypedValue 16 | { 17 | get => Convert.ToBoolean(Value); 18 | set => Value = value; 19 | } 20 | 21 | } -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /BlazorDynamicForm/Attributes/RangeAttribute.cs: -------------------------------------------------------------------------------- 1 | using TypeAnnotationParser; 2 | 3 | namespace BlazorDynamicForm.Attributes 4 | { 5 | public class RangeAttribute : AttributeScheme 6 | { 7 | public object Min { get; } 8 | public object Max { get; } 9 | 10 | public RangeAttribute(int min, int max) 11 | { 12 | Min = min; 13 | Max = max; 14 | } 15 | 16 | public RangeAttribute(float min, float max) 17 | { 18 | Min = min; 19 | Max = max; 20 | } 21 | 22 | public RangeAttribute(decimal min, decimal max) 23 | { 24 | Min = min; 25 | Max = max; 26 | } 27 | 28 | public RangeAttribute() {} 29 | 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /BlazorDynamicFormTest/Client/BlazorDynamicFormTest.Client.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net8.0 5 | enable 6 | enable 7 | 12 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /BlazorDynamicForm/Core/FormComponentBase.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection.Metadata; 2 | using Microsoft.AspNetCore.Components; 3 | using TypeAnnotationParser; 4 | 5 | namespace BlazorDynamicForm.Core 6 | { 7 | public abstract class FormComponentBase : ComponentBase 8 | { 9 | [Parameter] public required SchemeModel SchemeModel { get; set; } 10 | [Parameter] public required SchemeProperty SchemeProperty { get; set; } 11 | [Parameter] public string? PropertyName { get; set; } 12 | [Parameter] public bool IsFirst { get; set; } 13 | 14 | private object? _value; 15 | [Parameter] public EventCallback ValueChanged { get; set; } 16 | 17 | [Parameter] 18 | public object? Value 19 | { 20 | get => _value; 21 | set 22 | { 23 | if (!Equals(_value, value)) 24 | { 25 | _value = value; 26 | ValueChanged.InvokeAsync(value); 27 | } 28 | } 29 | } 30 | } 31 | 32 | } 33 | -------------------------------------------------------------------------------- /BlazorDynamicForm/Attributes/RequiredRule.cs: -------------------------------------------------------------------------------- 1 | using System.Collections; 2 | using TypeAnnotationParser; 3 | 4 | namespace BlazorDynamicForm.Attributes; 5 | 6 | public class RequiredRule : ValidationRule 7 | { 8 | public override bool IsValid(SchemeModel map, object? value) 9 | { 10 | if (value == null) 11 | { 12 | return false; 13 | } 14 | 15 | switch (value) 16 | { 17 | case string stringValue: 18 | return !string.IsNullOrWhiteSpace(stringValue); 19 | case Array arrayValue: 20 | return arrayValue.Length > 0; 21 | case IDictionary dictionaryValue: 22 | return dictionaryValue.Count > 0; 23 | default: 24 | return true; 25 | } 26 | } 27 | 28 | public override string FormatErrorMessage(string name) 29 | { 30 | return $"{name} is required."; 31 | } 32 | } -------------------------------------------------------------------------------- /BlazorDynamicFormTest/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "BlazorDynamicFormTest", 3 | "lockfileVersion": 2, 4 | "requires": true, 5 | "packages": { 6 | "": { 7 | "dependencies": { 8 | "@syncfusion/blazor-themes": "^21.2.10" 9 | } 10 | }, 11 | "node_modules/@syncfusion/blazor-themes": { 12 | "version": "21.2.10", 13 | "resolved": "https://registry.npmjs.org/@syncfusion/blazor-themes/-/blazor-themes-21.2.10.tgz", 14 | "integrity": "sha512-mnCo24MvItm5L96a9nqJLlECfr8/5l+mLPxLTBMZcTcnu5erPcVamAy8Y6777uIpMs3GOTc6wK/ycZLMc365rQ==" 15 | } 16 | }, 17 | "dependencies": { 18 | "@syncfusion/blazor-themes": { 19 | "version": "21.2.10", 20 | "resolved": "https://registry.npmjs.org/@syncfusion/blazor-themes/-/blazor-themes-21.2.10.tgz", 21 | "integrity": "sha512-mnCo24MvItm5L96a9nqJLlECfr8/5l+mLPxLTBMZcTcnu5erPcVamAy8Y6777uIpMs3GOTc6wK/ycZLMc365rQ==" 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /BlazorDynamicForm/AttributesComponents/LabelComponent.razor: -------------------------------------------------------------------------------- 1 | @using BlazorDynamicForm.Attributes 2 |
3 |
4 | @if (LabelInfo.Position == LabelAttribute.LabelPosition.Top) 5 | { 6 | 7 |
8 | @ChildContent 9 |
10 | } 11 | @if (LabelInfo.Position == LabelAttribute.LabelPosition.Inline) 12 | { 13 |
14 | 15 |
16 | @ChildContent 17 |
18 |
19 | } 20 | @if (LabelInfo.Position == LabelAttribute.LabelPosition.None) 21 | { 22 | @ChildContent 23 | } 24 |
25 |
26 | 27 | @code { 28 | [Parameter] public LabelAttribute? LabelInfo { get; set; } 29 | 30 | [Parameter] public RenderFragment? ChildContent { get; set; } 31 | 32 | [Parameter] public string PropertyName { get; set; } 33 | } 34 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior: 15 | 1. Go to '...' 16 | 2. Click on '....' 17 | 3. Scroll down to '....' 18 | 4. See error 19 | 20 | **Expected behavior** 21 | A clear and concise description of what you expected to happen. 22 | 23 | **Screenshots** 24 | If applicable, add screenshots to help explain your problem. 25 | 26 | **Desktop (please complete the following information):** 27 | - OS: [e.g. iOS] 28 | - Browser [e.g. chrome, safari] 29 | - Version [e.g. 22] 30 | 31 | **Smartphone (please complete the following information):** 32 | - Device: [e.g. iPhone6] 33 | - OS: [e.g. iOS8.1] 34 | - Browser [e.g. stock browser, safari] 35 | - Version [e.g. 22] 36 | 37 | **Additional context** 38 | Add any other context about the problem here. 39 | -------------------------------------------------------------------------------- /BlazorDynamicFormTest/Client/Program.cs: -------------------------------------------------------------------------------- 1 | using BlazorDynamicForm; 2 | 3 | //using BlazorDynamicFormSyncfusion; 4 | using BlazorDynamicFormTest.Client; 5 | using Microsoft.AspNetCore.Components.Web; 6 | using Microsoft.AspNetCore.Components.WebAssembly.Hosting; 7 | //using Syncfusion.Blazor; 8 | //Syncfusion.Licensing.SyncfusionLicenseProvider.RegisterLicense("Ngo9BigBOggjHTQxAR8/V1NHaF5cWWBCf1FpRmJGdld5fUVHYVZUTXxaS00DNHVRdkdgWH5ecnZXRWVeV0J0X0M="); 9 | 10 | var builder = WebAssemblyHostBuilder.CreateDefault(args); 11 | builder.RootComponents.Add("#app"); 12 | builder.RootComponents.Add("head::after"); 13 | 14 | builder.Services.AddScoped(sp => new HttpClient { BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) }); 15 | 16 | //builder.Services.AddSyncfusionBlazor(); 17 | builder.Services.AddBlazorDynamicForm();/*.SyncfusionForm();*/ 18 | //.AddDataProvider((attribute, name) => 19 | //{ 20 | // var data = new List(); 21 | // data.Add(new FormVar() { Id = "id", Name = "Cipolle" }); 22 | // return data; 23 | //}) 24 | 25 | await builder.Build().RunAsync(); 26 | -------------------------------------------------------------------------------- /BlazorDynamicFormTest/Client/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "iisSettings": { 3 | "windowsAuthentication": false, 4 | "anonymousAuthentication": true, 5 | "iisExpress": { 6 | "applicationUrl": "http://localhost:47634", 7 | "sslPort": 44315 8 | } 9 | }, 10 | "profiles": { 11 | "BlazorDynamicFormTest": { 12 | "commandName": "Project", 13 | "dotnetRunMessages": true, 14 | "launchBrowser": true, 15 | "inspectUri": "{wsProtocol}://{url.hostname}:{url.port}/_framework/debug/ws-proxy?browser={browserInspectUri}", 16 | "applicationUrl": "https://localhost:7163;http://localhost:5073", 17 | "environmentVariables": { 18 | "ASPNETCORE_ENVIRONMENT": "Development" 19 | } 20 | }, 21 | "IIS Express": { 22 | "commandName": "IISExpress", 23 | "launchBrowser": true, 24 | "inspectUri": "{wsProtocol}://{url.hostname}:{url.port}/_framework/debug/ws-proxy?browser={browserInspectUri}", 25 | "environmentVariables": { 26 | "ASPNETCORE_ENVIRONMENT": "Development" 27 | } 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2025 TheSmallPixel 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | # Security Policy 2 | 3 | ## Supported Versions 4 | 5 | We only apply security updates to the latest version of the BlazorDynamicForm project. The following table describes which versions are currently being supported with security updates: 6 | 7 | 8 | | Version | Supported | 9 | | ------- | ------------------ | 10 | | >= 1.0.4 | :white_check_mark: | 11 | | < 1.0.4 | :x: | 12 | 13 | Disclosure Policy 14 | When we receive a security bug report, we will assign it to a primary handler. This person will coordinate the fix and release process, involving the following steps: 15 | 16 | Confirm the problem and determine the affected versions. 17 | Audit code to find any potential similar problems. 18 | Prepare fixes for all releases still under maintenance. These fixes will be released as fast as possible. 19 | In general, we ask that you not disclose an issue publicly until we have had a chance to address it. 20 | 21 | Comments on this Policy 22 | If you have suggestions to improve this policy, please send an email to thesmallpixel@gmail.com. 23 | 24 | Thank you for helping to keep BlazorDynamicForm and its users safe. 25 | 26 | -------------------------------------------------------------------------------- /BlazorDynamicFormTest/Client/wwwroot/css/open-iconic/ICON-LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Waybury 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. -------------------------------------------------------------------------------- /TypeAnnotationParser/SchemeProperty.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | using Newtonsoft.Json.Converters; 3 | using YamlDotNet.Serialization; 4 | 5 | namespace TypeAnnotationParser; 6 | 7 | public record SchemeProperty 8 | { 9 | public string? Ref { get; set; } 10 | public string? Name { get; set; } 11 | // public string? Type { get; set; } 12 | [JsonConverter(typeof(StringEnumConverter))] 13 | public PropertyType? Type { get; set; } = null; 14 | 15 | public List? Attributes { get; set; } = null; 16 | public Dictionary? Properties { get; set; } = null; 17 | 18 | public List? Indices { get; set; } = null; 19 | public List? Enum { get; set; } = null; 20 | } 21 | 22 | public class AttributeScheme : Attribute 23 | { 24 | [YamlIgnore] 25 | public override object TypeId => GetType(); 26 | } 27 | public abstract class DynamicRendererComponent : AttributeScheme { } 28 | 29 | public class SelectBoxAttribute : DynamicRendererComponent 30 | { 31 | public SelectBoxAttribute(string[] choices) 32 | { 33 | Options = choices; 34 | } 35 | public SelectBoxAttribute() { } 36 | public string[] Options { get; set; } 37 | } -------------------------------------------------------------------------------- /TypeAnnotationParser/TypeAnnotationParser.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net8.0 5 | enable 6 | enable 7 | True 8 | https://github.com/TheSmallPixel/SchemeTypeParser 9 | 12 10 | 1.0.2 11 | https://github.com/TheSmallPixel/SchemeTypeParser 12 | Lorenzo Longiave(c) 2025 13 | git 14 | Scheme Type Parser 15 | Lorenzo Longiave 16 | GPL-3.0-only 17 | README.md 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | \ 28 | True 29 | 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /TypeAnnotationParser.Test/TypeAnnotationParser.Test.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net8.0 5 | enable 6 | enable 7 | 8 | false 9 | true 10 | 11 | 12 | 13 | 14 | all 15 | runtime; build; native; contentfiles; analyzers; buildtransitive 16 | 17 | 18 | 19 | 20 | all 21 | runtime; build; native; contentfiles; analyzers; buildtransitive 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /BlazorDynamicFormTest/Client/Shared/NavMenu.razor.css: -------------------------------------------------------------------------------- 1 | .navbar-toggler { 2 | background-color: rgba(255, 255, 255, 0.1); 3 | } 4 | 5 | .top-row { 6 | height: 3.5rem; 7 | background-color: rgba(0,0,0,0.4); 8 | } 9 | 10 | .navbar-brand { 11 | font-size: 1.1rem; 12 | } 13 | 14 | .oi { 15 | width: 2rem; 16 | font-size: 1.1rem; 17 | vertical-align: text-top; 18 | top: -2px; 19 | } 20 | 21 | .nav-item { 22 | font-size: 0.9rem; 23 | padding-bottom: 0.5rem; 24 | } 25 | 26 | .nav-item:first-of-type { 27 | padding-top: 1rem; 28 | } 29 | 30 | .nav-item:last-of-type { 31 | padding-bottom: 1rem; 32 | } 33 | 34 | .nav-item ::deep a { 35 | color: #d7d7d7; 36 | border-radius: 4px; 37 | height: 3rem; 38 | display: flex; 39 | align-items: center; 40 | line-height: 3rem; 41 | } 42 | 43 | .nav-item ::deep a.active { 44 | background-color: rgba(255,255,255,0.25); 45 | color: white; 46 | } 47 | 48 | .nav-item ::deep a:hover { 49 | background-color: rgba(255,255,255,0.1); 50 | color: white; 51 | } 52 | 53 | @media (min-width: 641px) { 54 | .navbar-toggler { 55 | display: none; 56 | } 57 | 58 | .collapse { 59 | /* Never collapse the sidebar for wide screens */ 60 | display: block; 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /BlazorDynamicFormTest/Client/wwwroot/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | BlazorDynamicFormTest 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 |
Loading...
21 | 22 |
23 | An unhandled error has occurred. 24 | Reload 25 | 🗙 26 |
27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /BlazorDynamicFormTest/Client/Shared/NavMenu.razor: -------------------------------------------------------------------------------- 1 | 9 | 10 |
11 | 28 |
29 | 30 | @code { 31 | private bool collapseNavMenu = true; 32 | 33 | private string? NavMenuCssClass => collapseNavMenu ? "collapse" : null; 34 | 35 | private void ToggleNavMenu() 36 | { 37 | collapseNavMenu = !collapseNavMenu; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /BlazorDynamicForm/Attributes/Grid.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.ComponentModel; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | using TypeAnnotationParser; 8 | using static BlazorDynamicForm.Attributes.LabelAttribute; 9 | 10 | namespace BlazorDynamicForm.Attributes 11 | { 12 | public class GridAttribute : AttributeScheme 13 | { 14 | public GridAttribute() { } 15 | 16 | public GridAttribute(int size) 17 | { 18 | Size = size; 19 | } 20 | 21 | public int Size { get; set; } = 12; 22 | } 23 | public class LabelAttribute : AttributeScheme 24 | { 25 | public enum LabelPosition {None, Inline, Top} 26 | public LabelAttribute() { } 27 | 28 | public LabelAttribute(string label, LabelPosition position = LabelPosition.Top) 29 | { 30 | Label = label; 31 | Position = position; 32 | } 33 | 34 | public string Label { get; set; } 35 | public LabelPosition Position { get; set; } 36 | 37 | public static LabelAttribute Instance => new LabelAttribute(string.Empty, LabelPosition.None); 38 | } 39 | 40 | public class BoxAttribute : AttributeScheme 41 | { 42 | public enum BoxVisibility { None, Visible } 43 | public BoxAttribute() { } 44 | 45 | public BoxAttribute(BoxVisibility visibility = BoxVisibility.Visible) 46 | { 47 | Visibility = visibility; 48 | } 49 | 50 | public BoxVisibility Visibility { get; set; } 51 | 52 | public static BoxAttribute Instance => new BoxAttribute(); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /TypeAnnotationParser.Test/UnitTest1.cs: -------------------------------------------------------------------------------- 1 | using BlazorDynamicForm.Attributes; 2 | using Newtonsoft.Json; 3 | using TypeAnnotationParser.Serialization; 4 | 5 | namespace TypeAnnotationParser.Test 6 | { 7 | public class UnitTest1 8 | { 9 | public class Cube 10 | { 11 | [SelectBox(["1","2"])] 12 | public int Value { get; set; } 13 | } 14 | 15 | public enum Color 16 | { 17 | Black, 18 | Yellow 19 | } 20 | public class Test 21 | { 22 | [CodeEditor("chsarp")] 23 | public string Name { get; set; } 24 | 25 | public Color Color { get; set; } 26 | public Cube M1 { get; set; } 27 | public Cube M3 { get; set; } 28 | public Cube M4 { get; set; } 29 | 30 | public List M2 { get; set; } 31 | 32 | public Test Data { get; set; } 33 | 34 | public List Data2 { get; set; } 35 | } 36 | [Fact] 37 | public void Test1() 38 | { 39 | var test = new Test() { Color = Color.Black }; 40 | 41 | var json = JsonConvert.SerializeObject(test); 42 | 43 | Console.WriteLine(json); 44 | } 45 | 46 | 47 | [Fact] 48 | public void Test2() 49 | { 50 | List attributesTypes = new List() 51 | { 52 | typeof(CodeEditorAttribute), 53 | typeof(SelectBoxAttribute), 54 | }; 55 | var yaml = Scheme.GetYamlFromScheme(attributesTypes); 56 | var scheme = Scheme.GetSchemeFromYaml(attributesTypes, yaml); 57 | 58 | 59 | System.Console.WriteLine(yaml); 60 | } 61 | 62 | 63 | } 64 | } -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing Guidelines 2 | 3 | First off, thank you for considering contributing to the `BlazorDynamicForm` project. It's people like you that make open source possible. Your contribution means a lot to us. 4 | 5 | ## How Can I Contribute? 6 | 7 | 1. **Reporting Bugs**: If you find any bugs or issues, please create an issue in the GitHub repository explaining the problem and include additional details to help reproduce the problem. 8 | 9 | 2. **Suggesting Enhancements**: If you have ideas for new features or improvements, we'd love to hear from you. Open an issue and clearly describe your suggestion. 10 | 11 | 3. **Code Contributions**: If you'd like to write code for fixing issues or adding new features, here's how you can do it: 12 | - Fork the repository and clone it locally. 13 | - Create a branch for your edits. 14 | - Write clear, concise commit messages. 15 | - Push your commits to the new branch in your forked repository. 16 | - Create a pull request to the main repository. 17 | 18 | ## Code of Conduct 19 | 20 | We aim to foster a welcoming and respectful community for everyone. Please adhere to our Code of Conduct, which can be found in the `CODE_OF_CONDUCT.md` file in the repository. In essence, respect other people, be kind, and behave professionally. 21 | 22 | ## Pull Request Process 23 | 24 | Our team will review your pull request as soon as possible and will provide any necessary feedback or corrections if required. 25 | 26 | Thanks again for your interest in contributing to `BlazorDynamicForm`, we look forward to working together to make this project better for everyone! 27 | -------------------------------------------------------------------------------- /BlazorDynamicForm/Components/IntComponent.razor: -------------------------------------------------------------------------------- 1 | @using Microsoft.AspNetCore.Components.Forms 2 | @using BlazorDynamicForm.Attributes 3 | @inherits BlazorDynamicForm.Core.FormComponentBase 4 | 5 |
6 | 7 |
8 |
9 | 10 | 11 | 12 | 13 | 14 | 15 |
16 |
17 |
18 | @code { 19 | 20 | private int TypedValue 21 | { 22 | get => Convert.ToInt32(Value); 23 | set => Value = value; 24 | } 25 | 26 | } -------------------------------------------------------------------------------- /BlazorDynamicForm/Components/FloatComponent.razor: -------------------------------------------------------------------------------- 1 | @using Microsoft.AspNetCore.Components.Forms 2 | @using BlazorDynamicForm.Attributes 3 | @inherits BlazorDynamicForm.Core.FormComponentBase 4 | 5 |
6 | 7 |
8 |
9 | 10 | 11 | 12 | 13 | 14 | 15 |
16 |
17 |
18 | @code { 19 | 20 | private float TypedValue 21 | { 22 | get => Convert.ToSingle(Value); 23 | set => Value = value; 24 | } 25 | 26 | } -------------------------------------------------------------------------------- /BlazorDynamicForm/Components/DecimalComponent.razor: -------------------------------------------------------------------------------- 1 | @using Microsoft.AspNetCore.Components.Forms 2 | @using BlazorDynamicForm.Attributes 3 | @inherits BlazorDynamicForm.Core.FormComponentBase 4 | 5 |
6 | 7 |
8 |
9 | 10 | 11 | 12 | 13 | 14 | 15 |
16 |
17 |
18 | @code { 19 | 20 | private decimal TypedValue 21 | { 22 | get => Convert.ToDecimal(Value); 23 | set => Value = value; 24 | } 25 | 26 | } -------------------------------------------------------------------------------- /BlazorDynamicForm/Components/TextAreaComponent.razor: -------------------------------------------------------------------------------- 1 | @using Microsoft.AspNetCore.Components.Forms 2 | @inherits BlazorDynamicForm.Core.FormComponentBase 3 | 4 |
5 | 6 |
7 |
8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 |
16 |
17 | @code { 18 | private string TypedValue 19 | { 20 | get => Value?.ToString() ?? string.Empty; 21 | set => Value = value; 22 | } 23 | } -------------------------------------------------------------------------------- /BlazorDynamicForm/BlazorDynamicForm.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net8.0 5 | enable 6 | enable 7 | True 8 | https://github.com/TheSmallPixel/BlazorDynamicForm 9 | 2.1.0 10 | Blazor;Dynamic;Form;Syncfusion;Validation; 11 | https://github.com/TheSmallPixel 12 | git 13 | Lorenzo Longiave(c) 2025 14 | GPL-3.0-only 15 | README.md 16 | Blazor Dynamic Form Generator 17 | Lorenzo Longiave 18 | 12 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | True 41 | \ 42 | 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /BlazorDynamicForm/Components/MultipleOptionsComponent.razor: -------------------------------------------------------------------------------- 1 | @using Microsoft.AspNetCore.Components.Forms 2 | @using BlazorDynamicForm.Attributes 3 | @using BlazorDynamicForm.AttributesComponents 4 | @inherits BlazorDynamicForm.Core.FormComponentBase 5 | 6 | 7 |
8 | 9 | 10 | 11 | 12 | 13 | 14 | @if (_config != null) 15 | { 16 | @foreach (var option in _config.Options) 17 | { 18 | 19 | } 20 | } 21 | 22 |
23 |
24 | @code { 25 | private string TypedValue 26 | { 27 | get => Value?.ToString() ?? string.Empty; 28 | set => Value = value; 29 | } 30 | private MultipleSelectAttribute? _config; 31 | 32 | private LabelAttribute _label; 33 | 34 | 35 | protected override async Task OnParametersSetAsync() 36 | { 37 | _config = SchemeProperty.Attributes?.OfType().FirstOrDefault(); 38 | if (string.IsNullOrEmpty(TypedValue)) 39 | { 40 | TypedValue = _config.Options.First(); 41 | } 42 | _label = SchemeProperty.Attributes?.OfType().FirstOrDefault() ?? LabelAttribute.Instance; 43 | } 44 | } -------------------------------------------------------------------------------- /BlazorDynamicFormTest/Client/Shared/MainLayout.razor.css: -------------------------------------------------------------------------------- 1 | .page { 2 | position: relative; 3 | display: flex; 4 | flex-direction: column; 5 | } 6 | 7 | main { 8 | flex: 1; 9 | } 10 | 11 | .sidebar { 12 | background-image: linear-gradient(180deg, rgb(5, 39, 103) 0%, #3a0647 70%); 13 | } 14 | 15 | .top-row { 16 | background-color: #f7f7f7; 17 | border-bottom: 1px solid #d6d5d5; 18 | justify-content: flex-end; 19 | height: 3.5rem; 20 | display: flex; 21 | align-items: center; 22 | } 23 | 24 | .top-row ::deep a, .top-row ::deep .btn-link { 25 | white-space: nowrap; 26 | margin-left: 1.5rem; 27 | text-decoration: none; 28 | } 29 | 30 | .top-row ::deep a:hover, .top-row ::deep .btn-link:hover { 31 | text-decoration: underline; 32 | } 33 | 34 | .top-row ::deep a:first-child { 35 | overflow: hidden; 36 | text-overflow: ellipsis; 37 | } 38 | 39 | @media (max-width: 640.98px) { 40 | .top-row:not(.auth) { 41 | display: none; 42 | } 43 | 44 | .top-row.auth { 45 | justify-content: space-between; 46 | } 47 | 48 | .top-row ::deep a, .top-row ::deep .btn-link { 49 | margin-left: 0; 50 | } 51 | } 52 | 53 | @media (min-width: 641px) { 54 | .page { 55 | flex-direction: row; 56 | } 57 | 58 | .sidebar { 59 | width: 250px; 60 | height: 100vh; 61 | position: sticky; 62 | top: 0; 63 | } 64 | 65 | .top-row { 66 | position: sticky; 67 | top: 0; 68 | z-index: 1; 69 | } 70 | 71 | .top-row.auth ::deep a:first-child { 72 | flex: 1; 73 | text-align: right; 74 | width: 0; 75 | } 76 | 77 | .top-row, article { 78 | padding-left: 2rem !important; 79 | padding-right: 1.5rem !important; 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /BlazorDynamicForm/Components/StringComponent.razor: -------------------------------------------------------------------------------- 1 | @using Microsoft.AspNetCore.Components.Forms 2 | @using BlazorDynamicForm.Attributes 3 | @using BlazorDynamicForm.AttributesComponents 4 | @inherits BlazorDynamicForm.Core.FormComponentBase 5 | 6 | 7 |
8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 |
16 | @code { 17 | private string TypedValue 18 | { 19 | get => Value?.ToString() ?? string.Empty; 20 | set => Value = value; 21 | } 22 | private PlaceholderAttribute? _configPlaceholderAttribute; 23 | private LabelAttribute _label; 24 | 25 | 26 | protected override async Task OnParametersSetAsync() 27 | { 28 | _configPlaceholderAttribute = SchemeProperty.Attributes?.OfType().FirstOrDefault(); 29 | _label = SchemeProperty.Attributes?.OfType().FirstOrDefault() ?? LabelAttribute.Instance; 30 | } 31 | 32 | } -------------------------------------------------------------------------------- /TypeAnnotationParser/Serialization/Scheme.cs: -------------------------------------------------------------------------------- 1 | using YamlDotNet.Core; 2 | using YamlDotNet.Serialization.NamingConventions; 3 | using YamlDotNet.Serialization; 4 | 5 | namespace TypeAnnotationParser.Serialization 6 | { 7 | public static class Scheme 8 | { 9 | public static SchemeModel? GetSchemeFromYaml(List attributesTypes, string yaml) 10 | { 11 | try 12 | { 13 | var deserializerBuilder = new DeserializerBuilder() 14 | .WithNamingConvention(LowerCaseNamingConvention.Instance); 15 | foreach (var attribute in attributesTypes) 16 | { 17 | deserializerBuilder.WithTagMapping("!" + attribute.Name.Replace("Attribute", ""), attribute); 18 | } 19 | 20 | var deserializer = deserializerBuilder.Build(); 21 | var schemeDeserialize = deserializer.Deserialize(yaml); 22 | return schemeDeserialize; 23 | 24 | } 25 | catch (YamlException ex) 26 | { 27 | Console.WriteLine( 28 | $"Yaml error between ({ex.Start.Line},{ex.Start.Column}) and " + 29 | $"({ex.End.Line},{ex.End.Column}): {ex.Message}"); 30 | 31 | if (ex.InnerException != null) 32 | Console.WriteLine($"Inner: {ex.InnerException.GetType().Name} – {ex.InnerException.Message}"); 33 | 34 | throw; // or re‑throw a custom error 35 | } 36 | } 37 | 38 | public static string GetYamlFromScheme(List attributesToSerialize) 39 | { 40 | return GetYamlFromScheme(typeof(T), attributesToSerialize); 41 | } 42 | 43 | public static string GetYamlFromScheme(Type type, List attributesToSerialize) 44 | { 45 | var config = new ParserConfiguration(); 46 | config.Attributes = new List(); 47 | var parser = new TypeAnnotationParser(config); 48 | 49 | var scheme = parser.Parse(type); 50 | 51 | var serializerToDo = new SerializerBuilder() 52 | .WithNamingConvention(LowerCaseNamingConvention.Instance) 53 | .ConfigureDefaultValuesHandling(DefaultValuesHandling.OmitNull) 54 | .DisableAliases(); 55 | 56 | foreach (var attribute in attributesToSerialize) 57 | { 58 | serializerToDo.WithTagMapping("!" + attribute.Name.Replace("Attribute", ""), attribute); 59 | } 60 | 61 | var serializer = serializerToDo.Build(); 62 | var yaml = serializer.Serialize(scheme); 63 | return yaml; 64 | } 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /BlazorDynamicForm/Utility.cs: -------------------------------------------------------------------------------- 1 | using BlazorDynamicForm.Attributes; 2 | using BlazorDynamicForm.Components; 3 | using BlazorDynamicForm.Core; 4 | using Microsoft.Extensions.DependencyInjection; 5 | using Microsoft.Extensions.Logging; 6 | using TypeAnnotationParser; 7 | using CodeEditorAttribute = BlazorDynamicForm.Attributes.CodeEditorAttribute; 8 | 9 | namespace BlazorDynamicForm; 10 | 11 | public static class Utility 12 | { 13 | public static List DefaultComponents = new List 14 | { 15 | typeof(MultipleSelectAttribute), 16 | typeof(TextAreaAttribute), 17 | typeof(CodeEditorAttribute), 18 | typeof(NameAttribute), 19 | typeof(GridAttribute), 20 | typeof(BoxAttribute), 21 | typeof(LabelAttribute) 22 | 23 | }; 24 | 25 | public static IServiceCollection AddBlazorDynamicForm(this IServiceCollection services) 26 | { 27 | services.AddSingleton(serviceProvider => 28 | { 29 | var logger = serviceProvider.GetRequiredService>(); 30 | var config = new DynamicFormConfiguration(logger); 31 | 32 | config.AddRenderer(PropertyType.Enum); 33 | config.AddRenderer(PropertyType.Decimal); 34 | config.AddRenderer(PropertyType.Float); 35 | config.AddRenderer(PropertyType.Integer); 36 | config.AddRenderer(PropertyType.Boolean); 37 | config.AddRenderer(PropertyType.String); 38 | 39 | config.AddRenderer(PropertyType.Object); 40 | config.AddRenderer(PropertyType.Array); 41 | config.AddRenderer(PropertyType.Dictionary); 42 | 43 | config.AddCustomRenderer(PropertyType.File); 44 | 45 | config.AddCustomAttributeRenderer(); 46 | config.AddCustomAttributeRenderer(); 47 | config.AddCustomAttributeRenderer(); 48 | 49 | return config; 50 | }); 51 | 52 | return services; 53 | } 54 | 55 | } -------------------------------------------------------------------------------- /BlazorDynamicForm/AttributesComponents/BoxComponent.razor: -------------------------------------------------------------------------------- 1 | @using BlazorDynamicForm.Attributes 2 | @{ 3 | if (BoxAttribute.Visibility == BoxAttribute.BoxVisibility.Visible) 4 | { 5 | if (_status) 6 | { 7 |
8 |
9 |
10 |
11 |
12 | 13 | 14 | 15 | 16 | 17 | @PropertyName 18 | 19 |
20 |
21 |
22 | @ChildContent 23 |
24 |
25 |
26 |
27 | 28 | } 29 | else 30 | { 31 |
32 |
33 | 34 | 35 | 36 | 37 | 38 | @PropertyName 39 | 40 |
41 |
42 | } 43 | 44 | } 45 | else 46 | { 47 | @ChildContent 48 | } 49 | } 50 | @code { 51 | private bool _status = false; 52 | [Parameter] public BoxAttribute BoxAttribute { get; set; } 53 | 54 | [Parameter] public RenderFragment? ChildContent { get; set; } 55 | 56 | [Parameter] public string PropertyName { get; set; } 57 | private void Open() 58 | { 59 | _status = true; 60 | } 61 | 62 | private void Close() 63 | { 64 | _status = false; 65 | } 66 | 67 | } 68 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # BlazorDynamicForm 2 | 3 | A lightweight, flexible form generator for Blazor applications that creates dynamic forms from annotated C# classes. 4 | 5 | ## Features 6 | 7 | - Creates forms automatically from C# classes 8 | - Built-in validation through data annotations 9 | - Customizable form rendering and layout 10 | - Type-safe form handling 11 | 12 | ## Installation 13 | 14 | ```bash 15 | dotnet add package BlazorDynamicForm 16 | ``` 17 | 18 | ## Quick Start 19 | 20 | 1. **Add Service in Program.cs** 21 | 22 | ```csharp 23 | builder.Services.AddBlazorDynamicForm(); 24 | ``` 25 | 26 | 2. **Create a Model with Annotations** 27 | 28 | ```csharp 29 | public class ContactForm 30 | { 31 | [Required, Display(Name = "Name")] 32 | public string Name { get; set; } 33 | 34 | [EmailAddress] 35 | public string Email { get; set; } 36 | 37 | [Phone, Display(Name = "Phone Number")] 38 | public string PhoneNumber { get; set; } 39 | 40 | [TextArea] 41 | public string Message { get; set; } 42 | } 43 | ``` 44 | 45 | 3. **Use the DynamicForm Component** 46 | 47 | ```razor 48 | @using BlazorDynamicForm.Components 49 | @using TypeAnnotationParser 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | @code { 58 | private SchemeModel _formScheme; 59 | private IDictionary _formData = new Dictionary(); 60 | 61 | protected override void OnInitialized() 62 | { 63 | var parser = new TypeAnnotationParser(); 64 | _formScheme = parser.Parse(); 65 | } 66 | 67 | private void HandleSubmit(IDictionary data) 68 | { 69 | // Handle form data 70 | } 71 | } 72 | ``` 73 | 4. **Example** 74 | 75 | ![Screenshot 2025-05-06 170325](https://github.com/user-attachments/assets/a0df0795-c34c-491b-8bfd-e7fe91f6453e) 76 | 77 | 78 | ## Custom Attributes 79 | 80 | BlazorDynamicForm provides additional attributes for enhanced form customization: 81 | 82 | - `[TextArea]` - Creates a multi-line text input 83 | - `[Placeholder("Enter text...")]` - Adds placeholder text 84 | - `[Grid(6)]` - Controls the layout grid width 85 | - `[Name("Custom Field Name")]` - Sets a custom field name 86 | - `[MultipleSelect("Option1", "Option2", "Option3")]` - Creates a dropdown with options 87 | 88 | ## License 89 | 90 | [GPL-3.0](https://www.gnu.org/licenses/gpl-3.0.html) 91 | 92 | ## Contributing 93 | 94 | Pull requests are welcome. For major changes, please open an issue first to discuss what you would like to change. 95 | -------------------------------------------------------------------------------- /BlazorDynamicForm/Components/DynamicForm.razor: -------------------------------------------------------------------------------- 1 | @using System.Dynamic 2 | @using BlazorDynamicForm.Core 3 | @using TypeAnnotationParser 4 | @inject DynamicFormConfiguration Configuration 5 | 6 | 7 | 8 |
9 | @if (Scheme?.Properties != null) 10 | { 11 | var componentType = Configuration.GetElement(Scheme, Scheme); 12 | if (componentType is not null) 13 | { 14 | Data = (IDictionary)Scheme.CreateOrValidateData(Scheme, Data); 15 | var parameters = new Dictionary 16 | { 17 | ["SchemeProperty"] = Scheme, 18 | ["SchemeModel"] = Scheme, 19 | ["PropertyName"] = Scheme.Name, 20 | ["IsFirst"] = true, 21 | ["Value"] = Data, 22 | ["ValueChanged"] = EventCallback.Factory.Create(this, OnChildValueChanged) 23 | }; 24 | 25 | 26 | 27 | 28 | 29 |
30 | An error occurred: @ex.Message 31 |
32 |
33 |
34 | } 35 | else 36 | { 37 |

No component found for this scheme.

38 | } 39 | } 40 | @SubmitTemplate 41 | 42 | 43 | @code { 44 | // Holds your data, which can be shaped by ExpandoObject 45 | [Parameter] 46 | public IDictionary Data { get; set; } 47 | 48 | [Parameter] 49 | public EventCallback?> OnValidSubmit { get; set; } 50 | 51 | [Parameter] 52 | public RenderFragment SubmitTemplate { get; set; } 53 | 54 | [Parameter] 55 | public required SchemeModel Scheme { get; set; } 56 | 57 | protected override void OnInitialized() 58 | { 59 | // If needed, initialize "Data" or "Scheme" from services, etc. 60 | // This is just a placeholder for demonstration. 61 | base.OnInitialized(); 62 | } 63 | 64 | protected override void OnParametersSet() 65 | { 66 | Data ??= new Dictionary(); 67 | } 68 | 69 | private async Task HandleSubmitAsync() 70 | { 71 | await OnValidSubmit.InvokeAsync(Data); 72 | } 73 | 74 | private void OnChildValueChanged(object? newValue) 75 | { 76 | if (newValue is IDictionary updatedObj) 77 | { 78 | Data = updatedObj; 79 | } 80 | // Force a re-render if needed: 81 | StateHasChanged(); 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /BlazorDynamicForm/Components/EnumComponent.razor: -------------------------------------------------------------------------------- 1 | @using Microsoft.AspNetCore.Components.Forms 2 | @using BlazorDynamicForm.Attributes 3 | @inherits BlazorDynamicForm.Core.FormComponentBase 4 | 5 |
6 | 7 |
8 |
9 | 10 | 11 | 12 | 13 | 14 | 15 | @foreach (var option in _configEnum) 16 | { 17 | 18 | } 19 | 20 |
21 |
22 |
23 | @code { 24 | private string TypedValue 25 | { 26 | get 27 | { 28 | // Try to retrieve an integer index from Value. 29 | int index = Value is int intValue ? intValue : Convert.ToInt32(Value); 30 | if (index < 0 || index >= _configEnum.Count) 31 | { 32 | index = 0; 33 | } 34 | return _configEnum[index]; 35 | } 36 | set 37 | { 38 | int index = _configEnum.IndexOf(value); 39 | if (index == -1) 40 | { 41 | index = 0; 42 | } 43 | Value = index; 44 | } 45 | } 46 | private List _configEnum; 47 | 48 | 49 | 50 | protected override async Task OnParametersSetAsync() 51 | { 52 | // Ensure we have enum values. 53 | if (SchemeProperty.Enum is null || !SchemeProperty.Enum.Any()) 54 | { 55 | throw new InvalidOperationException("Missing enum values in the scheme property."); 56 | } 57 | _configEnum = SchemeProperty.Enum; 58 | 59 | // If Value is null, default to the first element. 60 | if (Value is null) 61 | { 62 | Value = 0; 63 | } 64 | else 65 | { 66 | // Validate that the current Value (as int) is within the valid range. 67 | int index = Value is int intValue ? intValue : Convert.ToInt32(Value); 68 | if (index < 0 || index >= _configEnum.Count) 69 | { 70 | // Optionally, reset to default or throw an error. 71 | Value = 0; 72 | } 73 | } 74 | } 75 | } -------------------------------------------------------------------------------- /BlazorDynamicForm/Components/CodeEditorComponent.razor: -------------------------------------------------------------------------------- 1 | @using BlazorDynamicForm.Attributes 2 | @using BlazorMonaco.Editor 3 | @using Microsoft.AspNetCore.Components.Forms 4 | @using BlazorMonaco 5 | @using BlazorMonaco.Editor 6 | @using BlazorMonaco.Languages 7 | @inherits BlazorDynamicForm.Core.FormComponentBase 8 | 9 | 10 |
11 | 12 |
13 |
14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 |
22 |
23 |
24 | 29 | @code { 30 | private StandaloneCodeEditor _codeEditor; 31 | private bool suppressCallback = false; 32 | private string TypedValue 33 | { 34 | get => Value?.ToString() ?? string.Empty; 35 | set => Value = value; 36 | } 37 | protected override async Task OnParametersSetAsync() 38 | { 39 | if (!string.IsNullOrWhiteSpace(TypedValue)) 40 | { 41 | var currentValue = await _codeEditor.GetValue(); 42 | if (currentValue != TypedValue) 43 | await _codeEditor.SetValue(TypedValue); 44 | } 45 | } 46 | 47 | 48 | private StandaloneEditorConstructionOptions EditorConstructionOptions(StandaloneCodeEditor editor) 49 | { 50 | var config = SchemeProperty.Attributes?.OfType().FirstOrDefault(); 51 | 52 | return new StandaloneEditorConstructionOptions 53 | { 54 | Theme = config != null ? config.Theme : "csharp", 55 | AutomaticLayout = true, 56 | Language = config.Language, 57 | Value = config.Example, 58 | // Padding = new EditorPaddingOptions(){Bottom = 0, Top = 0} 59 | }; 60 | } 61 | 62 | private async Task Callback(ModelContentChangedEvent obj) 63 | { 64 | TypedValue = await _codeEditor.GetValue(); 65 | } 66 | 67 | } -------------------------------------------------------------------------------- /BlazorDynamicForm/BlazorDynamicForm.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 17 4 | VisualStudioVersion = 17.12.35707.178 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BlazorDynamicForm", "BlazorDynamicForm.csproj", "{CABBEFCE-EA59-46D2-ADCC-2149E1BA0A2C}" 7 | EndProject 8 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TypeAnnotationParser", "..\TypeAnnotationParser\TypeAnnotationParser.csproj", "{A09EFDFE-24D3-4505-8576-281B83F03217}" 9 | EndProject 10 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BlazorDynamicFormTest.Client", "..\BlazorDynamicFormTest\Client\BlazorDynamicFormTest.Client.csproj", "{08A56C15-85C8-4471-9BAA-9A26430717AF}" 11 | EndProject 12 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TypeAnnotationParser.Test", "..\TypeAnnotationParser.Test\TypeAnnotationParser.Test.csproj", "{8B24D6F0-7252-4E21-BDB9-37A57A19693F}" 13 | EndProject 14 | Global 15 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 16 | Debug|Any CPU = Debug|Any CPU 17 | Release|Any CPU = Release|Any CPU 18 | EndGlobalSection 19 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 20 | {CABBEFCE-EA59-46D2-ADCC-2149E1BA0A2C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 21 | {CABBEFCE-EA59-46D2-ADCC-2149E1BA0A2C}.Debug|Any CPU.Build.0 = Debug|Any CPU 22 | {CABBEFCE-EA59-46D2-ADCC-2149E1BA0A2C}.Release|Any CPU.ActiveCfg = Release|Any CPU 23 | {CABBEFCE-EA59-46D2-ADCC-2149E1BA0A2C}.Release|Any CPU.Build.0 = Release|Any CPU 24 | {A09EFDFE-24D3-4505-8576-281B83F03217}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 25 | {A09EFDFE-24D3-4505-8576-281B83F03217}.Debug|Any CPU.Build.0 = Debug|Any CPU 26 | {A09EFDFE-24D3-4505-8576-281B83F03217}.Release|Any CPU.ActiveCfg = Release|Any CPU 27 | {A09EFDFE-24D3-4505-8576-281B83F03217}.Release|Any CPU.Build.0 = Release|Any CPU 28 | {08A56C15-85C8-4471-9BAA-9A26430717AF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 29 | {08A56C15-85C8-4471-9BAA-9A26430717AF}.Debug|Any CPU.Build.0 = Debug|Any CPU 30 | {08A56C15-85C8-4471-9BAA-9A26430717AF}.Release|Any CPU.ActiveCfg = Release|Any CPU 31 | {08A56C15-85C8-4471-9BAA-9A26430717AF}.Release|Any CPU.Build.0 = Release|Any CPU 32 | {8B24D6F0-7252-4E21-BDB9-37A57A19693F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 33 | {8B24D6F0-7252-4E21-BDB9-37A57A19693F}.Debug|Any CPU.Build.0 = Debug|Any CPU 34 | {8B24D6F0-7252-4E21-BDB9-37A57A19693F}.Release|Any CPU.ActiveCfg = Release|Any CPU 35 | {8B24D6F0-7252-4E21-BDB9-37A57A19693F}.Release|Any CPU.Build.0 = Release|Any CPU 36 | EndGlobalSection 37 | GlobalSection(SolutionProperties) = preSolution 38 | HideSolutionNode = FALSE 39 | EndGlobalSection 40 | GlobalSection(ExtensibilityGlobals) = postSolution 41 | SolutionGuid = {5BFF2379-9067-4486-A1C6-72167B12A972} 42 | EndGlobalSection 43 | EndGlobal 44 | -------------------------------------------------------------------------------- /BlazorDynamicForm/Core/DynamicFormConfiguration.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.Logging; 2 | using TypeAnnotationParser; 3 | 4 | namespace BlazorDynamicForm.Core 5 | { 6 | public class DynamicFormConfiguration(ILogger logger) 7 | { 8 | 9 | public Dictionary RendererMappings { get; private set; } = new(); 10 | 11 | public Dictionary CustomAttributeRenderer { get; private set; } = new(); 12 | 13 | public Dictionary CustomRenderer { get; private set; } = new(); 14 | 15 | public void AddRenderer(PropertyType type) where R : FormComponentBase 16 | { 17 | RendererMappings[type] = typeof(R); 18 | } 19 | 20 | public void AddCustomAttributeRenderer() where R : FormComponentBase where T : DynamicRendererComponent 21 | { 22 | CustomAttributeRenderer[typeof(T)] = typeof(R); 23 | } 24 | 25 | public void AddCustomRenderer(PropertyType type) where R : FormComponentBase 26 | { 27 | CustomRenderer[type] = typeof(R); 28 | } 29 | 30 | public SchemeProperty? ResolveReference(SchemeModel model, SchemeProperty property) 31 | { 32 | if (string.IsNullOrEmpty(property.Ref)) 33 | { 34 | return property; 35 | } 36 | if (model.References.TryGetValue(property.Ref, out var prop)) 37 | { 38 | return prop; 39 | } 40 | return null; 41 | } 42 | 43 | public Type? GetElement(SchemeModel model, SchemeProperty property) 44 | { 45 | 46 | if (property.Type is null && string.IsNullOrEmpty(property.Ref)) 47 | { 48 | return null; //exeption invalid 49 | } 50 | 51 | if (property.Type is null && !string.IsNullOrEmpty(property.Ref)) 52 | { 53 | if (model.References.TryGetValue(property.Ref, out var prop)) 54 | { 55 | return GetElement(model, prop); 56 | } 57 | } 58 | else 59 | { 60 | var customRenderer = property.Attributes?.OfType().FirstOrDefault(); 61 | if (customRenderer != null && CustomAttributeRenderer.TryGetValue(customRenderer.GetType(), out var component)) 62 | { 63 | return component; 64 | } 65 | 66 | if (CustomRenderer.TryGetValue(property.Type.Value, out var customElement)) 67 | { 68 | return customElement; 69 | } 70 | if (RendererMappings.TryGetValue(property.Type.Value, out var element)) 71 | { 72 | return element; 73 | } 74 | } 75 | 76 | return null; 77 | 78 | } 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /BlazorDynamicFormTest/Client/wwwroot/css/app.css: -------------------------------------------------------------------------------- 1 | @import url('open-iconic/font/css/open-iconic-bootstrap.min.css'); 2 | 3 | html, body { 4 | font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; 5 | } 6 | 7 | h1:focus { 8 | outline: none; 9 | } 10 | 11 | a, .btn-link { 12 | color: #0071c1; 13 | } 14 | 15 | .btn-primary { 16 | color: #fff; 17 | background-color: #1b6ec2; 18 | border-color: #1861ac; 19 | } 20 | 21 | .content { 22 | padding-top: 1.1rem; 23 | } 24 | 25 | .valid.modified:not([type=checkbox]) { 26 | outline: 1px solid #26b050; 27 | } 28 | 29 | .invalid { 30 | outline: 1px solid red; 31 | } 32 | 33 | .validation-message { 34 | color: red; 35 | } 36 | 37 | #blazor-error-ui { 38 | background: lightyellow; 39 | bottom: 0; 40 | box-shadow: 0 -1px 2px rgba(0, 0, 0, 0.2); 41 | display: none; 42 | left: 0; 43 | padding: 0.6rem 1.25rem 0.7rem 1.25rem; 44 | position: fixed; 45 | width: 100%; 46 | z-index: 1000; 47 | } 48 | 49 | #blazor-error-ui .dismiss { 50 | cursor: pointer; 51 | position: absolute; 52 | right: 0.75rem; 53 | top: 0.5rem; 54 | } 55 | 56 | .blazor-error-boundary { 57 | background: url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNTYiIGhlaWdodD0iNDkiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIG92ZXJmbG93PSJoaWRkZW4iPjxkZWZzPjxjbGlwUGF0aCBpZD0iY2xpcDAiPjxyZWN0IHg9IjIzNSIgeT0iNTEiIHdpZHRoPSI1NiIgaGVpZ2h0PSI0OSIvPjwvY2xpcFBhdGg+PC9kZWZzPjxnIGNsaXAtcGF0aD0idXJsKCNjbGlwMCkiIHRyYW5zZm9ybT0idHJhbnNsYXRlKC0yMzUgLTUxKSI+PHBhdGggZD0iTTI2My41MDYgNTFDMjY0LjcxNyA1MSAyNjUuODEzIDUxLjQ4MzcgMjY2LjYwNiA1Mi4yNjU4TDI2Ny4wNTIgNTIuNzk4NyAyNjcuNTM5IDUzLjYyODMgMjkwLjE4NSA5Mi4xODMxIDI5MC41NDUgOTIuNzk1IDI5MC42NTYgOTIuOTk2QzI5MC44NzcgOTMuNTEzIDI5MSA5NC4wODE1IDI5MSA5NC42NzgyIDI5MSA5Ny4wNjUxIDI4OS4wMzggOTkgMjg2LjYxNyA5OUwyNDAuMzgzIDk5QzIzNy45NjMgOTkgMjM2IDk3LjA2NTEgMjM2IDk0LjY3ODIgMjM2IDk0LjM3OTkgMjM2LjAzMSA5NC4wODg2IDIzNi4wODkgOTMuODA3MkwyMzYuMzM4IDkzLjAxNjIgMjM2Ljg1OCA5Mi4xMzE0IDI1OS40NzMgNTMuNjI5NCAyNTkuOTYxIDUyLjc5ODUgMjYwLjQwNyA1Mi4yNjU4QzI2MS4yIDUxLjQ4MzcgMjYyLjI5NiA1MSAyNjMuNTA2IDUxWk0yNjMuNTg2IDY2LjAxODNDMjYwLjczNyA2Ni4wMTgzIDI1OS4zMTMgNjcuMTI0NSAyNTkuMzEzIDY5LjMzNyAyNTkuMzEzIDY5LjYxMDIgMjU5LjMzMiA2OS44NjA4IDI1OS4zNzEgNzAuMDg4N0wyNjEuNzk1IDg0LjAxNjEgMjY1LjM4IDg0LjAxNjEgMjY3LjgyMSA2OS43NDc1QzI2Ny44NiA2OS43MzA5IDI2Ny44NzkgNjkuNTg3NyAyNjcuODc5IDY5LjMxNzkgMjY3Ljg3OSA2Ny4xMTgyIDI2Ni40NDggNjYuMDE4MyAyNjMuNTg2IDY2LjAxODNaTTI2My41NzYgODYuMDU0N0MyNjEuMDQ5IDg2LjA1NDcgMjU5Ljc4NiA4Ny4zMDA1IDI1OS43ODYgODkuNzkyMSAyNTkuNzg2IDkyLjI4MzcgMjYxLjA0OSA5My41Mjk1IDI2My41NzYgOTMuNTI5NSAyNjYuMTE2IDkzLjUyOTUgMjY3LjM4NyA5Mi4yODM3IDI2Ny4zODcgODkuNzkyMSAyNjcuMzg3IDg3LjMwMDUgMjY2LjExNiA4Ni4wNTQ3IDI2My41NzYgODYuMDU0N1oiIGZpbGw9IiNGRkU1MDAiIGZpbGwtcnVsZT0iZXZlbm9kZCIvPjwvZz48L3N2Zz4=) no-repeat 1rem/1.8rem, #b32121; 58 | padding: 1rem 1rem 1rem 3.7rem; 59 | color: white; 60 | } 61 | 62 | .blazor-error-boundary::after { 63 | content: "An error has occurred." 64 | } 65 | -------------------------------------------------------------------------------- /BlazorDynamicForm/Components/ObjectComponent.razor: -------------------------------------------------------------------------------- 1 | @using BlazorDynamicForm.Attributes 2 | @using BlazorDynamicForm.Core 3 | @using Microsoft.AspNetCore.Components.Forms 4 | @using TypeAnnotationParser 5 | @using BlazorDynamicForm.AttributesComponents 6 | @inject DynamicFormConfiguration Configuration 7 | @inherits BlazorDynamicForm.Core.FormComponentBase 8 | 9 | 10 | 11 |
12 | @{ 13 | 14 | if (Value is not IDictionary dict) 15 | { 16 |
17 | Value is not a dictionary. 18 | Cannot render properties. 19 |
20 | } 21 | else 22 | { 23 | SchemeProperty objectScheme = SchemeProperty; 24 | if (!string.IsNullOrEmpty(SchemeProperty.Ref)) 25 | { 26 | if (!SchemeModel.References.TryGetValue(SchemeProperty.Ref, out objectScheme)) 27 | { 28 |
29 | Ref not found. 30 | Cannot render properties. 31 |
32 | } 33 | } 34 | 35 | if (objectScheme?.Properties != null) 36 | { 37 | 38 | foreach (var (propertyName, lookupSchemaProperty) in objectScheme.Properties) 39 | { 40 | var propertySchema = Configuration.ResolveReference(SchemeModel, lookupSchemaProperty); 41 | var componentType = Configuration.GetElement(SchemeModel, propertySchema); 42 | // If no component type found, skip or show a message 43 | if (componentType is null) 44 | { 45 |
46 | No renderer for property '@propertyName' of type '@propertySchema.Type' 47 |
48 | continue; 49 | } 50 | 51 | var callback = EventCallback.Factory.Create 52 | (this, (object? newVal) => 53 | { 54 | // Your update logic here 55 | dict[propertyName] = newVal; 56 | }); 57 | var parameters = new Dictionary 58 | { 59 | ["SchemeProperty"] = propertySchema, 60 | ["SchemeModel"] = SchemeModel, 61 | ["PropertyName"] = propertyName, 62 | ["IsFirst"] = false, 63 | ["Value"] = dict[propertyName], 64 | ["ValueChanged"] = callback 65 | }; 66 | var grid = propertySchema.Attributes?.OfType().FirstOrDefault() ?? new GridAttribute(12); 67 | 68 | var size = "col-md-" + grid.Size; 69 |
70 | 71 |
72 | 73 | 74 | } 75 | 76 | } 77 | else 78 | { 79 |
80 | Properties not found. 81 |
82 | } 83 | } 84 | } 85 |
86 |
87 | @code { 88 | private bool _status = false; 89 | 90 | private BoxAttribute _boxAttribute; 91 | 92 | protected override async Task OnParametersSetAsync() 93 | { 94 | _boxAttribute = SchemeProperty.Attributes?.OfType().FirstOrDefault() ?? BoxAttribute.Instance; 95 | } 96 | 97 | } 98 | -------------------------------------------------------------------------------- /BlazorDynamicFormTest/BlazorDynamicFormTest.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 17 4 | VisualStudioVersion = 17.6.33723.286 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BlazorDynamicFormTest.Server", "Server\BlazorDynamicFormTest.Server.csproj", "{93DC7582-4982-4CEB-B1B8-355FA0B3EEAF}" 7 | EndProject 8 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BlazorDynamicFormTest.Client", "Client\BlazorDynamicFormTest.Client.csproj", "{0108E0B9-37EE-4A6F-9296-995CD387C41C}" 9 | EndProject 10 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BlazorDynamicFormTest.Shared", "Shared\BlazorDynamicFormTest.Shared.csproj", "{D11FC3AA-2BA8-4CE1-A394-A55C80317227}" 11 | EndProject 12 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BlazorDynamicForm", "..\BlazorDynamicForm\BlazorDynamicForm.csproj", "{992A9CFF-6BEC-43EF-85CF-EF14684CC7F0}" 13 | EndProject 14 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TypeAnnotationParser", "..\..\TypeAnnotationParser\TypeAnnotationParser\TypeAnnotationParser.csproj", "{A1086B4E-C0CE-43AF-8706-817FBDADC6B3}" 15 | EndProject 16 | Global 17 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 18 | Debug|Any CPU = Debug|Any CPU 19 | Release|Any CPU = Release|Any CPU 20 | EndGlobalSection 21 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 22 | {93DC7582-4982-4CEB-B1B8-355FA0B3EEAF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 23 | {93DC7582-4982-4CEB-B1B8-355FA0B3EEAF}.Debug|Any CPU.Build.0 = Debug|Any CPU 24 | {93DC7582-4982-4CEB-B1B8-355FA0B3EEAF}.Release|Any CPU.ActiveCfg = Release|Any CPU 25 | {93DC7582-4982-4CEB-B1B8-355FA0B3EEAF}.Release|Any CPU.Build.0 = Release|Any CPU 26 | {0108E0B9-37EE-4A6F-9296-995CD387C41C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 27 | {0108E0B9-37EE-4A6F-9296-995CD387C41C}.Debug|Any CPU.Build.0 = Debug|Any CPU 28 | {0108E0B9-37EE-4A6F-9296-995CD387C41C}.Release|Any CPU.ActiveCfg = Release|Any CPU 29 | {0108E0B9-37EE-4A6F-9296-995CD387C41C}.Release|Any CPU.Build.0 = Release|Any CPU 30 | {D11FC3AA-2BA8-4CE1-A394-A55C80317227}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 31 | {D11FC3AA-2BA8-4CE1-A394-A55C80317227}.Debug|Any CPU.Build.0 = Debug|Any CPU 32 | {D11FC3AA-2BA8-4CE1-A394-A55C80317227}.Release|Any CPU.ActiveCfg = Release|Any CPU 33 | {D11FC3AA-2BA8-4CE1-A394-A55C80317227}.Release|Any CPU.Build.0 = Release|Any CPU 34 | {992A9CFF-6BEC-43EF-85CF-EF14684CC7F0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 35 | {992A9CFF-6BEC-43EF-85CF-EF14684CC7F0}.Debug|Any CPU.Build.0 = Debug|Any CPU 36 | {992A9CFF-6BEC-43EF-85CF-EF14684CC7F0}.Release|Any CPU.ActiveCfg = Release|Any CPU 37 | {992A9CFF-6BEC-43EF-85CF-EF14684CC7F0}.Release|Any CPU.Build.0 = Release|Any CPU 38 | {A1086B4E-C0CE-43AF-8706-817FBDADC6B3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 39 | {A1086B4E-C0CE-43AF-8706-817FBDADC6B3}.Debug|Any CPU.Build.0 = Debug|Any CPU 40 | {A1086B4E-C0CE-43AF-8706-817FBDADC6B3}.Release|Any CPU.ActiveCfg = Release|Any CPU 41 | {A1086B4E-C0CE-43AF-8706-817FBDADC6B3}.Release|Any CPU.Build.0 = Release|Any CPU 42 | EndGlobalSection 43 | GlobalSection(SolutionProperties) = preSolution 44 | HideSolutionNode = FALSE 45 | EndGlobalSection 46 | GlobalSection(ExtensibilityGlobals) = postSolution 47 | SolutionGuid = {67F5FF38-609E-41D3-8723-78F7F8FA6F1F} 48 | EndGlobalSection 49 | EndGlobal 50 | -------------------------------------------------------------------------------- /BlazorDynamicForm/Components/FileComponent.razor: -------------------------------------------------------------------------------- 1 | @using BlazorDynamicForm.Attributes 2 | @using Microsoft.AspNetCore.Components.Forms 3 | @using Microsoft.Extensions.Logging 4 | @using Microsoft.AspNetCore.Components.Forms 5 | @using Microsoft.Extensions.Logging 6 | @inherits BlazorDynamicForm.Core.FormComponentBase 7 | @inject ILogger Logger 8 | 9 |
10 | 11 |
12 |
13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | @if (isLoading) 22 | { 23 | 24 | 25 | 26 | } 27 |
28 | @if (error != null) 29 | { 30 |
31 | Error: @error 32 |
33 | } 34 |
35 |
36 | @code { 37 | private CancellationTokenSource? cts; 38 | long maxFileSize = 1024 * 1024 * 15; 39 | private bool isLoading; 40 | private string? error; 41 | 42 | 43 | private async Task LoadFiles(InputFileChangeEventArgs e) 44 | { 45 | isLoading = true; 46 | error = null; 47 | UpdateState(); 48 | 49 | cts = new CancellationTokenSource(); 50 | var cancellationToken = cts.Token; 51 | 52 | foreach (var file in e.GetMultipleFiles(1)) 53 | { 54 | try 55 | { 56 | using var stream = file.OpenReadStream(maxFileSize, cancellationToken); 57 | if (stream == null) 58 | { 59 | error = "Failed to obtain a readable stream from the file."; 60 | Logger.LogError("Stream is null for file: {FileName}", file.Name); 61 | continue; 62 | } 63 | 64 | await using var memoryStream = new MemoryStream(); 65 | await stream.CopyToAsync(memoryStream, cancellationToken); 66 | memoryStream.Position = 0; // Reset position after copy 67 | var data = new FileData { Data = Convert.ToBase64String(memoryStream.ToArray()), Name = file.Name, ContentType = file.ContentType }; 68 | Value = data; 69 | } 70 | catch (OperationCanceledException) 71 | { 72 | error = "Loading canceled by user."; 73 | Logger.LogWarning("File loading was canceled by user."); 74 | } 75 | catch (Exception ex) 76 | { 77 | error = $"Failed to load file: {ex.Message}"; 78 | Logger.LogError("File: {FileName} Error: {Error}", file.Name, ex.Message); 79 | } 80 | finally 81 | { 82 | isLoading = false; 83 | UpdateState(); 84 | } 85 | } 86 | 87 | 88 | } 89 | 90 | private void UpdateState() 91 | { 92 | InvokeAsync(StateHasChanged); 93 | } 94 | } -------------------------------------------------------------------------------- /BlazorDynamicFormTest/Client/wwwroot/css/open-iconic/README.md: -------------------------------------------------------------------------------- 1 | [Open Iconic v1.1.1](https://github.com/iconic/open-iconic) 2 | =========== 3 | 4 | ### Open Iconic is the open source sibling of [Iconic](https://github.com/iconic/open-iconic). It is a hyper-legible collection of 223 icons with a tiny footprint—ready to use with Bootstrap and Foundation. [View the collection](https://github.com/iconic/open-iconic) 5 | 6 | 7 | 8 | ## What's in Open Iconic? 9 | 10 | * 223 icons designed to be legible down to 8 pixels 11 | * Super-light SVG files - 61.8 for the entire set 12 | * SVG sprite—the modern replacement for icon fonts 13 | * Webfont (EOT, OTF, SVG, TTF, WOFF), PNG and WebP formats 14 | * Webfont stylesheets (including versions for Bootstrap and Foundation) in CSS, LESS, SCSS and Stylus formats 15 | * PNG and WebP raster images in 8px, 16px, 24px, 32px, 48px and 64px. 16 | 17 | 18 | ## Getting Started 19 | 20 | #### For code samples and everything else you need to get started with Open Iconic, check out our [Icons](https://github.com/iconic/open-iconic) and [Reference](https://github.com/iconic/open-iconic) sections. 21 | 22 | ### General Usage 23 | 24 | #### Using Open Iconic's SVGs 25 | 26 | We like SVGs and we think they're the way to display icons on the web. Since Open Iconic are just basic SVGs, we suggest you display them like you would any other image (don't forget the `alt` attribute). 27 | 28 | ``` 29 | icon name 30 | ``` 31 | 32 | #### Using Open Iconic's SVG Sprite 33 | 34 | Open Iconic also comes in a SVG sprite which allows you to display all the icons in the set with a single request. It's like an icon font, without being a hack. 35 | 36 | Adding an icon from an SVG sprite is a little different than what you're used to, but it's still a piece of cake. *Tip: To make your icons easily style able, we suggest adding a general class to the* `` *tag and a unique class name for each different icon in the* `` *tag.* 37 | 38 | ``` 39 | 40 | 41 | 42 | ``` 43 | 44 | Sizing icons only needs basic CSS. All the icons are in a square format, so just set the `` tag with equal width and height dimensions. 45 | 46 | ``` 47 | .icon { 48 | width: 16px; 49 | height: 16px; 50 | } 51 | ``` 52 | 53 | Coloring icons is even easier. All you need to do is set the `fill` rule on the `` tag. 54 | 55 | ``` 56 | .icon-account-login { 57 | fill: #f00; 58 | } 59 | ``` 60 | 61 | To learn more about SVG Sprites, read [Chris Coyier's guide](http://css-tricks.com/svg-sprites-use-better-icon-fonts/). 62 | 63 | #### Using Open Iconic's Icon Font... 64 | 65 | 66 | ##### …with Bootstrap 67 | 68 | You can find our Bootstrap stylesheets in `font/css/open-iconic-bootstrap.{css, less, scss, styl}` 69 | 70 | 71 | ``` 72 | 73 | ``` 74 | 75 | 76 | ``` 77 | 78 | ``` 79 | 80 | ##### …with Foundation 81 | 82 | You can find our Foundation stylesheets in `font/css/open-iconic-foundation.{css, less, scss, styl}` 83 | 84 | ``` 85 | 86 | ``` 87 | 88 | 89 | ``` 90 | 91 | ``` 92 | 93 | ##### …on its own 94 | 95 | You can find our default stylesheets in `font/css/open-iconic.{css, less, scss, styl}` 96 | 97 | ``` 98 | 99 | ``` 100 | 101 | ``` 102 | 103 | ``` 104 | 105 | 106 | ## License 107 | 108 | ### Icons 109 | 110 | All code (including SVG markup) is under the [MIT License](http://opensource.org/licenses/MIT). 111 | 112 | ### Fonts 113 | 114 | All fonts are under the [SIL Licensed](http://scripts.sil.org/cms/scripts/page.php?item_id=OFL_web). 115 | -------------------------------------------------------------------------------- /BlazorDynamicFormTest/Client/wwwroot/css/open-iconic/FONT-LICENSE: -------------------------------------------------------------------------------- 1 | SIL OPEN FONT LICENSE Version 1.1 2 | 3 | Copyright (c) 2014 Waybury 4 | 5 | PREAMBLE 6 | The goals of the Open Font License (OFL) are to stimulate worldwide 7 | development of collaborative font projects, to support the font creation 8 | efforts of academic and linguistic communities, and to provide a free and 9 | open framework in which fonts may be shared and improved in partnership 10 | with others. 11 | 12 | The OFL allows the licensed fonts to be used, studied, modified and 13 | redistributed freely as long as they are not sold by themselves. The 14 | fonts, including any derivative works, can be bundled, embedded, 15 | redistributed and/or sold with any software provided that any reserved 16 | names are not used by derivative works. The fonts and derivatives, 17 | however, cannot be released under any other type of license. The 18 | requirement for fonts to remain under this license does not apply 19 | to any document created using the fonts or their derivatives. 20 | 21 | DEFINITIONS 22 | "Font Software" refers to the set of files released by the Copyright 23 | Holder(s) under this license and clearly marked as such. This may 24 | include source files, build scripts and documentation. 25 | 26 | "Reserved Font Name" refers to any names specified as such after the 27 | copyright statement(s). 28 | 29 | "Original Version" refers to the collection of Font Software components as 30 | distributed by the Copyright Holder(s). 31 | 32 | "Modified Version" refers to any derivative made by adding to, deleting, 33 | or substituting -- in part or in whole -- any of the components of the 34 | Original Version, by changing formats or by porting the Font Software to a 35 | new environment. 36 | 37 | "Author" refers to any designer, engineer, programmer, technical 38 | writer or other person who contributed to the Font Software. 39 | 40 | PERMISSION & CONDITIONS 41 | Permission is hereby granted, free of charge, to any person obtaining 42 | a copy of the Font Software, to use, study, copy, merge, embed, modify, 43 | redistribute, and sell modified and unmodified copies of the Font 44 | Software, subject to the following conditions: 45 | 46 | 1) Neither the Font Software nor any of its individual components, 47 | in Original or Modified Versions, may be sold by itself. 48 | 49 | 2) Original or Modified Versions of the Font Software may be bundled, 50 | redistributed and/or sold with any software, provided that each copy 51 | contains the above copyright notice and this license. These can be 52 | included either as stand-alone text files, human-readable headers or 53 | in the appropriate machine-readable metadata fields within text or 54 | binary files as long as those fields can be easily viewed by the user. 55 | 56 | 3) No Modified Version of the Font Software may use the Reserved Font 57 | Name(s) unless explicit written permission is granted by the corresponding 58 | Copyright Holder. This restriction only applies to the primary font name as 59 | presented to the users. 60 | 61 | 4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font 62 | Software shall not be used to promote, endorse or advertise any 63 | Modified Version, except to acknowledge the contribution(s) of the 64 | Copyright Holder(s) and the Author(s) or with their explicit written 65 | permission. 66 | 67 | 5) The Font Software, modified or unmodified, in part or in whole, 68 | must be distributed entirely under this license, and must not be 69 | distributed under any other license. The requirement for fonts to 70 | remain under this license does not apply to any document created 71 | using the Font Software. 72 | 73 | TERMINATION 74 | This license becomes null and void if any of the above conditions are 75 | not met. 76 | 77 | DISCLAIMER 78 | THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 79 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF 80 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT 81 | OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE 82 | COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 83 | INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL 84 | DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 85 | FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM 86 | OTHER DEALINGS IN THE FONT SOFTWARE. 87 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | We as members, contributors, and leaders pledge to make participation in our 6 | community a harassment-free experience for everyone, regardless of age, body 7 | size, visible or invisible disability, ethnicity, sex characteristics, gender 8 | identity and expression, level of experience, education, socio-economic status, 9 | nationality, personal appearance, race, religion, or sexual identity 10 | and orientation. 11 | 12 | We pledge to act and interact in ways that contribute to an open, welcoming, 13 | diverse, inclusive, and healthy community. 14 | 15 | ## Our Standards 16 | 17 | Examples of behavior that contributes to a positive environment for our 18 | community include: 19 | 20 | * Demonstrating empathy and kindness toward other people 21 | * Being respectful of differing opinions, viewpoints, and experiences 22 | * Giving and gracefully accepting constructive feedback 23 | * Accepting responsibility and apologizing to those affected by our mistakes, 24 | and learning from the experience 25 | * Focusing on what is best not just for us as individuals, but for the 26 | overall community 27 | 28 | Examples of unacceptable behavior include: 29 | 30 | * The use of sexualized language or imagery, and sexual attention or 31 | advances of any kind 32 | * Trolling, insulting or derogatory comments, and personal or political attacks 33 | * Public or private harassment 34 | * Publishing others' private information, such as a physical or email 35 | address, without their explicit permission 36 | * Other conduct which could reasonably be considered inappropriate in a 37 | professional setting 38 | 39 | ## Enforcement Responsibilities 40 | 41 | Community leaders are responsible for clarifying and enforcing our standards of 42 | acceptable behavior and will take appropriate and fair corrective action in 43 | response to any behavior that they deem inappropriate, threatening, offensive, 44 | or harmful. 45 | 46 | Community leaders have the right and responsibility to remove, edit, or reject 47 | comments, commits, code, wiki edits, issues, and other contributions that are 48 | not aligned to this Code of Conduct, and will communicate reasons for moderation 49 | decisions when appropriate. 50 | 51 | ## Scope 52 | 53 | This Code of Conduct applies within all community spaces, and also applies when 54 | an individual is officially representing the community in public spaces. 55 | Examples of representing our community include using an official e-mail address, 56 | posting via an official social media account, or acting as an appointed 57 | representative at an online or offline event. 58 | 59 | ## Enforcement 60 | 61 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 62 | reported to the community leaders responsible for enforcement at 63 | thesmallpixel@gmail.com. 64 | All complaints will be reviewed and investigated promptly and fairly. 65 | 66 | All community leaders are obligated to respect the privacy and security of the 67 | reporter of any incident. 68 | 69 | ## Enforcement Guidelines 70 | 71 | Community leaders will follow these Community Impact Guidelines in determining 72 | the consequences for any action they deem in violation of this Code of Conduct: 73 | 74 | ### 1. Correction 75 | 76 | **Community Impact**: Use of inappropriate language or other behavior deemed 77 | unprofessional or unwelcome in the community. 78 | 79 | **Consequence**: A private, written warning from community leaders, providing 80 | clarity around the nature of the violation and an explanation of why the 81 | behavior was inappropriate. A public apology may be requested. 82 | 83 | ### 2. Warning 84 | 85 | **Community Impact**: A violation through a single incident or series 86 | of actions. 87 | 88 | **Consequence**: A warning with consequences for continued behavior. No 89 | interaction with the people involved, including unsolicited interaction with 90 | those enforcing the Code of Conduct, for a specified period of time. This 91 | includes avoiding interactions in community spaces as well as external channels 92 | like social media. Violating these terms may lead to a temporary or 93 | permanent ban. 94 | 95 | ### 3. Temporary Ban 96 | 97 | **Community Impact**: A serious violation of community standards, including 98 | sustained inappropriate behavior. 99 | 100 | **Consequence**: A temporary ban from any sort of interaction or public 101 | communication with the community for a specified period of time. No public or 102 | private interaction with the people involved, including unsolicited interaction 103 | with those enforcing the Code of Conduct, is allowed during this period. 104 | Violating these terms may lead to a permanent ban. 105 | 106 | ### 4. Permanent Ban 107 | 108 | **Community Impact**: Demonstrating a pattern of violation of community 109 | standards, including sustained inappropriate behavior, harassment of an 110 | individual, or aggression toward or disparagement of classes of individuals. 111 | 112 | **Consequence**: A permanent ban from any sort of public interaction within 113 | the community. 114 | 115 | ## Attribution 116 | 117 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], 118 | version 2.0, available at 119 | https://www.contributor-covenant.org/version/2/0/code_of_conduct.html. 120 | 121 | Community Impact Guidelines were inspired by [Mozilla's code of conduct 122 | enforcement ladder](https://github.com/mozilla/diversity). 123 | 124 | [homepage]: https://www.contributor-covenant.org 125 | 126 | For answers to common questions about this code of conduct, see the FAQ at 127 | https://www.contributor-covenant.org/faq. Translations are available at 128 | https://www.contributor-covenant.org/translations. 129 | -------------------------------------------------------------------------------- /BlazorDynamicForm/Components/DictionaryComponent.razor: -------------------------------------------------------------------------------- 1 | @inherits BlazorDynamicForm.Core.FormComponentBase 2 | @inject DynamicFormConfiguration Configuration 3 | @using BlazorDynamicForm.Core 4 | @using Microsoft.AspNetCore.Components.Forms 5 | 6 |
7 |
8 | 9 | 10 | 11 | 12 | 13 | @PropertyName 14 | 19 |
20 |
21 | @if (Value != null) 22 | { 23 | foreach (var item in TypedValue) 24 | { 25 | var itemValuePair = item; 26 | string currentKey = item.Key; 27 | if (!_elementStatus.ContainsKey(currentKey)) 28 | { 29 | _elementStatus.Add(currentKey, false); 30 | } 31 |
32 |
33 |
34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | @if (_elementStatus[currentKey]) 42 | { 43 | 44 | 45 | } 46 | else 47 | { 48 | 49 | } 50 | 56 |
57 |
58 |
59 | @if (_elementStatus[currentKey]) 60 | { 61 |
62 |
63 | @{ 64 | var itemProperty = SchemeProperty.Indices?.Last(); 65 | var componentType = Configuration.GetElement(SchemeModel, itemProperty); 66 | var callback = EventCallback.Factory.Create(this, (object? newVal) => 67 | { 68 | // Your update logic here 69 | TypedValue[item.Key] = newVal; 70 | }); 71 | var parameters = new Dictionary 72 | { 73 | ["SchemeProperty"] = itemProperty, 74 | ["SchemeModel"] = SchemeModel, 75 | ["PropertyName"] = itemProperty.Name, 76 | ["IsFirst"] = true, 77 | ["Value"] = TypedValue[item.Key], 78 | ["ValueChanged"] = callback 79 | }; 80 | } 81 | 82 |
83 |
84 | } 85 | } 86 | } 87 | 88 | @code { 89 | 90 | private Dictionary _elementStatus = new(); 91 | 92 | private IDictionary TypedValue 93 | { 94 | get => (IDictionary)Value ?? new Dictionary(); 95 | set => Value = value; 96 | } 97 | private void Open(string key) 98 | { 99 | 100 | _elementStatus[key] = true; 101 | } 102 | 103 | private void Close(string key) 104 | { 105 | _elementStatus[key] = false; 106 | } 107 | 108 | private void UpdateKey(string oldKey, string newKey) 109 | { 110 | if (TypedValue.ContainsKey(newKey)) return; 111 | var value = TypedValue[oldKey]; 112 | TypedValue.Remove(oldKey); 113 | TypedValue[newKey] = value; 114 | } 115 | 116 | 117 | private void RemoveItem(string key) 118 | { 119 | TypedValue.Remove(key); 120 | } 121 | 122 | private void AddItem() 123 | { 124 | var itemProperty = SchemeProperty.Indices.Single(); 125 | var item = SchemeModel.CreateOrValidateData(itemProperty, null); 126 | if (item != null) 127 | { 128 | TypedValue.Add(Guid.NewGuid().ToString(), item); 129 | } 130 | } 131 | } -------------------------------------------------------------------------------- /BlazorDynamicForm/Components/ListComponent.razor: -------------------------------------------------------------------------------- 1 | @using BlazorDynamicForm.Core 2 | @using Microsoft.AspNetCore.Components.Forms 3 | @inject DynamicFormConfiguration Configuration 4 | @inherits BlazorDynamicForm.Core.FormComponentBase 5 |
6 |
7 |
8 | 9 | 10 | 11 | 12 | 13 | 14 | @PropertyName 15 | 16 | 21 |
22 |
23 | 24 | @{ 25 | 26 | var indexScheme = SchemeProperty.Indices.SingleOrDefault(); 27 | if (indexScheme is null) 28 | { 29 |
30 | No itemProperty found in SchemeProperty.Indices. 31 |
32 | } 33 | else 34 | { 35 | var componentType = Configuration.GetElement(SchemeModel, indexScheme); 36 | if (componentType is null) 37 | { 38 |
39 | No renderer for property '@indexScheme' of type '@indexScheme.Type' 40 |
41 | } 42 | else 43 | { 44 | if (TypedValue.Count != 0) 45 | { 46 |
47 | @{ 48 | for (int i = 0; i < TypedValue.Count; i++) 49 | { 50 | var guid = Guid.NewGuid(); 51 | var callback = EventCallback.Factory.Create 52 | 53 | (this, (object? newVal) => 54 | { 55 | var index = TypedValue.IndexOf(ListReference[guid]); 56 | // Your update logic here 57 | ListReference[guid] = newVal; 58 | TypedValue[index] = newVal; 59 | 60 | }); 61 | ListReference[guid] = TypedValue[i]; 62 | var parameters = new Dictionary 63 | 64 | { 65 | ["SchemeProperty"] = indexScheme, 66 | ["SchemeModel"] = SchemeModel, 67 | ["PropertyName"] = $"Item {i}", 68 | ["IsFirst"] = false, 69 | ["Value"] = ListReference[guid], 70 | ["ValueChanged"] = callback 71 | }; 72 | 73 |
74 |
75 | 76 | 77 | 78 | 79 | 80 |
81 |
82 | 83 |
84 |
85 | 90 |
91 |
92 | @if (i + 1 < TypedValue.Count) 93 | { 94 |
95 | } 96 | } 97 | } 98 |
99 | } 100 | } 101 | } 102 | } 103 |
104 | 105 | 106 | @code { 107 | private Dictionary ListReference = new Dictionary(); 108 | private IList TypedValue 109 | { 110 | get => Value as IList ?? new List(); 111 | set => Value = value; 112 | } 113 | 114 | private void RemoveItem(Guid i) 115 | { 116 | var index = TypedValue.IndexOf(ListReference[i]); 117 | TypedValue.RemoveAt(index); 118 | 119 | } 120 | private void AddItem() 121 | { 122 | var itemProperty = SchemeProperty?.Indices?.SingleOrDefault(); 123 | if (itemProperty is null) 124 | { 125 | // Show a warning or do nothing if no item property 126 | return; 127 | } 128 | var item = SchemeModel.CreateOrValidateData(itemProperty, null); 129 | if (item is not null) 130 | { 131 | TypedValue.Add(item); 132 | } 133 | } 134 | } -------------------------------------------------------------------------------- /TypeAnnotationParser/TypeAnnotationParser.cs: -------------------------------------------------------------------------------- 1 | using System.Collections; 2 | using System.Reflection; 3 | 4 | namespace TypeAnnotationParser; 5 | 6 | public class TypeAnnotationParser(ParserConfiguration configuration) 7 | { 8 | public SchemeModel Parse() 9 | { 10 | var type = typeof(T); 11 | var form = new SchemeModel(); 12 | AddFormProperty(form, form, type); 13 | return form; 14 | } 15 | 16 | public SchemeModel Parse(Type type) 17 | { 18 | var form = new SchemeModel(); 19 | AddFormProperty(form, form, type); 20 | return form; 21 | } 22 | 23 | private string GetUniquePropertyKey(Type parentType) 24 | { 25 | return $"#{parentType.Name}"; 26 | } 27 | 28 | private SchemeProperty AddFormProperty(SchemeModel scheme, SchemeProperty schemeProperty, Type propertyType, uint depth = 0, PropertyInfo? propertyInfo = null) 29 | { 30 | depth += 1; 31 | if (depth >= 20) 32 | return schemeProperty; 33 | schemeProperty.Type = DeterminePropertyType(propertyType); 34 | 35 | //if (schemeProperty is not { Type: PropertyType.Object }) 36 | // schemeProperty.Name = propertyInfo != null ? propertyInfo.Name : propertyType.Name; 37 | if (schemeProperty.Type is PropertyType.Object) 38 | schemeProperty.Name = propertyType.Name; 39 | AssignAttributesToProperty(propertyType, schemeProperty); 40 | if (propertyInfo != null) 41 | AssignAttributesToProperty(propertyInfo, schemeProperty); 42 | 43 | if (schemeProperty.Type is PropertyType.Enum) 44 | { 45 | schemeProperty.Enum = Enum.GetNames(propertyType).ToList(); 46 | } 47 | 48 | switch (schemeProperty.Type) 49 | { 50 | case PropertyType.Object: 51 | { 52 | foreach (var propInfo in propertyType.GetProperties()) 53 | { 54 | var attributes = propInfo.GetCustomAttributes(typeof(AttributeScheme), true).Cast().ToList(); 55 | var isSpecial = (attributes.Any()); 56 | var keyProp = GetUniquePropertyKey(propInfo.PropertyType); 57 | if (isSpecial) 58 | { 59 | keyProp += $"#{schemeProperty.Name}"; 60 | } 61 | if (!scheme.References.TryGetValue(keyProp, out var objectPropertyScheme)) 62 | { 63 | if (DeterminePropertyType(propInfo.PropertyType) is PropertyType.Object) 64 | { 65 | scheme.References.TryAdd(keyProp, new SchemeProperty() { Type = PropertyType.Object }); 66 | } 67 | objectPropertyScheme = AddFormProperty(scheme, new SchemeProperty(), propInfo.PropertyType, depth, propInfo); 68 | if (objectPropertyScheme.Type is PropertyType.Object) 69 | scheme.References[keyProp] = objectPropertyScheme; 70 | } 71 | schemeProperty.Properties ??= new(); 72 | //add reference 73 | if (objectPropertyScheme.Type is PropertyType.Object) 74 | { 75 | schemeProperty.Properties.Add(propInfo.Name, new SchemeProperty() { Ref = keyProp }); 76 | } 77 | else 78 | { 79 | schemeProperty.Properties.Add(propInfo.Name, objectPropertyScheme); 80 | } 81 | 82 | } 83 | break; 84 | } 85 | case PropertyType.Array or PropertyType.Dictionary: 86 | { 87 | var elementTypes = GetBaseArrayType(propertyType); 88 | foreach (var indexType in elementTypes) 89 | { 90 | var keyProp = GetUniquePropertyKey(indexType); 91 | if (!scheme.References.TryGetValue(keyProp, out var objectPropertyScheme)) 92 | { 93 | 94 | objectPropertyScheme = AddFormProperty(scheme, new SchemeProperty(), indexType, depth); 95 | if (objectPropertyScheme.Type is PropertyType.Object) 96 | scheme.References.Add(keyProp, objectPropertyScheme); 97 | } 98 | schemeProperty.Indices ??= new(); 99 | if (objectPropertyScheme.Type is PropertyType.Object) 100 | { 101 | schemeProperty.Indices.Add(new SchemeProperty() { Ref = keyProp }); 102 | } 103 | else 104 | { 105 | schemeProperty.Indices.Add(objectPropertyScheme); 106 | } 107 | } 108 | break; 109 | } 110 | default: 111 | break; 112 | } 113 | 114 | 115 | return schemeProperty; 116 | } 117 | 118 | private void AssignAttributesToProperty(MemberInfo property, SchemeProperty schemeProperty) 119 | { 120 | var attributes = property.GetCustomAttributes(typeof(AttributeScheme), true).Cast().ToList(); 121 | if (!attributes.Any()) return; 122 | schemeProperty.Attributes ??= []; 123 | schemeProperty.Attributes.AddRange(attributes); 124 | } 125 | 126 | public PropertyType DeterminePropertyType(Type type) 127 | { 128 | if (type == null) 129 | throw new ArgumentNullException(nameof(type)); 130 | 131 | // Check for Enum first because enums are treated as value types. 132 | if (type.IsEnum) 133 | return PropertyType.Enum; 134 | 135 | if (type == typeof(bool)) 136 | return PropertyType.Boolean; 137 | 138 | // Handle the string type. 139 | if (type == typeof(string)) 140 | return PropertyType.String; 141 | 142 | // Check for specific numeric types. 143 | if (type == typeof(float)) 144 | return PropertyType.Float; 145 | 146 | if (type == typeof(double)) 147 | return PropertyType.Double; 148 | 149 | if (type == typeof(decimal)) 150 | return PropertyType.Decimal; 151 | 152 | // Check for various integer types. 153 | if (type == typeof(byte) || type == typeof(sbyte) || 154 | type == typeof(short) || type == typeof(ushort) || 155 | type == typeof(int) || type == typeof(uint) || 156 | type == typeof(long) || type == typeof(ulong)) 157 | { 158 | return PropertyType.Integer; 159 | } 160 | 161 | // Arrays and collections: 162 | // Check if the type is an array or implements IList. 163 | if (type.IsArray || typeof(IList).IsAssignableFrom(type)) 164 | return PropertyType.Array; 165 | 166 | // Check if the type implements IDictionary. 167 | if (typeof(IDictionary).IsAssignableFrom(type)) 168 | return PropertyType.Dictionary; 169 | 170 | // Fallback to object for any other type. 171 | return PropertyType.Object; 172 | } 173 | 174 | private Type[] GetBaseArrayType(Type type) 175 | { 176 | if (type.IsArray) 177 | { 178 | return [type.GetElementType()!]; 179 | } 180 | 181 | if (typeof(IEnumerable).IsAssignableFrom(type) || typeof(IDictionary).IsAssignableFrom(type)) 182 | { 183 | if (type.IsGenericType) 184 | { 185 | return type.GetGenericArguments(); 186 | } 187 | return Array.Empty(); 188 | } 189 | return Array.Empty(); 190 | } 191 | } -------------------------------------------------------------------------------- /BlazorDynamicFormTest/Client/Properties/Pages/Form.razor: -------------------------------------------------------------------------------- 1 | @page "/" 2 | @using System.Dynamic 3 | @using BlazorDynamicForm.Attributes 4 | @using BlazorDynamicForm.Components 5 | @using Newtonsoft.Json 6 | @using Newtonsoft.Json.Converters 7 | @using TypeAnnotationParser 8 | @using TypeAnnotationParser.Serialization 9 | 10 | @if (_scheme is not null) 11 | { 12 | 13 | 14 |
15 | 16 |
17 |
18 |
19 | } 20 | 21 | @if (!string.IsNullOrEmpty(SerializedJson)) 22 | { 23 |
@SerializedJson
24 | } 25 | 26 | @code { 27 | 28 | void OnValidResult(IDictionary? data) 29 | { 30 | if (data is not null) 31 | { 32 | // Serialize using Newtonsoft.Json 33 | SerializedJson = JsonConvert.SerializeObject(data, Formatting.Indented); 34 | Console.WriteLine(SerializedJson); 35 | } 36 | else 37 | { 38 | SerializedJson = "Form returned null data."; 39 | } 40 | } 41 | 42 | private SchemeModel? _scheme; 43 | // ExpandoObject? data; 44 | public string? SerializedJson = null; 45 | public class Cube 46 | { 47 | [SelectBox(["1", "2"])] 48 | public int Value { get; set; } 49 | } 50 | 51 | public enum Colors 52 | { 53 | Black, 54 | Red, 55 | Yellow 56 | }; 57 | public class Test 58 | { 59 | [MultipleSelect("Italian", "Mandarino", "Ananas")] 60 | public string TTS { get; set; } 61 | 62 | [CodeEditor("chsarp")] 63 | public string Name { get; set; } 64 | 65 | [TextArea()] 66 | public string Message { get; set; } 67 | 68 | [Placeholder("This should be a prova")] 69 | public string Message2 { get; set; } 70 | 71 | public float Limit { get; set; } 72 | 73 | [Range(0, 100)] 74 | public int LimitInt { get; set; } 75 | 76 | [CodeEditor("javascript")] 77 | public string JSCode { get; set; } 78 | 79 | public decimal DecimalLimit { get; set; } 80 | 81 | public Colors Colors { get; set; } 82 | 83 | } 84 | 85 | public class SwitchTask 86 | { 87 | 88 | [Name("Variable"), Label("Variabile")] 89 | public string Variable { get; set; } 90 | 91 | 92 | [Name("Outputs"), Grid(6)] 93 | public List Outputs { get; set; } 94 | 95 | [Name("Outputs"), Grid(6)] 96 | public List OutputsDuo { get; set; } 97 | 98 | [Name("Outputs"), Grid(8)] 99 | public List Outputsthree { get; set; } 100 | 101 | [Name("Outputs"), Grid(4)] 102 | public List OutputsFour { get; set; } 103 | 104 | [Box(BoxAttribute.BoxVisibility.Visible)] 105 | public SwitchOutput Optiooons { get; set; } 106 | 107 | [Box(BoxAttribute.BoxVisibility.Visible)] 108 | public SwitchOutput OptiooonsDuo { get; set; } 109 | } 110 | public class SwitchOutput 111 | { 112 | [Name("Output"), Label("Output", LabelAttribute.LabelPosition.Inline)] 113 | public string OutputNameA { get; set; } 114 | 115 | [Box(BoxAttribute.BoxVisibility.None)] 116 | public List Options { get; set; } 117 | 118 | [Name("Is default")] 119 | public bool IsDefault 120 | { 121 | get; 122 | set; 123 | } 124 | } 125 | public class SwitchOption 126 | { 127 | [Name("Comparator Type"), MultipleSelect(new[] { "Equals", "Regex", "Contains", ">", "<" }), Grid(4)] 128 | public string ComparatorType { get; set; } 129 | [Name("Value"), Grid(8)] 130 | public string Value { get; set; } 131 | } 132 | private IDictionary _formData; 133 | protected override void OnParametersSet() 134 | { 135 | if (_scheme == null) 136 | { 137 | var config = new ParserConfiguration(); 138 | config.Attributes = new List() { }; 139 | var parser = new TypeAnnotationParser(config); 140 | var schme = Scheme.GetSchemeFromYaml(BlazorDynamicForm.Utility.DefaultComponents, customConfig); 141 | 142 | 143 | //_formData = new(); 144 | 145 | dynamic d = JsonConvert.DeserializeObject(jsonData, new ExpandoObjectConverter()); 146 | _formData = (IDictionary)d; 147 | if (_formData is null) 148 | { 149 | _formData = new Dictionary(); 150 | } 151 | _scheme = parser.Parse(); 152 | var schemeYaml = Scheme.GetYamlFromScheme(BlazorDynamicForm.Utility.DefaultComponents); 153 | Console.WriteLine(schemeYaml); 154 | } 155 | 156 | 157 | 158 | 159 | base.OnParametersSet(); 160 | } 161 | protected override async Task OnInitializedAsync() 162 | { 163 | await base.OnInitializedAsync(); 164 | } 165 | 166 | string jsonData = @"{ 167 | ""Variable"": ""Ciao"", 168 | ""Outputs"": [ 169 | { 170 | ""OutputName"": """", 171 | ""Options"": [ 172 | { 173 | ""ComparatorType"": ""Equals"", 174 | ""Value"": ""1"" 175 | }, 176 | { 177 | ""ComparatorType"": ""Equals"", 178 | ""Value"": ""2"" 179 | }, 180 | { 181 | ""ComparatorType"": ""Equals"", 182 | ""Value"": ""3"" 183 | } 184 | ], 185 | ""IsDefault"": true 186 | } 187 | ], 188 | ""OutputsDuo"": [ 189 | { 190 | ""OutputName"": """", 191 | ""Options"": [ 192 | { 193 | ""ComparatorType"": ""Equals"", 194 | ""Value"": ""nono"" 195 | } 196 | ], 197 | ""IsDefault"": false 198 | } 199 | ], 200 | ""Outputsthree"": [], 201 | ""OutputsFour"": [ 202 | { 203 | ""OutputName"": ""di"", 204 | ""Options"": [], 205 | ""IsDefault"": false 206 | }, 207 | { 208 | ""OutputName"": ""no"", 209 | ""Options"": [], 210 | ""IsDefault"": true 211 | } 212 | ] 213 | }"; 214 | string customConfig = "references:\r\n '#SwitchOption':\r\n name: SwitchOption\r\n type: Object\r\n properties:\r\n ComparatorType:\r\n type: String\r\n attributes:\r\n - !Name\r\n name: Comparator Type\r\n - !MultipleSelect\r\n options:\r\n - Equals\r\n - Regex\r\n - Contains\r\n - '>'\r\n - <\r\n Value:\r\n type: String\r\n attributes:\r\n - !Name\r\n name: Value\r\n '#Boolean':\r\n name: Boolean\r\n type: Object\r\n attributes:\r\n - !Name\r\n name: Is default\r\n '#SwitchOutput':\r\n name: SwitchOutput\r\n type: Object\r\n properties:\r\n OutputName:\r\n type: String\r\n attributes:\r\n - !Name\r\n name: Output\r\n Options:\r\n type: Array\r\n indices:\r\n - ref: '#SwitchOption'\r\n IsDefault:\r\n ref: '#Boolean'\r\nname: SwitchTask\r\ntype: Object\r\nproperties:\r\n Variable:\r\n type: String\r\n attributes:\r\n - !Name\r\n name: Variable\r\n Outputs:\r\n type: Array\r\n attributes:\r\n - !Name\r\n name: Outputs\r\n indices:\r\n - ref: '#SwitchOutput'\r\n"; 215 | } 216 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | ## 4 | ## Get latest from https://github.com/github/gitignore/blob/main/VisualStudio.gitignore 5 | 6 | # User-specific files 7 | *.rsuser 8 | *.suo 9 | *.user 10 | *.userosscache 11 | *.sln.docstates 12 | 13 | # User-specific files (MonoDevelop/Xamarin Studio) 14 | *.userprefs 15 | 16 | # Mono auto generated files 17 | mono_crash.* 18 | 19 | # Build results 20 | [Dd]ebug/ 21 | [Dd]ebugPublic/ 22 | [Rr]elease/ 23 | [Rr]eleases/ 24 | x64/ 25 | x86/ 26 | [Ww][Ii][Nn]32/ 27 | [Aa][Rr][Mm]/ 28 | [Aa][Rr][Mm]64/ 29 | bld/ 30 | [Bb]in/ 31 | [Oo]bj/ 32 | [Ll]og/ 33 | [Ll]ogs/ 34 | 35 | # Visual Studio 2015/2017 cache/options directory 36 | .vs/ 37 | # Uncomment if you have tasks that create the project's static files in wwwroot 38 | #wwwroot/ 39 | 40 | # Visual Studio 2017 auto generated files 41 | Generated\ Files/ 42 | 43 | # MSTest test Results 44 | [Tt]est[Rr]esult*/ 45 | [Bb]uild[Ll]og.* 46 | 47 | # NUnit 48 | *.VisualState.xml 49 | TestResult.xml 50 | nunit-*.xml 51 | 52 | # Build Results of an ATL Project 53 | [Dd]ebugPS/ 54 | [Rr]eleasePS/ 55 | dlldata.c 56 | 57 | # Benchmark Results 58 | BenchmarkDotNet.Artifacts/ 59 | 60 | # .NET Core 61 | project.lock.json 62 | project.fragment.lock.json 63 | artifacts/ 64 | 65 | # ASP.NET Scaffolding 66 | ScaffoldingReadMe.txt 67 | 68 | # StyleCop 69 | StyleCopReport.xml 70 | 71 | # Files built by Visual Studio 72 | *_i.c 73 | *_p.c 74 | *_h.h 75 | *.ilk 76 | *.meta 77 | *.obj 78 | *.iobj 79 | *.pch 80 | *.pdb 81 | *.ipdb 82 | *.pgc 83 | *.pgd 84 | *.rsp 85 | *.sbr 86 | *.tlb 87 | *.tli 88 | *.tlh 89 | *.tmp 90 | *.tmp_proj 91 | *_wpftmp.csproj 92 | *.log 93 | *.tlog 94 | *.vspscc 95 | *.vssscc 96 | .builds 97 | *.pidb 98 | *.svclog 99 | *.scc 100 | 101 | # Chutzpah Test files 102 | _Chutzpah* 103 | 104 | # Visual C++ cache files 105 | ipch/ 106 | *.aps 107 | *.ncb 108 | *.opendb 109 | *.opensdf 110 | *.sdf 111 | *.cachefile 112 | *.VC.db 113 | *.VC.VC.opendb 114 | 115 | # Visual Studio profiler 116 | *.psess 117 | *.vsp 118 | *.vspx 119 | *.sap 120 | 121 | # Visual Studio Trace Files 122 | *.e2e 123 | 124 | # TFS 2012 Local Workspace 125 | $tf/ 126 | 127 | # Guidance Automation Toolkit 128 | *.gpState 129 | 130 | # ReSharper is a .NET coding add-in 131 | _ReSharper*/ 132 | *.[Rr]e[Ss]harper 133 | *.DotSettings.user 134 | 135 | # TeamCity is a build add-in 136 | _TeamCity* 137 | 138 | # DotCover is a Code Coverage Tool 139 | *.dotCover 140 | 141 | # AxoCover is a Code Coverage Tool 142 | .axoCover/* 143 | !.axoCover/settings.json 144 | 145 | # Coverlet is a free, cross platform Code Coverage Tool 146 | coverage*.json 147 | coverage*.xml 148 | coverage*.info 149 | 150 | # Visual Studio code coverage results 151 | *.coverage 152 | *.coveragexml 153 | 154 | # NCrunch 155 | _NCrunch_* 156 | .*crunch*.local.xml 157 | nCrunchTemp_* 158 | 159 | # MightyMoose 160 | *.mm.* 161 | AutoTest.Net/ 162 | 163 | # Web workbench (sass) 164 | .sass-cache/ 165 | 166 | # Installshield output folder 167 | [Ee]xpress/ 168 | 169 | # DocProject is a documentation generator add-in 170 | DocProject/buildhelp/ 171 | DocProject/Help/*.HxT 172 | DocProject/Help/*.HxC 173 | DocProject/Help/*.hhc 174 | DocProject/Help/*.hhk 175 | DocProject/Help/*.hhp 176 | DocProject/Help/Html2 177 | DocProject/Help/html 178 | 179 | # Click-Once directory 180 | publish/ 181 | 182 | # Publish Web Output 183 | *.[Pp]ublish.xml 184 | *.azurePubxml 185 | # Note: Comment the next line if you want to checkin your web deploy settings, 186 | # but database connection strings (with potential passwords) will be unencrypted 187 | *.pubxml 188 | *.publishproj 189 | 190 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 191 | # checkin your Azure Web App publish settings, but sensitive information contained 192 | # in these scripts will be unencrypted 193 | PublishScripts/ 194 | 195 | # NuGet Packages 196 | *.nupkg 197 | # NuGet Symbol Packages 198 | *.snupkg 199 | # The packages folder can be ignored because of Package Restore 200 | **/[Pp]ackages/* 201 | # except build/, which is used as an MSBuild target. 202 | !**/[Pp]ackages/build/ 203 | # Uncomment if necessary however generally it will be regenerated when needed 204 | #!**/[Pp]ackages/repositories.config 205 | # NuGet v3's project.json files produces more ignorable files 206 | *.nuget.props 207 | *.nuget.targets 208 | 209 | # Microsoft Azure Build Output 210 | csx/ 211 | *.build.csdef 212 | 213 | # Microsoft Azure Emulator 214 | ecf/ 215 | rcf/ 216 | 217 | # Windows Store app package directories and files 218 | AppPackages/ 219 | BundleArtifacts/ 220 | Package.StoreAssociation.xml 221 | _pkginfo.txt 222 | *.appx 223 | *.appxbundle 224 | *.appxupload 225 | 226 | # Visual Studio cache files 227 | # files ending in .cache can be ignored 228 | *.[Cc]ache 229 | # but keep track of directories ending in .cache 230 | !?*.[Cc]ache/ 231 | 232 | # Others 233 | ClientBin/ 234 | ~$* 235 | *~ 236 | *.dbmdl 237 | *.dbproj.schemaview 238 | *.jfm 239 | *.pfx 240 | *.publishsettings 241 | orleans.codegen.cs 242 | 243 | # Including strong name files can present a security risk 244 | # (https://github.com/github/gitignore/pull/2483#issue-259490424) 245 | #*.snk 246 | 247 | # Since there are multiple workflows, uncomment next line to ignore bower_components 248 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 249 | #bower_components/ 250 | 251 | # RIA/Silverlight projects 252 | Generated_Code/ 253 | 254 | # Backup & report files from converting an old project file 255 | # to a newer Visual Studio version. Backup files are not needed, 256 | # because we have git ;-) 257 | _UpgradeReport_Files/ 258 | Backup*/ 259 | UpgradeLog*.XML 260 | UpgradeLog*.htm 261 | ServiceFabricBackup/ 262 | *.rptproj.bak 263 | 264 | # SQL Server files 265 | *.mdf 266 | *.ldf 267 | *.ndf 268 | 269 | # Business Intelligence projects 270 | *.rdl.data 271 | *.bim.layout 272 | *.bim_*.settings 273 | *.rptproj.rsuser 274 | *- [Bb]ackup.rdl 275 | *- [Bb]ackup ([0-9]).rdl 276 | *- [Bb]ackup ([0-9][0-9]).rdl 277 | 278 | # Microsoft Fakes 279 | FakesAssemblies/ 280 | 281 | # GhostDoc plugin setting file 282 | *.GhostDoc.xml 283 | 284 | # Node.js Tools for Visual Studio 285 | .ntvs_analysis.dat 286 | node_modules/ 287 | 288 | # Visual Studio 6 build log 289 | *.plg 290 | 291 | # Visual Studio 6 workspace options file 292 | *.opt 293 | 294 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) 295 | *.vbw 296 | 297 | # Visual Studio 6 auto-generated project file (contains which files were open etc.) 298 | *.vbp 299 | 300 | # Visual Studio 6 workspace and project file (working project files containing files to include in project) 301 | *.dsw 302 | *.dsp 303 | 304 | # Visual Studio 6 technical files 305 | *.ncb 306 | *.aps 307 | 308 | # Visual Studio LightSwitch build output 309 | **/*.HTMLClient/GeneratedArtifacts 310 | **/*.DesktopClient/GeneratedArtifacts 311 | **/*.DesktopClient/ModelManifest.xml 312 | **/*.Server/GeneratedArtifacts 313 | **/*.Server/ModelManifest.xml 314 | _Pvt_Extensions 315 | 316 | # Paket dependency manager 317 | .paket/paket.exe 318 | paket-files/ 319 | 320 | # FAKE - F# Make 321 | .fake/ 322 | 323 | # CodeRush personal settings 324 | .cr/personal 325 | 326 | # Python Tools for Visual Studio (PTVS) 327 | __pycache__/ 328 | *.pyc 329 | 330 | # Cake - Uncomment if you are using it 331 | # tools/** 332 | # !tools/packages.config 333 | 334 | # Tabs Studio 335 | *.tss 336 | 337 | # Telerik's JustMock configuration file 338 | *.jmconfig 339 | 340 | # BizTalk build output 341 | *.btp.cs 342 | *.btm.cs 343 | *.odx.cs 344 | *.xsd.cs 345 | 346 | # OpenCover UI analysis results 347 | OpenCover/ 348 | 349 | # Azure Stream Analytics local run output 350 | ASALocalRun/ 351 | 352 | # MSBuild Binary and Structured Log 353 | *.binlog 354 | 355 | # NVidia Nsight GPU debugger configuration file 356 | *.nvuser 357 | 358 | # MFractors (Xamarin productivity tool) working folder 359 | .mfractor/ 360 | 361 | # Local History for Visual Studio 362 | .localhistory/ 363 | 364 | # Visual Studio History (VSHistory) files 365 | .vshistory/ 366 | 367 | # BeatPulse healthcheck temp database 368 | healthchecksdb 369 | 370 | # Backup folder for Package Reference Convert tool in Visual Studio 2017 371 | MigrationBackup/ 372 | 373 | # Ionide (cross platform F# VS Code tools) working folder 374 | .ionide/ 375 | 376 | # Fody - auto-generated XML schema 377 | FodyWeavers.xsd 378 | 379 | # VS Code files for those working on multiple tools 380 | .vscode/* 381 | !.vscode/settings.json 382 | !.vscode/tasks.json 383 | !.vscode/launch.json 384 | !.vscode/extensions.json 385 | *.code-workspace 386 | 387 | # Local History for Visual Studio Code 388 | .history/ 389 | 390 | # Windows Installer files from build outputs 391 | *.cab 392 | *.msi 393 | *.msix 394 | *.msm 395 | *.msp 396 | 397 | # JetBrains Rider 398 | *.sln.iml 399 | -------------------------------------------------------------------------------- /BlazorDynamicFormTest/Client/wwwroot/css/open-iconic/font/css/open-iconic-bootstrap.min.css: -------------------------------------------------------------------------------- 1 | @font-face{font-family:Icons;src:url(../fonts/open-iconic.eot);src:url(../fonts/open-iconic.eot?#iconic-sm) format('embedded-opentype'),url(../fonts/open-iconic.woff) format('woff'),url(../fonts/open-iconic.ttf) format('truetype'),url(../fonts/open-iconic.otf) format('opentype'),url(../fonts/open-iconic.svg#iconic-sm) format('svg');font-weight:400;font-style:normal}.oi{position:relative;top:1px;display:inline-block;speak:none;font-family:Icons;font-style:normal;font-weight:400;line-height:1;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.oi:empty:before{width:1em;text-align:center;box-sizing:content-box}.oi.oi-align-center:before{text-align:center}.oi.oi-align-left:before{text-align:left}.oi.oi-align-right:before{text-align:right}.oi.oi-flip-horizontal:before{-webkit-transform:scale(-1,1);-ms-transform:scale(-1,1);transform:scale(-1,1)}.oi.oi-flip-vertical:before{-webkit-transform:scale(1,-1);-ms-transform:scale(-1,1);transform:scale(1,-1)}.oi.oi-flip-horizontal-vertical:before{-webkit-transform:scale(-1,-1);-ms-transform:scale(-1,1);transform:scale(-1,-1)}.oi-account-login:before{content:'\e000'}.oi-account-logout:before{content:'\e001'}.oi-action-redo:before{content:'\e002'}.oi-action-undo:before{content:'\e003'}.oi-align-center:before{content:'\e004'}.oi-align-left:before{content:'\e005'}.oi-align-right:before{content:'\e006'}.oi-aperture:before{content:'\e007'}.oi-arrow-bottom:before{content:'\e008'}.oi-arrow-circle-bottom:before{content:'\e009'}.oi-arrow-circle-left:before{content:'\e00a'}.oi-arrow-circle-right:before{content:'\e00b'}.oi-arrow-circle-top:before{content:'\e00c'}.oi-arrow-left:before{content:'\e00d'}.oi-arrow-right:before{content:'\e00e'}.oi-arrow-thick-bottom:before{content:'\e00f'}.oi-arrow-thick-left:before{content:'\e010'}.oi-arrow-thick-right:before{content:'\e011'}.oi-arrow-thick-top:before{content:'\e012'}.oi-arrow-top:before{content:'\e013'}.oi-audio-spectrum:before{content:'\e014'}.oi-audio:before{content:'\e015'}.oi-badge:before{content:'\e016'}.oi-ban:before{content:'\e017'}.oi-bar-chart:before{content:'\e018'}.oi-basket:before{content:'\e019'}.oi-battery-empty:before{content:'\e01a'}.oi-battery-full:before{content:'\e01b'}.oi-beaker:before{content:'\e01c'}.oi-bell:before{content:'\e01d'}.oi-bluetooth:before{content:'\e01e'}.oi-bold:before{content:'\e01f'}.oi-bolt:before{content:'\e020'}.oi-book:before{content:'\e021'}.oi-bookmark:before{content:'\e022'}.oi-box:before{content:'\e023'}.oi-briefcase:before{content:'\e024'}.oi-british-pound:before{content:'\e025'}.oi-browser:before{content:'\e026'}.oi-brush:before{content:'\e027'}.oi-bug:before{content:'\e028'}.oi-bullhorn:before{content:'\e029'}.oi-calculator:before{content:'\e02a'}.oi-calendar:before{content:'\e02b'}.oi-camera-slr:before{content:'\e02c'}.oi-caret-bottom:before{content:'\e02d'}.oi-caret-left:before{content:'\e02e'}.oi-caret-right:before{content:'\e02f'}.oi-caret-top:before{content:'\e030'}.oi-cart:before{content:'\e031'}.oi-chat:before{content:'\e032'}.oi-check:before{content:'\e033'}.oi-chevron-bottom:before{content:'\e034'}.oi-chevron-left:before{content:'\e035'}.oi-chevron-right:before{content:'\e036'}.oi-chevron-top:before{content:'\e037'}.oi-circle-check:before{content:'\e038'}.oi-circle-x:before{content:'\e039'}.oi-clipboard:before{content:'\e03a'}.oi-clock:before{content:'\e03b'}.oi-cloud-download:before{content:'\e03c'}.oi-cloud-upload:before{content:'\e03d'}.oi-cloud:before{content:'\e03e'}.oi-cloudy:before{content:'\e03f'}.oi-code:before{content:'\e040'}.oi-cog:before{content:'\e041'}.oi-collapse-down:before{content:'\e042'}.oi-collapse-left:before{content:'\e043'}.oi-collapse-right:before{content:'\e044'}.oi-collapse-up:before{content:'\e045'}.oi-command:before{content:'\e046'}.oi-comment-square:before{content:'\e047'}.oi-compass:before{content:'\e048'}.oi-contrast:before{content:'\e049'}.oi-copywriting:before{content:'\e04a'}.oi-credit-card:before{content:'\e04b'}.oi-crop:before{content:'\e04c'}.oi-dashboard:before{content:'\e04d'}.oi-data-transfer-download:before{content:'\e04e'}.oi-data-transfer-upload:before{content:'\e04f'}.oi-delete:before{content:'\e050'}.oi-dial:before{content:'\e051'}.oi-document:before{content:'\e052'}.oi-dollar:before{content:'\e053'}.oi-double-quote-sans-left:before{content:'\e054'}.oi-double-quote-sans-right:before{content:'\e055'}.oi-double-quote-serif-left:before{content:'\e056'}.oi-double-quote-serif-right:before{content:'\e057'}.oi-droplet:before{content:'\e058'}.oi-eject:before{content:'\e059'}.oi-elevator:before{content:'\e05a'}.oi-ellipses:before{content:'\e05b'}.oi-envelope-closed:before{content:'\e05c'}.oi-envelope-open:before{content:'\e05d'}.oi-euro:before{content:'\e05e'}.oi-excerpt:before{content:'\e05f'}.oi-expand-down:before{content:'\e060'}.oi-expand-left:before{content:'\e061'}.oi-expand-right:before{content:'\e062'}.oi-expand-up:before{content:'\e063'}.oi-external-link:before{content:'\e064'}.oi-eye:before{content:'\e065'}.oi-eyedropper:before{content:'\e066'}.oi-file:before{content:'\e067'}.oi-fire:before{content:'\e068'}.oi-flag:before{content:'\e069'}.oi-flash:before{content:'\e06a'}.oi-folder:before{content:'\e06b'}.oi-fork:before{content:'\e06c'}.oi-fullscreen-enter:before{content:'\e06d'}.oi-fullscreen-exit:before{content:'\e06e'}.oi-globe:before{content:'\e06f'}.oi-graph:before{content:'\e070'}.oi-grid-four-up:before{content:'\e071'}.oi-grid-three-up:before{content:'\e072'}.oi-grid-two-up:before{content:'\e073'}.oi-hard-drive:before{content:'\e074'}.oi-header:before{content:'\e075'}.oi-headphones:before{content:'\e076'}.oi-heart:before{content:'\e077'}.oi-home:before{content:'\e078'}.oi-image:before{content:'\e079'}.oi-inbox:before{content:'\e07a'}.oi-infinity:before{content:'\e07b'}.oi-info:before{content:'\e07c'}.oi-italic:before{content:'\e07d'}.oi-justify-center:before{content:'\e07e'}.oi-justify-left:before{content:'\e07f'}.oi-justify-right:before{content:'\e080'}.oi-key:before{content:'\e081'}.oi-laptop:before{content:'\e082'}.oi-layers:before{content:'\e083'}.oi-lightbulb:before{content:'\e084'}.oi-link-broken:before{content:'\e085'}.oi-link-intact:before{content:'\e086'}.oi-list-rich:before{content:'\e087'}.oi-list:before{content:'\e088'}.oi-location:before{content:'\e089'}.oi-lock-locked:before{content:'\e08a'}.oi-lock-unlocked:before{content:'\e08b'}.oi-loop-circular:before{content:'\e08c'}.oi-loop-square:before{content:'\e08d'}.oi-loop:before{content:'\e08e'}.oi-magnifying-glass:before{content:'\e08f'}.oi-map-marker:before{content:'\e090'}.oi-map:before{content:'\e091'}.oi-media-pause:before{content:'\e092'}.oi-media-play:before{content:'\e093'}.oi-media-record:before{content:'\e094'}.oi-media-skip-backward:before{content:'\e095'}.oi-media-skip-forward:before{content:'\e096'}.oi-media-step-backward:before{content:'\e097'}.oi-media-step-forward:before{content:'\e098'}.oi-media-stop:before{content:'\e099'}.oi-medical-cross:before{content:'\e09a'}.oi-menu:before{content:'\e09b'}.oi-microphone:before{content:'\e09c'}.oi-minus:before{content:'\e09d'}.oi-monitor:before{content:'\e09e'}.oi-moon:before{content:'\e09f'}.oi-move:before{content:'\e0a0'}.oi-musical-note:before{content:'\e0a1'}.oi-paperclip:before{content:'\e0a2'}.oi-pencil:before{content:'\e0a3'}.oi-people:before{content:'\e0a4'}.oi-person:before{content:'\e0a5'}.oi-phone:before{content:'\e0a6'}.oi-pie-chart:before{content:'\e0a7'}.oi-pin:before{content:'\e0a8'}.oi-play-circle:before{content:'\e0a9'}.oi-plus:before{content:'\e0aa'}.oi-power-standby:before{content:'\e0ab'}.oi-print:before{content:'\e0ac'}.oi-project:before{content:'\e0ad'}.oi-pulse:before{content:'\e0ae'}.oi-puzzle-piece:before{content:'\e0af'}.oi-question-mark:before{content:'\e0b0'}.oi-rain:before{content:'\e0b1'}.oi-random:before{content:'\e0b2'}.oi-reload:before{content:'\e0b3'}.oi-resize-both:before{content:'\e0b4'}.oi-resize-height:before{content:'\e0b5'}.oi-resize-width:before{content:'\e0b6'}.oi-rss-alt:before{content:'\e0b7'}.oi-rss:before{content:'\e0b8'}.oi-script:before{content:'\e0b9'}.oi-share-boxed:before{content:'\e0ba'}.oi-share:before{content:'\e0bb'}.oi-shield:before{content:'\e0bc'}.oi-signal:before{content:'\e0bd'}.oi-signpost:before{content:'\e0be'}.oi-sort-ascending:before{content:'\e0bf'}.oi-sort-descending:before{content:'\e0c0'}.oi-spreadsheet:before{content:'\e0c1'}.oi-star:before{content:'\e0c2'}.oi-sun:before{content:'\e0c3'}.oi-tablet:before{content:'\e0c4'}.oi-tag:before{content:'\e0c5'}.oi-tags:before{content:'\e0c6'}.oi-target:before{content:'\e0c7'}.oi-task:before{content:'\e0c8'}.oi-terminal:before{content:'\e0c9'}.oi-text:before{content:'\e0ca'}.oi-thumb-down:before{content:'\e0cb'}.oi-thumb-up:before{content:'\e0cc'}.oi-timer:before{content:'\e0cd'}.oi-transfer:before{content:'\e0ce'}.oi-trash:before{content:'\e0cf'}.oi-underline:before{content:'\e0d0'}.oi-vertical-align-bottom:before{content:'\e0d1'}.oi-vertical-align-center:before{content:'\e0d2'}.oi-vertical-align-top:before{content:'\e0d3'}.oi-video:before{content:'\e0d4'}.oi-volume-high:before{content:'\e0d5'}.oi-volume-low:before{content:'\e0d6'}.oi-volume-off:before{content:'\e0d7'}.oi-warning:before{content:'\e0d8'}.oi-wifi:before{content:'\e0d9'}.oi-wrench:before{content:'\e0da'}.oi-x:before{content:'\e0db'}.oi-yen:before{content:'\e0dc'}.oi-zoom-in:before{content:'\e0dd'}.oi-zoom-out:before{content:'\e0de'} -------------------------------------------------------------------------------- /TypeAnnotationParser/Generator/ObjectGenerator.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json.Linq; 2 | using TypeAnnotationParser; 3 | 4 | namespace BlazorDynamicForm 5 | { 6 | public static class ObjectGenerator 7 | { 8 | public static object? CreateOrValidateData(this SchemeModel definition, SchemeProperty prop, object? data, ObjectGeneratorOptions? options = null, int depth = 0) 9 | { 10 | options ??= new ObjectGeneratorOptions(); 11 | 12 | // Avoid infinite recursion 13 | if (depth >= options.MaxRecursiveDepth) 14 | return null; 15 | 16 | 17 | depth++; 18 | 19 | // If the property references something else, delegate to that 20 | if (!string.IsNullOrEmpty(prop.Ref)) 21 | { 22 | if (!definition.References.TryGetValue(prop.Ref, out var refProp)) 23 | throw new InvalidOperationException($"Reference '{prop.Ref}' not found in definition."); 24 | 25 | return definition.CreateOrValidateData(refProp, data, options, depth); 26 | } 27 | // If no 'Type' is specified, we can't validate 28 | if (prop.Type == null) 29 | throw new InvalidOperationException($"Type not specified in definition."); 30 | 31 | 32 | switch (prop.Type.Value) 33 | { 34 | case PropertyType.Boolean: 35 | return data is bool ? data : false; 36 | case PropertyType.Integer: 37 | // If 'data' is an integer, keep it; otherwise set default 38 | return data is int ? data : 0; 39 | 40 | case PropertyType.Double: 41 | return data is double ? data : 0.0; 42 | 43 | case PropertyType.Float: 44 | return data is float ? data : 0f; 45 | 46 | case PropertyType.Decimal: 47 | return data is decimal ? data : 0d; 48 | 49 | case PropertyType.Enum: 50 | return data is int ? data : 0; 51 | 52 | case PropertyType.String: 53 | // Force empty string if not a string 54 | return data is string s ? s : string.Empty; 55 | 56 | case PropertyType.Object: 57 | return EnsureObject(definition, prop, data, options, depth); 58 | 59 | case PropertyType.Array: 60 | return EnsureArray(definition, prop, data, options, depth); 61 | 62 | case PropertyType.Dictionary: 63 | return EnsureDictionary(definition, prop, data, options, depth); 64 | 65 | default: 66 | throw new ArgumentOutOfRangeException($"Unsupported property type: {prop.Type}"); 67 | } 68 | } 69 | /// 70 | /// Ensure 'data' is a Dictionary for an Object schema, 71 | /// and for each sub-property, validate or create data. 72 | /// 73 | private static object EnsureObject( 74 | SchemeModel definition, 75 | SchemeProperty prop, 76 | object? data, 77 | ObjectGeneratorOptions options, 78 | int depth 79 | ) 80 | { 81 | // Make sure we have a dictionary 82 | var dict = data as IDictionary; 83 | 84 | if (dict == null) 85 | { 86 | dict = new Dictionary(); 87 | } 88 | 89 | // If schema has sub-properties, ensure each is validated/created 90 | if (prop.Properties != null && depth < 10) 91 | { 92 | foreach (var (key, subProp) in prop.Properties) 93 | { 94 | object? existing = dict.TryGetValue(key, out var value) ? value : null; 95 | dict[key] = definition.CreateOrValidateData(subProp, existing, options, depth); 96 | } 97 | 98 | // Optionally remove extra fields not in the schema 99 | // if you want to disallow unknown properties: 100 | var keysToRemove = dict.Keys.Except(prop.Properties.Keys).ToList(); 101 | foreach (var extraKey in keysToRemove) 102 | { 103 | dict.Remove(extraKey); 104 | } 105 | } 106 | 107 | return dict; 108 | } 109 | /// 110 | /// Ensure 'data' is a List for an Array schema. 111 | /// Optionally create an initial item if CreateCollectionElement is true. 112 | /// 113 | private static object EnsureArray( 114 | SchemeModel definition, 115 | SchemeProperty prop, 116 | object? data, 117 | ObjectGeneratorOptions options, 118 | int depth 119 | ) 120 | { 121 | var list = data as List; 122 | 123 | if (list == null) 124 | { 125 | list = new List(); 126 | } 127 | 128 | // If you want to automatically create an element, 129 | // check if the schema has Indices or something describing the item type. 130 | // This snippet does not handle item-level schema because 131 | // the user can have multiple Indices or subprops. 132 | // Adapt as necessary. 133 | 134 | if (list.Count == 0 && options.CreateCollectionElement && prop.Indices != null && prop.Indices.Count > 0) 135 | { 136 | // Example: create one item based on the first index 137 | var firstItemProp = prop.Indices[0]; 138 | var itemData = definition.CreateOrValidateData(firstItemProp, null, options, depth); 139 | if (itemData != null) 140 | { 141 | list.Add(itemData); 142 | } 143 | } 144 | else 145 | { 146 | // Optionally validate each existing item in the list if you know the item schema 147 | // For example, if there's exactly one Indices item, you might do: 148 | 149 | if (prop.Indices != null && prop.Indices.Count == 1) 150 | { 151 | var itemProp = prop.Indices[0]; 152 | for (int i = 0; i < list.Count; i++) 153 | { 154 | list[i] = definition.CreateOrValidateData(itemProp, list[i], options, depth); 155 | } 156 | } 157 | 158 | } 159 | 160 | return list; 161 | } 162 | 163 | /// 164 | /// Ensure 'data' is a Dictionary for a Dictionary schema. 165 | /// The schema typically has 'Properties' describing the key/value types. 166 | /// 167 | private static object EnsureDictionary( 168 | SchemeModel definition, 169 | SchemeProperty prop, 170 | object? data, 171 | ObjectGeneratorOptions options, 172 | int depth 173 | ) 174 | { 175 | // We expect a dictionary of 176 | var dict = data as Dictionary; 177 | 178 | 179 | if (dict is null && data is JObject jobj) 180 | { 181 | dict = jobj.ToObject>(); 182 | 183 | } 184 | 185 | if (dict == null) 186 | { 187 | dict = new Dictionary(); 188 | } 189 | 190 | // If you want to auto-create one key-value pair 191 | // if none exists and options.CreateDictionaryElement == true: 192 | if (options.CreateDictionaryElement && dict.Count == 0 && prop.Properties != null && prop.Properties.Count == 2) 193 | { 194 | // Typically for dictionary, you have something like: 195 | // prop.Properties["key"] = 196 | // prop.Properties["value"] = 197 | // This is an example approach; your real schema might differ. 198 | 199 | var dicKeyProp = prop.Properties["key"]; 200 | var dicValueProp = prop.Properties["value"]; 201 | 202 | //object? newKey = definition.CreateOrValidateData(dicKeyProp, null, options, depth); 203 | object? newValue = definition.CreateOrValidateData(dicValueProp, null, options, depth); 204 | 205 | dict[Guid.NewGuid().ToString()] = newValue; 206 | 207 | } 208 | 209 | // If you want to validate each existing key-value pair by the "value" type 210 | // you'd do something like: 211 | 212 | if (prop.Properties != null && prop.Properties.TryGetValue("value", out var valueProp)) 213 | { 214 | var keys = dict.Keys.ToList(); 215 | foreach (var k in keys) 216 | { 217 | dict[k] = definition.CreateOrValidateData(valueProp, dict[k], options, depth); 218 | } 219 | } 220 | 221 | 222 | return dict; 223 | } 224 | } 225 | } 226 | -------------------------------------------------------------------------------- /BlazorDynamicForm/README.md: -------------------------------------------------------------------------------- 1 | # Blazor Dynamic Form 2 | 3 | BlazorDynamicFormSyncfusion is a dynamic form generator built with C# for Blazor applications, with an integration of the Syncfusion Blazor UI library. It uses data annotation attributes to automatically build forms with validation included. 4 | 5 | - [Installation](#installation) 6 | - [.NET CLI](#net-cli) 7 | - [Packet Manager](#packet-manager) 8 | - [Packages Description](#packages-description) 9 | - [Form Usage](#form-usage) 10 | - [Data Providers](#data-providers) 11 | - [Create a Custom Integration](#create-a-custom-integration) 12 | - [Add Method](#add-method) 13 | - [AddCustom Method](#addcustom-method) 14 | - [Contributing](#contributing) 15 | - [License](#license) 16 | 17 | ## Features 18 | 19 | - Built-in data annotations support for forms. 20 | - Syncfusion Blazor components integration. 21 | - Customization options for form fields. 22 | - Automatic form generation and handling. 23 | 24 | ## Coming soon 25 | - CSS customization for each property, so you can use the boostrap grid system. 26 | 27 | ## Installation 28 | 29 | .NET CLI 30 | ```bash 31 | dotnet add package BlazorDynamicFormDataAnnotation --version 1.0.5 32 | dotnet add package BlazorDynamicFormGenerator --version 1.0.5 33 | dotnet add package BlazorDynamicFormSyncfusion --version 1.0.5 34 | ``` 35 | 36 | Packet Manager 37 | ```bash 38 | Install-Package BlazorDynamicFormDataAnnotation -Version 1.0.5 39 | Install-Package BlazorDynamicFormGenerator -Version 1.0.5 40 | Install-Package BlazorDynamicFormSyncfusion -Version 1.0.5 41 | ``` 42 | # Packages 43 | 44 | This project includes three essential packages that handle various aspects of dynamic form generation and management in Blazor. Here's a brief rundown of what each package offers: 45 | 46 | 1. **BlazorDynamicFormDataAnnotation**: This package is the heart of the form generation process in Blazor. It employs DataAnnotations for generating dynamic forms, taking the grunt work out of the process. Leveraging the framework's capabilities, it automates form creation based on your annotated model. 47 | 48 | 2. **BlazorDynamicFormGenerator**: This package acts as a blueprint generator. It dynamically produces class description templates, allowing the frontend to function independently of hardcoded class definitions. The generated templates cater to both fixed and fluid design requirements, offering you the flexibility to adapt to evolving needs. 49 | 50 | 3. **BlazorDynamicFormSyncfusion**: This extension package is a bridge between the Blazor Dynamic Form and Syncfusion Blazor components. It allows seamless integration of Syncfusion components into the generated forms, offering enhanced user interaction and richer data handling. Each component can be assigned to specific data types or attributes, promoting consistent and streamlined form structures. 51 | 52 | Together, these packages serve as a robust toolset for handling form generation in Blazor applications, offering flexibility, ease of use, and integration with industry-leading component libraries. 53 | ## Form Usage 54 | 55 | In your Blazor application, include the namespace for the dynamic form generator. 56 | 57 | ```razor 58 | @using BlazorDynamicFormDataAnnotation 59 | ``` 60 | 61 | Here is an example of a dynamic form in a Razor component: 62 | 63 | ```razor 64 | 65 | 66 | @error 67 | 68 | 69 |
70 | 71 |
72 |
73 | @template.DynamicComponent 74 |
75 | @if (!template.IsValid) 76 | { 77 |
78 | @template.ValidationComponent 79 |
80 | } 81 |
82 |
83 |
84 | 85 |
86 | 87 |
88 |
89 |
90 | ``` 91 | 92 | Refer to the included `Test` class in the `OnInitialized()` method for an example of a model with data annotations. 93 | 94 | ```csharp 95 | public class Test 96 | { 97 | [Required, Display(Name = "First Name"), DataType(DataType.Text)] 98 | public string FirstName { get; set; } 99 | 100 | [Display(Name = "Last Name"), DataType(DataType.Text)] 101 | public string LastName { get; set; } 102 | 103 | [Required, Display(Name = "Email Address"), DataType(DataType.EmailAddress), EmailAddress] 104 | public string Email { get; set; } 105 | 106 | [Required, Display(Name = "PhoneNumber"), DataType(DataType.PhoneNumber), Phone] 107 | public string PhoneNumber { get; set; } 108 | 109 | [Required, Display(Name = "Date of Birth"), DataType(DataType.DateTime)] 110 | public DateTime? DOB { get; set; } 111 | 112 | [Required, DataType(DataType.Duration), Display(Name = "Total Experience"), Range(0, 20, ErrorMessage = "The Experience range should be 0 to 20"), DefaultValue(10.0)] 113 | public decimal TotalExperience { get; set; } = 22; 114 | 115 | [Required, Display(Name = "Select a Country"), DataType("DropdownList"), LinkedAttribute(typeof(int))] 116 | public string Country { get; set; } 117 | 118 | [Required, DataType(DataType.MultilineText), Display(Name = "Address"), DefaultValue("piazza 24 maggio"), BlazorDynamicFormGenerator.ReadOnly] 119 | public string Address { get; set; } 120 | } 121 | ``` 122 | Generate the model description: 123 | ```csharp 124 | var definition = ModuleNodePropertyDefinitionExtensions.GetDefinition(); 125 | ``` 126 | Get the result direclty as JSON: 127 | ```csharp 128 | void OnValidResult(string data){..} 129 | ``` 130 | 131 | Result: 132 | ```json 133 | {"FirstName":"Lorenzo","LastName":null,"Email":"l@hoy.com","PhoneNumber":"331","DOB":"2023-06-22T00:00:00+02:00","TotalExperience":10.0,"Country":"id","Address":"piazza 24 maggio"} 134 | ``` 135 | 136 | Showcase with Boostrap and Syncfusion 137 | | | | 138 | |:---:|:---:| 139 | | ![image](https://github.com/TheSmallPixel/BlazorDynamicForm/assets/25280244/8cfc9458-681b-49ce-a2e6-0cebffe7364e) | ![image](https://github.com/TheSmallPixel/BlazorDynamicForm/assets/25280244/f802568d-ebde-4e03-8bd2-30e5cc34804b) | 140 | 141 | ## Data Providers 142 | 143 | The `AddDataProvider` method is used to set up a source of data for certain elements in the form, particularly for elements like dropdown lists or combo boxes that require a list of options to present to the user. 144 | 145 | This method accepts a delegate that can retrieve or generate the data required for a given attribute and name. The method should return the data in a format that can be used to populate the dropdown list or other similar components. 146 | 147 | Here's how it is used in the given code example: 148 | 149 | ```csharp 150 | builder.Services.AddBlazorDynamicForm() 151 | .AddDataProvider((attribute, name) => 152 | { 153 | var data = new List(); 154 | data.Add(new FormVar() { Id = "id", Name = "Cipolle" }); 155 | return data; 156 | }) 157 | .SyncfusionForm(); 158 | ``` 159 | 160 | In this example, `AddDataProvider` is being used to provide a list of `FormVar` objects whenever needed. In a more realistic scenario, the delegate might retrieve the data from a database or an API depending on the attribute and name provided. 161 | 162 | This makes `AddDataProvider` a powerful tool for dynamic form generation, as it allows you to tailor the data shown in each field based on the requirements of that field. 163 | 164 | ## Create a Custom Integration 165 | SyncfusionIntegration is a static class that serves as an example of how to extend the Dynamic Form Configuration to integrate custom data types and components. 166 | 167 | The SyncfusionForm method of this class modifies the form's configuration, adding a series of Syncfusion Blazor components with their respective data types. The attributes of these components are set through various builder functions. 168 | 169 | 170 | 171 | ```csharp 172 | public static class SyncfusionIntegration 173 | { 174 | public static void SyncfusionForm(this DynamicFormConfiguration config) 175 | { 176 | config 177 | .Add(DataType.Text, (builder, sequence, attribute) => 178 | { 179 | builder.AddAttribute(sequence++, "Readonly", attribute.ReadOnly); 180 | }) 181 | .Add(DataType.EmailAddress) 182 | .Add(DataType.PhoneNumber) 183 | .Add(DataType.MultilineText, (builder, sequence, attribute) => 184 | { 185 | builder.AddAttribute(sequence++, "Multiline", true); 186 | builder.AddAttribute(sequence++, "Readonly", attribute.ReadOnly); 187 | }) 188 | .Add, DateTime?>(DataType.DateTime) 189 | .Add, decimal?>(DataType.Duration) 190 | .AddCustom, string>("DropdownList", (builder, sequence, attribute) => 191 | { 192 | var linked = attribute.ValidationRules.OfType().FirstOrDefault(); 193 | if (linked is not null) 194 | builder.AddAttribute(sequence++, "DataSource", config.DataSource(linked, attribute.Name)); 195 | builder.AddAttribute(sequence++, "ChildContent", (RenderFragment)((builder2) => 196 | { 197 | builder2.AddMarkupContent(0, "\r\n"); 198 | builder2.OpenComponent(1); 199 | builder2.AddAttribute(2, "Value", "Id"); 200 | builder2.AddAttribute(3, "Text", "Name"); 201 | builder2.CloseComponent(); 202 | builder2.AddMarkupContent(4, "\r\n"); 203 | })); 204 | }) 205 | .AddError((builder, sequence, value, name, error) => 206 | { 207 | builder.AddAttribute(sequence++, "Value", value); 208 | builder.AddAttribute(sequence++, "PlaceHolder", name); 209 | builder.AddAttribute(sequence++, "class", error ? "is-invalid" : "is-valid"); 210 | }); 211 | } 212 | } 213 | ``` 214 | 215 | ## Add Method 216 | 217 | The `Add` method is used to map a data annotation attribute to a Blazor component. This method accepts the type of the component, the DataType for which the component should be used, and an optional configuration delegate. 218 | 219 | Here's an example of how it is used: 220 | 221 | ```csharp 222 | config.Add(DataType.Text, (builder, sequence, attribute) => 223 | { 224 | builder.AddAttribute(sequence++, "Readonly", attribute.ReadOnly); 225 | }); 226 | ``` 227 | 228 | ## AddCustom Method 229 | 230 | The `AddCustom` method allows for more advanced configurations. It is used when you want to map a custom data annotation to a specific Blazor component. This method also accepts the type of the component, the custom data annotation name, and a configuration delegate. 231 | 232 | Here's an example: 233 | 234 | ```csharp 235 | config.AddCustom, string>("DropdownList", (builder, sequence, attribute) => 236 | { 237 | var linked = attribute.ValidationRules.OfType().FirstOrDefault(); 238 | if (linked is not null) 239 | builder.AddAttribute(sequence++, "DataSource", config.DataSource(linked, attribute.Name)); 240 | builder.AddAttribute(sequence++, "ChildContent", (RenderFragment)((builder2) => 241 | { 242 | builder2.AddMarkupContent(0, "\r\n"); 243 | builder2.OpenComponent(1); 244 | builder2.AddAttribute(2, "Value", "Id"); 245 | builder2.AddAttribute(3, "Text", "Name"); 246 | builder2.CloseComponent(); 247 | builder2.AddMarkupContent(4, "\r\n"); 248 | })); 249 | }); 250 | ``` 251 | 252 | In the above example, the `AddCustom` method maps a custom data annotation named "DropdownList" to a `SfDropDownList` component. The configuration delegate is used to configure the `DataSource` and `ChildContent` attributes of the `SfDropDownList` component. 253 | 254 | These two methods are very powerful as they give you the ability to customize the way your forms work with different data types and annotations, leveraging the variety of components provided by Syncfusion Blazor. 255 | 256 | ## Contributing 257 | 258 | Pull requests are welcome. For major changes, please open an issue first to discuss what you would like to change. Please make sure to update tests as appropriate. 259 | 260 | ## License 261 | 262 | This project is licensed under the GPL-3.0 license. See the [LICENSE](LICENSE) file for details. 263 | -------------------------------------------------------------------------------- /TypeAnnotationParser/README.md: -------------------------------------------------------------------------------- 1 | # Blazor Dynamic Form 2 | 3 | BlazorDynamicFormSyncfusion is a dynamic form generator built with C# for Blazor applications, with an integration of the Syncfusion Blazor UI library. It uses data annotation attributes to automatically build forms with validation included. 4 | 5 | - [Installation](#installation) 6 | - [.NET CLI](#net-cli) 7 | - [Packet Manager](#packet-manager) 8 | - [Packages Description](#packages-description) 9 | - [Form Usage](#form-usage) 10 | - [Data Providers](#data-providers) 11 | - [Create a Custom Integration](#create-a-custom-integration) 12 | - [Add Method](#add-method) 13 | - [AddCustom Method](#addcustom-method) 14 | - [Contributing](#contributing) 15 | - [License](#license) 16 | 17 | ## Features 18 | 19 | - Built-in data annotations support for forms. 20 | - Syncfusion Blazor components integration. 21 | - Customization options for form fields. 22 | - Automatic form generation and handling. 23 | 24 | ## Coming soon 25 | - CSS customization for each property, so you can use the boostrap grid system. 26 | 27 | ## Installation 28 | 29 | .NET CLI 30 | ```bash 31 | dotnet add package BlazorDynamicFormDataAnnotation --version 1.0.5 32 | dotnet add package BlazorDynamicFormGenerator --version 1.0.5 33 | dotnet add package BlazorDynamicFormSyncfusion --version 1.0.5 34 | ``` 35 | 36 | Packet Manager 37 | ```bash 38 | Install-Package BlazorDynamicFormDataAnnotation -Version 1.0.5 39 | Install-Package BlazorDynamicFormGenerator -Version 1.0.5 40 | Install-Package BlazorDynamicFormSyncfusion -Version 1.0.5 41 | ``` 42 | # Packages 43 | 44 | This project includes three essential packages that handle various aspects of dynamic form generation and management in Blazor. Here's a brief rundown of what each package offers: 45 | 46 | 1. **BlazorDynamicFormDataAnnotation**: This package is the heart of the form generation process in Blazor. It employs DataAnnotations for generating dynamic forms, taking the grunt work out of the process. Leveraging the framework's capabilities, it automates form creation based on your annotated model. 47 | 48 | 2. **BlazorDynamicFormGenerator**: This package acts as a blueprint generator. It dynamically produces class description templates, allowing the frontend to function independently of hardcoded class definitions. The generated templates cater to both fixed and fluid design requirements, offering you the flexibility to adapt to evolving needs. 49 | 50 | 3. **BlazorDynamicFormSyncfusion**: This extension package is a bridge between the Blazor Dynamic Form and Syncfusion Blazor components. It allows seamless integration of Syncfusion components into the generated forms, offering enhanced user interaction and richer data handling. Each component can be assigned to specific data types or attributes, promoting consistent and streamlined form structures. 51 | 52 | Together, these packages serve as a robust toolset for handling form generation in Blazor applications, offering flexibility, ease of use, and integration with industry-leading component libraries. 53 | ## Form Usage 54 | 55 | In your Blazor application, include the namespace for the dynamic form generator. 56 | 57 | ```razor 58 | @using BlazorDynamicFormDataAnnotation 59 | ``` 60 | 61 | Here is an example of a dynamic form in a Razor component: 62 | 63 | ```razor 64 | 65 | 66 | @error 67 | 68 | 69 |
70 | 71 |
72 |
73 | @template.DynamicComponent 74 |
75 | @if (!template.IsValid) 76 | { 77 |
78 | @template.ValidationComponent 79 |
80 | } 81 |
82 |
83 |
84 | 85 |
86 | 87 |
88 |
89 |
90 | ``` 91 | 92 | Refer to the included `Test` class in the `OnInitialized()` method for an example of a model with data annotations. 93 | 94 | ```csharp 95 | public class Test 96 | { 97 | [Required, Display(Name = "First Name"), DataType(DataType.Text)] 98 | public string FirstName { get; set; } 99 | 100 | [Display(Name = "Last Name"), DataType(DataType.Text)] 101 | public string LastName { get; set; } 102 | 103 | [Required, Display(Name = "Email Address"), DataType(DataType.EmailAddress), EmailAddress] 104 | public string Email { get; set; } 105 | 106 | [Required, Display(Name = "PhoneNumber"), DataType(DataType.PhoneNumber), Phone] 107 | public string PhoneNumber { get; set; } 108 | 109 | [Required, Display(Name = "Date of Birth"), DataType(DataType.DateTime)] 110 | public DateTime? DOB { get; set; } 111 | 112 | [Required, DataType(DataType.Duration), Display(Name = "Total Experience"), Range(0, 20, ErrorMessage = "The Experience range should be 0 to 20"), DefaultValue(10.0)] 113 | public decimal TotalExperience { get; set; } = 22; 114 | 115 | [Required, Display(Name = "Select a Country"), DataType("DropdownList"), LinkedAttribute(typeof(int))] 116 | public string Country { get; set; } 117 | 118 | [Required, DataType(DataType.MultilineText), Display(Name = "Address"), DefaultValue("piazza 24 maggio"), BlazorDynamicFormGenerator.ReadOnly] 119 | public string Address { get; set; } 120 | } 121 | ``` 122 | Generate the model description: 123 | ```csharp 124 | var definition = ModuleNodePropertyDefinitionExtensions.GetDefinition(); 125 | ``` 126 | Get the result direclty as JSON: 127 | ```csharp 128 | void OnValidResult(string data){..} 129 | ``` 130 | 131 | Result: 132 | ```json 133 | {"FirstName":"Lorenzo","LastName":null,"Email":"l@hoy.com","PhoneNumber":"331","DOB":"2023-06-22T00:00:00+02:00","TotalExperience":10.0,"Country":"id","Address":"piazza 24 maggio"} 134 | ``` 135 | 136 | Showcase with Boostrap and Syncfusion 137 | | | | 138 | |:---:|:---:| 139 | | ![image](https://github.com/TheSmallPixel/BlazorDynamicForm/assets/25280244/8cfc9458-681b-49ce-a2e6-0cebffe7364e) | ![image](https://github.com/TheSmallPixel/BlazorDynamicForm/assets/25280244/f802568d-ebde-4e03-8bd2-30e5cc34804b) | 140 | 141 | ## Data Providers 142 | 143 | The `AddDataProvider` method is used to set up a source of data for certain elements in the form, particularly for elements like dropdown lists or combo boxes that require a list of options to present to the user. 144 | 145 | This method accepts a delegate that can retrieve or generate the data required for a given attribute and name. The method should return the data in a format that can be used to populate the dropdown list or other similar components. 146 | 147 | Here's how it is used in the given code example: 148 | 149 | ```csharp 150 | builder.Services.AddBlazorDynamicForm() 151 | .AddDataProvider((attribute, name) => 152 | { 153 | var data = new List(); 154 | data.Add(new FormVar() { Id = "id", Name = "Cipolle" }); 155 | return data; 156 | }) 157 | .SyncfusionForm(); 158 | ``` 159 | 160 | In this example, `AddDataProvider` is being used to provide a list of `FormVar` objects whenever needed. In a more realistic scenario, the delegate might retrieve the data from a database or an API depending on the attribute and name provided. 161 | 162 | This makes `AddDataProvider` a powerful tool for dynamic form generation, as it allows you to tailor the data shown in each field based on the requirements of that field. 163 | 164 | ## Create a Custom Integration 165 | SyncfusionIntegration is a static class that serves as an example of how to extend the Dynamic Form Configuration to integrate custom data types and components. 166 | 167 | The SyncfusionForm method of this class modifies the form's configuration, adding a series of Syncfusion Blazor components with their respective data types. The attributes of these components are set through various builder functions. 168 | 169 | 170 | 171 | ```csharp 172 | public static class SyncfusionIntegration 173 | { 174 | public static void SyncfusionForm(this DynamicFormConfiguration config) 175 | { 176 | config 177 | .Add(DataType.Text, (builder, sequence, attribute) => 178 | { 179 | builder.AddAttribute(sequence++, "Readonly", attribute.ReadOnly); 180 | }) 181 | .Add(DataType.EmailAddress) 182 | .Add(DataType.PhoneNumber) 183 | .Add(DataType.MultilineText, (builder, sequence, attribute) => 184 | { 185 | builder.AddAttribute(sequence++, "Multiline", true); 186 | builder.AddAttribute(sequence++, "Readonly", attribute.ReadOnly); 187 | }) 188 | .Add, DateTime?>(DataType.DateTime) 189 | .Add, decimal?>(DataType.Duration) 190 | .AddCustom, string>("DropdownList", (builder, sequence, attribute) => 191 | { 192 | var linked = attribute.ValidationRules.OfType().FirstOrDefault(); 193 | if (linked is not null) 194 | builder.AddAttribute(sequence++, "DataSource", config.DataSource(linked, attribute.Name)); 195 | builder.AddAttribute(sequence++, "ChildContent", (RenderFragment)((builder2) => 196 | { 197 | builder2.AddMarkupContent(0, "\r\n"); 198 | builder2.OpenComponent(1); 199 | builder2.AddAttribute(2, "Value", "Id"); 200 | builder2.AddAttribute(3, "Text", "Name"); 201 | builder2.CloseComponent(); 202 | builder2.AddMarkupContent(4, "\r\n"); 203 | })); 204 | }) 205 | .AddError((builder, sequence, value, name, error) => 206 | { 207 | builder.AddAttribute(sequence++, "Value", value); 208 | builder.AddAttribute(sequence++, "PlaceHolder", name); 209 | builder.AddAttribute(sequence++, "class", error ? "is-invalid" : "is-valid"); 210 | }); 211 | } 212 | } 213 | ``` 214 | 215 | ## Add Method 216 | 217 | The `Add` method is used to map a data annotation attribute to a Blazor component. This method accepts the type of the component, the DataType for which the component should be used, and an optional configuration delegate. 218 | 219 | Here's an example of how it is used: 220 | 221 | ```csharp 222 | config.Add(DataType.Text, (builder, sequence, attribute) => 223 | { 224 | builder.AddAttribute(sequence++, "Readonly", attribute.ReadOnly); 225 | }); 226 | ``` 227 | 228 | ## AddCustom Method 229 | 230 | The `AddCustom` method allows for more advanced configurations. It is used when you want to map a custom data annotation to a specific Blazor component. This method also accepts the type of the component, the custom data annotation name, and a configuration delegate. 231 | 232 | Here's an example: 233 | 234 | ```csharp 235 | config.AddCustom, string>("DropdownList", (builder, sequence, attribute) => 236 | { 237 | var linked = attribute.ValidationRules.OfType().FirstOrDefault(); 238 | if (linked is not null) 239 | builder.AddAttribute(sequence++, "DataSource", config.DataSource(linked, attribute.Name)); 240 | builder.AddAttribute(sequence++, "ChildContent", (RenderFragment)((builder2) => 241 | { 242 | builder2.AddMarkupContent(0, "\r\n"); 243 | builder2.OpenComponent(1); 244 | builder2.AddAttribute(2, "Value", "Id"); 245 | builder2.AddAttribute(3, "Text", "Name"); 246 | builder2.CloseComponent(); 247 | builder2.AddMarkupContent(4, "\r\n"); 248 | })); 249 | }); 250 | ``` 251 | 252 | In the above example, the `AddCustom` method maps a custom data annotation named "DropdownList" to a `SfDropDownList` component. The configuration delegate is used to configure the `DataSource` and `ChildContent` attributes of the `SfDropDownList` component. 253 | 254 | These two methods are very powerful as they give you the ability to customize the way your forms work with different data types and annotations, leveraging the variety of components provided by Syncfusion Blazor. 255 | 256 | ## Contributing 257 | 258 | Pull requests are welcome. For major changes, please open an issue first to discuss what you would like to change. Please make sure to update tests as appropriate. 259 | 260 | ## License 261 | 262 | This project is licensed under the GPL-3.0 license. See the [LICENSE](LICENSE) file for details. 263 | --------------------------------------------------------------------------------