I work on asp.net core 2.2 web API using C# language
I need to rewrite function below with best syntax and with best practice
web API below get Excel file from upload and return Excel file
it working without any issue but I need to rewrite it with best syntax and practice
some points I need to changes :
concatenate path is best thing using
are streaming using correctly
are memory copying file is correct
[HttpPost, DisableRequestSizeLimit]
[Route("Upload")]
public IActionResult Upload()
{
try
{
var DisplayFileName = Request.Form.Files[0];
string fileName = DisplayFileName.FileName.Replace(".xlsx", "-") + Guid.NewGuid().ToString() + ".xlsx";
string Month = DateTime.Now.Month.ToString();
string DirectoryCreate = myValue1 + "\\" + Month + "\\" + fileName;
string exportDirectory = myValue2 + "\\" + Month;
string exportPath = myValue2 + "\\" + Month + "\\" + fileName;
string FinalPath = exportPath;
if (!Directory.Exists(DirectoryCreate))
{
Directory.CreateDirectory(DirectoryCreate);
}
if (!Directory.Exists(exportDirectory))
{
Directory.CreateDirectory(exportDirectory);
}
CExcel ex = new CExcel();
if (DisplayFileName.Length > 0)
{
var filedata = ContentDispositionHeaderValue.Parse(Request.Form.Files[0].ContentDisposition).FileName.Trim('"');
var dbPath = Path.Combine(DirectoryCreate, fileName);
using (var stream = new FileStream(dbPath, FileMode.Create))
{
Request.Form.Files[0].CopyTo(stream);
stream.Flush();
stream.Close();
}
GC.Collect();
string error = "";
int rowCount = 0;
string inputTemplatePath = "";
var InputfilePath = System.IO.Path.Combine(GetFilesDownload, "DeliveryGeneration_Input.xlsx");
bool areIdentical = ex.CompareExcel(dbPath, InputfilePath, out rowCount, out error);
if (areIdentical == true)
{
List<InputExcel> inputexcellist = new List<InputExcel>();
inputexcellist = ex.Import(dbPath);
List<string> mods = new List<string>();
mods = inputexcellist.Select(x => x.ModuleName).Distinct().ToList();
var OutputfilePath = System.IO.Path.Combine(GetFilesDownload, "DeliveryGeneration_Output.xlsx");
if (System.IO.Directory.Exists(Path.Combine(exportDirectory, fileName)))
{
throw new Exception("Ok so the error message IS right.");
}
System.IO.File.Copy(OutputfilePath, Path.Combine(exportDirectory, fileName), true);
SqlConnection con;
foreach (var m in mods)
{
List<InputExcel> inputmodulelist = new List<InputExcel>();
inputmodulelist = inputexcellist.Where(x => x.ModuleName == m).ToList();
var dtimport = DatatableConversion.ToDataTable(inputmodulelist);
DataTable dtexport = new DataTable();
dtexport = _deliveryService.LoadExcelToDataTable(_connectionString, dtimport);
ex.Export(dtexport, m, exportPath);
}
}
var memory2 = new MemoryStream();
using (var stream = new FileStream(exportPath, FileMode.Open))
{
stream.CopyTo(memory2);
}
memory2.Position = 0;
return File(memory2, "text/plain", Path.GetFileName(exportPath));
}
else
{
return BadRequest();
}
}
catch (Exception ex)
{
return StatusCode(500, $"Internal server error: {ex}");
}
}
Update original post
function above get excel file as input with some data
then I search on database for matched data input
then get matched data on database on output file
then on last download it with output data
I using name space
using ClosedXML.Excel;
using OfficeOpenXml;
1 Answer 1
Naming Convention
current code is not using camelCase naming convention on local variables, and also is not given a good naming to some variables like DisplayFileName, Month, DirectoryCreate ..etc.it should be cameled-case.
Some variables have wrong naming like DisplayFileName it should be file because it's used for Request.Form.Files[0] and not to Request.Form.Files[0].FileName. dbPath is not best name for explaining a file that is related to the stored one. It could be changed to fileToComparePath since it's going to be compared with the input file and the exportPath can be fileExportPath.
it's important to always differs between Directory and File paths by either suffix the file or dir to the name, otherwise, it won't be a clear enough to other developers.
When dealing with paths you should always use the built-in methods for like System.IO.File and System.IO.Directory. This would ensure the compatibility with the current system requirements.
Short names are fast to type, easy to miss, and hard to follow. So, try to avoid short names like CExcel ex = new CExcel(); it would be better to name it excel rather than ex, and also var m in mods in the foreach loop.
Always give your variables a good explainable naming.
using
with using clause, there is no need to Flush or Close or Dispose, as it would automatically do that at the end of the block.
So this :
using (var stream = new FileStream(dbPath, FileMode.Create))
{
Request.Form.Files[0].CopyTo(stream);
stream.Flush();
stream.Close();
}
should be like this :
using (var stream = new FileStream(dbPath, FileMode.Create))
{
// you should use Write(stream) instead, or reset the stream position.
Request.Form.Files[0].CopyTo(stream);
}
Unused variables
FinalPath, filedata, inputTemplatePath, con why they're still there ? you should remove unused variables.
Other Notes
- When working with files and directories, it would be wise to use the built-in methods
Directory,FileandPathto avoid any unexcepted exceptions that have been already covered by them. - with
ifconditions, don't put the exception or the error at theelse, keep it at the top as it would be clearer than to be at the end of the code. - when doing
ToList()it'll return a newList. - with
Streamalways useWriteinstead ofCopyToasWriteis specific use and optimized forStream, whileCopyTois built for all compatible collections types.
Code Comments
here is some comments on the code (code has been shorten for brevity).
if (DisplayFileName.Length > 0) // should be inverted.
{
// var filedata = ContentDispositionHeaderValue.Parse(Request.Form.Files[0].ContentDisposition).FileName.Trim('"'); //unused
var dbPath = Path.Combine(DirectoryCreate, fileName); // this would return ../path/month/fileName-SomeGUID.xlsx/fileName-SomeGUID.xlsx
using (var stream = new FileStream(dbPath, FileMode.Create))
{
Request.Form.Files[0].CopyTo(stream);
// the Flush() and Close are handled by the `using` blocks
// stream.Flush();
// stream.Close();
}
// GC.Collect(); // unnecessary
string error = ""; // use null;
int rowCount = 0;
// string inputTemplatePath = ""; //unused
var InputfilePath = System.IO.Path.Combine(GetFilesDownload, "DeliveryGeneration_Input.xlsx");
bool areIdentical = ex.CompareExcel(dbPath, InputfilePath, out rowCount, out error); // rowCount and error have never been checked. what happens if they have values ?
if (areIdentical == true)
{
// List<InputExcel> inputexcellist = new List<InputExcel>();// unnecessary
List<InputExcel> inputexcellist = ex.Import(dbPath);
// List<string> mods = new List<string>(); // unnecessary
// it could be used in the foreach directlry
List<string> mods = inputexcellist.Select(x => x.ModuleName).Distinct().ToList();
var OutputfilePath = System.IO.Path.Combine(GetFilesDownload, "DeliveryGeneration_Output.xlsx");
// This is unnecessary as the `File.Copy` ovewrite flag is true.
// Although Directory.Exists should be File.Exists
// if (System.IO.Directory.Exists(Path.Combine(exportDirectory, fileName)))
// {
// throw new Exception("Ok so the error message IS right.");
// }
// Path.Combine(exportDirectory, fileName) has the same value of exportPath
System.IO.File.Copy(OutputfilePath, Path.Combine(exportDirectory, fileName), true);
// SqlConnection con; // unused
foreach (var m in mods) // m should be moduleName or name
{
// List<InputExcel> inputmodulelist = new List<InputExcel>(); // unnecessary
List<InputExcel> inputmodulelist = inputexcellist.Where(x => x.ModuleName == m).ToList();
DataTable dtimport = DatatableConversion.ToDataTable(inputmodulelist);
// DataTable dtexport = new DataTable(); // unnecessary
DataTable dtexport = _deliveryService.LoadExcelToDataTable(_connectionString, dtimport);
ex.Export(dtexport, m, exportPath);
}
}
var memory2 = new MemoryStream();
using (var stream = new FileStream(exportPath, FileMode.Open))
{
stream.CopyTo(memory2);
}
memory2.Position = 0;
// Path.GetFileName(exportPath) is equal to fileName
// "text/plain" ? why ? you should use the appropriate mime type for Excel
// for .xls = application/vnd.ms-excel
// for .xlsx = application/vnd.openxmlformats-officedocument.spreadsheetml.sheet
// or just use the ClosedXML Web API Extension
return File(memory2, "text/plain", Path.GetFileName(exportPath));
}
else
{
// invert the if, and move this at the top of blocks
return BadRequest();
}
Suggested Revision
if(Request.Form.Files.Length == 0)
{
throw new InvalidOperationException(" No Files");
}
var file = Request.Form.Files[0];
var fileName = $"{Path.GetFileNameWithoutExtension(file.FileName)}-{Guid.NewGuid()}.xlsx";
Func<string, string> generateFilePath = (path) =>
!string.IsNullOrWhiteSpace(path) && !string.IsNullOrWhiteSpace(fileName)
? Path.Combine(Directory.CreateDirectory(path).FullName, DateTime.Today.Month.ToString() , fileName)
: null;
var fileToComparePath = generateFilePath(myValue1);
var fileToExportPath = generateFilePath(myValue2);
if(fileToComparePath == null)
{
throw new ArgumentNullException(nameof(fileToComparePath));
}
if(fileToExportPath == null)
{
throw new ArgumentNullException(nameof(fileToExportPath));
}
using(var fileStream = File.Create(dbPath))
{
fileStream.Write(file);
}
if(!ex.CompareExcel(fileToComparePath, Path.Combine(GetFilesDownload, "DeliveryGeneration_Input.xlsx"), out int rowCount, out string error))
{
return BadRequest();
}
File.Copy(Path.Combine(GetFilesDownload, "DeliveryGeneration_Output.xlsx"), fileToExportPath, true);
CExcel excel = new CExcel();
foreach (var moduleName in excel.Import(dbPath).Select(x => x.ModuleName).Distinct())
{
var inputModuleList = inputexcellist.Where(x => x.ModuleName == moduleName).ToList();
var tableImport = DatatableConversion.ToDataTable(inputModuleList);
var tableExport = _deliveryService.LoadExcelToDataTable(_connectionString, tableImport);
excel.Export(tableExport, moduleName, fileToExportPath);
}
MemoryStream excelFileStream = new MemoryStream();
using(var fileStream = File.Open(fileToExportPath, FileMode.Open))
{
fileStream.Write(excelFileStream);
}
return File(excelFileStream, "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", fileName);
-
\$\begingroup\$ thank you very much for your advices but there are more things i need to ask 1-string exportDirectory = myValue2 + "\\" + Month; so my question are there are another way to concate path without using "\\" \$\endgroup\$ahmed barbary– ahmed barbary2021年08月29日 07:04:57 +00:00Commented Aug 29, 2021 at 7:04
-
\$\begingroup\$ also another thing how to avoid error The process cannot access the file because it is being used by another process when create new file on my code \$\endgroup\$ahmed barbary– ahmed barbary2021年08月29日 07:07:26 +00:00Commented Aug 29, 2021 at 7:07
-
\$\begingroup\$ @ahmedbarbary use
Path.Combineto concatenate paths as of my example above. For file processing, you should consider usingSafeFileHandledocs.microsoft.com/en-us/dotnet/api/… \$\endgroup\$iSR5– iSR52021年08月29日 14:14:22 +00:00Commented Aug 29, 2021 at 14:14 -
\$\begingroup\$ can you please help me how to use safefilehandle according to code above please only that what i need how to use safe file handle on code above. can you please help me \$\endgroup\$ahmed barbary– ahmed barbary2021年08月29日 15:57:12 +00:00Commented Aug 29, 2021 at 15:57
-
\$\begingroup\$ also how to use async task on web api above \$\endgroup\$ahmed barbary– ahmed barbary2021年08月30日 06:22:34 +00:00Commented Aug 30, 2021 at 6:22
You must log in to answer this question.
Explore related questions
See similar questions with these tags.
DataTablefrom the database, not some metadata that could reference the actual file. please give us more details. \$\endgroup\$