GrootUI 0.1.0
See the version list below for details.
dotnet add package GrootUI --version 0.1.0
NuGet\Install-Package GrootUI -Version 0.1.0
<PackageReference Include="GrootUI" Version="0.1.0" />
<PackageVersion Include="GrootUI" Version="0.1.0" />
<PackageReference Include="GrootUI" />
paket add GrootUI --version 0.1.0
#r "nuget: GrootUI, 0.1.0"
#:package GrootUI@0.1.0
#addin nuget:?package=GrootUI&version=0.1.0
#tool nuget:?package=GrootUI&version=0.1.0
<p align="center"> <img src="groot-logo.png" alt="Groot" width="160" /> </p>
Groot
ASP.NET Core uchun dev-time admin panel — istalgan EF Core
DbContextustida avtomatik CRUD UI.
v0.1.0 — birinchi test versiya. Public API hali barqaror emas va keyingi versiyalarda o'zgarishi mumkin.
Groot nima?
Groot — bu sizning DbContextingiz uchun Hangfire dashboard yoki Swagger UI uslubidagi
panel. NuGet'dan o'rnatasiz, DbSet propertysiga [GrootTable] qo'yasiz va /groot-ui
sahifasida to'liq CRUD interfeysini olasiz: jadval, paging, search, sort, forma, validation,
bog'lanishlar va lifecycle hook'lar.
Groot — dev-time tool. U production CRUD framework emas. Ma'lumotlaringizni development va internal staging muhitida tezroq ko'rib, tahrirlash uchun.
Features
- Avtomatik CRUD UI har bir
[GrootTable]belgilanganDbSetuchun. - SignalR orqali realtime wire protocol (
/groot/hub). - Bog'lanishlar: many-to-one select, many-to-many uchun qidiruvli multi-select.
- Mass-assignment'dan himoya:
[GrootIgnore],[GrootReadOnly]. - DataAnnotations validation (
[Required],[StringLength], va h.k.) avtomatik. - Lifecycle hook'lar:
IGrootInterceptor<TEntity>orqaliBefore/After Create/Update/Delete. - Paging (cap 100), case-insensitive ILIKE search, sort.
- Ixtiyoriy login: UI sahifasi + environment guard.
- EF Core model'idan schema endpoint:
GET /groot/entities.
Requirements
- .NET 10
- EF Core 10
- PostgreSQL (Npgsql.EntityFrameworkCore.PostgreSQL 10.0.1+)
- ASP.NET Core (
Microsoft.AspNetCore.Appframework reference)
Groot faqat PostgreSQL'ni qo'llab-quvvatlaydi. SQL Server, SQLite, MySQL — qo'llab-quvvatlanmaydi. Multi-tenant DI scenariy ham qo'llab-quvvatlanmaydi (har bir scope'da bitta
DbContext).
Install
dotnet add package GrootUI
Paket
GrootUIdeb nomlanadi (NuGet'daGrootid band), lekin namespace o'zgarmagan — koddausing Groot;yozasiz.
Quickstart
1. DbContext'da entity'larni belgilang
using Groot;
using Microsoft.EntityFrameworkCore;
public class AppDbContext(DbContextOptions<AppDbContext> options) : DbContext(options)
{
[GrootTable]
public DbSet<Product> Products => Set<Product>();
[GrootTable]
public DbSet<Category> Categories => Set<Category>();
// [GrootTable] qo'yilmagan DbSet'lar panelda ko'rinmaydi.
public DbSet<InternalLog> Logs => Set<InternalLog>();
}
2. Entity'larni yozing
using System.ComponentModel.DataAnnotations;
using Groot;
public class Product
{
[GrootReadOnly]
public int Id { get; set; }
[Required, StringLength(200)]
[GrootSearchable]
public string Name { get; set; } = string.Empty;
public decimal Price { get; set; }
[GrootReadOnly]
public DateTime CreatedAt { get; set; }
[GrootIgnore]
public string? InternalNotes { get; set; }
}
3. Program.cs'da ulang
using Groot;
using Microsoft.EntityFrameworkCore;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddDbContext<AppDbContext>(options =>
options.UseNpgsql(builder.Configuration.GetConnectionString("DefaultConnection")));
builder.Services.AddGroot<AppDbContext, int>();
var app = builder.Build();
app.UseGroot<AppDbContext, int>();
app.Run();
<AppDbContext, int> — DbContext turi va primary key turi (int, Guid, long,
string va h.k.).
4. Ishga tushiring va kiring
dotnet run
Brauzerda oching: http://localhost:5000/groot-ui
UseGroot chaqirilganda Groot quyidagilarni qiladi:
- Environment va auth guard middleware'ini ulaydi.
- Bundled UI'ni
/groot-uiostida static fayl sifatida xizmat qiladi. - Auth endpoint'larini (
/groot/auth,/groot/login,/groot/logout) ro'yxatga oladi. GET /groot/entitiesschema endpoint'ini ro'yxatga oladi.- SignalR hub'ini
/groot/hubmanzilida ulaydi.
Migratsiya: Groot migratsiyalarni ishga tushirmaydi — sxema sizning ilovangiz mas'uliyatida. Jadval yo'q bo'lsa EF Core'ning o'zi tushunarli xato beradi.
Mark entities for the panel
Faqat [GrootTable] qo'yilgan DbSet<> propertysi panelda ko'rinadi. Bu sizga aniq nazorat
beradi: ichki audit jadvallari, identity tablesi va shu kabilar yashirin qoladi.
[GrootTable]
public DbSet<Product> Products => Set<Product>();
Attribute property level'da ishlaydi (AttributeTargets.Property), shuning uchun Set<>()
shorthand bilan bir qatorda yozilishi mumkin.
Securing your fields
Mass-assignment Groot'dagi eng muhim xavf vektor — chunki har qanday client JSON yuborib, propertyni o'rnatishga harakat qilishi mumkin. Ikkita attribute bu xavfni yopadi:
[GrootIgnore] — butunlay tashlab ketiladi
public class User
{
public int Id { get; set; }
public string Email { get; set; } = "";
[GrootIgnore]
public bool IsAdmin { get; set; } // Create/Update'da o'qilmaydi.
[GrootIgnore]
public string PasswordHash { get; set; } = ""; // Read'da yashirish uchun [JsonIgnore] qo'shing.
}
[GrootIgnore] — Create va Update'da inkor qilinadi. Read javobida property hali ham qaytadi
(uni yashirish uchun [JsonIgnore] qo'shing).
[GrootReadOnly] — server boshqaradi
public class Order
{
[GrootReadOnly] public int Id { get; set; }
[GrootReadOnly] public DateTime CreatedAt { get; set; }
[GrootReadOnly] public DateTime UpdatedAt { get; set; }
public string CustomerName { get; set; } = "";
public decimal Total { get; set; }
}
Read'da qaytariladi, Create/Update'da inkor qilinadi. Server-managed maydonlar uchun: PK, timestamps va h.k.
Ichki mexanizm: Create'da Groot yangi instance yaratadi va faqat writable propertylarni copy qiladi. Update'da mavjud yozuvni yuklab, DB holatiga qaytaradi va ustiga faqat writable propertylarni qo'yadi. Sizning ID/timestamp'laringiz hech qachon client payload'idan ta'sirlanmaydi.
Relationships
Many-to-one
FK propertysi (CategoryId) va navigation (Category) bo'lsa, UI avtomatik select ko'rsatadi
va bog'langan yozuv nomini jadval hamda details sahifasida chiqaradi.
public class Product
{
public int Id { get; set; }
public int? CategoryId { get; set; }
public Category? Category { get; set; }
}
Many-to-many va [GrootDominant]
Many-to-many bog'lanish ikkala tomondan ham ko'rinadi, lekin uni bir tomondan tahrirlash
mantiqiy. [GrootDominant] qaysi tomon egalik qilishini belgilaydi:
public class Product
{
[GrootDominant]
public List<Tag>? Tags { get; set; } // Product formasida multi-select bor
}
public class Tag
{
public List<Product>? Products { get; set; } // Tag formasida select yo'q
}
Qoida [GrootSearchable] bilan bir xil: agar hech qaysi tomon belgilanmagan bo'lsa —
ikkalasi ham tahrirlanadi. Belgilangan holatda non-dominant tomon server darajasida ham inkor
qilinadi, ya'ni qo'lda yasalgan payload orqali ham yozib bo'lmaydi.
Muhim: collection'ni nullable qilib e'lon qiling (
List<Tag>?). Groot uchunnull— "mijoz bu maydonni yubormadi, tegilmasin", bo'sh massiv esa — "barcha bog'lanishni o'chir".= []initializer bilan har bir Update kutilmaganda bog'lanishlarni tozalab yuborishi mumkin.
Validation
Standart System.ComponentModel.DataAnnotations attribute'lari avtomatik tekshiriladi. Valid
bo'lmasa — GrootValidationException tashlanadi va Hub uni client'ga HubException sifatida
uzatadi.
public class Customer
{
[GrootReadOnly] public int Id { get; set; }
[Required(ErrorMessage = "Ism majburiy")]
[StringLength(100, MinimumLength = 2)]
public string Name { get; set; } = "";
[EmailAddress, Required]
public string Email { get; set; } = "";
[Range(0, 150)]
public int Age { get; set; }
}
HubException.Message ichida struktura qilingan JSON keladi:
{
"type": "validation",
"errors": [{ "fields": ["Email"], "message": "The Email field is not a valid e-mail address." }]
}
Validation Validator.TryValidateObject(..., validateAllProperties: true) orqali ishlaydi va
Before* hook'larigacha ishga tushadi.
Search
GrootTableQuery.Search ishlatilganda Groot case-insensitive PostgreSQL ILIKE '%term%'
so'rovini ishlab chiqaradi. Qidiriladigan property'lar quyidagicha aniqlanadi:
- Agar entity'da kamida bitta
[GrootSearchable]belgilangan property bo'lsa — faqat shu property'lar qidiriladi. - Aks holda — barcha
stringproperty'lar qidiriladi.
public class Article
{
[GrootReadOnly] public int Id { get; set; }
[GrootSearchable] public string Title { get; set; } = "";
[GrootSearchable] public string Body { get; set; } = "";
public string AuthorIp { get; set; } = ""; // Qidiruvga kirmaydi
}
PostgreSQL hint: pg_trgm GIN index
Leading-wildcard %term% ILIKE'lar default B-tree index'ni ishlatmaydi. Katta jadval'lar uchun
pg_trgm GIN indeksi qo'shing:
CREATE EXTENSION IF NOT EXISTS pg_trgm;
CREATE INDEX idx_articles_title_trgm
ON "Articles" USING gin ("Title" gin_trgm_ops);
Sorting & paging
public class GrootTableQuery
{
public int Page { get; set; } = 1;
public int PageSize { get; set; } = 10;
public string? Search { get; set; }
public string? SortBy { get; set; }
public bool SortDescending { get; set; }
}
public class GrootTableResult<T>
{
public int Total { get; set; }
public List<T> Items { get; set; } = [];
}
Qoidalar:
Page < 1→1'ga clamp qilinadi.PageSize <= 0→10(default).PageSize > 100→100(max cap, abuse'dan himoya).SortBy— property nomi, case-insensitive ("name"ham,"Name"ham ishlaydi).- Faqat scalar property'lar bo'yicha tartiblanadi; navigation so'ralsa jim o'tkaziladi.
SortByberilmasa — primary key bo'yicha tartiblanadi, shunda sahifalash barqaror bo'ladi.
Lifecycle hooks
IGrootInterceptor<TEntity> interfeysini implement qilib DI orqali registratsiya qiling:
public class AuditTimestampsInterceptor : IGrootInterceptor<Order>
{
public Task BeforeCreateAsync(Order entity, CancellationToken ct)
{
entity.CreatedAt = DateTime.UtcNow;
entity.UpdatedAt = DateTime.UtcNow;
return Task.CompletedTask;
}
public Task BeforeUpdateAsync(Order entity, CancellationToken ct)
{
entity.UpdatedAt = DateTime.UtcNow;
return Task.CompletedTask;
}
}
builder.Services.AddScoped<IGrootInterceptor<Order>, AuditTimestampsInterceptor>();
Chaqiriluv tartibi:
- DataAnnotations validation muvaffaqiyatli o'tdi.
- Mass-assignment sanitization (Create) yoki property patch (Update).
BeforeCreateAsync/BeforeUpdateAsync/BeforeDeleteAsync.SaveChangesAsync/ExecuteDeleteAsync.AfterCreateAsync/AfterUpdateAsync/AfterDeleteAsync(faqat operatsiya rows ga ta'sir qilgan bo'lsa).
Hook ichida exception tashlash operatsiyani to'xtatadi va xato Hub'ga propagate qilinadi. Bir entity uchun bir nechta interceptor registratsiya qilish mumkin — barchasi navbatma-navbat chaqiriladi.
Before/AfterDeleteAsync(object id, ...) — id object sifatida qabul qilinadi.
Hub API
SignalR hub: /groot/hub.
| Metod | Argument'lar | Qaytaradi |
|---|---|---|
GetTable |
entityName: string, query: GrootTableQuery |
GrootTableResult<T> |
Get |
entityName: string, id: any |
T? |
Create |
entityName: string, data: T |
T (sanitized + saved) |
Update |
entityName: string, data: T |
T (yangilangan) |
Delete |
entityName: string, id: any |
bool |
BatchDelete |
entityName: string, ids: TKey[] |
bool |
entityName — case-insensitive CLR type name (masalan "Product").
ID JSON konvertatsiyasi avtomatik: string ham, raqam ham, GUID ham qabul qilinadi va TKey'ga
o'giriladi.
Service'dan keladigan xatolar HubException'ga map qilinadi:
GrootValidationException→ yuqoridagi JSON payload bilan.InvalidOperationException(yo'q PK, yo'q row) → message bilan.- Boshqa exceptions — xom holatda propagate.
TypeScript client (@microsoft/signalr)
import { HubConnectionBuilder } from "@microsoft/signalr";
const connection = new HubConnectionBuilder()
.withUrl("/groot/hub")
.withAutomaticReconnect()
.build();
await connection.start();
const page = await connection.invoke("GetTable", "Product", {
page: 1,
pageSize: 25,
search: "phone",
sortBy: "Price",
sortDescending: true,
});
console.log(page.total, page.items);
const created = await connection.invoke("Create", "Product", { name: "Pixel 9", price: 799 });
await connection.invoke("Update", "Product", { id: created.id, name: "Pixel 9 Pro", price: 999 });
await connection.invoke("Delete", "Product", created.id);
await connection.invoke("BatchDelete", "Product", [1, 2, 3]);
Schema endpoint
GET /groot/entities
Barcha [GrootTable] belgilangan entity'lar metadata'sini qaytaradi. UI shu endpoint'dan
kolonkalar va relations'ni quradi.
public class GrootEntityMetadata
{
public string EntityName { get; set; }
public string PrimaryKey { get; set; }
public List<GrootFieldMetadata> Fields { get; set; }
}
public class GrootFieldMetadata
{
public string Name { get; set; }
public string ClrType { get; set; } // CLR type name (Nullable unwrap qilingan)
public bool IsNullable { get; set; }
public bool IsPrimaryKey { get; set; }
public bool IsForeignKey { get; set; }
public bool IsCollection { get; set; }
public bool IsNavigation { get; set; }
public string? RelationKind { get; set; } // "one-to-one" | "one-to-many" | "many-to-one" | "many-to-many"
public string? TargetEntity { get; set; }
public string? ForeignKeyProperty { get; set; }
public string? TargetKeyProperty { get; set; }
public bool IsDominant { get; set; } // many-to-many: shu tomondan tahrirlanadimi
}
Metadata EF Core model'idan o'qiladi: context.Model.FindEntityType(...). Shadow property'lar
tashlab ketiladi. Skip-navigation'lar (many-to-many) ham qo'llab-quvvatlanadi.
Cancellation
Barcha IGrootService metodlari CancellationToken qabul qiladi. Hub har bir invocation'da
Context.ConnectionAborted token'ini service'ga forward qiladi — agar client uzilsa, EF Core
query rad qilinadi.
Task<GrootTableResult<TEntity>> GetTableAsync<TEntity>(
GrootTableQuery query,
CancellationToken cancellationToken = default) where TEntity : class;
Configuration
builder.Services.AddGroot<AppDbContext, int>(options =>
{
options.Username = "admin";
options.Password = builder.Configuration["Groot:Password"]!;
options.EnabledEnvironments = new List<string> { "Development", "Staging" };
options.SessionLifetime = TimeSpan.FromHours(8);
});
| Option | Default | Tavsif |
|---|---|---|
Username |
"" |
Login foydalanuvchi nomi. |
Password |
"" |
Login paroli. Constant-time taqqoslash. |
EnabledEnvironments |
["Development"] |
Bu ro'yxatda bo'lmagan env'da /groot/* va /groot-ui/* 404 qaytaradi. |
SessionLifetime |
8 hours |
Login'dan keyin set qilinadigan groot.session cookie umri. |
Auth optional
Username va Password ikkalasi ham bo'sh bo'lsa — auth butunlay o'chadi va panel
to'g'ridan-to'g'ri ochiladi. Bittasi to'ldirilsa — login sahifasi ko'rsatiladi.
| Endpoint | Tavsif |
|---|---|
GET /groot/auth |
{ authRequired, authenticated } — UI login kerakligini shundan biladi. |
POST /groot/login |
{ username, password }. Muvaffaqiyatda groot.session cookie o'rnatiladi. |
POST /groot/logout |
Cookie'ni o'chiradi. |
Static UI fayllari (/groot-ui/*) va shu uchta endpoint auth'dan ozod — aks holda login
sahifasining o'zi yuklana olmasdi. Barcha ma'lumot (/groot/hub, /groot/entities) esa
yopiqligicha qoladi.
Why "dev mode only"
Groot — sizning DbContext'ingiz uchun universal CRUD. Bu siz ataylab production'da ochmoqchi bo'lmasligingiz kerak bo'lgan ko'p narsani anglatadi:
- Fine-grained authz yo'q. Login bo'lgan har kim barcha
[GrootTable]jadvallarini boshqara oladi. - Audit trail yo'q. Kim nimani o'zgartirgani avtomatik yozilmaydi (lekin
IGrootInterceptorbilan o'zingiz qo'shishingiz mumkin). - Soft-delete default emas.
DeletehaqiqiyDELETEishlatadi (ExecuteDeleteAsync). - Rate-limit yo'q. Login urinishlari cheklanmaydi.
- CSRF himoyasi yo'q (SignalR hub).
Tavsiya: VPN ortida yoki internal staging'da ishlating.
Development
# UI (React + Vite). Backend'ni /groot ga proxy qiladi.
cd vue-app && npm ci && npm run build
# Backend
dotnet build Groot.slnx -c Release
# Testlar — Docker kerak (Testcontainers PostgreSQL konteyner ko'taradi).
dotnet test tests/Groot.Tests/Groot.Tests.csproj
vue-app/dist NuGet paketiga bundled UI sifatida kiradi, shuning uchun paket yasashdan oldin
uni build qilish shart.
License
MIT — qarang LICENSE.txt.
Status
- Joriy versiya: v0.1.0 (birinchi test versiya)
- Target: .NET 10 / EF Core 10 / Npgsql 10.0.1
- NuGet:
GrootUI(namespace:Groot)
Issue va PR'lar: https://github.com/Nodirbek-Abdulaxadov/Groot
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net10.0 is compatible. 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. |
-
net10.0
- Npgsql.EntityFrameworkCore.PostgreSQL (>= 10.0.1)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.