Modulo Import
Il modulo Import permette di eseguire un processo di elaborazione dati.
I dati possono essere recuperate da uno storage accessibile all'app oppure è possibile impostare un form con l'upload del set dati nel formato preferito (Excel, Csv o altro).
Il modulo si appoggia a IJobService per tenere traccia dell'avanzamento del processo e generare un log con gli eventi eseguiti.
Per attivare una sezione con modulo Import vanno configurati i settaggi nella relativa sezione dati specificando l'eventuale form di impostazioni e la classe di implementazione dei metodi.
Il modulo richiede di impostare una classe ereditata da ModuleImport in cui vanno implementati i metodi di validazione del form impostazioni (opzionale) e l'effettiva elaborazione dei dati.
Analizziamo ora la struttura di un modulo di import tipico prendendo come esempio quello degli iscritti newsletter.
Come prima cosa impostiamo un Form per gestire il caricamento del file Excel:
Dove configuriamo il controllo Upload per gestire file in formato Excel:
Procediamo quindi creando la classe derivata ed ereditando il metodo ImportAsync che qui suddividiamo per parti. La prima parte gestisce il recupero dei dati dal file Excel
public override async Task<List<ValidationError>> ImportAsync(Item item, Dictionary<string, object> moduleData, CancellationToken cancellationToken = default)
{
var providedValues = GetProvidedValues(moduleData);
string navigationPath = navigationContext.Navigation.GetCurrentSegment().NavigationPath;
ExcelData exchangeData = null;
var errors = new List<ValidationError>();
if (await jobService.IsWorkingJobAsync(navigationPath, cancellationToken))
{
errors.Add(new ValidationError { Name = "Import", Message = "Working job already in progress" });
return errors;
}
var fileDescriptions = JsonSerializer.Deserialize<List<FileDescription>>(Convert.ToString(providedValues.FirstOrDefault(x => x.Name == "FileUpload").Value), new JsonSerializerOptions { PropertyNameCaseInsensitive = true });
string fileName = fileDescriptions[0].FileName;
string fileId = fileDescriptions[0].FileId;
var uploadPath = environmentService.GetRootPath() + Path.Uploads;
string filePath = System.IO.Path.Combine(uploadPath, fileId + ".upload");
try
{
await using (var stream = new System.IO.FileStream(filePath, System.IO.FileMode.Open))
{
exchangeData = await excelService.StreamToExcelDataAsync(stream, cancellationToken);
}
}
catch (Exception ex)
{
errors.Add(new ValidationError { Name = "Import", Message = ex.Message });
return errors;
}
var providedIsReplaceValues = providedValues.FirstOrDefault(x => x.Name == "IsReplaceValues");
bool isReplaceValues = providedIsReplaceValues != null && providedIsReplaceValues.Value != null && Convert.ToBoolean(providedIsReplaceValues.Value) == true;
var thread = new Thread(new ParameterizedThreadStart(Process));
thread.Start(new ProcessData { ExchangeData = exchangeData, IsReplaceValues = isReplaceValues, NavigationPath = navigationPath, AppUrl = httpService.GetAppUrl() });
return errors;
}
Da qui avviamo il thread passando i parametri definiti in questa DTO:
private class ProcessData
{
public ExcelData ExchangeData { get; set; }
public bool IsReplaceValues { get; set; }
public string NavigationPath { get; set; }
public string AppUrl { get; set; }
}
Il metodo Process elabora i dati in backgroud:
private async void Process(object processData)
{
var data = (ProcessData)processData;
bool isCancelled = false;
string[] columns = ["Email", "AdditionalValues", "Culture", "Country", "Zone", "Interests", "Groups", "Source", "TermsName", "TermsVersion"];
var job = await jobService.StartNewJobAsync(data.NavigationPath, (int)data.ExchangeData.Items.Count);
// Check columns
foreach (string column in columns)
{
if (!data.ExchangeData.Fields.ContainsKey(column))
{
await job.AppendErrorAsync("Missing column " + column);
}
}
if (job.Errors != 0)
{
await job.UpdateAsync();
await job.CompleteAsync();
return;
}
// Check values
for (int i = 0; i < data.ExchangeData.Items.Count; i++)
{
var item = data.ExchangeData.Items[i];
int rowIndex = i + 2;
}
if (job.Errors != 0)
{
await job.UpdateAsync();
await job.CompleteAsync();
return;
}
for (int i = 0; i < data.ExchangeData.Items.Count; i++)
{
var item = data.ExchangeData.Items[i];
int rowIndex = i + 2;
if (!await jobService.IsWorkingJobAsync(data.NavigationPath))
{
isCancelled = true;
break;
}
job.Progress++;
bool isNew = true;
string status = "Insert";
string providedEmail = item["Email"].ToLower();
// Check values
bool isValidItem = true;
if (string.IsNullOrEmpty(item["Email"]))
{
await job.AppendErrorAsync(string.Format("Email is empty at {0}", rowIndex));
isValidItem = false;
}
if (isValidItem)
{
var subscriber = new NewsletterSubscriber()
{
Id = StructureDefinition.VoidId,
Email = providedEmail,
Status = SubscriberStatus.Active,
SubscriptionDate = timeProvider.GetLocalNow(),
Interests = [],
Groups = [],
AdditionalValues = [],
Source = item["Source"]
};
var existingSubscriber = await newsletterSubscriberService.GetSubscriberAsync(new NewsletterSubscriberFilter { Email = providedEmail });
if (existingSubscriber != null)
{
subscriber = existingSubscriber;
isNew = false;
status = "Update";
}
subscriber.Culture = item["Culture"];
subscriber.Country = item["Country"];
subscriber.Zone = item["Zone"];
try
{
await newsletterSubscriberService.SetAsync(subscriber);
await job.AppendLogAsync(status, providedEmail);
}
catch (Exception ex)
{
await job.AppendErrorAsync(ex.Message);
}
}
await job.UpdateAsync();
}
if (!isCancelled)
{
await job.CompleteAsync();
}
}
Questa tabella può essere usata come riferimento per identificare le aree della classe base ModuleImport che possono essere personalizzate creando una classe derivata.
Metodo | Descrizione | Possibile Personalizzazione |
---|---|---|
ValidateImportDataAsync | Valida i dati che devono essere importati per un item specifico. Verifica che i dati forniti soddisfino i requisiti definiti nel form associato. | Sovrascrivibile per aggiungere logica di validazione personalizzata, come regole aziendali specifiche o controlli su dati complessi prima dell'importazione. |
ImportAsync | Esegue l'importazione dei dati per un item specifico. Fornisce un elenco di eventuali errori di validazione che si verificano durante il processo di importazione. | Sovrascrivibile per implementare la logica di importazione dei dati, come la gestione di formati specifici (es. JSON, CSV), trasformazioni dei dati o aggiornamenti su entità correlate. |