Chd.AutoUI
8.0.4
See the version list below for details.
dotnet add package Chd.AutoUI --version 8.0.4
NuGet\Install-Package Chd.AutoUI -Version 8.0.4
<PackageReference Include="Chd.AutoUI" Version="8.0.4" />
<PackageVersion Include="Chd.AutoUI" Version="8.0.4" />
<PackageReference Include="Chd.AutoUI" />
paket add Chd.AutoUI --version 8.0.4
#r "nuget: Chd.AutoUI, 8.0.4"
#:package Chd.AutoUI@8.0.4
#addin nuget:?package=Chd.AutoUI&version=8.0.4
#tool nuget:?package=Chd.AutoUI&version=8.0.4
Chd.AutoUI β Attribute-Driven UI Metadata for .NET
Define your UI once in C#. Let the library handle the rest.
π Table of Contents
- About
- Features
- Installation
- How It Works
- Quick Start
- Permission Management
- Exposed Endpoints
- Attribute Reference
- JSON Metadata Format
- Architecture Overview
- Advanced Usage
- Roadmap
- Related Projects
About
Chd.AutoUI is a .NET library that generates UI metadata from C# attributes on your DTOs. You annotate your classes with [AutoCRUD], [GridColumn], and [FormField], and the library exposes that information as a JSON endpoint. Any frontend that can consume a REST API can use that metadata to render forms, grids, and validation rules without you writing them manually.
The workflow is: define once in C#, render anywhere in the frontend. When a field changes on the backend, the UI reflects it automatically on the next request β no JavaScript to update, no configuration files to maintain.
π― Features
- Attribute-driven:
[AutoCRUD],[GridColumn],[FormField] - 20+ field types: text, number, date, dropdown, multiselect, radio, file, tag input, autocomplete, rich text editor, image preview, tree select, color picker, date range picker, stepper
- Role-based permissions via
[CreatePermission],[UpdatePermission],[DeletePermission] - Auto-detection of foreign key relationships for dropdown fields (e.g.
CategoryIdβcategoriesendpoint) - Built-in JWT login endpoint β implement one interface, get a working login flow
- Metadata and identity endpoints registered automatically in a single
UseAutoUI<T>call - No code generation, no build steps β pure reflection at runtime
- Works with any frontend framework (React, Vue, Angular, Blazor)
π¦ Installation
dotnet add package Chd.AutoUI
βοΈ How It Works
βββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β Your C# DTO β
β [AutoCRUD] [GridColumn] [FormField] [Permissions] β
βββββββββββββββββββββββββ¬ββββββββββββββββββββββββββββββ
β reflection at runtime
βΌ
βββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β MetadataGenerator β
β ScanAssemblyForEntities() / GenerateMetadata() β
βββββββββββββββββββββββββ¬ββββββββββββββββββββββββββββββ
β JSON
βΌ
βββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β REST Endpoints (auto-registered) β
β GET /api/metadata β
β GET /api/metadata/{entityName} β
βββββββββββββββββββββββββ¬ββββββββββββββββββββββββββββββ
β consumed by frontend
βΌ
βββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β Frontend (React / Vue / Angular / Blazor) β
β DynamicGrid DynamicForm Permission-aware UI β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββ
π Quick Start
1. Annotate Your DTO
using Chd.AutoUI.Attributes;
[AutoCRUD(Title = "Products", Icon = "shopping-bag", Route = "/products", Description = "Manage your product catalog")]
[CreatePermission("Admin", "Manager")]
[UpdatePermission("Admin", "Editor")]
[DeletePermission("Admin")]
public class ProductDto
{
// Read-only field shown in grid but not editable in form
[GridColumn(Order = 1, Width = 80, Sortable = false)]
[FormField(ReadOnly = true)]
public int Id { get; set; }
[GridColumn(Order = 2, Width = 200)]
[FormField(Label = "Product Name", Type = FieldType.Text, Required = true, MaxLength = 100, Order = 1)]
public string Name { get; set; } = string.Empty;
[GridColumn(Order = 3, Width = 300)]
[FormField(Label = "Description", Type = FieldType.TextArea, MaxLength = 500, Order = 2)]
public string? Description { get; set; }
[GridColumn(Order = 4, Width = 150, Format = "currency")]
[FormField(Label = "Price", Type = FieldType.Number, Required = true, Order = 3)]
public decimal Price { get; set; }
// Dropdown with remote data β RelatedEntity points to the API endpoint
[GridColumn(Order = 5, Width = 150)]
[FormField(Label = "Category", Type = FieldType.Dropdown, Order = 4, RelatedEntity = "categories")]
public int? CategoryId { get; set; }
// Auto-detected: property ends with "Id" so MetadataGenerator infers the endpoint automatically
[GridColumn(Order = 6, Width = 150)]
[FormField(Label = "Supplier", Type = FieldType.Dropdown, Order = 5)]
public int? SupplierId { get; set; }
// Radio with static options
[GridColumn(Order = 7, Width = 120)]
[FormField(Label = "Status", Type = FieldType.Radio, Required = true, Order = 6,
Options = new[] { "Active", "Inactive", "Discontinued" })]
public string Status { get; set; } = "Active";
// Checkbox
[GridColumn(Order = 8, Width = 100)]
[FormField(Label = "Featured", Type = FieldType.Checkbox, Order = 7)]
public bool IsFeatured { get; set; }
// Tag input β stored as comma-separated string on the backend
[GridColumn(Order = 9, Width = 200)]
[FormField(Label = "Tags", Type = FieldType.TagInput, Order = 8)]
public string? Tags { get; set; }
// Date picker
[GridColumn(Order = 10, Width = 130)]
[FormField(Label = "Expiry Date", Type = FieldType.Date, Order = 9)]
public DateTime? ExpiryDate { get; set; }
}
2. Implement IUserTokenProvider
UseAutoUI registers a POST /api/account/login endpoint that issues JWT tokens. You control the authentication logic by implementing IUserTokenProvider. The interface has a single async method β validate credentials, build the UserDTO, and return it. Return null to reject the login.
using Chd.AutoUI.Extensions;
using Chd.Security.DTOs;
using Chd.Security.Models;
public class AppUserTokenProvider : IUserTokenProvider
{
private readonly AppDbContext _db;
private readonly IPasswordHasher _hasher;
public AppUserTokenProvider(AppDbContext db, IPasswordHasher hasher)
{
_db = db;
_hasher = hasher;
}
public async Task<UserDTO?> GetUserTokenInfoAsync(UserModel model)
{
var user = await _db.Users
.Include(u => u.Roles)
.FirstOrDefaultAsync(u => u.UserName == model.UserName);
if (user == null || !_hasher.Verify(model.Password, user.PasswordHash))
return null;
return new UserDTO
{
UserName = user.UserName,
Roles = user.Roles.Select(r => r.Name).ToList(),
ExpirationSecond = 3600
};
}
}
UserDTO properties:
| Property | Type | Description |
|---|---|---|
UserName |
string | Becomes the JWT subject (sub claim) |
Roles |
List<string> |
Each role maps to a role claim in the token |
ExpirationSecond |
int? | Token lifetime in seconds (default: 3600) |
Because the provider is resolved from DI, you can inject DbContext, password hashers, external identity services, or anything else through the constructor.
3. Configure Program.cs
using Chd.AutoUI.EF.Services;
using Chd.AutoUI.Extensions;
using Serilog;
var builder = WebApplication.CreateBuilder(args);
// Logging (optional, but useful)
var logger = new LoggerConfiguration()
.MinimumLevel.Debug()
.WriteTo.Console()
.CreateLogger();
builder.Logging.ClearProviders();
builder.Logging.AddSerilog(logger);
// Database
builder.Services.AddDbContext<AppDbContext>(options =>
options.UseNpgsql(builder.Configuration.GetConnectionString("DefaultConnection")));
// Generic repository β one line covers all entities
builder.Services.AddScoped(typeof(IGenericRepository<>), typeof(GenericRepository<>));
// Controllers, Swagger
builder.Services.AddControllers();
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
// CORS
builder.Services.AddCors(options =>
{
options.AddDefaultPolicy(policy =>
policy.WithOrigins("http://localhost:3000")
.AllowAnyMethod()
.AllowAnyHeader());
});
// UseAutoUI<TProvider> does the following in one call:
// - Registers AppUserTokenProvider as IUserTokenProvider in DI
// - Configures JWT middleware (UseJwtTokenAuthorization internally)
// - Calls UseAuthentication() and UseAuthorization()
// - Registers GET /api/metadata, GET /api/metadata/{entityName}, GET /api/me, POST /api/account/login
var app = builder.UseAutoUI<AppUserTokenProvider>(typeof(ProductDto).Assembly);
if (app.Environment.IsDevelopment())
{
app.UseSwagger();
app.UseSwaggerUI();
}
app.UseHttpsRedirection();
app.MapControllers();
app.Run();
Note: Do not call
app.UseAuthentication()orapp.UseAuthorization()again afterUseAutoUIβ the method already calls them internally in the correct order.
π Permission Management
One of the more useful things about the library is that permissions are defined where your data model is, not scattered across controllers and React components.
Defining Permissions in C#
[AutoCRUD(Title = "Products", Route = "/products")]
[CreatePermission("Admin", "Manager")] // who can create
[UpdatePermission("Admin", "Editor")] // who can edit
[DeletePermission("Admin")] // who can delete
// no ReadPermission = everyone can read (open)
public class ProductDto { ... }
Roles are strings β they match the role claims in your JWT. The IUserTokenProvider you implement sets those roles when issuing a token.
What the Metadata Looks Like
When the frontend calls GET /api/metadata/ProductDto, the permissions are part of the response:
{
"entityName": "ProductDto",
"title": "Products",
"permissions": {
"create": ["Admin", "Manager"],
"read": [],
"update": ["Admin", "Editor"],
"delete": ["Admin"]
},
"properties": [...]
}
An empty read array means no restrictions β everyone can see the list.
Consuming Permissions in React
The chd-auto-ui-react components read the permissions field from the metadata and compare it against the current user's roles (fetched from GET /api/me). The Create button is hidden if the user lacks the create role. The Edit and Delete buttons follow the same logic.
// The DynamicGrid component does this internally:
const can = (action: 'create' | 'update' | 'delete') => {
const allowed = metadata.permissions?.[action] ?? [];
if (allowed.length === 0) return true; // no restriction
return currentUser?.roles?.some(r => allowed.includes(r)) ?? false;
};
// Create button only renders if can('create') is true
// Edit/Delete only render if can('update') / can('delete') is true
No extra configuration needed in React β the permission flow is automatic once the attributes are on the DTO.
π Exposed Endpoints
These are registered automatically when you call UseAutoUI:
| Method | Path | Auth required | Description |
|---|---|---|---|
GET |
/api/metadata |
No | All [AutoCRUD] entity metadata in the assembly |
GET |
/api/metadata/{entityName} |
No | Metadata for a single entity by class name |
GET |
/api/me |
Yes (JWT) | Current user β { id, name, roles } |
POST |
/api/account/login |
No | Issues a JWT token via IUserTokenProvider |
Login request:
POST /api/account/login
Content-Type: application/json
{ "UserName": "admin", "Password": "your-password" }
Response:
{ "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..." }
π Attribute Reference
AutoCRUD
Applied to a class. Marks the DTO for inclusion in the metadata scan and defines entity-level UI settings.
[AutoCRUD(
Title = "Products",
Icon = "shopping-bag",
Route = "/products",
Description = "Manage your product catalog"
)]
public class ProductDto { }
| Property | Type | Description |
|---|---|---|
Title |
string | Display name shown in headers and navigation |
Icon |
string | Icon identifier β emoji or a CSS class name (depends on your icon library) |
Route |
string | Frontend route path, e.g. /products |
Description |
string | Short description displayed below the title |
GridColumn
Applied to a property. Controls whether and how the field appears in a data table.
[GridColumn(
Order = 3,
Width = 150,
Sortable = true,
Filterable = true,
Format = "currency",
Hidden = false
)]
public decimal Price { get; set; }
| Property | Type | Default | Description |
|---|---|---|---|
Order |
int | 0 | Column position left to right |
Width |
int | 100 | Column width in pixels |
Sortable |
bool | true | Allows column sorting |
Filterable |
bool | false | Allows column filtering |
Format |
string | null | "currency", "date", "percent", "number" |
Hidden |
bool | false | Hides the column from the grid but still includes it in metadata |
FormField
Applied to a property. Controls whether and how the field appears in create/edit forms.
[FormField(
Label = "Product Name",
Type = FieldType.Text,
Required = true,
MaxLength = 100,
Placeholder = "Enter product name",
Order = 1,
ReadOnly = false
)]
public string Name { get; set; } = string.Empty;
| Property | Type | Default | Description |
|---|---|---|---|
Label |
string | property name | Field label text |
Type |
FieldType | Text | Input type (see field types below) |
Required |
bool | false | Marks the field as required |
MaxLength |
int | null | Maximum allowed characters |
Placeholder |
string | null | Placeholder text |
Order |
int | 0 | Field position in the form |
ReadOnly |
bool | false | Renders the field as non-editable |
ValidationPattern |
string | null | Regex pattern for client-side validation |
RelatedEntity |
string | null | For Dropdown/MultiSelect β API endpoint to load options from |
DisplayProperty |
string | "Name" |
Property name to show as option label |
ValueProperty |
string | "Id" |
Property name to use as option value |
Options |
string[] | null | For Radio/Select β static list of options |
Accept |
string | null | For File β accepted MIME types, e.g. "image/*" |
Multiple |
bool | false | For File β allow multiple file selection |
AutocompleteEndpoint |
string | null | For Autocomplete β search endpoint URL |
AutocompleteMinChars |
int | 2 | For Autocomplete β minimum chars before search triggers |
TreeEntity |
string | null | For TreeSelect β endpoint returning hierarchical data |
AllowCustomTags |
bool | false | For TagInput β allow user-defined tags not in suggestions |
TagSuggestions |
string[] | null | For TagInput β preset tag suggestions |
ColorFormat |
string | "hex" |
For ColorPicker β "hex" or "rgb" |
ImageMaxWidth |
int | null | For ImagePreview β max width in pixels |
ImageMaxHeight |
int | null | For ImagePreview β max height in pixels |
IsRange |
bool | false | For DateRangePicker β enables two-date selection |
EditorConfig |
object | null | For RichTextEditor β configuration passed to the editor |
Field Types
| Type | Description | Key Options |
|---|---|---|
Text |
Single-line text | MaxLength, Placeholder, ValidationPattern |
Number |
Numeric input | β |
Email |
Email address | ValidationPattern |
Password |
Password (masked) | MaxLength |
TextArea |
Multi-line text | MaxLength, Placeholder |
Date |
Date picker | β |
DateTime |
Date and time picker | β |
Checkbox |
Boolean toggle | β |
Radio |
Radio button group | Options (required) |
Dropdown |
Single-select dropdown | RelatedEntity or Options, DisplayProperty, ValueProperty |
MultiSelect |
Multi-select with chips | RelatedEntity, DisplayProperty, ValueProperty |
File |
File upload | Accept, Multiple |
TagInput |
Freeform tag entry | AllowCustomTags, TagSuggestions |
Autocomplete |
Search-as-you-type | AutocompleteEndpoint, AutocompleteMinChars |
RichTextEditor |
WYSIWYG HTML editor | EditorConfig |
ImagePreview |
Image upload + preview | Accept, ImageMaxWidth, ImageMaxHeight |
TreeSelect |
Hierarchical select | TreeEntity |
ColorPicker |
Color selector | ColorFormat |
DateRangePicker |
Two-date range | IsRange = true |
Stepper |
Multi-step form grouping | β |
Permission Attributes
Applied to a class. The metadata generator reads these and includes them in the permissions object of the JSON output.
[CreatePermission("Admin", "Manager")]
[UpdatePermission("Admin", "Editor")]
[DeletePermission("Admin")]
public class ProductDto { }
If neither custom permission attributes nor [Authorize] are present on the class, all actions are permitted by default.
π JSON Metadata Format
This is what GET /api/metadata/ProductDto returns:
{
"entityName": "ProductDto",
"title": "Products",
"icon": "shopping-bag",
"route": "/products",
"description": "Manage your product catalog",
"permissions": {
"create": ["Admin", "Manager"],
"read": [],
"update": ["Admin", "Editor"],
"delete": ["Admin"]
},
"properties": [
{
"name": "Id",
"type": "number",
"grid": { "order": 1, "width": 80, "sortable": false, "hidden": false },
"form": { "readOnly": true, "order": 0 }
},
{
"name": "Name",
"type": "string",
"grid": { "order": 2, "width": 200, "sortable": true },
"form": {
"label": "Product Name",
"type": "text",
"required": true,
"maxLength": 100,
"order": 1
}
},
{
"name": "CategoryId",
"type": "number",
"grid": { "order": 5, "width": 150 },
"form": {
"label": "Category",
"type": "dropdown",
"relatedEntity": "categories",
"displayProperty": "Name",
"valueProperty": "Id",
"order": 4
}
}
]
}
π¬ Advanced Usage
Assembly Scanning
Scan an entire assembly to get metadata for all [AutoCRUD] types at once:
var generator = new MetadataGenerator();
var all = generator.ScanAssemblyForEntities(typeof(ProductDto).Assembly);
// returns List<EntityMetadata> for every class decorated with [AutoCRUD]
This is what the /api/metadata endpoint uses internally.
Auto-Detection of Foreign Keys
If a form field is a Dropdown or MultiSelect and the property name ends with Id, the library infers the related endpoint automatically:
// No RelatedEntity needed β "SupplierId" β MetadataGenerator sets relatedEntity to "suppliers"
[FormField(Label = "Supplier", Type = FieldType.Dropdown, Order = 5)]
public int? SupplierId { get; set; }
You can still set RelatedEntity explicitly if your endpoint name does not follow the convention.
Obsolete Overload
The old UseAutoUI(assembly, Func<UserModel, UserDTO>) overload still works but is marked [Obsolete]. Migrate to UseAutoUI<TProvider>(assembly) when you get the chance β it integrates properly with DI and supports async validation.
// Old (still works, but obsolete)
var app = builder.UseAutoUI(assembly, UserRepository.GetUserTokenInfo);
// New (recommended)
var app = builder.UseAutoUI<UserRepository>(typeof(ProductDto).Assembly);
πΊοΈ Roadmap
- React integration (
chd-auto-ui-react) - Role-based permission attributes
- Built-in JWT authentication endpoint
- Auto-detection of foreign key dropdown relationships
- Vue.js support
- Angular support
- Blazor support
- Improved localization and multi-language labels
- Advanced grid features (grouping, virtual scroll, export)
π Related Projects
| Project | Description |
|---|---|
| chd-auto-ui-react | React components that render the metadata as forms, grids, and login |
| Chd.AutoUI.EF | Generic EF Core repository β pairs naturally with the metadata setup |
| Chd.Pos.Api | Full POS demo showing everything working end-to-end |
π License
MIT
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net8.0 is compatible. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. net9.0 was computed. net9.0-android was computed. net9.0-browser was computed. net9.0-ios was computed. net9.0-maccatalyst was computed. net9.0-macos was computed. net9.0-tvos was computed. net9.0-windows was computed. net10.0 was computed. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
-
net8.0
- Chd.Security (>= 8.5.9)
- Microsoft.AspNetCore.Http.Abstractions (>= 2.2.0)
- Microsoft.AspNetCore.Routing (>= 2.2.0)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.