Sommario
Questo tutorial è rivolto a sviluppatori .NET che devono integrare applicazioni esterne con Perfetto4 tramite Web Service SOAP. È richiesta una conoscenza base di C#, Windows Forms e XML.
Nel presente articolo sono descritti i passi da seguire per poter creare un esempio di un'applicazione scritta in linguaggio C# che consente di effettuare l'autenticazione al prodotto Perfetto4 e l'estrazione dei dati attraverso un report di Perfetto4, utilizzando la tecnologia Magic Link.
Per facilitare la lettura dell'articolo di seguito un indice delle sezioni dell'articolo:
- Creazione del progetto
- Creazione della maschera di "Connessione & Login" e "Lettura Rapportini"
- Connessione ai Web Services
- Autenticazione
- Creazione della maschera di ricerca e di visualizzazione dati
- Lettura e visualizzazione dati report
- Disconnessione
Creazione del progetto
- aprire "Visual Studio", in questo esempio è stato utilizzato Visual Studio 2022;
- selezionare "Nuovo progetto...";
- scegliere "Applicazione Windows Form";
- inserire il nome del progetto, in questo caso: "ExampleProject_Utility_CS".
Creazione della maschera di "Connessione & Login" e "Lettura Rapportini"
Nel seguente sezione è mostrato come creare l'interfaccia grafica dell'applicazione. Essa è divisa in due parti, una rivolta alla gestione dell'autenticazione e una con una tab dedicata all'inserimento del documento.
I passi da seguire per realizzare l'interfaccia sono:
-
Inserire i campi relativi alla fase di login:
Schermata di "Login" -
Inserire i campi relativi alla fase di lettura dei Rapportini:
Connessione ai Web Service di Perfetto4
Per poter accedere ai metodi web che consentono all'utente di registrarsi e inserire i dati in Perfetto4, è necessario aggiungere i riferimenti ai servizi di Perfetto4.
Le operazioni da seguire per aggiungere un riferimento ad un servizio sono le seguenti:
- posizionarsi sul nodo del progetto dell'albero dell'"Esplora soluzioni";
- fare click con il tasto destro del mouse;
- selezionare la voce "Aggiungi riferimento al servizio";
- selezionare la voce "WCF Web Service" e procedere;
- procedere lasciando le opzioni di default come nello screenshot seguente;
- inserire il percorso del Web Service ed effettuare l'individuazione del servizio per avere una conferma;
- scegliere un Namespace per il servizio che stiamo aggiungendo;
- finalizzare l'aggiunta del servizio premendo Avanti;
Per l'esempio è necessario inserire il riferimento per LoginManager e TbServices.
Una volta inseriti i riferimenti ai Web Service è necessario creare una loro istanza nel seguente modo:
Esempio C#: esempio di instanziazione dei client dei servizi
AppSession.BaseUrl = $"{protocol.Trim()}://{server.Trim()}:{port.Trim()}/{instance.Trim()}"; // Nella Solution i valori vengono presi dalla schermata di Login iniziale
using var loginManagerClient = new MicroareaLoginManagerSoapClient(
MicroareaLoginManagerSoapClient.EndpointConfiguration.MicroareaLoginManagerSoap,
$"{AppSession.BaseUrl.TrimEnd('/')}/LoginManager/LoginManager.asmx");
using var tbServicesClient = new TbServicesSoapClient(
TbServicesSoapClient.EndpointConfiguration.TbServicesSoap,
$"{AppSession.BaseUrl.TrimEnd('/')}/TbServices/TbServices.asmx");
Autenticazione
Per effettuare l'autenticazione al sistema è utilizzato il metodo web LoginCompact del LoginManager.
Nel seguente snippet di codice è mostrato come recuperare i valori dei campi relativi alla login e passare tali valori al metodo LoginCompact per ottenere il codice di autenticazione:
Esempio C#: logica di connessione al server e login ad un'azienda
//--------------------------------------------------------------------------
private async void buttonConnect_Click(object? sender, EventArgs e)
{
string protocol = comboBoxProtocol.SelectedItem?.ToString() ?? "http";
string server = textBoxServer.Text;
string port = string.IsNullOrWhiteSpace(textBoxPort.Text) ? "80" : textBoxPort.Text;
string instance = textBoxInstanceFolder.Text;
toolStripStatusLoginConnectedColor.BackColor = Color.Orange;
toolStripStatusLoginConnectedLabel.Text = "Connecting...";
Cursor.Current = Cursors.WaitCursor;
var result = await _connectionService.TestConnectionAsync(protocol, server, port, instance);
if (result.Success)
{
AppSession.BaseUrl = $"{protocol.Trim()}://{server.Trim()}:{port.Trim()}/{instance.Trim()}";
groupBoxLogin.Enabled = true;
LoadCompanies(false);
Cursor.Current = Cursors.Default;
toolStripStatusLoginConnectedColor.BackColor = Color.Green;
toolStripStatusLoginConnectedLabel.Text = "Connected ";
MessageBox.Show(result.Message, "Connected ", MessageBoxButtons.OK, MessageBoxIcon.Information);
groupBoxConnection.Enabled = false;
buttonDisconnect.Enabled = true;
}
else
{
groupBoxLogin.Enabled = false;
Cursor.Current = Cursors.Default;
MessageBox.Show(result.Message, "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
}
//--------------------------------------------------------------------------
private void buttonLogin_Click(object? sender, EventArgs e)
{
Cursor.Current = Cursors.WaitCursor;
if (!groupBoxLogin.Enabled)
{
MessageBox.Show("Connect first.", "Warning", MessageBoxButtons.OK, MessageBoxIcon.Warning);
return;
}
if (string.IsNullOrWhiteSpace(textBoxUser.Text) || string.IsNullOrWhiteSpace(textBoxPwd.Text))
{
MessageBox.Show("Username and password are required.", "Warning", MessageBoxButtons.OK, MessageBoxIcon.Warning);
return;
}
AppSession.Username = textBoxUser.Text.Trim();
Password = textBoxPwd.Text.Trim();
AppSession.ProducerKey = textBoxProducerKey.Text.Trim();
if (!PerformLogin())
return;
Cursor.Current = Cursors.Default;
DialogResult = DialogResult.OK;
Close();
}
//--------------------------------------------------------------------------
private bool PerformLogin()
{
string authToken = string.Empty;
if (string.IsNullOrEmpty(textBoxUser.Text) || string.IsNullOrEmpty(comboBoxCompany.Text))
{
MessageBox.Show("Select a company.");
return false;
}
string user = textBoxUser.Text;
string company = comboBoxCompany.Text;
using var client = new MicroareaLoginManagerSoapClient(
MicroareaLoginManagerSoapClient.EndpointConfiguration.MicroareaLoginManagerSoap,
$"{AppSession.BaseUrl.TrimEnd('/')}/LoginManager/LoginManager.asmx");
int result = client.LoginCompact(ref user, ref company, Password, AppSession.ProducerKey, false, out authToken);
if (result == 14)
{
var force = MessageBox.Show(
"Session already exists. Do you want to force the login?",
"Simultaneous login",
MessageBoxButtons.YesNo,
MessageBoxIcon.Warning);
if (force == DialogResult.Yes)
{
result = client.LoginCompact(ref user, ref company, Password, AppSession.ProducerKey, true, out authToken);
}
}
if (result == 0)
{
AppSession.AuthenticationToken = authToken;
AppSession.Company = company;
AppSession.Username = textBoxUser.Text;
AppSession.ProducerKey = textBoxProducerKey.Text.Trim();
return true;
}
MessageBox.Show($"Failed login. Code: {result}", "Login Failed", MessageBoxButtons.OK, MessageBoxIcon.Error);
return false;
}
Se la fase di autenticazione va a buon fine viene salvato il codice di autenticazione.
Creazione della maschera di ricerca e di visualizzazione dati
Nel seguente paragrafo è illustrato come creare l'interfaccia grafica di selezione e di visualizzazione dati di un report di Perfetto4. Essa è divisa in due parti, una rivolta ai parametri di filtro da passare al report e una dedicata alla visualizzazione dei dati in formato tabellare e grafico.
Richiesta dati report a Perfetto4
Per inviare a Perfetto4 una richiesta relativa un report si deve utilizzare il metodo XmlExecuteReport del Web Service EasyLook e passare ad esso una stringa xml contenente i parametri di filtro del report.
Nel codice seguente è mostrato come creare la stringa xml dei parametri da inviare e come invocare il metodo XmlExecuteReport impostando i parametri in modo corretto:
//--------------------------------------------------------------------------
private void buttonFilter_Click(object? sender, EventArgs e)
{
string job = textBoxJob.Text.Trim();
string yearText = textBoxYear.Text.Trim();
string nsRep = "Report.Perfetto.WorkingReports.EmployeeHoursByJob";
string xmlNs = "http://www.microarea.it/Schema/2004/Smart/Perfetto/WorkingReports/EmployeeHoursByJob.xsd";
AppendLog2("--------------------------------------------------");
AppendLog2("Avvio filtro report EmployeeHoursByJob.");
AppendLog2($"Input: job='{job}', yearText='{yearText}'");
int year = DateTime.Now.Year;
if (!int.TryParse(yearText, out year))
{
year = DateTime.Now.Year;
AppendLog2($"Anno non valido. Uso anno corrente: {year}");
}
string repParams = "<?xml version=\"1.0\" encoding=\"utf-8\"?>" +
"<maxs:EmployeeHoursByJob tbNamespace=\"" + nsRep + "\" xmlns:maxs=\"" + xmlNs + "\">" +
"<maxs:Parameters>" +
"<maxs:NoName title=\"Selezione\">" +
"<maxs:Group1 title=\"Commessa\">" +
"<maxs:AskComm type=\"String\" length=\"15\" title=\"Dal\" controlType=\"Text\" inputLimit=\"Lower\">" + job + "</maxs:AskComm>" +
"<maxs:EndComm type=\"String\" length=\"15\" title=\"Al\" controlType=\"Text\" inputLimit=\"Upper\">" + job + "</maxs:EndComm>" +
"</maxs:Group1>" +
"<maxs:Group2 title=\"Dipendente\">" +
"<maxs:ASkDip type=\"DateTime\" length=\"8\" title=\"Dal\" controlType=\"Text\"></maxs:ASkDip>" +
"<maxs:EndDip type=\"DateTime\" length=\"8\" title=\"Al\" controlType=\"Text\"></maxs:EndDip>" +
"</maxs:Group2>" +
"<maxs:Group3 title=\"Periodo\">" +
"<maxs:AskData type=\"String\" length=\"15\" title=\"Dal\" controlType=\"Text\" inputLimit=\"Lower\">" + year + "-01-01</maxs:AskData>" +
"<maxs:EndData type=\"String\" length=\"15\" title=\"Al\" controlType=\"Text\" inputLimit=\"Upper\">" + year + "-12-01</maxs:EndData>" +
"</maxs:Group3>" +
"</maxs:NoName>" +
"</maxs:Parameters>" +
"</maxs:EmployeeHoursByJob>";
...Lettura e visualizzazione dati report
Nel seguente sezione è mostrato come leggere e visualizzare i dati del report ritornati dal metodo XmlExecuteReport.
Di seguito la classe che rappresenta l'entità dipendente:
public class Employee
{
public string Code { get; set; } = null!;
public string Name { get; set; } = null!;
public string Hours { get; set; } = null!;
}Nel codice seguente è mostrato come recuperare i dati relativi al dipendente dalla stringa xml, come valorizzare una mappa e una lista di oggetti Employee, come associare la lista alla griglia e come valorizzare il grafico:
...
try
{
string easyLookUrl = $"{AppSession.BaseUrl.TrimEnd('/')}/EasyLook/EasyLookService.asmx";
AppendLog2($"Endpoint EasyLook: {easyLookUrl}");
AppendLog2($"Request XML:{Environment.NewLine}{repParams}");
using var aEasyLook = new EasyLookServiceSoapClient(
EasyLookServiceSoapClient.EndpointConfiguration.EasyLookServiceSoap,
easyLookUrl);
DateTime applicationDate = DateTime.Now;
Stopwatch sw = Stopwatch.StartNew();
EasyLookService.ArrayOfString getDataTextResult = aEasyLook.XmlExecuteReport(
AppSession.AuthenticationToken,
repParams,
applicationDate,
"Standard",
false);
sw.Stop();
AppendLog2($"XmlExecuteReport completato in {sw.ElapsedMilliseconds} ms.");
if (getDataTextResult == null || getDataTextResult.Count == 0)
{
AppendLog2("Nessuna risposta ricevuta da XmlExecuteReport.");
MessageBox.Show("Nessuna risposta ricevuta da XmlExecuteReport.");
return;
}
string xmlReport = getDataTextResult[0];
if (string.IsNullOrWhiteSpace(xmlReport))
{
AppendLog2("XML report vuoto.");
return;
}
AppendLog2($"Lunghezza XML report: {xmlReport.Length} caratteri.");
var emplList = new List<Data.Employee>();
var emplMap = new Dictionary<string, int>();
var doc = new XmlDocument();
doc.LoadXml(xmlReport);
var xmlNsManager = new XmlNamespaceManager(doc.NameTable);
xmlNsManager.AddNamespace("maxs", xmlNs);
var rows = doc.SelectNodes("//maxs:EmployeeHoursByJob/maxs:ReportData/maxs:Table15/maxs:Row", xmlNsManager);
int rowsCount = rows?.Count ?? 0;
AppendLog2($"Righe trovate nel report: {rowsCount}");
if (rows != null)
{
foreach (XmlNode node in rows)
{
string code = node.SelectSingleNode("maxs:Dipendente", xmlNsManager)?.InnerText ?? string.Empty;
string name = node.SelectSingleNode("maxs:Nome", xmlNsManager)?.InnerText ?? string.Empty;
string totSecondsText = node.SelectSingleNode("maxs:OTotale", xmlNsManager)?.InnerText ?? "0";
int.TryParse(totSecondsText, out int totalSeconds);
if (emplMap.ContainsKey(code))
{
emplMap[code] += totalSeconds;
}
else
{
emplMap.Add(code, totalSeconds);
emplList.Add(new Data.Employee { Code = code, Name = name });
}
}
}
foreach (var empl in emplList)
{
if (emplMap.TryGetValue(empl.Code, out int mapValue))
{
int nH = mapValue / 3600;
int nM = (mapValue % 3600) / 60;
empl.Hours = $"{nH}:{nM:00}";
}
}
dataGridViewChartHours.Rows.Clear();
dataGridViewChartHours.Columns.Clear();
dataGridViewChartHours.AutoGenerateColumns = false;
dataGridViewChartHours.Columns.Add("Code", "Codice dipendente");
dataGridViewChartHours.Columns.Add("Name", "Nome dipendente");
dataGridViewChartHours.Columns.Add("Hours", "Ore");
foreach (var empl in emplList)
{
dataGridViewChartHours.Rows.Add(empl.Code, empl.Name, empl.Hours);
}
AppendLog2($"DataGrid popolata con {emplList.Count} dipendenti.");
}
catch (Exception ex)
{
AppendLog2($"EXCEPTION: {ex.GetType().Name} - {ex.Message}");
MessageBox.Show($"Exception caught in XmlExecuteReport: {ex.Message}");
}
}Disconnessione
Alla chiusura dell'applicazione è necessario disconnettere l'utente dal sistema invocando il metodo LogOff.
Di seguito il codice per eseguire la disconnessione:
private void LogoutCurrentUser()
{
try
{
if (!string.IsNullOrEmpty(AppSession.AuthenticationToken))
{
using var client = new MicroareaLoginManagerSoapClient(
MicroareaLoginManagerSoapClient.EndpointConfiguration.MicroareaLoginManagerSoap,
$"{AppSession.BaseUrl.TrimEnd('/')}/LoginManager/LoginManager.asmx");
client.LogOff(AppSession.AuthenticationToken);
}
}
catch { }
}
Commenti