Lezione 9 – Tabelle DB per lo Shop – Prima Parte

E’ venuto il momento di inserire della definizione delle tabelle per i dati dei prodotti, categorie, condizioni, prodotti alternativi, fornitori, ecc. L’argomento è abbastanza vasto e richiederà più di una singola lezione.

Iniziamo creando nel folder Models un file “Product.cs”. La classe “Product” è nella box.


    public class Product
    {
        [Key, StringLength(32), Display(Name = "PartNumber")]
        public string Id { get; set; }

        [Display(Name = "Nome")]
        public string Name { get; set; }

        //[Required(ErrorMessage = "E' richiesto un Prezzo in Euro!")]
        [Display(Name = "Prezzo")]
        [Column(TypeName = "decimal(18,2)")]
        [DisplayFormat(DataFormatString = "{0:c}")]
        public decimal? Price { get; set; }

        [Display(Name = "Codice IVA"), StringLength(2)]
        public string TaxCodeId { get; set; }

        public int? PromoDiscount { get; set; }

        [DefaultValue("1 Pz"), StringLength(115)]
        public string QuantityPerUnit { get; set; }

        [Display(Name = "Descrizione")]
        public string Description { get; set; }

        [Display(Name = "Immagine")]
        public string Image { get; set; }

        [Display(Name = "Modificato il")]
        [Column(TypeName = "datetime2")]
        public DateTime? LastUpdated { get; set; }

        public int CategoryId { get; set; }

        public int ConditionId { get; set; }

        [DefaultValue(0)]
        public int? InStock { get; set; }

        [DefaultValue(0)]
        public int? InOrder { get; set; }

        [DefaultValue(false)]
        public bool? Unavailable { get; set; }

        [DefaultValue(0)]
        public int? MinimalInStock { get; set; }
        public int? SupplierId { get; set; }

        [Display(Name = "Voto")]
        [DefaultValue(1)]
        [Range(0, 5, ErrorMessage = "Inserire un valore di {0} da {1} a {2}!")]
        public int Quote { get; set; }

        public string Notes { get; set; }
        public int? ShippingWeight { get; set; }
        public int? ShippingLengthSum { get; set; }
    }

All’interno dei campi della classe ne troviamo diversi che contengono Id di altre classi che andremo ad aggiungere. Poi vedremo se in alcuni casi convenga creare delle relazioni rispetto a queste classi.

La prossima è appunto la classe “Category”.


public class Category
{
    [Key, ScaffoldColumn(false)]
    public int Id { get; set; }

    [Display(Name = "Nome")]
    [Required(ErrorMessage = "E' richiesto la Categoria del Prodotto")]
    [MaxLength(69, ErrorMessage = "Il Nome la Categoria deve essere lungo massimo di 69 caratteri")]
    public string Name { get; set; }

    [Display(Name = "Immagine")]
    public string Image { get; set; }
}

 “ProductAlternate”.


public class ProductAlternate
{
    [Key, ScaffoldColumn(false)]
    public int Id { get; set; }

    [StringLength(32), Display(Name = "PartNumber")]
    public string ProductId { get; set; }

    [StringLength(32), Display(Name = "Sostitutivo")]
    public string Alternate { get; set; }

}

TaxCode”.


public class TaxCode
{
    [Required, Key, StringLength(2)]
    [Display(Name = "Codice IVA")]
    public string Id { get; set; }

    [Display(Name = "Nome")]
    [Required(ErrorMessage = "E' richiesto Il Nome del Codice di Tassazione")]
    [MaxLength(46, ErrorMessage = "Il Nome del Codice di Tassazione deve essere lungo massimo di 46 caratteri")]
    public string Name { get; set; }

    [Required]
    public int Percentuale { get; set; }
}

Supplier”.


    public class Supplier
    {
        [ScaffoldColumn(false)]
        [Required, Key]
        public int Id { get; set; }

        [Required, StringLength(146), Display(Name = "Nome")]
        public string SupplierName { get; set; }

        [StringLength(15), Display(Name = "Codice Fiscale")]
        public string Fcode { get; set; }

        [StringLength(15), Display(Name = "Partita IVA")]
        public string Piva { get; set; }

        [StringLength(92), Display(Name = "REA")]
        public string Rea { get; set; }

        [StringLength(256), Display(Name = "Indirizzo")]
        public string Address { get; set; }

        [StringLength(146), Display(Name = "Città")]
        public string City { get; set; }

        [StringLength(13), Display(Name = "CAP")]
        public string PostalCode { get; set; }

        [StringLength(3), Display(Name = "Prov.")]
        public string Country { get; set; }

        [StringLength(146), Display(Name = "Stato")]
        public string State { get; set; }

        [StringLength(256), Display(Name = "Telefono")]
        public string Phone { get; set; }

        [StringLength(256), Display(Name = "Email")]
        public string Email { get; set; }

        [Display(Name = "Descrizione")]
        public string Description { get; set; }

        public string Image { get; set; }
        public string WebSiteUrl { get; set; }
    }

Aggiungiamo nella classe ApplicationDbContext i DbSet per stabilire il collegamento con le tabelle nel database e facciamo una build di verifica. Se tutto è andato bene passiamo alla Package Manager Console (PMC) e migriamo le modifiche a database con add-migration e update-database.

Quello che faremo ora è utilizzare le funzionalità di gestione della SSMS. Aprite il file “Product.xlsx”. Nel foglio “Product” troviamo una serie di righe con dati nelle celle. Selezioniamo tutte le righe del foglio clickando sul numero della riga “2” + Shift + numero della riga “31”, poi Copia.

Andiamo sulla SSMS, e dopo aver aperto il database facciamo click destro sulla tabella “Product” e selezioniamo “ Modifica le prime N righe ”.

E dopo aver selezionato la prima riga (al momento con solo null) clickando nella prima cella a sinistra,

Facciamo sulla stessa cella click destro + incolla. I dati copiati dal foglio Excel dopo una elaborazione più o meno lunga (dipende dalla velocità del Server SQL) verranno tradotti e trasferiti nella tabella del database da SSMS.

Facciamo la stessa cosa con i dati presenti negli altri fogli Excel. Adesso abbiamo diverse tabelle con dati da poter utilizzare nell’applicazione.

Introduciamo un’altra delle funzionalità incluse nel IDE di VS2022 e Entity Framework. Se dovessimo inserire Controller e View per visualizzare, modificare o cancellare record delle tabelle create per l’applicazione dovremmo fare ognuna di queste cose singolarmente e solo per una singola tabella richiederebbe almeno un Controller con relative Action e 5 View (per Creare, Cancellare, Modificare, Visualizzare il Dettaglio e una Lista). Fare il conto di quanto codice e tempo richiesti non è facile, ma VS2022 ci aiuta enormemente a risolvere e con poca fatica, solo qualche click e selezione.

Facciamo click destro sul folder “Controllers” e selezioniamo Add>Controller.

Scegliamo “MVC Controller with views, using Entity Framework”.

Nella finestra “Add” troviamo “Model class” dove possiamo selezionare la classe (tabella DB) da utilizzare, nel nostro caso selezioniamo “Banner”, il “Data context class” dovrà essere “ApplicationDBContext”, “Generate views” checked, “ Use a layout page ” checked e nella input box sotto selezionare il layout di amministrazione “_AdminLayout.cshtml”. Il “Controller name” viene generato automaticamente, potete modificarlo, ma potete evitare di perdere tempo e fare Add.

Dopo un certo numero di operazioni elencate una piccola modal verranno creati sei file, un Controller con le Action per i dati e 5 View (con relativo folder che li raccoglie).

Utilizziamo le modifiche fatte per aggiornare l’area di Admin. Nel file “_NavAdminPartial” rendiamo eseguibile (togliamo il commento) la linea con il link a “Index” del controller “Banners” e lanciamo il debug. Entriamo in Admin.

Sotto l’etichetta “Database” troviamo il link “Banner”, se clicckiamo nella parte destra della pagina avremo la lista dei record della tabella “Banner”, con le funzionalità di modifica, dettaglio e cancellazione, e in alto a sinistra “Create New” per creare un nuovo record. Al momento sono abbastanza grezze in fatto di visualizzazione, ma basteranno pochi comandi di visualizzazione Bootstrap per migliorarne l’aspetto in modo definitivo.

Facciamo quanto fatto per Banner per le altre tabelle (classi): Country; Notizia; Product; Category; ProductAlternate; TaxCode; Supplier . Alla fine la visualizzazione dovrebbe essere quella dell’immagine che segue.

Possiamo chiudere e fare delle considerazioni. La prima è che siamo entrati nell’area Admin senza nessun tipo di autenticazione e questo non può essere sicuro nella gestione di un sito di vendite online, perché chiunque potrebbe accedere a dati sensibili e pericolosi. Poi che la zona di gestione delle tabelle deve essere visualizzata meglio.

Per permettere l’accesso alla pagina di Amministrazione solo a determinati utenti dobbiamo inserire alcune “Autorizzazione” (AuthorizeAttribute) nel controller “AdminController” e “BannersController”. Prima della dichiarazione della classe Controller inserite la seguente Microsoft.AspNetCore.Authorization.


    [Authorize(Roles = "Admin,PowerUser")]
    public class BannersController : Controller
    { …

    [Authorize(Roles = "Admin,PowerUser")]
    public class AdminController : Controller
    { …

Questo permetterà l’accesso all’area Admin solo ad utenti loggati con ruolo “Admin” o “PowerUser”. Proviamo le modifiche. F5 per il debug. Se proviamo ad entrare dal menu nella pagina Admin verremo rimandati alla pagina di Login. Se proviamo ad fare login con un utente che non sia un “Admin” o “PowerUser”, per esempio con l’utente “user@local.net”, dopo l’accesso andremo in una pagina di “Access denied”. Logout e fate accesso con admin@local.net. Ancora “Access denied”. Perché? Perché non abbiamo ancora attivato i Ruoli all’interno della gestione del Identity.

Nel file “Program.cs” aggiungete alle opzioni di builder.Services.AddDefaultIdentity la gestione dei Roles modificando come segue.

builder.Services.AddDefaultIdentity<ApplicationUser>(options => options.SignIn.RequireConfirmedAccount = true)

    .AddRoles<IdentityRole>()

    .AddEntityFrameworkStores<ApplicationDbContext>();

F5 e riproviamo. Adesso possiamo accedere con admin@local.net ma non con user@local.net.

Quello che ci resta da fare per la gestione delle credenziali all’amministrazione del sito è inserire il codice all’interno del menu, per non permettere il possibile accesso di utenti indesiderati in tale area. Nel file _MenuPartial.cshtml e modifichiamolo come segue. In testa aggiungiamo delle using per utilizzare delle classi predefinite dal framework e una inject per la definizione dell’oggetto AuthorizationService. Poi più in basso una if per selezionare la tipologia (ruolo) degli utenti che posso amministrare il sito “Admin e PowerUser”.


@using Microsoft.AspNetCore.Authorization
@using System.Security.Claims
@inject IAuthorizationService AuthorizationService

<nav class="navbar navbar-expand-sm navbar-toggleable-sm navbar-light bg-white border-bottom box-shadow mb-3">
    <div class="container-fluid">
        <a class="navbar-brand" asp-area="" asp-controller="Home" asp-action="Index">FoodShop</a>
        <button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target=".navbar-collapse" aria-controls="navbarSupportedContent"
                aria-expanded="false" aria-label="Toggle navigation">
            <span class="navbar-toggler-icon"></span>
        </button>
        <div class="navbar-collapse collapse d-sm-inline-flex justify-content-between">
            <ul class="navbar-nav flex-grow-1">
                <li class="nav-item">
                    <a class="nav-link text-dark" asp-area="" asp-controller="Home" asp-action="Index">Home</a>
                </li>
                <li class="nav-item">
                    <a class="nav-link text-dark" asp-area="" asp-controller="Home" asp-action="Privacy">Privacy</a>
                </li>
                @if ((await AuthorizationService.AuthorizeAsync(User, ClaimTypes.Role, "Admin")).Succeeded ||
                 (await AuthorizationService.AuthorizeAsync(User, ClaimTypes.Role, "PowerUser")).Succeeded)
                {
                    <li class="nav-item">
                        <a class="nav-link" asp-controller="Admin" asp-action="Index">Admin</a>
                    </li>
                }
            </ul>
            <partial name="_LoginPartial" />
        </div>
    </div>
</nav>

Lanciamo il Debug per verificarne il funzionamento

L’errore che viene generato è determinato dal fatto che non abbiamo ancora “autorizzato” il ruolo “Admin” all’interno delle policy di AuthorizationService. Aggiungiamo le nuove autorizzazioni Admin e PowerUser prima della Build() del builder in Program.cs.


builder.Services.AddDefaultIdentity(options => options.SignIn.RequireConfirmedAccount = true)
    .AddRoles()
    .AddEntityFrameworkStores();
builder.Services.AddControllersWithViews();

//ETBWDDEV v
builder.Services.AddAuthorization(options =>
{
    options.AddPolicy("Admin", policy => policy.RequireRole("Admin"));
    options.AddPolicy("PowerUser", policy => policy.RequireRole("PowerUser"));
});
//ETBWDDEV ^

var app = builder.Build();

Per completare la lezione ed usufruire delle gestioni generate da Entity Framework aggiungiamo le relative linee di chiamata ai link delle pagine Index nella partial view_NavAdminPartial”.

<li><a class="nav-link" asp-controller="Banners" asp-action="Index">Banner</a></li>

<li><a class="nav-link" asp-controller="Notizias" asp-action="Index">News</a></li>
<li><a class="nav-link" asp-controller="Countries" asp-action="Index">Countries</a></li>
<li><a class="nav-link" asp-controller="Products" asp-action="Index">Products</a></li>
<li><a class="nav-link" asp-controller="Categories" asp-action="Index">Categories</a></li>
<li><a class="nav-link" asp-controller="ProductAlternates" asp-action="Index">ProductAlternates</a></li>
<li><a class="nav-link" asp-controller="Suppliers" asp-action="Index">Suppliers</a></li>
<li><a class="nav-link" asp-controller="TaxCodes" asp-action="Index">TaxCodes</a></li>

Possiamo lanciare il debug e facciamo login per accedere all’area Admin e proviamo le modifiche.

Nella prossima lezione aggiungeremo altre tabelle e cominceremo a visualizzare prodotti e immagini nella HomePage.

vs2022.png
aspnetcore.svg
entiryfrm.svg

Lezioni Food Shop

Inserite le prime lezioni
1/23/2022 12:00:00 AM

Inizia una nuova serie di Lezioni per la realizzazione di siti (Web App) per la vendita onli ...