Lead Manager: differenze tra le versioni
| (67 versioni intermedie di 2 utenti non mostrate) | |||
| Riga 1: | Riga 1: | ||
Progetto che prevede '''API''' e '''Frontend''',<br/> | Progetto che prevede '''API''' e '''Frontend''',<br/> | ||
il primo sviluppato in ''MVC'' e il secondo in ''Web Forms''. | il primo sviluppato in ''MVC'' e il secondo in ''Web Forms''. | ||
== Configurazioni == | |||
=== Scadenze dei lead === | |||
Non ce le ricordiamo mai.<br/> | |||
* ''Lead Pubblico'' : '''5 giorni''' (120hours) | |||
* ''Lead Privato'' : '''30 giorni''' (720hour) | |||
Sono definiti nel file <code>Web.config</code> del progetto <code>LeadManager.API</code> | |||
=== Database Credenziali === | |||
LM Database Azure: | |||
<pre> | |||
leadmanager-db-server.database.windows.net | |||
LeadManagerDBAdmin | |||
Lm20!@!.20Px | |||
</pre> | |||
=== Password per Publish === | |||
Quando si esegue il '''deploy su Azure''' vengono richieste le seguenti password: | |||
{| class="wikitable" | |||
|+ produzione | |||
|- | |||
! Progetto !! username !! password | |||
|- | |||
| '''LeadManager'''|| <code>$leadman</code>|| <code>lWsBF5bqAQm0t3pjTk3onlphKFKadeeu4MXZGaKfCtDtilAJrbYtFfjlofmo</code> | |||
|- | |||
| '''LeadManager API'''|| <code>$leadmanapi</code>|| <code>HrE4xwlPSnpZh3BT9y1t13gNjlvt9v3kJ2XWbKCTNj5T8Jl0eNYjZmG3gzpj</code> | |||
|- | |||
| '''LeadManager Admin'''|| <code>$leadmanadmin</code>|| <code>mQg6JRZaP6Bajsskr9tRyMtqWNBqgZQ73f0vPRM2e5tb9kL5lkFdnkwxe1h6</code> | |||
|} | |||
{| class="wikitable" | |||
|+ staging | |||
|- | |||
! Progetto !! username !! password | |||
|- | |||
| '''LeadManager'''|| <code>$leadmantest</code>|| <code>tYcDYbkhilS34g8oNz9sK2taK9fuzfsfAFuDf1X5jaLM5hMh1Zci7uwP26Eb</code> | |||
|- | |||
| '''LeadManager API'''|| <code>$leadmanapitest</code>|| <code>C3JiWBtxkvKGSc7t5k0ccYfvRmgmK2ZbtNotyvYyoY7HwoblfXyb1dnc0eMP</code> | |||
|} | |||
=== API Access Token === | |||
Abbiamo generato un '''Access Token senza scadenza''' per utilizzarlo nei vari servizi (es. ''Zapier'').<br/> | |||
Eccolo | |||
<pre> | |||
eyJhbGciOiJodHRwOi8vd3d3LnczLm9yZy8yMDAxLzA0L3htbGRzaWctbW9yZSNobWFjLXNoYTI1NiIsInR5cCI6IkpXVCJ9.eyJodHRwOi8vc2NoZW1hcy54bWxzb2FwLm9yZy93cy8yMDA1LzA1L2lkZW50aXR5L2NsYWltcy9uYW1laWRlbnRpZmllciI6IjYwZDFlMjAwLTk4MTctNDIxNC04NDk1LWFkZWRlNjY3NWI3OCIsImh0dHA6Ly9zY2hlbWFzLnhtbHNvYXAub3JnL3dzLzIwMDUvMDUvaWRlbnRpdHkvY2xhaW1zL25hbWUiOiJsbWFkbWluIiwiaHR0cDovL3NjaGVtYXMubWljcm9zb2Z0LmNvbS9hY2Nlc3Njb250cm9sc2VydmljZS8yMDEwLzA3L2NsYWltcy9pZGVudGl0eXByb3ZpZGVyIjoiQVNQLk5FVCBJZGVudGl0eSIsIkFzcE5ldC5JZGVudGl0eS5TZWN1cml0eVN0YW1wIjoiNGY1NTA5ZjgtMjFjNC00NTg0LTkxODUtZjYyNGM2Yjk0YWNlIiwiaHR0cDovL3NjaGVtYXMubWljcm9zb2Z0LmNvbS93cy8yMDA4LzA2L2lkZW50aXR5L2NsYWltcy9yb2xlIjoiQWRtaW5pc3RyYXRvcnMiLCJsYXN0bG9naW5kYXRldGltZSI6IjA0LzA1LzIwMjEgMDk6NDk6NTkiLCJsb2dpbmNvdW50IjoiMSIsIlJvbGUiOiJBZG1pbmlzdHJhdG9ycyIsIm5iZiI6MTczMTk0NDI1OSwiZXhwIjo0ODg1NTQ0MjU5LCJpc3MiOiIxMjcuMC4wLjEiLCJhdWQiOiI5MjNhNzI5MXg4NDIxZDk5cXdlMDFoMDEzODk5M3h5MyJ9.40iDZ188V5c_6xrPpks1HIQ1yT7LEgvvP5_rKTo4W80 | |||
</pre> | |||
=== Admin Credenziali === | |||
* http://leadmanadmin.azurewebsites.net/ | |||
* user: <code>admin</code> | |||
* pass: <code>2kdb@2020!</code> | |||
=== DevExpress === | |||
Il progetto utilizza l'estensione di Visual Studio '''DevExpress'''.<br/> | |||
Per aggiornare i progetti della soluzione: | |||
* Farsi scaricare l'ultima versione da ''Mengoli'' (hanno loro l'account) | |||
* Runnare l'installer | |||
* Chiedere a ''Mengoli'' di inserire le credenziali (via TeamViewer) | |||
* Aprire Visual Studio | |||
* <code>Extensions</code> => <code>DevExpress</code> => <code>Project Converter</code> | |||
* Selezionare la versione nuova e seguire il wizard | |||
Procedere ''disinstallando la vecchia versione'' dal pannello di controllo di Windows. | |||
https://docs.devexpress.com/GeneralInformation/2221/updates/upgrade-notes | |||
== Struttura == | == Struttura == | ||
| Riga 18: | Riga 82: | ||
* creare una nuova classe <code>NomeClasse : BaseEntity</code> che definisca esattamente i campi della tabella come property | * creare una nuova classe <code>NomeClasse : BaseEntity</code> che definisca esattamente i campi della tabella come property | ||
* creare una nuova classe <code>NomeClasse_List : BaseEntityList<NomeClasse></code> | * creare una nuova classe <code>NomeClasse_List : BaseEntityList<NomeClasse></code> | ||
* creare una nuova classe <code>NomeClasse_SearchParameters : BaseEntitySearchParameters</code> | * creare una nuova classe <code>NomeClasse_SearchParameters : BaseEntitySearchParameters</code> con i filtri necessari per avere una get delle opzioni budget di una company | ||
* creare una nuova classe <code>NomeClasse_ActionParameters : BaseEntityActionParameters</code> | * creare una nuova classe <code>NomeClasse_ActionParameters : BaseEntityActionParameters</code> | ||
* aggiungere la definizione delle enum che definiscono il tipo di filtro | |||
Avendo <code>NomeClasse</code> come <code>ConfigCompanyBudget</code>: | |||
<syntaxhighlight lang="c#"> | <syntaxhighlight lang="c#"> | ||
public class ConfigCompanyBudget : BaseEntity { | public class ConfigCompanyBudget : BaseEntity { | ||
public int config_company_budgetID { get; set; } | public int config_company_budgetID { get; set; } | ||
public int companyID { get; set; } | public int companyID { get; set; } | ||
public int budgetID { get; set; } | public int? budgetID { get; set; } | ||
} | |||
public class ConfigCompanyBudget_List : BaseEntityList<ConfigCompanyBudget> {} | |||
public class ConfigCompanyBudget_SearchParameters : BaseEntitySearchParameters { | |||
public int companyID { get; set; } | |||
} | |||
public class ConfigCompanyBudget_ActionParameters : BaseEntityActionParameters {} | |||
public enum ConfigCompanyBudget_SearchType { | |||
CompleteSearch = 0, | |||
ListSelected_by_CompanyId = 1 | |||
} | |||
public enum ConfigCompanyBudget_ActionType {} | |||
</syntaxhighlight> | |||
È possibile definire delle '''enum''' | |||
* ''SearchType'' per specificare le tipologie di filtri di ricerca che verranno applicate prima di restituire la lista | |||
* ''ActionType'' per definire eventuali azioni custom che non sono tra quelle base (CRUD). | |||
==== Livello DAL ==== | |||
Nel progetto <code>LeadManager.Dal.sql</code><br/> | |||
creare una classe che estenda tutte quelle appena definite nel livello entity. | |||
<syntaxhighlight lang="c#"> | |||
public class ConfigCompanyBudgetDal : BaseDal<ConfigCompanyBudget>, IDataLayer<ConfigCompanyBudget, ConfigCompanyBudget_List,int,ConfigCompanyBudget_SearchParameters, ConfigCompanyBudget_ActionParameters> | |||
</syntaxhighlight> | |||
col compilatore di VS implementare tutti i metodi astratti e di interfaccia con il default <code>throw new NotImplementedException()</code>.<br/> | |||
In generale ci sono i classici '''metodi CRUD''': ''CREATE'', ''READ'', ''UPDATE'', ''DELETE'' da implementare a seconda dell'occorrenza. | |||
Il primo metodo da implementare è <code>RecordToEntity()</code>,<br/> | |||
che definisce il modello base di ritorno. | |||
<syntaxhighlight lang="c#"> | |||
protected override ConfigCompanyBudget RecordToEntity(SqlDataReader result, Hashtable MyOrdinals) { | |||
try { | |||
ConfigCompanyBudget MyEntity = new ConfigCompanyBudget { | |||
config_company_budgetID = ReadField_Integer(ref result, (int?)MyOrdinals["config_company_budgetID"]), | |||
companyID = ReadField_Integer(ref result, (int?)MyOrdinals["companyID"]), | |||
budgetID = ReadField_Integer(ref result, (int?)MyOrdinals["budgetID"]) | |||
}; | |||
return MyEntity; | |||
} | |||
catch (Exception ex) { | |||
throw new Exceptions.LeadManager_DAL_Exception("Errore in " + global::System.Reflection.MethodBase.GetCurrentMethod().Name, ex); | |||
} | |||
} | } | ||
</syntaxhighlight> | |||
public | Il secondo è <code>List()</code><br/> | ||
che fa la classica ''select''. | |||
<syntaxhighlight lang="c#"> | |||
public ConfigCompanyBudget_List List(ConfigCompanyBudget_SearchParameters MySearchCriteria, int PageIndex = 0, int PageSize = 100000) { | |||
string StoredProcedureName = ""; | |||
SqlParameter[] MyParameters = null; | |||
switch (MySearchCriteria.SearchType) { | |||
case (int)ConfigCompanyBudget_SearchType.ListSelected_by_CompanyId: | |||
MyParameters = new SqlParameter[] { | |||
new SqlParameter("@companyid", SqlDbType.Int,4) {Value = MySearchCriteria.companyID} | |||
}; | |||
StoredProcedureName = "ConfigCompanyBudget_GetSelected_byCompany"; | |||
break; | |||
} | |||
try { | |||
using (SqlDataReader result = SqlHelper.ExecSPForReader(ConnectionString, StoredProcedureName, MyParameters)) { | |||
ConfigCompanyBudget_List arrayoggetti = new ConfigCompanyBudget_List(); | |||
Hashtable MyOrdinals = ReadOrdinals(result); | |||
while (result.Read()) { | |||
arrayoggetti.Add(RecordToEntity(result, MyOrdinals)); | |||
} | |||
return arrayoggetti; | |||
} | |||
} | |||
catch (Exception ex) { | |||
throw new Exceptions.LeadManager_DAL_Exception("Errore in " + global::System.Reflection.MethodBase.GetCurrentMethod().Name + "- Stored Procedure: " + StoredProcedureName, ex); | |||
} | |||
} | |||
</syntaxhighlight> | |||
che sta utilizzando una '''stored procedure''' <code>ConfigCompanyBudget_GetSelected_byCompany</code> che andremo a definire sul database. | |||
Il terzo è <code>Add()</code><br/> | |||
che fa la classica ''insert''. | |||
<syntaxhighlight lang="c#"> | |||
public ConfigCompanyBudget Add(ConfigCompanyBudget record) { | |||
string StoredProcedureName = ""; | |||
SqlParameter[] MyParameters = null; | |||
MyParameters = new SqlParameter[] { | |||
new SqlParameter("@companyid", SqlDbType.Int,4) {Value = record.companyID}, | |||
new SqlParameter("@budgetid", SqlDbType.Int,4) {Value = record.budgetID} | |||
}; | |||
StoredProcedureName = "ConfigCompanyBudget_Add_byCompany"; | |||
return record; | |||
} | } | ||
</syntaxhighlight> | |||
utilizzando una '''stored procedure''' <code>ConfigCompanyBudget_Add_byCompany</code> che andremo a definire sul database. | |||
public | ===== Metodo Action() ===== | ||
public int | Si utilizza quando si devono implementare azioni diverse dalle standard CRUD.<br/> | ||
Ognuna di queste azioni vengono identificate dalla '''enum''' con ''_ActionType'' definita nel livello ''entity''. | |||
<syntaxhighlight lang="c#"> | |||
public bool Action(LeadRequest record, LeadRequest_ActionParameters MyActionParameters) { | |||
string StoredProcedureName = ""; | |||
SqlParameter[] MyParameters = null; | |||
switch (MyActionParameters.ActionType) { | |||
case (int)LeadRequest_ActionType.New_DBAdvice: | |||
// ... Gestione prima custom action | |||
break; | |||
case (int)LeadRequest_ActionType.Restore_Lead: | |||
// ... Gestione seconda custom action | |||
break; | |||
} | |||
try { | |||
if (SqlHelper.ExecSPNonQuery(ConnectionString, StoredProcedureName, MyParameters) > 0) | |||
return true; | |||
return false; | |||
} | |||
catch (Exception ex) { | |||
throw new Exceptions.LeadManager_DAL_Exception("Errore in " + global::System.Reflection.MethodBase.GetCurrentMethod().Name + "- Stored Procedure: " + StoredProcedureName, ex); | |||
} | |||
} | |||
</syntaxhighlight> | |||
==== Database ==== | |||
Creare la nuova '''store procedure''' <code>ConfigCompanyBudget_GetSelected_byCompany</code><br/> | |||
che prenda come parametro <code>@companyID</code><br/> | |||
anche definito in <code>ConfigCompanyBudget_SearchParameters</code><br/> | |||
che restituisca tutte le configurazioni budget della company selezionata<br/> | |||
nel formato della classe <code>ConfigCompanyBudget_List</code>. | |||
Creare la nuova '''store procedure''' <code>ConfigCompanyBudget_Add_byCompany</code> allo stesso modo. | |||
==== Livello Biz ==== | |||
Progetto <code>LeadManager.BIZ</code><br/> | |||
Questo livello fa da tramite tra l'applicazione e il '''dal'''.<br/> | |||
Creare una classe <code>ConfigCompanyBudgetBiz</code> che estenda <code>BaseBiz</code> come questa: | |||
<syntaxhighlight lang="c#"> | |||
public class ConfigCompanyBudgetBiz : BaseBiz<ConfigCompanyBudget,ConfigCompanyBudget_List,int,ConfigCompanyBudget_SearchParameters,ConfigCompanyBudget_ActionParameters> | |||
</syntaxhighlight> | |||
la quale può implementare qualsiasi metodo andando ad attingere al livello '''dal''' per quanto riguarda le query. | |||
== ASP.NET Web Forms == | |||
Tecnologia largamente superata da MVC,<br/> | |||
lenta a caricare e complessa da mantenere. | |||
In questa sezione faremo approfondimenti sulle parti che ci hanno messo in difficoltà. | |||
=== Repeater === | |||
Quello che in MVC sarebbe un semplice ciclo <code>for</code> qui diventa un mostro monolitico.<br/> | |||
Il fatto che non si possano utilizzare <code>form</code> complica ancora di più le cose nel caso in cui si debba implementare un submit in uno degli elementi del ciclo. | |||
==== Frontend aspx ==== | |||
<syntaxhighlight lang="xml"> | |||
<asp:Repeater ID="RPT_LeadRequests" runat="server" OnItemDataBound="RPT_LeadRequests_ItemDataBound" OnItemCommand="RPT_LeadRequests_ItemCommand"> | |||
<ItemTemplate> | |||
<!-- Elementi del ciclo --> | |||
<div data-nome="<%# Eval("nome") %>" data-cognome="<%# Eval("cognome") %>"> | |||
<asp:LinkButton | |||
ID="btnSubmit" runat="server" Text="Submit" | |||
CommandName="SubmitData" CommandArgument='<%# Container.ItemIndex %>'> | |||
</asp:LinkButton> | |||
</div> | |||
</ItemTemplate> | |||
</asp:Repeater> | |||
</syntaxhighlight> | |||
==== Backend aspx.cs ==== | |||
===== Bind del DataSource ===== | |||
Bindare la data nel Repeater | |||
<syntaxhighlight lang="c#"> | |||
protected void Page_Load(object sender, EventArgs e) { | |||
// ... | |||
// Questa operazione dev'essere fatta sempre sia in PostBack che non | |||
// Recuperare la data da mettere nel repeater (una lista) | |||
RPT_LeadRequests.DataSource = data; | |||
RPT_LeadRequests.DataBind(); | |||
//... | |||
} | } | ||
</syntaxhighlight> | |||
===== ItemDataBound ===== | |||
Creare un evento '''ItemDataBound''',<br/> | |||
il cui nome va specificato dall'attributo <code>OnItemDataBound</code> del frontend,<br/> | |||
che si occupa del loop del ciclo | |||
<syntaxhighlight lang="c#"> | |||
protected void RPT_LeadRequests_ItemDataBound(object sender, RepeaterItemEventArgs e) { | |||
if (e.Item.ItemType == ListItemType.Item || e.Item.ItemType == ListItemType.AlternatingItem) { | |||
// Recupera l'oggetto dati per l'elemento corrente | |||
var dataItem = (MioOggetto)e.Item.DataItem; // cast necessario | |||
// eventuali operazioni sugli elementi di questa riga di ciclo | |||
} | |||
} | |||
</syntaxhighlight> | |||
===== ItemCommand ===== | |||
Se è necessario effettuare un submit all'interno di una riga del Repeater,<br/> | |||
bisogna creare un evento '''ItemCommand'''<br/> | |||
il cui nome va specificato dall'attributo <code>OnItemCommand</code> del frontend,<br/> | |||
<syntaxhighlight lang="c#"> | |||
protected void rpt_LeadRequests_ItemCommand(object source, RepeaterCommandEventArgs e) { | |||
if (e.CommandName == "SubmitData") { | |||
// Ottieni l'indice dell'elemento che ha attivato l'evento | |||
int index = Convert.ToInt32(e.CommandArgument); | |||
var repeaterData = (IEnumerable<MioOggetto>)RPT_LeadRequests.DataSource; | |||
var currentRowData = repeaterData[index]; | |||
// ... | |||
} | |||
} | } | ||
</syntaxhighlight> | </syntaxhighlight> | ||
Perché questo funzioni è necessario che l'elemento che manda il '''submit''' possieda gli attributi <code>CommandName</code> e <code>CommandArgument</code> | |||
<syntaxhighlight lang="xml"> | |||
<asp:LinkButton ID="btnSubmit" runat="server" Text="Submit" | |||
CommandName="SubmitData" | |||
CommandArgument='<%# Container.ItemIndex %>'> | |||
</asp:LinkButton> | |||
</syntaxhighlight> | |||
⚠ '''ATTENZIONE''' : Non utilizzare l'elemento <code><asp:Button></asp:Button></code> perché non lancia l'evento ''ItemCommand'', dev'essere un limite della tecnologia. | |||
== Nuovi Sviluppi == | |||
=== Lead Manager Budget === | |||
Si tratta di aggiungere un campo <code>budget</code> al singolo Lead che può avere i valori | |||
* ''null'' | |||
* ''Medio'' | |||
* ''Alto'' | |||
In Lead Manager, nel '''pannello delle configurazioni''' di brand e province, apparirà anche la possibilità di selezionare i lead in base al valore di <code>budget</code>. | |||
Un utente, ad esempio, potrà scegliere di ricevere lead ''medi'' e ''alti'' ma non ''bassi''. | |||
I lead con valore ''null'' saranno quelli privati e riferiti ad un certo brand. Su questi non ci saranno filtri. | |||
Versione attuale delle 10:11, 19 mar 2025
Progetto che prevede API e Frontend,
il primo sviluppato in MVC e il secondo in Web Forms.
Configurazioni
[modifica]Scadenze dei lead
[modifica]Non ce le ricordiamo mai.
- Lead Pubblico : 5 giorni (120hours)
- Lead Privato : 30 giorni (720hour)
Sono definiti nel file Web.config del progetto LeadManager.API
Database Credenziali
[modifica]LM Database Azure:
leadmanager-db-server.database.windows.net LeadManagerDBAdmin Lm20!@!.20Px
Password per Publish
[modifica]Quando si esegue il deploy su Azure vengono richieste le seguenti password:
| Progetto | username | password |
|---|---|---|
| LeadManager | $leadman |
lWsBF5bqAQm0t3pjTk3onlphKFKadeeu4MXZGaKfCtDtilAJrbYtFfjlofmo
|
| LeadManager API | $leadmanapi |
HrE4xwlPSnpZh3BT9y1t13gNjlvt9v3kJ2XWbKCTNj5T8Jl0eNYjZmG3gzpj
|
| LeadManager Admin | $leadmanadmin |
mQg6JRZaP6Bajsskr9tRyMtqWNBqgZQ73f0vPRM2e5tb9kL5lkFdnkwxe1h6
|
| Progetto | username | password |
|---|---|---|
| LeadManager | $leadmantest |
tYcDYbkhilS34g8oNz9sK2taK9fuzfsfAFuDf1X5jaLM5hMh1Zci7uwP26Eb
|
| LeadManager API | $leadmanapitest |
C3JiWBtxkvKGSc7t5k0ccYfvRmgmK2ZbtNotyvYyoY7HwoblfXyb1dnc0eMP
|
API Access Token
[modifica]Abbiamo generato un Access Token senza scadenza per utilizzarlo nei vari servizi (es. Zapier).
Eccolo
eyJhbGciOiJodHRwOi8vd3d3LnczLm9yZy8yMDAxLzA0L3htbGRzaWctbW9yZSNobWFjLXNoYTI1NiIsInR5cCI6IkpXVCJ9.eyJodHRwOi8vc2NoZW1hcy54bWxzb2FwLm9yZy93cy8yMDA1LzA1L2lkZW50aXR5L2NsYWltcy9uYW1laWRlbnRpZmllciI6IjYwZDFlMjAwLTk4MTctNDIxNC04NDk1LWFkZWRlNjY3NWI3OCIsImh0dHA6Ly9zY2hlbWFzLnhtbHNvYXAub3JnL3dzLzIwMDUvMDUvaWRlbnRpdHkvY2xhaW1zL25hbWUiOiJsbWFkbWluIiwiaHR0cDovL3NjaGVtYXMubWljcm9zb2Z0LmNvbS9hY2Nlc3Njb250cm9sc2VydmljZS8yMDEwLzA3L2NsYWltcy9pZGVudGl0eXByb3ZpZGVyIjoiQVNQLk5FVCBJZGVudGl0eSIsIkFzcE5ldC5JZGVudGl0eS5TZWN1cml0eVN0YW1wIjoiNGY1NTA5ZjgtMjFjNC00NTg0LTkxODUtZjYyNGM2Yjk0YWNlIiwiaHR0cDovL3NjaGVtYXMubWljcm9zb2Z0LmNvbS93cy8yMDA4LzA2L2lkZW50aXR5L2NsYWltcy9yb2xlIjoiQWRtaW5pc3RyYXRvcnMiLCJsYXN0bG9naW5kYXRldGltZSI6IjA0LzA1LzIwMjEgMDk6NDk6NTkiLCJsb2dpbmNvdW50IjoiMSIsIlJvbGUiOiJBZG1pbmlzdHJhdG9ycyIsIm5iZiI6MTczMTk0NDI1OSwiZXhwIjo0ODg1NTQ0MjU5LCJpc3MiOiIxMjcuMC4wLjEiLCJhdWQiOiI5MjNhNzI5MXg4NDIxZDk5cXdlMDFoMDEzODk5M3h5MyJ9.40iDZ188V5c_6xrPpks1HIQ1yT7LEgvvP5_rKTo4W80
Admin Credenziali
[modifica]- http://leadmanadmin.azurewebsites.net/
- user:
admin - pass:
2kdb@2020!
DevExpress
[modifica]Il progetto utilizza l'estensione di Visual Studio DevExpress.
Per aggiornare i progetti della soluzione:
- Farsi scaricare l'ultima versione da Mengoli (hanno loro l'account)
- Runnare l'installer
- Chiedere a Mengoli di inserire le credenziali (via TeamViewer)
- Aprire Visual Studio
Extensions=>DevExpress=>Project Converter- Selezionare la versione nuova e seguire il wizard
Procedere disinstallando la vecchia versione dal pannello di controllo di Windows. https://docs.devexpress.com/GeneralInformation/2221/updates/upgrade-notes
Struttura
[modifica]Il Frontend utilizza due livelli di astrazione: biz e dal.
- biz : La logica applicativa, mette a disposizione i modelli e quando deve recuperare informazioni dal database si affida al livello inferiore dal
- dal : Il livello basso, nel quale vengono chiamate le vere e proprie stored procedure.
- entity: Definizione delle entità tabelle usato da dal
biz e dal sono veri e propri progetti nella soluzione.
Esempio di implementazione
[modifica]Dovendo implementare la nuova configurazione che permette ai negozi di filtrare le richieste in base al budget,
vogliamo costruire da zero i livelli dal e biz seguendo la struttura del progetto.
Livello Entity
[modifica]Nel progetto LeadManager.Entities creare un file NomeClasse.cs nel quale
- creare una nuova classe
NomeClasse : BaseEntityche definisca esattamente i campi della tabella come property - creare una nuova classe
NomeClasse_List : BaseEntityList<NomeClasse> - creare una nuova classe
NomeClasse_SearchParameters : BaseEntitySearchParameterscon i filtri necessari per avere una get delle opzioni budget di una company - creare una nuova classe
NomeClasse_ActionParameters : BaseEntityActionParameters - aggiungere la definizione delle enum che definiscono il tipo di filtro
Avendo NomeClasse come ConfigCompanyBudget:
public class ConfigCompanyBudget : BaseEntity {
public int config_company_budgetID { get; set; }
public int companyID { get; set; }
public int? budgetID { get; set; }
}
public class ConfigCompanyBudget_List : BaseEntityList<ConfigCompanyBudget> {}
public class ConfigCompanyBudget_SearchParameters : BaseEntitySearchParameters {
public int companyID { get; set; }
}
public class ConfigCompanyBudget_ActionParameters : BaseEntityActionParameters {}
public enum ConfigCompanyBudget_SearchType {
CompleteSearch = 0,
ListSelected_by_CompanyId = 1
}
public enum ConfigCompanyBudget_ActionType {}
È possibile definire delle enum
- SearchType per specificare le tipologie di filtri di ricerca che verranno applicate prima di restituire la lista
- ActionType per definire eventuali azioni custom che non sono tra quelle base (CRUD).
Livello DAL
[modifica]Nel progetto LeadManager.Dal.sql
creare una classe che estenda tutte quelle appena definite nel livello entity.
public class ConfigCompanyBudgetDal : BaseDal<ConfigCompanyBudget>, IDataLayer<ConfigCompanyBudget, ConfigCompanyBudget_List,int,ConfigCompanyBudget_SearchParameters, ConfigCompanyBudget_ActionParameters>
col compilatore di VS implementare tutti i metodi astratti e di interfaccia con il default throw new NotImplementedException().
In generale ci sono i classici metodi CRUD: CREATE, READ, UPDATE, DELETE da implementare a seconda dell'occorrenza.
Il primo metodo da implementare è RecordToEntity(),
che definisce il modello base di ritorno.
protected override ConfigCompanyBudget RecordToEntity(SqlDataReader result, Hashtable MyOrdinals) {
try {
ConfigCompanyBudget MyEntity = new ConfigCompanyBudget {
config_company_budgetID = ReadField_Integer(ref result, (int?)MyOrdinals["config_company_budgetID"]),
companyID = ReadField_Integer(ref result, (int?)MyOrdinals["companyID"]),
budgetID = ReadField_Integer(ref result, (int?)MyOrdinals["budgetID"])
};
return MyEntity;
}
catch (Exception ex) {
throw new Exceptions.LeadManager_DAL_Exception("Errore in " + global::System.Reflection.MethodBase.GetCurrentMethod().Name, ex);
}
}
Il secondo è List()
che fa la classica select.
public ConfigCompanyBudget_List List(ConfigCompanyBudget_SearchParameters MySearchCriteria, int PageIndex = 0, int PageSize = 100000) {
string StoredProcedureName = "";
SqlParameter[] MyParameters = null;
switch (MySearchCriteria.SearchType) {
case (int)ConfigCompanyBudget_SearchType.ListSelected_by_CompanyId:
MyParameters = new SqlParameter[] {
new SqlParameter("@companyid", SqlDbType.Int,4) {Value = MySearchCriteria.companyID}
};
StoredProcedureName = "ConfigCompanyBudget_GetSelected_byCompany";
break;
}
try {
using (SqlDataReader result = SqlHelper.ExecSPForReader(ConnectionString, StoredProcedureName, MyParameters)) {
ConfigCompanyBudget_List arrayoggetti = new ConfigCompanyBudget_List();
Hashtable MyOrdinals = ReadOrdinals(result);
while (result.Read()) {
arrayoggetti.Add(RecordToEntity(result, MyOrdinals));
}
return arrayoggetti;
}
}
catch (Exception ex) {
throw new Exceptions.LeadManager_DAL_Exception("Errore in " + global::System.Reflection.MethodBase.GetCurrentMethod().Name + "- Stored Procedure: " + StoredProcedureName, ex);
}
}
che sta utilizzando una stored procedure ConfigCompanyBudget_GetSelected_byCompany che andremo a definire sul database.
Il terzo è Add()
che fa la classica insert.
public ConfigCompanyBudget Add(ConfigCompanyBudget record) {
string StoredProcedureName = "";
SqlParameter[] MyParameters = null;
MyParameters = new SqlParameter[] {
new SqlParameter("@companyid", SqlDbType.Int,4) {Value = record.companyID},
new SqlParameter("@budgetid", SqlDbType.Int,4) {Value = record.budgetID}
};
StoredProcedureName = "ConfigCompanyBudget_Add_byCompany";
return record;
}
utilizzando una stored procedure ConfigCompanyBudget_Add_byCompany che andremo a definire sul database.
Metodo Action()
[modifica]Si utilizza quando si devono implementare azioni diverse dalle standard CRUD.
Ognuna di queste azioni vengono identificate dalla enum con _ActionType definita nel livello entity.
public bool Action(LeadRequest record, LeadRequest_ActionParameters MyActionParameters) {
string StoredProcedureName = "";
SqlParameter[] MyParameters = null;
switch (MyActionParameters.ActionType) {
case (int)LeadRequest_ActionType.New_DBAdvice:
// ... Gestione prima custom action
break;
case (int)LeadRequest_ActionType.Restore_Lead:
// ... Gestione seconda custom action
break;
}
try {
if (SqlHelper.ExecSPNonQuery(ConnectionString, StoredProcedureName, MyParameters) > 0)
return true;
return false;
}
catch (Exception ex) {
throw new Exceptions.LeadManager_DAL_Exception("Errore in " + global::System.Reflection.MethodBase.GetCurrentMethod().Name + "- Stored Procedure: " + StoredProcedureName, ex);
}
}
Database
[modifica]Creare la nuova store procedure ConfigCompanyBudget_GetSelected_byCompany
che prenda come parametro @companyID
anche definito in ConfigCompanyBudget_SearchParameters
che restituisca tutte le configurazioni budget della company selezionata
nel formato della classe ConfigCompanyBudget_List.
Creare la nuova store procedure ConfigCompanyBudget_Add_byCompany allo stesso modo.
Livello Biz
[modifica]Progetto LeadManager.BIZ
Questo livello fa da tramite tra l'applicazione e il dal.
Creare una classe ConfigCompanyBudgetBiz che estenda BaseBiz come questa:
public class ConfigCompanyBudgetBiz : BaseBiz<ConfigCompanyBudget,ConfigCompanyBudget_List,int,ConfigCompanyBudget_SearchParameters,ConfigCompanyBudget_ActionParameters>
la quale può implementare qualsiasi metodo andando ad attingere al livello dal per quanto riguarda le query.
ASP.NET Web Forms
[modifica]Tecnologia largamente superata da MVC,
lenta a caricare e complessa da mantenere.
In questa sezione faremo approfondimenti sulle parti che ci hanno messo in difficoltà.
Repeater
[modifica]Quello che in MVC sarebbe un semplice ciclo for qui diventa un mostro monolitico.
Il fatto che non si possano utilizzare form complica ancora di più le cose nel caso in cui si debba implementare un submit in uno degli elementi del ciclo.
Frontend aspx
[modifica]<asp:Repeater ID="RPT_LeadRequests" runat="server" OnItemDataBound="RPT_LeadRequests_ItemDataBound" OnItemCommand="RPT_LeadRequests_ItemCommand">
<ItemTemplate>
<!-- Elementi del ciclo -->
<div data-nome="<%# Eval("nome") %>" data-cognome="<%# Eval("cognome") %>">
<asp:LinkButton
ID="btnSubmit" runat="server" Text="Submit"
CommandName="SubmitData" CommandArgument='<%# Container.ItemIndex %>'>
</asp:LinkButton>
</div>
</ItemTemplate>
</asp:Repeater>
Backend aspx.cs
[modifica]Bind del DataSource
[modifica]Bindare la data nel Repeater
protected void Page_Load(object sender, EventArgs e) {
// ...
// Questa operazione dev'essere fatta sempre sia in PostBack che non
// Recuperare la data da mettere nel repeater (una lista)
RPT_LeadRequests.DataSource = data;
RPT_LeadRequests.DataBind();
//...
}
ItemDataBound
[modifica]Creare un evento ItemDataBound,
il cui nome va specificato dall'attributo OnItemDataBound del frontend,
che si occupa del loop del ciclo
protected void RPT_LeadRequests_ItemDataBound(object sender, RepeaterItemEventArgs e) {
if (e.Item.ItemType == ListItemType.Item || e.Item.ItemType == ListItemType.AlternatingItem) {
// Recupera l'oggetto dati per l'elemento corrente
var dataItem = (MioOggetto)e.Item.DataItem; // cast necessario
// eventuali operazioni sugli elementi di questa riga di ciclo
}
}
ItemCommand
[modifica]Se è necessario effettuare un submit all'interno di una riga del Repeater,
bisogna creare un evento ItemCommand
il cui nome va specificato dall'attributo OnItemCommand del frontend,
protected void rpt_LeadRequests_ItemCommand(object source, RepeaterCommandEventArgs e) {
if (e.CommandName == "SubmitData") {
// Ottieni l'indice dell'elemento che ha attivato l'evento
int index = Convert.ToInt32(e.CommandArgument);
var repeaterData = (IEnumerable<MioOggetto>)RPT_LeadRequests.DataSource;
var currentRowData = repeaterData[index];
// ...
}
}
Perché questo funzioni è necessario che l'elemento che manda il submit possieda gli attributi CommandName e CommandArgument
<asp:LinkButton ID="btnSubmit" runat="server" Text="Submit"
CommandName="SubmitData"
CommandArgument='<%# Container.ItemIndex %>'>
</asp:LinkButton>
⚠ ATTENZIONE : Non utilizzare l'elemento <asp:Button></asp:Button> perché non lancia l'evento ItemCommand, dev'essere un limite della tecnologia.
Nuovi Sviluppi
[modifica]Lead Manager Budget
[modifica]Si tratta di aggiungere un campo budget al singolo Lead che può avere i valori
- null
- Medio
- Alto
In Lead Manager, nel pannello delle configurazioni di brand e province, apparirà anche la possibilità di selezionare i lead in base al valore di budget.
Un utente, ad esempio, potrà scegliere di ricevere lead medi e alti ma non bassi.
I lead con valore null saranno quelli privati e riferiti ad un certo brand. Su questi non ci saranno filtri.