Overview

Pattern: Framework component + Custom data

I componenti UI ad alto livello del framework (<wuic-list-grid>, <wuic-chart-list>, ...) accettano un datasource costruito a mano (hardcodedDatasource) che non passa dal data layer del framework. I dati arrivano dal tuo backend custom (Controller .NET tuo, REST esterna, file statici, websocket, ...).

Quando usarlo

  • Vuoi l'UX completa di una list-grid/chart-list (filtri, sort, paging client, export, edit dialog) ma i dati sono prodotti da:

- Un endpoint REST esterno (3rd-party API, microservizio).

- Un Controller .NET tuo non integrato col data layer del framework.

- File statici, calcoli aggregati, dati live (websocket, polling).

  • Hai un dominio legacy che non vuoi modellare nel framework.
  • Stai prototipando senza ancora aver definito la struttura dati.

Architettura

  • Sviluppatore: scrive un piccolo componente Angular che recupera i dati dal proprio backend e li impacchetta in un datasource locale.
  • Framework: la list-grid si comporta esattamente come se i dati venissero dal data layer standard (filtri, sort, paging, export funzionano).
  • Backend: liberta' totale. Endpoint REST classici, niente convenzioni di metadata.

Cosa fai tu (frontend)

Crei un componente Angular standalone che:

1. Chiama il tuo endpoint custom con HttpClient.

2. Definisce le colonne (nome, label, tipo) per il datasource locale.

3. Pubblica righe + colonne sul datasource e lo passa alla list-grid.

<!-- source: wwwroot/src/app/component/examples/pattern-3/3a-external-rest-grid/3a-external-rest-grid.component.html -->

Snippet 1HTML
<wuic-data-source #ds></wuic-data-source>
<wuic-list-grid [hardcodedDatasource]="ds" [hideToolbar]="false"></wuic-list-grid>

<!-- source: wwwroot/src/app/component/examples/pattern-3/3a-external-rest-grid/3a-external-rest-grid.component.ts -->

Snippet 2ts
import { Component, ViewChild, AfterViewInit, inject } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { BehaviorSubject } from 'rxjs';
import { DataSourceComponent, ListGridComponent, MetaInfo, MetadatiColonna, MetadatiTabella } from 'wuic-framework-lib';

@Component({
  selector: 'app-external-rest-grid',

Cosa fai tu (backend, opzionale)

Se i dati vengono dal tuo backend interno, basta un Controller REST classico. Niente convenzioni del framework, niente metadata da scrivere.

<!-- source: WuicTest/Controllers/SamplesController.cs -->

Snippet 3C#
[ApiController]
[Route("api/samples")]
public class SamplesController : ControllerBase
{
    [HttpGet("inventory")]
    public IActionResult GetInventory()
    {

Se apri tu la connessione: su Oracle porta la sessione sullo schema

Quando il controller apre una connessione propria con la connection string DataSQLConnection

(ADO.NET, Dapper, EF Core con UseOracle(...)), su Oracle c'e' un passaggio in piu' che gli

altri motori non hanno: nella stringa non c'e' nessun Database / Initial Catalog, perche' su

Oracle lo schema non fa parte della connessione. DataSQLConnection autentica soltanto; allo

schema dei dati il framework porta la sessione da solo, eseguendo

ALTER SESSION SET CURRENT_SCHEMA = <schema> all'apertura.

Una connessione aperta a mano quel passaggio non ce l'ha: resta sullo schema dell'utente con cui

si e' autenticata, e la prima query sulle tue tabelle risponde `ORA-00942: table or view does not

exist` anche se la tabella esiste e i permessi ci sono. Due modi per farlo bene:

Snippet 4C#
await using var cn = new OracleConnection(cs);
await cn.OpenAsync();

// 1) porta la sessione sullo schema dei dati (equivalente a cio' che fa il framework)
await using (var set = new OracleCommand("ALTER SESSION SET CURRENT_SCHEMA = MIO_SCHEMA", cn))
    await set.ExecuteNonQueryAsync();

Sugli altri tre motori non serve: il database sta nella connection string e la sessione ci nasce

dentro.

Trade-off

ProContro
UX wuic-list-grid con filtri/sort/paging/export gratis (client-side)Mantieni tu la coerenza tra le righe e la definizione delle colonne
Backend completamente liberoCRUD non funziona out-of-the-box: edit/insert vanno wirati a mano sul tuo backend
Buono per integrazioni 3rd-partyPaging/sort/filter server-side richiede wiring custom (vedi sotto)
Nessun lavoro di scaffoldingType safety solo via cast

Filtri / sort / paging: client-side vs server-side

La frase "filtri/sort/paging/export gratis" della tabella Trade-off vale solo in modalita' client-side, ed e' subordinata a un flag metadata che va impostato esplicitamente nel datasource hardcoded.

Il flag chiave: md_server_side_operations

md_server_side_operations (proprieta' di MetadatiTabella) controlla dove vengono eseguite paging/sort/filter:

ValoreSignificatoQuando usarlo
true (default)La list-grid invia gli eventi paging/sort/filter al backend tramite l'endpoint CRUD standard del framework. Il backend ritorna solo la pagina richiesta e applica sort/filter SQL-side.Pattern 1 e 2 (con route metadata reale e backend WUIC dietro).
falseLa list-grid esegue paging/sort/filter in-memory sull'array gia' caricato. Niente roundtrip server.Sempre nei datasource hardcoded del Pattern 3 (e ogni volta che pubblichi tutte le righe in un colpo solo via fetchInfo$.next).

Trappola tipica del Pattern 3: se dimentichi di forzare md_server_side_operations: false, la list-grid mostra le 50/100 righe ricevute, ma cliccare la pagina 2, ordinare una colonna o digitare nel filtro non fa niente — la grid invia l'evento al "backend del framework" che non esiste, e l'UX appare bloccata pur senza errori in console.

> Nota framework: DataSourceComponent.fetchData() rileva automaticamente il caso "hardcoded datasource" (nessun [hardcodedRoute] impostato) e salta la chiamata al backend ad ogni cambio paging/sort/filter, ripubblicando il payload gia' presente in memoria su fetchInfo$. Significa che, una volta popolato fetchInfo$.next(...) la prima volta nel tuo ngAfterViewInit, paging/sort/filter funzionano client-side senza nessun roundtrip server, anche se il backend WUIC non ha registrato la route. Vedi data-source.component.ts (short-circuit dentro fetchData()).

Modalita' client-side (default consigliato per Pattern 3)

Snippet 5ts
const meta = new MetaInfo();
// new MetadatiTabella() per ereditare i default (md_sortable, ecc.).
const tableMeta = new MetadatiTabella();
tableMeta.md_server_side_operations = false;  // <-- chiave: forza in-memory
tableMeta.md_pageable = true;                  // abilita paginazione UI
tableMeta.md_pagesize = 10;                    // righe per pagina
meta.tableMetadata = tableMeta;
  • Carichi tutte le righe con un singolo fetchInfo$.next.
  • La list-grid applica filtri/sort/paging/export sull'array gia' presente.
  • Zero codice aggiuntivo.
  • Indicato per dataset piccoli/medi (ordine di qualche migliaio di righe).

Modalita' server-side (wiring manuale)

Per dataset grandi (decine/centinaia di migliaia di righe) non vuoi caricare tutto in memoria. Lasci md_server_side_operations: true (default), ti sottoscrivi agli @Output di `<wuic-list-grid>` (onPaging, onSorting, onFiltering) e re-chiami il tuo endpoint REST a ogni cambio di stato. La list-grid aggiorna ds.currentPage / pageSize / sortInfo / filterInfo prima di emettere l'evento, quindi nel handler basta leggere lo stato corrente.

Snippet 6ts
import { Component, ViewChild, AfterViewInit, inject } from '@angular/core';
import { HttpClient, HttpParams } from '@angular/common/http';
import { BehaviorSubject } from 'rxjs';
import { DataSourceComponent, ListGridComponent, MetaInfo, MetadatiColonna, MetadatiTabella, WtoolboxService } from 'wuic-framework-lib';

interface InventoryResponse { rows: any[]; total: number; }

Endpoint server complementare (esempio C#, vedi SamplesController.GetInventory):

Snippet 7C#
[HttpGet("inventory")]
public IActionResult GetInventory(
    int offset = 0,
    int limit = 10,
    string? sortField = null,
    string? sortDir = "asc",
    string? filterField = null,

Punti chiave:

  • Gli @Output (onPaging) / (onSorting) / (onFiltering) di <wuic-list-grid> espongono gli eventi UI dopo che il list-grid handler ha gia' aggiornato lo stato del datasource. Niente da reimplementare: leggi ds.currentPage, ds.pageSize, ds.sortInfo[0], ds.filterInfo.filters[0].
  • Il backend deve ritornare { rows, total } dove total e' il count POST-filter / PRE-page. Senza questo il pager UI non sa quante pagine esistono e non funziona correttamente.
  • L'operatore di filtro arriva nel campo operatore della filter entry ('eq', 'contains', 'startswith', ecc., vedi tabella matchMode in List Grid). Mappalo coerentemente lato server.

Variante: consumare l'endpoint OData del framework

Se l'entita' che vuoi visualizzare e' gia' esposta dal framework come entity set OData (/odata/<EntitySet>), non serve scrivere NESSUN controller: basta tradurre lo stato UI della list-grid in query string OData v4 standard.

> Alternativa 100% framework-driven (Pattern 1 con backend OData): se accetti di registrare una route metadata standard per l'entita', puoi configurare md_props_bag.endpoint = {"type":"odata","uri":"/odata/Cities"} e il datasource fa tutto da solo (filter/sort/paging/export) tramite il provider OData interno. Niente codice Angular custom. Vedi OData per il setup completo. Pattern 3 (questa pagina) si applica invece quando vuoi controllo esplicito lato frontend o non hai metadata registrati per l'entita'.

Il framework espone DataProviderOdataService.filterInfoToOdata(filterInfo, entitySetName) che fa tutto il mapping operator WUIC -> $filter OData (contains/startswith/endswith/eq/ne/gt/ge/lt/le) con quoting automatico per string/numeric, supporto nested filter groups (AND/OR ricorsivi) e isnull/isnotnull. Il return e' una URL relativa tipo /odata/Cities?$filter=<espressione encoded>. Ti basta prefissare la base URL e aggiungere $top / $skip / $orderby.

Snippet 8ts
import { Component, ViewChild, AfterViewInit, inject } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { BehaviorSubject, forkJoin } from 'rxjs';
import {
  DataProviderOdataService,
  DataSourceComponent, ListGridComponent,
  FilterInfo, MetaInfo, MetadatiColonna, MetadatiTabella,

> Nota su total/count: se l'endpoint OData che usi e' configurato per ritornare il wrapper OData standard { value: [...], "@odata.count": N } (via $count=true), puoi leggere il total direttamente dalla response senza la seconda query. L'endpoint del framework WUIC attualmente ritorna il plain array e richiede la query parallela.

Esempi vivi nel WuicTest

I tre esempi coprono le tre strategie principali del Pattern 3:

  • External REST grid (client-side) → carica TUTTI i 50 post da un endpoint esterno (jsonplaceholder.typicode.com/posts) in un colpo solo, paging/sort/filter applicati in-memory dalla list-grid (md_server_side_operations: false). Cartella sorgente: wwwroot/src/app/component/examples/pattern-3/3a-external-rest-grid/. Apri demo.
  • Custom .NET grid (server-side, REST custom) → chiama il Controller SamplesController.GetInventory con offset/limit/sort/filter come query params ad hoc, ricarica solo la pagina corrente ad ogni cambio (md_server_side_operations: true + wiring esplicito su (onPaging)/(onSorting)/(onFiltering)). Cartella sorgente: wwwroot/src/app/component/examples/pattern-3/3b-custom-dotnet-grid/ + Controllers/SamplesController.cs. Apri demo.
  • OData Cities grid (server-side, standard OData v4) → consuma l'endpoint OData generico del framework (GET /odata/Cities) con query string standard $top / $skip / $filter / $orderby, nessun controller custom da scrivere. Traduce gli eventi UI della list-grid in query OData (contains(name,'v'), field eq value, ecc.). Cartella sorgente: wwwroot/src/app/component/examples/pattern-3/3c-odata-cities-grid/. Apri demo.

Quando scegliere quale variante

EsempioStrategiaBackendQuando usarla
3aClient-sideEndpoint REST classico che ritorna un arrayDataset piccolo-medio (< qualche migliaio di righe), semplicita' massima, API 3rd-party senza controllo server-side
3bServer-side REST customController REST tuo con query params di paging/sort/filterDataset grande, vuoi controllo totale sulla query; l'end-dev ha gia' un endpoint esistente con offset/limit/ecc.
3cServer-side ODataEndpoint OData del framework (/odata/<EntitySet>)Dataset grande esposto automaticamente dal framework come OData set; zero codice backend; sintassi standard compatibile con altri client

Vedi anche