Webmobili 5: differenze tra le versioni
| (58 versioni intermedie di un altro utente non mostrate) | |||
| Riga 1: | Riga 1: | ||
{{InfoProgetto|nome=Webmobili 5|URL=http://www.designbest.com|testURL=http://www.dbdemo47.it ([[Webmobili 5\Ambiente di test|altri dettagli]])|versioning=SVN|versioning_trunkURL=https://svn.office.webmobili.it/svn/WM4/WM45|versioning_branchesURL=https://svn.office.webmobili.it/svn/WM4/branches/}} | {{InfoProgetto|nome=Webmobili 5|URL=http://www.designbest.com|testURL=http://www.dbdemo47.it ([[Webmobili 5\Ambiente di test|altri dettagli]])|versioning=SVN|versioning_trunkURL=https://svn.office.webmobili.it/svn/WM4/WM45|versioning_branchesURL=https://svn.office.webmobili.it/svn/WM4/branches/}} | ||
'''Webmobili 5''' è l'attuale implementazione del progetto Webmobili. | '''Webmobili 5''' è l'attuale implementazione del progetto Webmobili.<br/> | ||
La versione ha come soprannome '''NoSoft'''. | |||
== CSS - Organizzazione == | == CSS - Organizzazione == | ||
| Riga 20: | Riga 21: | ||
Sono globali, sempre inclusi nel '''bundle''' e devono rispettare la nomenclatura che segue: | Sono globali, sempre inclusi nel '''bundle''' e devono rispettare la nomenclatura che segue: | ||
<syntaxhighlight lang="css"> | <syntaxhighlight lang="css"> | ||
.wm- | .wm-font-family1 { font-family:$font-theme1; } | ||
.wm-font-family2 { font-family:$font-theme2; } | |||
} | |||
.wm- | |||
} | |||
.wm- | .wm-col-white { color:white; } | ||
.wm-col-theme { color:$col-theme; } | |||
} | .wm-col-commerce { color:$col-commerce; } | ||
/* ... */ | /* ... */ | ||
| Riga 59: | Riga 55: | ||
</syntaxhighlight> | </syntaxhighlight> | ||
Il metodo helper <code>Html.InlineCss()</code> va fisicamente a leggere il contenuto di ogni CSS specificato, lo minifica e lo appende in pagina nel tag <code><style/></code>. | Il metodo helper <code>Html.InlineCss()</code> va fisicamente a leggere il contenuto di ogni CSS specificato, lo minifica e lo appende in pagina nel tag <code><style/></code>. | ||
==== Effetto Iniziale di pagina vuota dovuto al caricamento asincrono ==== | |||
Caricando il CSS in modo asincrono il browser mostra per pochi millisecondi la schermata bianca.<br/> | |||
Per rimediare a questo problema tutto il CSS Bundle dovrebbe stare in pagina INLINE (come nelle pagine AMP).<br/> | |||
Una soluzione potrebbe essere in questo link:<br/> | |||
https://mariusschulz.com/blog/inlining-css-and-javascript-bundles-with-asp-net-mvc | |||
== JavaScript - Organizzazione == | == JavaScript - Organizzazione == | ||
Come da specifiche Lighthouse, si è scelto di rendere '''tutto il JavaScript in modo asincrono'''.<br/> | |||
Per gli script interni si utilizza l'attributo <code>defer="defer"</code> in modo da mantenere l'ordine di inclusione.<br/><br/> | |||
=== JavaScript - Funzioni da onready === | |||
Per quando riguarda gli script inline, questi devono essere eseguiti come ultimi in modo da avere la certezza che tutte le librerie siano state caricate(jquery, bootstrap, wmcarousel....).<br/> | |||
La convenzione utilizzata è la seguente: | |||
<syntaxhighlight lang="javascript"> | |||
<script> | |||
var wmOnReady = wmOnReady || []; | |||
wmOnReady.push(function () { | |||
$('.wm-carousel-single').wmCarousel(true, false); | |||
}); | |||
</script> | |||
</syntaxhighlight> | |||
Viene dichiarata globalmente una variabile di tipo array di nome '''wmOnReady''' che ospita delle funzioni. Queste verranno lanciate non appena la pagina avrà finito di caricare tutti gli script in defer. | |||
L'ultimo script in assoluto deve essere <code>Scripts/js-designbest/WM_StartAll.js</code>, il quale, memore di avere le librerie necessarie precaricate, si occupa di eseguire le funzioni di '''wmOnReady''' in ordine cronologico di inserimento. | |||
== IMMAGINI - Organizzazione == | |||
Le immagini sono caricate in maniera '''lazy''' all'occorrenza. Per i nuovi browser questo avviene tramite l'utilizzo del nuovo attributo <code>loading="lazy"</code> all'interno del tag <code><img /></code>.<br/> | |||
Al fine di avere retrocompatibilità con tutti i browser è stata implementata la libreria <code>lazysizes.js</code>.<br/> | |||
Una immagine deve avere questi '''requisiti minimi''': | |||
<syntaxhighlight lang="html"> | |||
<img data-src="@picturePath" alt="@altText" loading="lazy" class="lazyload" /> | |||
</syntaxhighlight> | |||
*Nel caso di ''Browser normali''<br/> | |||
Un comando all'interno di <code>Scripts/js-designbest/WM45Common.js</code>, che parte al document ready, copierà nell'attributo <code>src</code> delle immagini il valore dell'attributo <code>data-src</code> e di conseguenza l'attributo <code>loading="lazy"</code> farà nativamente il suo dovere. | |||
* Nel caso di ''Browser antiquati'' | |||
Verrà inclusa asincronamente la libreria <code>lazysizes.js</code> e il lavoro verrà effettuato dalla stessa su tutte le immagini che hanno la classe <code>class="lazyload"</code>.<br/><br/> | |||
Ricordarsi che se un'immagine viene inserita asincronamente nel documento, questa non viene parsata in automatico, perciò va inserita subito con l'attributo <code>src</code> perché <code>data-src</code> verrebbe ignorato. | |||
== Viste Parziali / Moduli == | |||
Qui di seguito i moduli principali dell'applicazione e le istruzioni su come includerli nelle pagine. | |||
=== ShopsLeadContactForm === | |||
Il modale per spedire Lead al Lead Manager.<br/><br/> | |||
Nell' '''Head Custom''' includere il css necessario<br/><br/> | |||
<syntaxhighlight> | |||
@section HeadCustom { | |||
@Html.InlineCss("~/Content/css/components/lead-generator.min.css") | |||
} | |||
</syntaxhighlight> | |||
Nel '''Post Footer''' | |||
<syntaxhighlight> | |||
@section PostFooter{ | |||
@Html.Partial("~/Views/NoSoft/Modules/_ModaleDinamico.cshtml", new string[] { | |||
"leadformRetailers", | |||
Url.Action("ShopsLeadsContactForm", "Modules", new { | |||
modale = false, | |||
provenienza = WM45.Models.Modules.LeadRetailGeneratorModel.PaginaDiProvenienza.SCHEDA_OCCASIONE, | |||
StringhePersonalizzate = string.Join(",", new[] { Model.rivenditore.Name, Model.occCul.NomeOccasione }), | |||
productID = Model.occ.ID, | |||
shopID = Model.rivenditore.ID | |||
}) | |||
}) | |||
} | |||
</syntaxhighlight> | |||
Nel '''body''' inserire un pulsante che richiami il modale | |||
<syntaxhighlight lang="html"> | |||
<button class="wm-button-theme-inverted text-uppercase mb-2" data-bs-toggle="modal" data-bs-target="#leadformRetailers" onclick="tracker.track('leadgen_openleadform')">@Html.trad("shop_contatta_negozio")</button> | |||
</syntaxhighlight> | |||
Funzioni js e css sono già inclusi nel bundle principale. | |||
=== GenericContactForm === | |||
Il vecchio modale, prima di LM, utilizzato solo in Scheda Catalogo, Brand Channel, Listing progetti di catalogo, Listing progetti di professional.<br/><br/> | |||
Nella sezione '''Scripts''' includere la libreria e l'inizializzazione. | |||
<syntaxhighlight lang="html"> | |||
@section Scripts { | |||
<script> | |||
ScriptsLoader.LoadScripts([ | |||
"/Scripts/js/WMModals.js" | |||
]); | |||
//<!-- | |||
var mod = null; | |||
var wmOnReady = wmOnReady || []; | |||
wmOnReady.push(function () { | |||
mod = new WMModals("contactModal"); | |||
}); | |||
// --> | |||
</script> | |||
} | |||
</syntaxhighlight> | |||
Nel '''Post Footer''' includere il modale | |||
<syntaxhighlight> | |||
@section PostFooter { | |||
@Html.Partial("_ModalPopup") | |||
} | |||
</syntaxhighlight> | |||
I css sono inclusi nel bundle principale. | |||
=== WM Carousel Lite === | |||
Il nuovo carosello totalmente implementato dalla premiata ditta Vinci-Cerry.<br/><br/> | |||
Nel' '''Head Custom''' inserire il css di <code>slick.css</code> e quello del modulo | |||
<syntaxhighlight> | |||
@section HeadCustom { | |||
@Html.InlineCss("~/Content/css/components/carousel-lite.min.css") | |||
} | |||
</syntaxhighlight> | |||
Nella sezione '''Scripts''' aggiungere <code>WMCarouselLite.js</code> e inizializzare a seconda delle esigenze. | |||
<syntaxhighlight lang="html"> | |||
@section Scripts { | |||
<script> | |||
ScriptsLoader.LoadScripts([ | |||
"/Scripts/js/WMCarouselLite.js" | |||
]); | |||
var wmOnReady = wmOnReady || []; | |||
wmOnReady.push(function () { | |||
new WMCarouselLite('#sliderMagazine', false); // chiamata con parametri | |||
}); | |||
</script> | |||
} | |||
</syntaxhighlight> | |||
Nel '''body''' inserire il markup, tipo questo | |||
<syntaxhighlight lang="html"> | |||
<div class="wm-carousel-lite"> | |||
<div class="wm-carousel-lite-content"> | |||
@foreach (var ph in Model.shop.photoGallery) { | |||
<div class="d-block"> | |||
<img data-src="https://immagini.designbest.com/Rivenditori/ImmaginiFotogallery/big/@ph.Item1" alt="@ph" title="@Model.shop.name" loading="lazy" class="lazyload img-fluid" /> | |||
</div> | |||
} | |||
</div> | |||
<div class="left-control"> | |||
<svg xmlns="http://www.w3.org/2000/svg" width="50" height="50" fill="white" class="bi bi-chevron-compact-left" viewBox="0 0 16 16"> | |||
<path fill-rule="evenodd" d="M9.224 1.553a.5.5 0 0 1 .223.67L6.56 8l2.888 5.776a.5.5 0 1 1-.894.448l-3-6a.5.5 0 0 1 0-.448l3-6a.5.5 0 0 1 .67-.223z" /> | |||
</svg> | |||
</div> | |||
<div class="right-control"> | |||
<svg xmlns="http://www.w3.org/2000/svg" width="50" height="50" fill="white" class="bi bi-chevron-compact-right" viewBox="0 0 16 16"> | |||
<path fill-rule="evenodd" d="M6.776 1.553a.5.5 0 0 1 .671.223l3 6a.5.5 0 0 1 0 .448l-3 6a.5.5 0 1 1-.894-.448L9.44 8 6.553 2.224a.5.5 0 0 1 .223-.671z" /> | |||
</svg> | |||
</div> | |||
</div> | |||
</syntaxhighlight> | |||
== Sottoprogetti == | == Sottoprogetti == | ||
| Riga 67: | Riga 216: | ||
* [[Webmobili 4/Progetti|Progetti]] | * [[Webmobili 4/Progetti|Progetti]] | ||
* [[Sitemaps di Webmobili]] | * [[Sitemaps di Webmobili]] | ||
== Operazioni Async == | |||
Con il nuovo '''.NET 5''' spingono molto sulla '''programmazione asincrona'''.<br/> | |||
Per runnare un metodo sincrono in asincrono basta wrapparlo in una <code>Task.Run( )</code> come da esempio | |||
<syntaxhighlight lang="c#"> | |||
var mioTask = Task.Run( () => core.GetCazzoMiPare() ); | |||
var valore = await mioTask; | |||
// o meglio | |||
var valore = await Task.Run( () => core.GetCazzoMiPare() ); | |||
</syntaxhighlight> | |||
L' '''esecuzione parallela dei task''' all'interno di un controller potrebbe essere la seguente: | |||
<syntaxhighlight lang="c#">var prodottiTask = Task.Run(()=> core.GetProdotti() ); | |||
var correlatiTask = Task.Run(()=> core.GetCorrelati() ); | |||
var magazineTask = Task.Run(()=> core.GetMagazine() ); | |||
await Task.WhenAll(prodottiTask, correlatiTask , magazineTask ); | |||
var prodotti = await prodottiTask; | |||
var correlati = await correlatiTask ; | |||
var magazine = await magazineTask ;</syntaxhighlight> | |||
Se nella libreria core fossero implementate le versioni '''async''' di tutti i metodi sarebbe: | |||
<syntaxhighlight lang="c#">var prodottiTask = core.GetProdottiAsync(); | |||
var correlatiTask = core.GetCorrelatiAsync(); | |||
var magazineTask = core.GetMagazineAsync(); | |||
await Task.WhenAll(prodottiTask, correlatiTask , magazineTask ); | |||
var prodotti = await prodottiTask; | |||
var correlati = await correlatiTask ; | |||
var magazine = await magazineTask ;</syntaxhighlight> | |||
Un tutorial qua https://www.youtube.com/watch?v=2moh18sh5p4 | |||
=== Problemi con ASYNC === | |||
==== No child-action asincrone ==== | |||
Dato che usiamo la tecnologia '''.NET Framework 4''' è impossibile rendere asincrone le chiamate alle '''child actions''', cioè tutte le robe tipo <syntaxhighlight>@Html.Action("Azione", "Controller", new { param = Model })</syntaxhighlight> | |||
In .NET core viene risolta con l'utilizzo dei '''Components''' invocato in async. | |||
==== Perdita della sessione ==== | |||
I metodi async non sono in grado di leggere ''staticamente'' <code>HttpContext</code> e di conseguenza il suo oggetto <code>Session</code> (i metodi async perdono il contesto statico).<br/> | |||
Se invece ''HttpContext'' è stato salvato in una variabile interna leggibile dal metodo asincrono, allora funziona. | |||
==== Metodi async da libreria ==== | |||
Dopo aver scritto una propria libreria async, nel caso in cui si voglia usare un '''metodo async in modo sincrono''' ''basterebbe'' utilizzare la proprietà <code>Result</code> di <code>Task</code>. | |||
<syntaxhighlight lang="c#">var ris = GetMedodoAsync().Result;</syntaxhighlight> | |||
Se questo metodo chiama a sua volta altri metodi async si genera un '''deadlock'''.<br/><br/> | |||
La '''soluzione''' sta nell'aggiungere <code>.ConfigureAwait(false)</code> a tutte le chiamate async della libreria, in questo modo l'eventuale chiamata sincrona risulterà deadlock-free.<br/> | |||
https://medium.com/bynder-tech/c-why-you-should-use-configureawait-false-in-your-library-code-d7837dce3d7f | |||
<syntaxhighlight lang="c#"> | |||
public async Task<string> GetTokenAsync() { | |||
if(_Token == null) { | |||
_Token = await _ExistingAccessTokenAsync().ConfigureAwait(false); // check token già presente su DB | |||
if(_Token == null) { // richiesta nuovo token | |||
AccessToken at = await _RequestAccessTokenAsync(new Dictionary<string, string> { | |||
{ "username", token_username }, | |||
{ "password", token_password }, | |||
{ "grant_type", "password" }, | |||
{ "content-type", "application/x-www-form-urlencoded" } | |||
}).ConfigureAwait(false); | |||
await Task.Run(() => { | |||
using (Db db = new Db(connectionString)) { | |||
db.nonQuery("INSERT INTO Tokens VALUES(@TokenGetUrl, @Token, @Expire)", | |||
Db.par("@TokenGetUrl", getTokenUrl), | |||
Db.par("@Token", at.Token), | |||
Db.par("@Expire", at.Expire)); | |||
} | |||
}).ConfigureAwait(false); | |||
_Token = at.Token; | |||
} | |||
} | |||
return _Token; | |||
} | |||
</syntaxhighlight> | |||
== 4Dem Integrazione == | |||
Grazie alle API di 4dem è possibile montare un meccanismo di tracciamento delle azioni dell'utente che scatenano successivamente flussi di comunicazioni (gestite dalla dashboard di 4dem).<br/><br/> | |||
Le liste utilizzate sono: | |||
* Designbest: '''70904 IT''' - '''70939 EN''' | |||
* Magazine: '''70904 IT e EN''' | |||
* Trovaprodotti: '''76757 IT e EN''' | |||
* Commerce: '''70904 IT''' | |||
=== Liste Designbest (70904 it - 70939 en) === | |||
Per far sì che funzioni è necessario valorizzare, tramite la chiamata API,<br/> | |||
i valori '''custom field''' <code>146249</code> e <code>146250</code> con ''ambiente'' (<code>Ambient.SeoName</code>) e ''data'' (<code>DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")</code>). | |||
* '''Ad ogni PIN di prodotto/occasione'''. | |||
* '''Ogni richiesta a LM che abbia ambient valorizzato'''<br/> | |||
Le occasioni avranno la radice <code>outlet-</code> davanti all'ambient stringid.<br/><br/> | |||
Il '''custom field''' <code>52041</code>, ''portale'', è valorizzato come segue | |||
* <code>mydesignbest</code> se si proviene dalla pagina di registrazione | |||
* <code>leadmanager</code> se si proviene da un contact form che va al LM | |||
* <code>directory</code> in tutti i casi rimanenti di Designbest (iscrizione dal footer, mail al produttore ecc...) | |||
* <code>trovaprodotti</code> se si scrive da un form di Trovaprodotti<br/><br/> | |||
In questo modo dalla dashboard di 4dem è possibile organizzare flussi di newsletter selezionando date, gusti e tipologie di persone.<br/><br/> | |||
'''Nuovo sviluppo''', ogni qualvolta che un utente scrive dal portale designbest,<br/> | |||
anche se sceglie di non entrare in newsletter viene comunque inserito con il '''custom field''' <code>148921</code>, ''NO NEWSLETTER'' (checkbox).<br/> | |||
In questo caso la mail verrà utilizzata nei flussi stabiliti dalla Dashboard 4Dem. | |||
Versione attuale delle 11:08, 4 mag 2022
| Webmobili 5 | |
|---|---|
| http://www.designbest.com | |
| Ambiente di test | http://www.dbdemo47.it (altri dettagli) |
| Sistema di versionamento | |
| Tipo | SVN |
| URL del trunk | https://svn.office.webmobili.it/svn/WM4/WM45 |
| URL base dei branch | https://svn.office.webmobili.it/svn/WM4/branches/ |
Webmobili 5 è l'attuale implementazione del progetto Webmobili.
La versione ha come soprannome NoSoft.
CSS - Organizzazione
[modifica]Abbiamo fatto largo utilizzo di SCSS in quanto generano un output minificato. File usati (in ordine crescente di specificità)
- designbest-custom.scss (è costruito importando tutti gli altri file)
- _designbest-custom.variables.scss (dove vengono definite i valori delle variabili di grandezza, colore, dimensioni più usate nel sito)
- _designbest-custom.mixins.scss (punto unico dei mixin tipo transition, shadow, transform ecc...)
- _designbest-custom.reset.scss (cambia il comportamento base dei tag)
- _designbest-custom.general.scss (classi generiche utilizzate in tutte le pagine)
- _designbest-custom.components.scss (moduli che si ripetono in diverse pagine)
- _designbest-custom.header.scss (specifiche regole per l'header)
- _designbest-custom.footer.scss (specifiche regole per il footer)
Approccio misto, definire gli elementi che si usano di più crearne le classe e in parallelo creare le classi mattoncino per sovrascrivere gli attributi che cambiano in situazioni particolari.
CSS generici
[modifica]Sono globali, sempre inclusi nel bundle e devono rispettare la nomenclatura che segue:
.wm-font-family1 { font-family:$font-theme1; }
.wm-font-family2 { font-family:$font-theme2; }
.wm-col-white { color:white; }
.wm-col-theme { color:$col-theme; }
.wm-col-commerce { color:$col-commerce; }
/* ... */
con wm- come prefisso.
CSS specifici per pagina
[modifica]Gli .scss specifici per pagina stanno nella cartella Content/scss/pages.
Possono essere più "sporchi" perché sono l'ultimo gradino di inclusione (si consiglia sempre e comunque di mantenere ordine ed eleganza).
CSS di moduli riutilizzabili
[modifica]Nella cartella Content/scss/components troviamo tutti quei css che fanno riferimento a moduli che vengono inclusi in alcune pagine (non tutte).
In questi casi, quando si dovrà includere il componente si includerà anche il CSS corrispondente (es. moduli carousel o telecomando).
Inclusione dei CSS in pagina
[modifica]Seguendo la guida di Lighthouse, i css vengono caricati in modo asincrono se viene utilizzato il seguente markup:
<link rel="preload" href="/Content/css/designbest-custom.min.css" as="style" onload="this.onload = null; this.rel = 'stylesheet'"/>
Per avere migliori prestazioni il contenuto custom delle pagine conviene che venga inserito direttamente INLINE, nell'header della pagina.
Per farlo utilizzare l'apposita @section HeadCustom come nel seguente esempio.
@section HeadCustom {
@Html.InlineCss("~/Content/css/slick.min.css", "~/Content/css/components/carousel.min.css", "~/Content/css/pages/scheda-progetto.min.css")
}Il metodo helper Html.InlineCss() va fisicamente a leggere il contenuto di ogni CSS specificato, lo minifica e lo appende in pagina nel tag <style/>.
Effetto Iniziale di pagina vuota dovuto al caricamento asincrono
[modifica]Caricando il CSS in modo asincrono il browser mostra per pochi millisecondi la schermata bianca.
Per rimediare a questo problema tutto il CSS Bundle dovrebbe stare in pagina INLINE (come nelle pagine AMP).
Una soluzione potrebbe essere in questo link:
https://mariusschulz.com/blog/inlining-css-and-javascript-bundles-with-asp-net-mvc
JavaScript - Organizzazione
[modifica]Come da specifiche Lighthouse, si è scelto di rendere tutto il JavaScript in modo asincrono.
Per gli script interni si utilizza l'attributo defer="defer" in modo da mantenere l'ordine di inclusione.
JavaScript - Funzioni da onready
[modifica]Per quando riguarda gli script inline, questi devono essere eseguiti come ultimi in modo da avere la certezza che tutte le librerie siano state caricate(jquery, bootstrap, wmcarousel....).
La convenzione utilizzata è la seguente:
<script>
var wmOnReady = wmOnReady || [];
wmOnReady.push(function () {
$('.wm-carousel-single').wmCarousel(true, false);
});
</script>
Viene dichiarata globalmente una variabile di tipo array di nome wmOnReady che ospita delle funzioni. Queste verranno lanciate non appena la pagina avrà finito di caricare tutti gli script in defer.
L'ultimo script in assoluto deve essere Scripts/js-designbest/WM_StartAll.js, il quale, memore di avere le librerie necessarie precaricate, si occupa di eseguire le funzioni di wmOnReady in ordine cronologico di inserimento.
IMMAGINI - Organizzazione
[modifica]Le immagini sono caricate in maniera lazy all'occorrenza. Per i nuovi browser questo avviene tramite l'utilizzo del nuovo attributo loading="lazy" all'interno del tag <img />.
Al fine di avere retrocompatibilità con tutti i browser è stata implementata la libreria lazysizes.js.
Una immagine deve avere questi requisiti minimi:
<img data-src="@picturePath" alt="@altText" loading="lazy" class="lazyload" />
- Nel caso di Browser normali
Un comando all'interno di Scripts/js-designbest/WM45Common.js, che parte al document ready, copierà nell'attributo src delle immagini il valore dell'attributo data-src e di conseguenza l'attributo loading="lazy" farà nativamente il suo dovere.
- Nel caso di Browser antiquati
Verrà inclusa asincronamente la libreria lazysizes.js e il lavoro verrà effettuato dalla stessa su tutte le immagini che hanno la classe class="lazyload".
Ricordarsi che se un'immagine viene inserita asincronamente nel documento, questa non viene parsata in automatico, perciò va inserita subito con l'attributo src perché data-src verrebbe ignorato.
Viste Parziali / Moduli
[modifica]Qui di seguito i moduli principali dell'applicazione e le istruzioni su come includerli nelle pagine.
ShopsLeadContactForm
[modifica]Il modale per spedire Lead al Lead Manager.
Nell' Head Custom includere il css necessario
@section HeadCustom {
@Html.InlineCss("~/Content/css/components/lead-generator.min.css")
}Nel Post Footer
@section PostFooter{
@Html.Partial("~/Views/NoSoft/Modules/_ModaleDinamico.cshtml", new string[] {
"leadformRetailers",
Url.Action("ShopsLeadsContactForm", "Modules", new {
modale = false,
provenienza = WM45.Models.Modules.LeadRetailGeneratorModel.PaginaDiProvenienza.SCHEDA_OCCASIONE,
StringhePersonalizzate = string.Join(",", new[] { Model.rivenditore.Name, Model.occCul.NomeOccasione }),
productID = Model.occ.ID,
shopID = Model.rivenditore.ID
})
})
}Nel body inserire un pulsante che richiami il modale
<button class="wm-button-theme-inverted text-uppercase mb-2" data-bs-toggle="modal" data-bs-target="#leadformRetailers" onclick="tracker.track('leadgen_openleadform')">@Html.trad("shop_contatta_negozio")</button>
Funzioni js e css sono già inclusi nel bundle principale.
GenericContactForm
[modifica]Il vecchio modale, prima di LM, utilizzato solo in Scheda Catalogo, Brand Channel, Listing progetti di catalogo, Listing progetti di professional.
Nella sezione Scripts includere la libreria e l'inizializzazione.
@section Scripts {
<script>
ScriptsLoader.LoadScripts([
"/Scripts/js/WMModals.js"
]);
//<!--
var mod = null;
var wmOnReady = wmOnReady || [];
wmOnReady.push(function () {
mod = new WMModals("contactModal");
});
// -->
</script>
}
Nel Post Footer includere il modale
@section PostFooter {
@Html.Partial("_ModalPopup")
}I css sono inclusi nel bundle principale.
WM Carousel Lite
[modifica]Il nuovo carosello totalmente implementato dalla premiata ditta Vinci-Cerry.
Nel' Head Custom inserire il css di slick.css e quello del modulo
@section HeadCustom {
@Html.InlineCss("~/Content/css/components/carousel-lite.min.css")
}Nella sezione Scripts aggiungere WMCarouselLite.js e inizializzare a seconda delle esigenze.
@section Scripts {
<script>
ScriptsLoader.LoadScripts([
"/Scripts/js/WMCarouselLite.js"
]);
var wmOnReady = wmOnReady || [];
wmOnReady.push(function () {
new WMCarouselLite('#sliderMagazine', false); // chiamata con parametri
});
</script>
}
Nel body inserire il markup, tipo questo
<div class="wm-carousel-lite">
<div class="wm-carousel-lite-content">
@foreach (var ph in Model.shop.photoGallery) {
<div class="d-block">
<img data-src="https://immagini.designbest.com/Rivenditori/ImmaginiFotogallery/big/@ph.Item1" alt="@ph" title="@Model.shop.name" loading="lazy" class="lazyload img-fluid" />
</div>
}
</div>
<div class="left-control">
<svg xmlns="http://www.w3.org/2000/svg" width="50" height="50" fill="white" class="bi bi-chevron-compact-left" viewBox="0 0 16 16">
<path fill-rule="evenodd" d="M9.224 1.553a.5.5 0 0 1 .223.67L6.56 8l2.888 5.776a.5.5 0 1 1-.894.448l-3-6a.5.5 0 0 1 0-.448l3-6a.5.5 0 0 1 .67-.223z" />
</svg>
</div>
<div class="right-control">
<svg xmlns="http://www.w3.org/2000/svg" width="50" height="50" fill="white" class="bi bi-chevron-compact-right" viewBox="0 0 16 16">
<path fill-rule="evenodd" d="M6.776 1.553a.5.5 0 0 1 .671.223l3 6a.5.5 0 0 1 0 .448l-3 6a.5.5 0 1 1-.894-.448L9.44 8 6.553 2.224a.5.5 0 0 1 .223-.671z" />
</svg>
</div>
</div>
Sottoprogetti
[modifica]
Operazioni Async
[modifica]Con il nuovo .NET 5 spingono molto sulla programmazione asincrona.
Per runnare un metodo sincrono in asincrono basta wrapparlo in una Task.Run( ) come da esempio
var mioTask = Task.Run( () => core.GetCazzoMiPare() );
var valore = await mioTask;
// o meglio
var valore = await Task.Run( () => core.GetCazzoMiPare() );
L' esecuzione parallela dei task all'interno di un controller potrebbe essere la seguente:
var prodottiTask = Task.Run(()=> core.GetProdotti() );
var correlatiTask = Task.Run(()=> core.GetCorrelati() );
var magazineTask = Task.Run(()=> core.GetMagazine() );
await Task.WhenAll(prodottiTask, correlatiTask , magazineTask );
var prodotti = await prodottiTask;
var correlati = await correlatiTask ;
var magazine = await magazineTask ;
Se nella libreria core fossero implementate le versioni async di tutti i metodi sarebbe:
var prodottiTask = core.GetProdottiAsync();
var correlatiTask = core.GetCorrelatiAsync();
var magazineTask = core.GetMagazineAsync();
await Task.WhenAll(prodottiTask, correlatiTask , magazineTask );
var prodotti = await prodottiTask;
var correlati = await correlatiTask ;
var magazine = await magazineTask ;
Un tutorial qua https://www.youtube.com/watch?v=2moh18sh5p4
Problemi con ASYNC
[modifica]No child-action asincrone
[modifica]Dato che usiamo la tecnologia .NET Framework 4 è impossibile rendere asincrone le chiamate alle child actions, cioè tutte le robe tipo
@Html.Action("Azione", "Controller", new { param = Model })In .NET core viene risolta con l'utilizzo dei Components invocato in async.
Perdita della sessione
[modifica]I metodi async non sono in grado di leggere staticamente HttpContext e di conseguenza il suo oggetto Session (i metodi async perdono il contesto statico).
Se invece HttpContext è stato salvato in una variabile interna leggibile dal metodo asincrono, allora funziona.
Metodi async da libreria
[modifica]Dopo aver scritto una propria libreria async, nel caso in cui si voglia usare un metodo async in modo sincrono basterebbe utilizzare la proprietà Result di Task.
var ris = GetMedodoAsync().Result;
Se questo metodo chiama a sua volta altri metodi async si genera un deadlock.
La soluzione sta nell'aggiungere .ConfigureAwait(false) a tutte le chiamate async della libreria, in questo modo l'eventuale chiamata sincrona risulterà deadlock-free.
https://medium.com/bynder-tech/c-why-you-should-use-configureawait-false-in-your-library-code-d7837dce3d7f
public async Task<string> GetTokenAsync() {
if(_Token == null) {
_Token = await _ExistingAccessTokenAsync().ConfigureAwait(false); // check token già presente su DB
if(_Token == null) { // richiesta nuovo token
AccessToken at = await _RequestAccessTokenAsync(new Dictionary<string, string> {
{ "username", token_username },
{ "password", token_password },
{ "grant_type", "password" },
{ "content-type", "application/x-www-form-urlencoded" }
}).ConfigureAwait(false);
await Task.Run(() => {
using (Db db = new Db(connectionString)) {
db.nonQuery("INSERT INTO Tokens VALUES(@TokenGetUrl, @Token, @Expire)",
Db.par("@TokenGetUrl", getTokenUrl),
Db.par("@Token", at.Token),
Db.par("@Expire", at.Expire));
}
}).ConfigureAwait(false);
_Token = at.Token;
}
}
return _Token;
}
4Dem Integrazione
[modifica]Grazie alle API di 4dem è possibile montare un meccanismo di tracciamento delle azioni dell'utente che scatenano successivamente flussi di comunicazioni (gestite dalla dashboard di 4dem).
Le liste utilizzate sono:
- Designbest: 70904 IT - 70939 EN
- Magazine: 70904 IT e EN
- Trovaprodotti: 76757 IT e EN
- Commerce: 70904 IT
Liste Designbest (70904 it - 70939 en)
[modifica]Per far sì che funzioni è necessario valorizzare, tramite la chiamata API,
i valori custom field 146249 e 146250 con ambiente (Ambient.SeoName) e data (DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")).
- Ad ogni PIN di prodotto/occasione.
- Ogni richiesta a LM che abbia ambient valorizzato
Le occasioni avranno la radice outlet- davanti all'ambient stringid.
Il custom field 52041, portale, è valorizzato come segue
mydesignbestse si proviene dalla pagina di registrazioneleadmanagerse si proviene da un contact form che va al LMdirectoryin tutti i casi rimanenti di Designbest (iscrizione dal footer, mail al produttore ecc...)trovaprodottise si scrive da un form di Trovaprodotti
In questo modo dalla dashboard di 4dem è possibile organizzare flussi di newsletter selezionando date, gusti e tipologie di persone.
Nuovo sviluppo, ogni qualvolta che un utente scrive dal portale designbest,
anche se sceglie di non entrare in newsletter viene comunque inserito con il custom field 148921, NO NEWSLETTER (checkbox).
In questo caso la mail verrà utilizzata nei flussi stabiliti dalla Dashboard 4Dem.