GrootUI 0.1.0

There is a newer version of this package available.
See the version list below for details.
dotnet add package GrootUI --version 0.1.0
                    
NuGet\Install-Package GrootUI -Version 0.1.0
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="GrootUI" Version="0.1.0" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="GrootUI" Version="0.1.0" />
                    
Directory.Packages.props
<PackageReference Include="GrootUI" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add GrootUI --version 0.1.0
                    
#r "nuget: GrootUI, 0.1.0"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package GrootUI@0.1.0
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=GrootUI&version=0.1.0
                    
Install as a Cake Addin
#tool nuget:?package=GrootUI&version=0.1.0
                    
Install as a Cake Tool

<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 DbContext ustida avtomatik CRUD UI.

NuGet License .NET

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] belgilangan DbSet uchun.
  • 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> orqali Before/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.App framework 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 GrootUI deb nomlanadi (NuGet'da Groot id band), lekin namespace o'zgarmagan — kodda using 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-ui ostida static fayl sifatida xizmat qiladi.
  • Auth endpoint'larini (/groot/auth, /groot/login, /groot/logout) ro'yxatga oladi.
  • GET /groot/entities schema endpoint'ini ro'yxatga oladi.
  • SignalR hub'ini /groot/hub manzilida 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 uchun null — "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.

GrootTableQuery.Search ishlatilganda Groot case-insensitive PostgreSQL ILIKE '%term%' so'rovini ishlab chiqaradi. Qidiriladigan property'lar quyidagicha aniqlanadi:

  1. Agar entity'da kamida bitta [GrootSearchable] belgilangan property bo'lsa — faqat shu property'lar qidiriladi.
  2. Aks holda — barcha string property'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 < 11'ga clamp qilinadi.
  • PageSize <= 010 (default).
  • PageSize > 100100 (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.
  • SortBy berilmasa — 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:

  1. DataAnnotations validation muvaffaqiyatli o'tdi.
  2. Mass-assignment sanitization (Create) yoki property patch (Update).
  3. BeforeCreateAsync / BeforeUpdateAsync / BeforeDeleteAsync.
  4. SaveChangesAsync / ExecuteDeleteAsync.
  5. 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 IGrootInterceptor bilan o'zingiz qo'shishingiz mumkin).
  • Soft-delete default emas. Delete haqiqiy DELETE ishlatadi (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 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. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
0.1.2 95 7/31/2026
0.1.1 87 7/31/2026
0.1.0 92 7/31/2026