Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

20 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

TinyCMS

TinyCMS è un sistema di gestione dei contenuti leggero e modulare basato su .NET, progettato per essere facilmente estendibile e personalizzabile.

Caratteristiche principali

  • Architettura modulare che consente di aggiungere o rimuovere funzionalità secondo necessità.

Esempi di utilizzo

Richiesta paginata con filtro e ordinamento:

var page = new PageRequest(Page: 1, PageSize: 10);
Expression<Func<Post, bool>> filter = p => p.IsPublished && p.Title.Contains("dotnet");
Func<IQueryable<Post>, IOrderedQueryable<Post>> orderBy = q => q.OrderByDescending(p => p.PublishedAt);

var result = await _postRepository.GetPagedAsync(
    pageRequest: page,
    predicate: filter,
    orderBy: orderBy,
    selector: p => new PostDto { Id = p.Id, Title = p.Title, Slug = p.Slug },
    includes: p => p.Author);

Filtri dinamici più potenti

  • Specification pattern (ISpecification) per incapsulare criteri, include, ordinamento.
  • Dynamic LINQ / System.Linq.Dynamic.Core per espressioni testuali (es. sort by string).
  • Costruzione di Expression a runtime usando Expression.AndAlso/OrElse per combinare più filtri.

Spec (esempio minimale)

public interface ISpecification<T>
{
    Expression<Func<T,bool>>? Criteria { get; }
    List<Expression<Func<T,object>>> Includes { get; }
    Func<IQueryable<T>, IOrderedQueryable<T>>? OrderBy { get; }
}

Poi un metodo ApplySpecification che trasforma IQueryable.

Unit of Work / Transactions

  • Puoi usare direttamente DbContext come UoW: SaveChangesAsync() o SaveChanges in a transaction scope.
  • Se vuoi separare, crea IUnitOfWork con CommitAsync() che chiama _db.SaveChangesAsync().

Registrazione in dependency injection

services.AddDbContext<MyDbContext>(options => options.UseSqlServer(connectionString));
services.AddScoped(typeof(IGenericRepository<,>), typeof(GenericRepository<,>));

Sintesi rapida

  • Architettura: .NET 8, EF Core 8, Repository generico + (opzionale) UnitOfWork o affidarsi al DbContext, DTOs, AutoMapper, CQRS per operazioni complesse.
  • Repository: operazioni CRUD asincrone, metodi per count/exists, GetPagedAsync con filtro dinamico (Expression<Func<T,bool>> o Specification), ordinamento dinamico, include dinamici, projection (Select).
  • Paginazione: PageRequest + PagedResult.
  • Filtri dinamici: Expression, Specification pattern, o costruzione di Expression a runtime (Dynamic LINQ).
  • Best practices: non esporre IQueryable fuori dal livello repository (se non consapevole), usare projection per evitare overfetch, soft-delete e audit, caching se necessario.

Note su ListAsync/GetPagedAsync:

  • selector permette projection (DTO) usando Select -> molto importante per evitare N+1 o overfetch.
  • predicate è Expression<Func<TEntity,bool>>: puoi costruirlo dinamicamente usando Expression combinators o Specification.
  • orderBy è Func<IQueryable, IOrderedQueryable> per supportare ordini complessi, ad es. q => q.OrderByDescending(x=>x.UpdatedAt)

Considerazioni pratiche e best practices

  • Non esporre IQueryable oltre il repository a meno che il chiamante non conosca il DbContext (evita leakage di implementazione).
  • Usare projection (Select) per DTO nelle query pubbliche.
  • Implementare soft delete e campi di audit (CreatedAt, UpdatedAt, DeletedAt, CreatedBy).
  • Aggiungere caching per endpoint costosi (Redis) e caching lato query.
  • Test: unit test con repository mock o in-memory provider; integration test con dockerized DB.
  • Migrations: EF Core migrations e strategie di deployment.
  • Sicurezza: autenticazione (JWT / Identity) e autorizzazioni per l’admin.
  • File/media: storage su blob (Azure, S3) e servizi per upload/thumbnail.
  • Performance: evitare Include multipli non necessari, usare projection e paginazione server-side.

Feature CMS aggiuntive da considerare

  • Gestione contenuti: pagine, articoli, categorie, tag, media, blocchi componibili.
  • Versioning e draft/publish workflow.
  • Rich text editor + sanitizzazione HTML.
  • Multilingua/localizzazione.
  • Roles, permissions e audit per operazioni (who changed, what and when).
  • Webhooks per integrazione esterne.
  • GraphQL opzionale per flessibilità query.

Registrazione DI:

  • In .AddProjectPersistence ho usato services.AddFusionCache() e AddStackExchangeRedisCache(...) — FusionCache può essere configurata per usare la cache distribuita (IDistributedCache) per sincronizzare tra istanze.
  • Se vuoi la cache distribuita, configura FusionCache per usare la Distributed cache (vedi documentazione FusionCache per l'opzione UseDistributedCache / DistributedCacheProvider).

Invalidazione:

  • Invalidate esplicitamente la cache quando modifichi/soft-delete/restore/hard-delete le entità.
  • Strategie di invalidazione: per semplicità rimuovere la chiave id-based dopo Update/SoftDelete/Remove. Per liste/pagine puoi usare chiavi derivate o prefissi e ClearByPrefix (se disponibile) o usare cache short TTL per pagine.

TTL e coerenza:

  • Per contenuti CMS puoi usare TTL medio/breve (es. 30s–5min) e caching distribuito per ridurre incoerenze cross-instance.
  • Se usi FusionCache con DistributedCache attivo, FusionCache gestirà fallback e sincronizzazione.

Audit:

  • Qui settiamo CreatedAt/UpdatedAt/DeletedAt nel repository; per assegnare CreatedBy/UpdatedBy/DeletedBy puoi popolare questi valori in service layer usando il current user id (es. IHttpContextAccessor).
  • Considera l'uso di SaveChanges override nel DbContext per impostare automaticamente i campi di audit (più centralizzato).
  • QueryOptions.IncludeDeleted ti permette di richiedere anche le entità soft-deleted quando necessario (per admin/restore).

Global Query Filter:

  • In alternativa alle filtrature nel repository, puoi definire HasQueryFilter(p => !p.IsDeleted) su ogni entità; se fai così, ricorda come bypassare il filtro per operazioni di restore (IgnoreQueryFilters()).

Esempio d'uso veloce in controller/service

Recuperare pagina pubblicata usa:

repository.GetPagedAsync(..., predicate: p => p.IsPublished && !p.IsDeleted, selector: ...).

Soft delete:

var p = await repo.GetByIdAsync(id); await repo.SoftDeleteAsync(p, currentUserId); await dbContext.SaveChangesAsync();

About

No description, website, or topics provided.

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages