Designbest Core Sviluppo: differenze tra le versioni
| Riga 226: | Riga 226: | ||
Utilizziamo il concetto di '''selezione''' per definire gruppi di prodotti (oppure negozi ecc) al fine di poterli richiamare in quei moduli che attualmente hanno una logica ''circostanziale'' spesso gestiti con xml.<br/> | Utilizziamo il concetto di '''selezione''' per definire gruppi di prodotti (oppure negozi ecc) al fine di poterli richiamare in quei moduli che attualmente hanno una logica ''circostanziale'' spesso gestiti con xml.<br/> | ||
In questo modo possiamo creare N selezioni tramite il backoffice da usare nei casi più svariati. | In questo modo possiamo creare N selezioni tramite il backoffice da usare nei casi più svariati. | ||
=== Importazione dati === | |||
Eseguire nell'ordine | |||
* WM_CategorySynchCore | |||
* WM_ProductPropertySynchCore | |||
* WM_ShopNetSynchCore | |||
* WM_ProductPropertySynchCore | |||
* WM_PicturesSynchCore | |||
* Eseguire script Python '''DesignbestBargainMigration.py''' | |||
* Eseguire script Python '''DesignbestShopCoverAltMigration.py''' | |||
* Eseguire la query SQL per generare il file batch che sposterà i file | |||
<div class="mw-collapsible mw-collapsed" style="border:solid 1px;padding:2rem;"> | |||
<syntaxhighlight lang="sql"> | |||
SELECT | |||
'IF EXIST "' + old_PicturePath + '" (', | |||
'xcopy /y "' + old_PicturePath + '" ' + '"D:\WM3Resources\immagini\' + EntityValue + '-'+ CAST(EntityID AS VARCHAR(10)) + '-' + CAST(ID AS VARCHAR(10)) + '.jpg*"', | |||
') ELSE ( echo ' + CAST(ID AS VARCHAR(10)) + ' BigPath >> missingimages.txt )' | |||
FROM Picture | |||
</syntaxhighlight > | |||
</div> | |||
== Progetto VS == | == Progetto VS == | ||
Versione delle 08:56, 20 apr 2022
Goal
Realizzare una versione agile e super performante di Designbest usando le ultime tecnologie Microsoft disponibili (.NET Core 6).
Database
Accorpamento database:
- Designbest
- Trovaprodotti
Tabelle
- Language
- LocalizedProperty
- Category
- ManufacturerContractType
- Manufacturer
- Product
- ProductCulture
- ProductBargain
- ProductBargainCulture
- ProductProperty
- ProductPropertyValue
- Mapping_Category_ProductProperty
- Mapping_Product_ProductPropertyValue
- ShopPointProperty
- ShopPointPropertyValue
- Mapping_ShopPoint_ShopPointPropertyValue
- Agent
- ShopNetStatus
- ShopNet
- ShopPointVisibilityType
- ShopPoint
- Trovaprodotti
- TrovaprodottiCulture
- Region
- Province
- City
- Picture
- Customer
- Mapping_Customer
- User
- UserUnregistered
- Wishlist
- Mapping_Wishlist_Product
- Selection
- Mapping_Selection_Product
Popolamento Region/Province/City
Attualmente abbiamo importato i dati sporchi così come sono con la seguente query
INSERT INTO DesignbestCore.dbo.Region
SELECT Region.ID,
CASE
WHEN Region.CountryID = 3 THEN 1
WHEN Region.CountryID = 2 THEN 3
WHEN Region.CountryID = 4 THEN 4
WHEN Region.CountryID = 1000 THEN 2
END AS LanguageID,
Region.Coordinates,
Region.LocalizerCode,
RegionCulture.Region AS [Name],
LOWER(RegionCulture.SEORegion) AS SeoName
FROM Region
INNER JOIN RegionCulture ON Region.ID = RegionCulture.RegionID
INSERT INTO DesignbestCore.dbo.Province
SELECT Province.ID,
CASE
WHEN Province.CountryID = 3 THEN 1
WHEN Province.CountryID = 2 THEN 3
WHEN Province.CountryID = 4 THEN 4
WHEN Province.CountryID = 1000 THEN 2
END AS LocalizationID,
Province.Coordinates,
Province.ProvinceCode,
Province.RegionID,
ProvinceCulture.Province AS Name,
LOWER(ProvinceCulture.SEOProvince) AS SeoName
FROM Province
INNER JOIN ProvinceCulture ON Province.ID = ProvinceCulture.ProvinceID
INNER JOIN Region ON Province.RegionID = Region.ID
INSERT INTO DesignbestCore.dbo.City
SELECT City.ID,
CASE
WHEN City.CountryID = 3 THEN 1
WHEN City.CountryID = 2 THEN 3
WHEN City.CountryID = 4 THEN 4
WHEN City.CountryID = 1000 THEN 2
END AS LocalizationID,
City.ProvinceID,
City.City AS [Name],
LOWER(City.SeoCity) AS SeoName
FROM City
INNER JOIN Province ON Province.ID = City.ProvinceID
INNER JOIN Region ON Province.RegionID = Region.ID
Custom Properties
L'idea è quella di creare da una semplicissima struttura di tabelle
- ProductProperty
- ProductPropertyValue
- Mapping_Category_ProductProperty
- Mapping_Product_ProductPropertyValue
l'assegnazione di qualsiasi proprietà (stile, finitura, colori, decori, formati, proprietà di tipologia, designer, campi custom)
Per ognuna di queste custom property è possibile specificare
- Nome
- Nome SEO
- Valore
- Immagine (opzionale, gestita da
Pictures)
Stesso concetto su tabelle separate per gli ShopPoint
- ShopPointProperty
- ShopPointPropertyValue
- Mapping_ShopPoint_ShopPointPropertyValue
assegnazione delle proprietà come gli ShopPointServices
Per quanto riguarda gli ShopPointServices, purtroppo abbiamo ereditato una logica per la quale, se esiste il valore PropertyValue nel mapping, allora il negozio possiede quella Property. Di conseguenza, se il negozio possiede una certa Property e non specifica nessun suo valore, è necessario inserire un PropertyValue vuoto.
Schifezze
Controllare i valori delle custom properties perché il database è pieno di duplicati assegnati a prodotti diversi.
Ad esempio la ProductProperty Caratteristiche con SeoName Caratteristiche - Sedie c'è due volte con ID differenti.
Per rimediare si potrebbero assegnare tutti i prodotti ad un unico di questi ID e cancellare l'altro doppio.
Tabelle escluse
Di seguito le tabelle che non riteniamo più utili:
ShopManufacturerExceptionShopContentExtra
Shop
ShopNet Casi Particolari
Tutti i negozi ora appartengono obbligatoriamente ad una ShopNet Queste ShopNet in particolare:
- ShopNet Trovaprodotti Orfani Tutti i Trovaprodotti che non avevano una ShopNet. Hanno
Piva = '000000000000'. Ne viene creata una per ognuno. - ShopNet Estero - Tutti gli ShopPoint Esteri. Hanno
Piva = '000000000004'. Ne viene creata una per ognuno. - ShopNet Anagrafiche - Contiene tutti gli ShopPoint di tipo != 0 che sono anagrafiche. Un padre per molti figli. Ha
Piva = '000000000003'.
ShopFulltextAdditions
Gestione della ricerca full-text dei negozi che aggiunge ambienti, tipologie e produttori alle chiavi di ricerca del singolo negozio.
Sarà necessario reimplementare tutto il passaggio con il task notturno che genera le righe?
Pictures
La gestione delle immagini sarà basata su quella di Nop.
| Picture | ||
|---|---|---|
| ID | int | ID della picture |
| EntityID | int | ID dell'entità |
| EntityKeyGroup | nvarchar(100) | Nome della Tabella di riferimento |
| EntityKey | nvarchar(100) | Formato dell'immagine desiderato |
| EntityValue | nvarchar(255) | Nome dell'immagine |
| DisplayOrder | int | Ordinamento |
Tutte le immagini saranno in un unico folder
product-<EntityID>-<ID>.jpg
bargain-<EntityID>-<ID>.jpg
manufacturer-<EntityID>-<ID>.jpg
shop-<EntityID>-<ID>.jpgQuesta soluzione è meno leggibile dall'umano ma molto più elastica nel caso in cui ci siano diversi cambiamenti di ordine, master/alternative (evita di dover rinominare l'immagine su disco).
Esempio Volendo trovare tutte le immagini relative ad uno shop si potrebbe usare la seguente query:
SELECT *
FROM Picture
WHERE EntityID = 15314 AND KeyGroup = 'Shop'
Avendo come risultato qualcosa di simile.
bla bla bla ....
Convenzioni
Per quanto riguarda i campi EntityGroup e EntityKey ecco cosa proponiamo
- ShopPoint
- cover
- logo
- gallery
- Product
- gallery
- square
- Manufacturer
- logo
- logo-square
- cover
- sponsored (bottone sponsor in HP)
- gallery
- Category
- ambient
Le selezioni
Utilizziamo il concetto di selezione per definire gruppi di prodotti (oppure negozi ecc) al fine di poterli richiamare in quei moduli che attualmente hanno una logica circostanziale spesso gestiti con xml.
In questo modo possiamo creare N selezioni tramite il backoffice da usare nei casi più svariati.
Importazione dati
Eseguire nell'ordine
- WM_CategorySynchCore
- WM_ProductPropertySynchCore
- WM_ShopNetSynchCore
- WM_ProductPropertySynchCore
- WM_PicturesSynchCore
- Eseguire script Python DesignbestBargainMigration.py
- Eseguire script Python DesignbestShopCoverAltMigration.py
- Eseguire la query SQL per generare il file batch che sposterà i file
SELECT
'IF EXIST "' + old_PicturePath + '" (',
'xcopy /y "' + old_PicturePath + '" ' + '"D:\WM3Resources\immagini\' + EntityValue + '-'+ CAST(EntityID AS VARCHAR(10)) + '-' + CAST(ID AS VARCHAR(10)) + '.jpg*"',
') ELSE ( echo ' + CAST(ID AS VARCHAR(10)) + ' BigPath >> missingimages.txt )'
FROM Picture
Progetto VS
Program.cs
Nel file Program.cs pregenerato aggiungere i seguenti concetti
Sessione
In un progetto Net.Core 6 la sessione parte disabilitata.
Per iniettare la sessione è necessario attivare un gestore della cache integrato (IDistributedCache) e la sessione stessa con le sue opzioni di configurazione
come da esempio
builder.Services.AddDistributedMemoryCache(); //L'implementazione IDistributedCache viene usata come archivio di backup per la sessione
builder.Services.AddSession(options => {
//options.IdleTimeout = TimeSpan.FromSeconds(10); // default 20 minuti
//options.Cookie.HttpOnly = true; // default true
options.Cookie.Name = ".Designbest.Session"; // nome del cookie di sessione
options.Cookie.IsEssential = true;
});
Dopodiché è necessario attivarla inserendo app.UseSession():
L' ordine del middleware è importante.
Chiamare UseSession dopo e UseRouting prima di MapRazorPages e MapDefaultControllerRoute
// ...
app.UseAuthorization();
app.UseSession(); // attivazione sessione
app.MapRazorPages();
app.Run();
HttpContextAccessor e HttpClient
Abilitare questi servizi
HttpContextAccessorpermette di accedere all' HttpContext da qualsiasi classe.HttpClientpermette di fare request.
builder.Services.AddHttpContextAccessor(); // per avere l'oggetto IHttpContextAccessor nelle classi
builder.Services.AddHttpClient(); // per avere l'oggetto IHttpClientFactory nelle classi
Services/Dipendenze
Implementare tutti i servizi bene separati utilizzando il pattern Interfaccia-Implementazione.
Per abilitarli nel progetto, segnalare interfaccia e implementazione come segue:
builder.Services.AddScoped<IDesignbestContext, DesignbestContext>();
builder.Services.AddScoped<IGeolocalization, MaxMindGeolocalization>();
Ci sono 3 diverse durate del servizio
- Temporaneo
builder.Services.AddTransient(..)
istanza normale che muore alla fine dello scope della funzione in cui viene chiamata - Con Ambito
builder.Services.AddScoped(..)
viene utilizzata la stessa istanza per tutta la durata della richiesta utente - Singleton
builder.Services.AddSingleton(...)
viene utilizzata la stessa istanza finche IIS non viene stoppato