From 12f9ed10c291059ea1a8226370f7d21794381dd8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Radek=20Pu=C5=A1?= <pusradek@fit.cvut.cz> Date: Sun, 1 Dec 2019 22:01:54 +0100 Subject: [PATCH] fully implementedy csv import to DB (via web interface) + DI --- .../ImportCSVLogic/IModelImporter.cs | 10 ++ .../ImportCSVLogic/ImportTester.cs | 91 +----------------- .../ImportCSVLogic/ModelImporter.cs | 95 ++++++++++++++++++- .../home/components/import/import.service.ts | 4 - Core/Core/Controllers/ImportController.cs | 44 +++++++-- Core/Core/Startup.cs | 4 + 6 files changed, 144 insertions(+), 104 deletions(-) create mode 100644 Core/Core/BusinessLogic/ImportCSVLogic/IModelImporter.cs diff --git a/Core/Core/BusinessLogic/ImportCSVLogic/IModelImporter.cs b/Core/Core/BusinessLogic/ImportCSVLogic/IModelImporter.cs new file mode 100644 index 0000000..4f19ce5 --- /dev/null +++ b/Core/Core/BusinessLogic/ImportCSVLogic/IModelImporter.cs @@ -0,0 +1,10 @@ +using System.Collections.Generic; + +namespace Core.BusinessLogic.ImportCSVLogic +{ + public interface IModelImporter + { + void Import(string filePath, string fileName, long userID); + void SaveRecords(long userID, string fileName, IEnumerable<DocumentLayout> records); + } +} \ No newline at end of file diff --git a/Core/Core/BusinessLogic/ImportCSVLogic/ImportTester.cs b/Core/Core/BusinessLogic/ImportCSVLogic/ImportTester.cs index 623fc36..fa5bd56 100644 --- a/Core/Core/BusinessLogic/ImportCSVLogic/ImportTester.cs +++ b/Core/Core/BusinessLogic/ImportCSVLogic/ImportTester.cs @@ -21,8 +21,6 @@ namespace Core.BusinessLogic.ImportCSVLogic public void Test() { - IEnumerable<DocumentLayout> records; - //using (var reader = new StreamReader("C:\\Users\\Krypton0\\Desktop\\History_01_original.csv")) //using (var reader = new StreamReader("C:\\Users\\Krypton0\\Desktop\\2178754183-0800_2019-01-01_2020-01-01.csv"))//csas using (var reader = new StreamReader("C:\\Users\\Krypton0\\Desktop\\data-1565252156518.csv"))//csas @@ -38,94 +36,9 @@ namespace Core.BusinessLogic.ImportCSVLogic Console.WriteLine($"Field with names ['{string.Join("', '", headerNames)}'] at index '{index}' was not found."); }; - records = csv.GetRecords<DocumentLayout>(); - - SaveRecords(1,"testName" + DateTime.Now.ToString() + ".csv", records); - - string text = string.Empty; - int i = 0; - - foreach (var doc in records) - { - text += doc.Dump(", ") + '\n'; - if (i++ > 10) - break; - } - - Console.WriteLine(text); - } - } - - private void SaveRecords(long userID, string fileName, IEnumerable<DocumentLayout> records) - { - User user = model.Users - .Include(u => u.Files) - .First(u => u.ID == userID); - - if (user == null) - throw new ArgumentException($"UnknownUser with ID {userID}"); - - if(user.Files.Any(f => f.Name == fileName)) - throw new ArgumentException($"Filename {fileName} already exists"); - - Models.File file = new Models.File() { Name = fileName, Transactions = new List<Transaction>() }; - - var transactionTypesList = model.TransactionTypes.ToList(); //DB performance - var constantSymbolsList = model.ConstantSymbols.ToList(); - var accountList = model.Accounts.ToList(); - - foreach(var record in records) - { - Transaction transaction = new Transaction(record); - - if (!string.IsNullOrEmpty(record.TransactionType)) - transaction.TransactionType = transactionTypesList.FirstOrDefault(t => record.TransactionType.StartsWith(t.Description)); - - transaction.ConstantSymbol = ParseConstantSymbol(record.ConstantSymbol, constantSymbolsList); - - transaction.SenderAccount = ParseAccount(record.Account, accountList); - if(transaction.SenderAccount == null) - { - Console.WriteLine($"Error parsing mandatory attribut SenderAccount: {record.Account}"); - continue; - } - - transaction.RecipientAccount = ParseAccount(record.RecipientAccount, accountList); - - - file.Transactions.Add(transaction); + ModelImporter importer = new ModelImporter(model); + importer.SaveRecords(1,"testName" + DateTime.Now.ToString() + ".csv", csv.GetRecords<DocumentLayout>()); } - user.Files.Add(file); - model.SaveChanges(); - } - - private Account ParseAccount(string accountToParse, List<Account> accountList) - { - Account parsedAccount = Account.CreateAccount(accountToParse); - if (parsedAccount == null) - return null; - - Account account = accountList.FirstOrDefault(a => a.Equals(parsedAccount)); - if (account != null) - return account; - - model.Accounts.Add(parsedAccount); - accountList.Add(parsedAccount); - return parsedAccount; - } - - private ConstantSymbol ParseConstantSymbol(string constantSymbolToParse, List<ConstantSymbol> constantSymbolsList) - { - if (!short.TryParse(constantSymbolToParse, out short parsedValue)) - return null; - - ConstantSymbol symbol = constantSymbolsList.FirstOrDefault(c => c.ProvidedID == parsedValue); - if (symbol != null) - return symbol; - - symbol = new ConstantSymbol(parsedValue); - constantSymbolsList.Add(symbol); - return symbol; } } } diff --git a/Core/Core/BusinessLogic/ImportCSVLogic/ModelImporter.cs b/Core/Core/BusinessLogic/ImportCSVLogic/ModelImporter.cs index 62f81a0..eb73125 100644 --- a/Core/Core/BusinessLogic/ImportCSVLogic/ModelImporter.cs +++ b/Core/Core/BusinessLogic/ImportCSVLogic/ModelImporter.cs @@ -1,12 +1,16 @@ using Core.Data; +using Core.Models; +using CsvHelper; +using Microsoft.EntityFrameworkCore; using System; using System.Collections.Generic; +using System.IO; using System.Linq; using System.Threading.Tasks; namespace Core.BusinessLogic.ImportCSVLogic { - public class ModelImporter + public class ModelImporter : IModelImporter { private readonly Model model; @@ -15,9 +19,94 @@ namespace Core.BusinessLogic.ImportCSVLogic this.model = model; } - public void Import() + public void Import(string filePath, string fileName, long userID) { - throw new NotImplementedException(); + using (var reader = new StreamReader(filePath)) + using (var csv = new CsvReader(reader)) + { + csv.Configuration.BadDataFound = null; + csv.Configuration.HeaderValidated = null; + csv.Configuration.Delimiter = ","; + csv.Configuration.MissingFieldFound = (headerNames, index, context) => + { + Console.WriteLine($"Field with names ['{string.Join("', '", headerNames)}'] at index '{index}' was not found."); + }; + + ModelImporter importer = new ModelImporter(model); + importer.SaveRecords(userID, fileName, csv.GetRecords<DocumentLayout>()); + } + } + + public void SaveRecords(long userID, string fileName, IEnumerable<DocumentLayout> records) + { + User user = model.Users + .Include(u => u.Files) + .First(u => u.ID == userID); + + if (user == null) + throw new ArgumentException($"UnknownUser with ID {userID}"); + + if (user.Files.Any(f => f.Name == fileName)) + throw new ArgumentException($"Filename {fileName} already exists"); + + Models.File file = new Models.File() { Name = fileName, Transactions = new List<Transaction>() }; + + var transactionTypesList = model.TransactionTypes.ToList(); //DB performance + var constantSymbolsList = model.ConstantSymbols.ToList(); + var accountList = model.Accounts.ToList(); + + foreach (var record in records) + { + Transaction transaction = new Transaction(record); + + if (!string.IsNullOrEmpty(record.TransactionType)) + transaction.TransactionType = transactionTypesList.FirstOrDefault(t => record.TransactionType.StartsWith(t.Description)); + + transaction.ConstantSymbol = ParseConstantSymbol(record.ConstantSymbol, constantSymbolsList); + + transaction.SenderAccount = ParseAccount(record.Account, accountList); + if (transaction.SenderAccount == null) + { + Console.WriteLine($"Error parsing mandatory attribut SenderAccount: {record.Account}"); + continue; + } + + transaction.RecipientAccount = ParseAccount(record.RecipientAccount, accountList); + + + file.Transactions.Add(transaction); + } + user.Files.Add(file); + model.SaveChanges(); + } + + private Account ParseAccount(string accountToParse, List<Account> accountList) + { + Account parsedAccount = Account.CreateAccount(accountToParse); + if (parsedAccount == null) + return null; + + Account account = accountList.FirstOrDefault(a => a.Equals(parsedAccount)); + if (account != null) + return account; + + model.Accounts.Add(parsedAccount); + accountList.Add(parsedAccount); + return parsedAccount; + } + + private ConstantSymbol ParseConstantSymbol(string constantSymbolToParse, List<ConstantSymbol> constantSymbolsList) + { + if (!short.TryParse(constantSymbolToParse, out short parsedValue)) + return null; + + ConstantSymbol symbol = constantSymbolsList.FirstOrDefault(c => c.ProvidedID == parsedValue); + if (symbol != null) + return symbol; + + symbol = new ConstantSymbol(parsedValue); + constantSymbolsList.Add(symbol); + return symbol; } } } diff --git a/Core/Core/ClientApp/src/app/home/components/import/import.service.ts b/Core/Core/ClientApp/src/app/home/components/import/import.service.ts index dbc8cdb..640d4dd 100644 --- a/Core/Core/ClientApp/src/app/home/components/import/import.service.ts +++ b/Core/Core/ClientApp/src/app/home/components/import/import.service.ts @@ -16,12 +16,8 @@ export class ImportService { } postFile(fileToUpload: File): Observable<Object> { - //const endpoint = 'your-destination-url'; const formData: FormData = new FormData(); formData.append('fileKey', fileToUpload, fileToUpload.name); return this.httpClient.post('api/Import', formData); - //.post(endpoint, formData, { headers: yourHeadersConfig }) - //.map(() => { return true; }) - //.catch((e) => console.log(e)); } } diff --git a/Core/Core/Controllers/ImportController.cs b/Core/Core/Controllers/ImportController.cs index 859ab03..fe34a09 100644 --- a/Core/Core/Controllers/ImportController.cs +++ b/Core/Core/Controllers/ImportController.cs @@ -2,7 +2,10 @@ using System.Collections.Generic; using System.IO; using System.Linq; +using System.Security.Claims; using System.Threading.Tasks; +using Core.BusinessLogic.ImportCSVLogic; +using Core.Data; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; @@ -14,6 +17,15 @@ namespace Core.Controllers [Authorize(Policy = "RequireLoggedIn")] public class ImportController : ControllerBase { + private readonly Model Context; + private IModelImporter ModelImporter; + + public ImportController(Model context, IModelImporter modelImporter) + { + Context = context; + ModelImporter = modelImporter; + } + // GET: api/Import/5 [HttpGet("{id}", Name = "GetImport")] public string Get(int id) @@ -23,33 +35,49 @@ namespace Core.Controllers // POST: api/Import [HttpPost] + [Authorize(Policy = "RequireLoggedIn")] [RequestFormLimits(MultipartBodyLengthLimit = 268435456)]// Set the limit to 256 MB - public async Task<IActionResult> Post([FromForm] List<IFormFile> files) + public async Task<IActionResult> Post() { + if (Request == null || Request.Form == null || Request.Form.Files.Count < 1) + return BadRequest("No file present"); + + var files = Request.Form.Files; + Claim userJwtID = User.Claims.First(c => c.Type == "UserID"); + long userID = long.Parse(userJwtID.Value); long size = files.Sum(f => f.Length); + // Don't rely on or trust the FileName property without validation. var filePaths = new List<string>(); - foreach (var formFile in files) + List<Task> asyncTasks = new List<Task>(files.Count); + foreach (IFormFile formFile in files) { if (formFile.Length <= 0) continue; // full path to file in temp location - var filePath = Path.GetTempFileName(); + string filePath = Path.GetTempFileName(); filePaths.Add(filePath); - using (var stream = new FileStream(filePath, FileMode.Create)) - { - await formFile.CopyToAsync(stream); - } + asyncTasks.Add(SaveAndImport(formFile, filePath, userID)); } // process uploaded files - // Don't rely on or trust the FileName property without validation. + foreach (var task in asyncTasks) + await task; return Ok(new { count = files.Count, size, filePaths }); } + + private async Task SaveAndImport(IFormFile formFile, string filePath, long userID) + { + using (var stream = new FileStream(filePath, FileMode.Create)) + { + await formFile.CopyToAsync(stream); + } + ModelImporter.Import(filePath, formFile.FileName, userID); + } // DELETE: api/ApiWithActions/5 [HttpDelete("{id}")] diff --git a/Core/Core/Startup.cs b/Core/Core/Startup.cs index 088c819..6e739f4 100644 --- a/Core/Core/Startup.cs +++ b/Core/Core/Startup.cs @@ -1,3 +1,4 @@ +using Core.BusinessLogic.ImportCSVLogic; using Core.Data; using Core.Helpers; using Microsoft.AspNetCore.Authentication.JwtBearer; @@ -64,6 +65,9 @@ namespace Core }); services.AddAuthorization( opt => opt.AddPolicy("RequireLoggedIn",policy => policy.RequireAuthenticatedUser())); + + // DI + services.AddScoped<IModelImporter, ModelImporter>(); } // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. -- GitLab