Segui

Tutorial C#: come leggere un report di Perfetto4

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

  • 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:

    Login.png
    Schermata di "Login"
  • Inserire i campi relativi alla fase di lettura dei Rapportini:

    Reading.png

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;
4.png
  • 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;
1.png

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.

Reading.png

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 { }
}
Altre domande? Invia una richiesta

Commenti

Powered by Zendesk