ChainAPI.Library 4.0.0

dotnet add package ChainAPI.Library --version 4.0.0
NuGet\Install-Package ChainAPI.Library -Version 4.0.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="ChainAPI.Library" Version="4.0.0" />
For projects that support PackageReference, copy this XML node into the project file to reference the package.
paket add ChainAPI.Library --version 4.0.0
#r "nuget: ChainAPI.Library, 4.0.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.
// Install ChainAPI.Library as a Cake Addin
#addin nuget:?package=ChainAPI.Library&version=4.0.0

// Install ChainAPI.Library as a Cake Tool
#tool nuget:?package=ChainAPI.Library&version=4.0.0

ChainAPI.Library

WHat is ChainAPI.Library

La libreria ChainAPI.Library può essere utilizzata per ottenere una costruzione semplice e veloce di un progetto WebApi netcore 6.0. Viene fornita la possibilità di creare i propri endpoint in maniera strutturata, in lettura o in scrittura, per le entità di cui si necessità.

La struttura di un API di accesso ai dati è la seguente:

Controller → Service → Context

La libreria fornisce un progetto API con le seguenti funzionalità:

  • Contesto personalizzato
  • Database Seeder
  • Servizi di accesso ai dati base personalizzabili
  • Controllers base personalizzabili
  • Sistema di autenticazione presente con Identity e Cookie authentication, da collegare a un controller, personalizzabile
  • Customizzazione Utente e Ruolo per gestione Identity

Getting started

Aggiungere la DLL di ChainAPI.Library al proprio progetto WebApi netcore 6.0.

Contesto

Per prima cosa, costruire il contesto della propria applicazione, si dovrà estendere il contesto base della libreria, ovvero ChainContext, a cui si dovrà passare come tipo il nuovo contesto per applicare correttamente il tipo delle opzioni del contesto:

public class AppContext : ChainContext<AppContext>
{

}

Successivamente generare il costruttore:

public class AppContext : ChainContext<AppContext>
{
    public AppContext(DbContextOptions<AppContext> options, UserInformation userInformation) : base(options, userInformation)
    {
    }
}

Possiamo successivamente effettuare l'override del metodo OnModelCreating per configurare le tabelle dell'applicazione, ne configureremo una di prova creando l'oggetto Prodotto, gli oggetti devono estendere la classe base della entità, ovvero BaseModel, che contiene 3 proprietà:

  • Id (int)
  • TsCreation (DateTime)
  • TsLastUpdate (DateTiime)
public class Prodotto : BaseEntity
{
    public string Nome { get; set; }
    public string Descrizione { get; set; }
    public decimal Prezzo { get; set; }
}

Aggiungiamo il DbSet della nostra entità al contesto:

public DbSet<Prodotto> Prodotti { get; set; }

e effettuiamo la configurazione in OnModelCreating, il contesto si presenterà così:

public class AppContext : ChainContext<AppContext>
{
    public AppContext(DbContextOptions<AppContext> options, UserInformation userInformation) : base(options, userInformation)
    {
    }

    public DbSet<Prodotto> Prodotti { get; set; }

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        base.OnModelCreating(modelBuilder);

        modelBuilder.Entity<Prodotto>(Entity =>
        {
            ConfigureBaseEntity(Entity);
            Entity.Property(d => d.Nome);
            Entity.Property(d => d.Descrizione);
            Entity.HasIndex(d => d.Nome).IsUnique();
        });
    }
}

ConfigureBaseEntity è un metodo che aiuta nella configurazione delle entità che estendono la classe precedentemente nominata BaseEntity, configurando la tabella, e le 3 proprietà sopra citate.

Seeder

Il Seeder è la classe destinata al popolamento dei dati iniziali di cui ha bisogno l'applicativo all'interno del database.

Costruiamo il Seeder per il nostro contesto AppContext:

public class AppDbSeeder : ChainDbSeeder<AppContext>
{

}

e generiamo il costruttore:

public class AppDbSeeder : ChainDbSeeder<AppContext>
{
    public AppDbSeeder(AppContext ctx, string connectionStringDefault) : base(ctx, connectionStringDefault)
    {
    }
}

sono presenti due metodi:

  • AddUserSafe
  • AddRoleSafe che permettono un inserimento di un Utente e di un Ruolo. La dicitura Safe indica che viene prima verificata la presenza dell'oggetto che si vuole inserire prima di inserirlo, in modo che non vengano più generati dal secondo avvio del progetto in poi.

Creiamo all'interno della classe quindi un nostro metodo per inserire i prodotti, AddProdottoSafe:

public class AppDbSeeder : ChainDbSeeder<AppContext>
{
    public AppDbSeeder(AppContext ctx, string connectionStringDefault) : base(ctx, connectionStringDefault)
    {
    }
    
    public void AddProductSafe(Prodotto entity)
    {
        var entityFound = _context.Prodotti.FirstOrDefault(u => u.Nome.ToLower() == entity.Nome.ToLower());
        if (entityFound != null) return;

        _context.Prodotti.Add(entity);
        _context.SaveChanges();
    }
}

Effettuiamo quindi l'override del metodo Seed dove inseriremo un nostro prodotto:

public class AppDbSeeder : ChainDbSeeder<AppContext>
{
    public AppDbSeeder(AppContext ctx, string connectionStringDefault) : base(ctx, connectionStringDefault)
    {
    }

    public override void Seed()
    {
        base.Seed();

        AddProductSafe(new Prodotto() { Nome = "Prodotto 1", Descrizione = "Descrizione prodotto 1", Prezzo = 18.92m });
    }
    
    public void AddProductSafe(Prodotto entity)
    {
        var entityFound = _context.Prodotti.FirstOrDefault(u => u.Nome.ToLower() == entity.Nome.ToLower());
        if (entityFound != null) return;

        _context.Prodotti.Add(entity);
        _context.SaveChanges();
    }
}

Service

Per costruire il servizio sarà necessario creare degli altri oggetti, come l'entità che verrà utilizzata come risposta per le API dell'entità, costruiamo quindi ProdottoResponse:

public class ProdottoResponse : Prodotto
{
}

In questo modo la response avrà le stesse caratteristiche dell'entità Prodotto, ma volendo ChainAPI.Library utilizza AutoMapper per il mapping di queste entità e possono essere personalizzate. Costruiamo la classe che si occupa del mapping delle entità e delle response delle API:

public class AppMappingProfile : MappingProfile
{
    public AppMappingProfile() : base()
    {
        CreateMap<Prodotto, ProdottoResponse>();
        CreateMap<ProdottoResponse, Prodotto>();
    }
}

MappingProfile è la classe base dove vengono effettuate le operazioni di mapping base della libreria.

Costruiamo solo più il filtro e poi possiamo costruire il servizio, il filtro ha lo scopo di contenere le proprietà che saranno utilizzate per filtrare i dati nell'API di ricerca dell'entità, in questo caso dei prodotti:

public class ProdottoFilter : BaseFilter
{
    public string Nome { get; set; }
    public string Descrizione { get; set; }
}

Con questa versione della libraria non è importante che le proprietà del filtro corrispondano con le proprietà dell'entità.

Ora possiamo costruire il servizio per la nostra entità Prodotto e generiamo il costruttore:

public class ProdottiService : BaseServiceWrite<AppContext, ProdottoResponse, int, Prodotto, ProdottoFilter>
{
    public ProdottiService(AppContext dbContext, IMapper mapper) : base(dbContext, mapper)
    {
    }
}

A questo punto, il servizio avrà tutti i metodi base configurati per la classe (ovvero la tabella del database).

E con il Controller seguente, ci saranno anche gli Endpoint:

[Route("api/[controller]")]
[ApiController]
[Authorize(AuthenticationSchemes = CookieAuthenticationDefaults.AuthenticationScheme)]

public class ProdottiController : BaseWriteController<AppContext, ProdottoResponse, ProdottiService, int, ProdottoFilter>
{
    public ProdottiController(ProdottiService prodottiService) : base(prodottiService)
    {
    }
}

Ecco gli endpoint:

  • POST api/Prodotti (Prodotto)
  • PUT api/Prodotti/{id} (int)
  • DELETE api/Prodotti/{id} (int)
  • GET api/Prodotti/{id} (int)
  • GET api/Prodotti/Find (ProdottoFilter)
  • GET api/Prodotti/Count (ProdottoFilter)

Autenticazione

Come scritto in precedenza, il sistema di login è già pronto, deve solo essere applicato a un nuovo controller in modo che venga correttamente utilizzato il contesto dell'applicazione, nel seguente modo:

public class AccountController : AccountController<AppContext>
{
    public AccountController(UserManager<User> userManager, AppContext context, IMapper mapper) : base(userManager, context, mapper)
    {
    }
}

Ora ci saranno anche i seguenti endpoint:

  • POST /Account/New (NewAccountRequest)
  • POST /Account/Start (AuthenticationRequest)
  • GET /Account/Check
  • POST /Account/Exit

Startup

Creare un file Startup e configurarlo nella seguente maniera:

public class Startup
{
    public IConfiguration Configuration { get; }
    private readonly IWebHostEnvironment _env;

    public Startup(IConfiguration configuration, IWebHostEnvironment env)
    {
        Configuration = configuration;
        _env = env;
    }

    // This method gets called by the runtime. Use this method to add services to the container.
    public void ConfigureServices(IServiceCollection services)
    {
        services.AddChain<AppContext, AppDbSeeder, ProdottiService>(Configuration, _env, BuildContext, BuildSeeder);
    }

    // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
    public void Configure(IApplicationBuilder app, IWebHostEnvironment env, AppContext dbContext, AppDbSeeder dbSeeder)
    {
        app.UseChain<AppContext, AppDbSeeder>(env, dbContext, dbSeeder, Configuration);
    }

    public AppContext BuildContext(IServiceProvider sp)
    {
        string connectionString = Configuration.GetConnectionString(ChainSettings.Instance.ConnectionString);
        var userInformation = sp.GetRequiredService<UserInformation>();

        var optionsBuilder = new DbContextOptionsBuilder<AppContext>();
        var se = ServerVersion.AutoDetect(connectionString);
        optionsBuilder.UseMySql(connectionString, se);
        optionsBuilder.EnableSensitiveDataLogging();

        return new AppContext(optionsBuilder.Options, userInformation);
    }

    public AppDbSeeder BuildSeeder(IServiceProvider sp) => 
        new AppDbSeeder(sp.GetRequiredService<AppContext>(), ChainSettings.Instance.ConnectionString);
}

Il database per questo esempio è quello di MySql, ma può essere utilizzato ogni Sql provider.

Personalizzazione endpoint autenticazione

Se si intendono modificare gli endpoint delle API relative all'autenticazione, si può costruire il Controller in questa maniera:

[Route("User")]
public class AccountController : AccountController<ChainAPIContext>
{
    public AccountController(UserManager<User> userManager, ChainAPIContext chainContext, IMapper mapper) : base(userManager, chainContext, mapper)
    {
    }

    [HttpPost("Register")]

    public override Task<IActionResult> New(RegistrationRequest request)
    {
        return base.New(request);
    }

    [HttpPost("Login")]
    public override Task<IActionResult> Start(AuthenticationRequest input)
    {
        return base.Start(input);
    }

    [HttpGet("CheckAuthentication")]
    public override Task<IActionResult> Check()
    {
        return base.Check();
    }

    [HttpPost("Logout")]
    public override Task<IActionResult> Exit()
    {
        return base.Exit();
    }
}

Personalizzazione utente e ruolo

E' possibile personalizzare l'utente e il ruolo utilizzato da Identity per l'autenticazione.

Per prima cosa creare le classi custom Utente e Ruolo:

public class Utente : User
{
    public string SomeInfo { get; set; }
}

public class Ruolo : Role {
    public const string COLLABORATOR = "COLLAB";
}

Le classi User e Role sono le classi base della libreria utilizzate se non vengono personalizzate, e si presentano come segue:

public class User : IdentityUser
{
}

public class Role : IdentityRole
{
    public const string ADMIN = "Admin";
    public const string USER = "User";
}

Successivamente è necessario aggiungere le due classi nella configurazione della libreria in Startup:

services.AddChain<AppContext, AppDbSeeder, ProdottiService, Utente, Ruolo>(Configuration, _env, BuildContext, BuildSeeder);
...
app.UseChain<AppContext, AppDbSeeder, Utente, Ruolo>(env, dbContext, dbSeeder, Configuration);

e poi aggiornare tutti gli oggetti personalizzati:

  • Contesto
  • Seeder
  • Servizi
  • Controller

Contesto:

public partial class AppContext : ChainContext<TimeSheetContext, Utente, Ruolo>
{
    public AppContext(DbContextOptions<AppContext> options, UserInformation userInformation) : base(options, userInformation)
    {
    }

Seeder:

public class AppDbSeeder : ChainDbSeeder<AppContext, Utente, Ruolo>
{
    public AppDbSeeder(AppContext ctx, string connectionStringDefault) : base(ctx, connectionStringDefault)
    {
    }

I Servizi devono essere tutti modificati:

public class ProdottiService : BaseServiceWrite<ChainAPIContext, ProdottoResponse, int, Prodotto, ProdottoFilter, Utente, Ruolo>

Stessa cosa per i Controller:

public class ProdottiController : BaseWriteController<ChainAPIContext, ProdottoResponse, ProdottiService, int, ProdottoFilter, Utente, Ruolo>
Product Compatible and additional computed target framework versions.
.NET net6.0 is compatible.  net6.0-android was computed.  net6.0-ios was computed.  net6.0-maccatalyst was computed.  net6.0-macos was computed.  net6.0-tvos was computed.  net6.0-windows was computed.  net7.0 was computed.  net7.0-android was computed.  net7.0-ios was computed.  net7.0-maccatalyst was computed.  net7.0-macos was computed.  net7.0-tvos was computed.  net7.0-windows was computed.  net8.0 was computed.  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. 
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
4.0.0 407 5/3/2022
3.2.2 371 5/3/2022
3.2.1 341 5/3/2022
3.2.0 363 5/2/2022
3.1.0 356 5/2/2022
3.0.0 358 5/2/2022
2.3.0 366 5/2/2022
2.2.0 354 5/2/2022
2.1.0 362 5/2/2022
2.0.0 355 5/2/2022
1.0.0 370 5/2/2022