Read column values with formula Excel Data Reader not working - c#

My Excelsheet looks like this:
http://cdn.imghack.se/images/cdc24215d3d5079628a58878520c3028.png
Here is the part of my method that matters:
[HttpPost]
public ActionResult ShowExcelFile(GetExcel model)
{
DataSet result = null;
var file = model.Files[0];
if (file != null && file.ContentLength > 0)
{
// .xlsx
IExcelDataReader reader = ExcelReaderFactory.CreateOpenXmlReader(file.InputStream);
// .xls
//IExcelDataReader reader = ExcelReaderFactory.CreateBinaryReader(file.InputStream);
reader.IsFirstRowAsColumnNames = true; // if your first row contains column names
result = reader.AsDataSet();
reader.Close();
}
for (int i = 1; i < result.Tables[0].Rows.Count; i++)
{
DataRow data = result.Tables[0].Rows[i];
System.Diagnostics.Debug.Write(data.Table.Rows[i]["Amortization"]);
}
return View("ShowExcelFile");
}
System.Diagnostics.Debug.Write gives me no output, like "Amortization" doesn't exist.
My model:
public class GetExcel
{
public List<HttpPostedFileBase> Files { get; set; }
public GetExcel()
{
Files = new List<HttpPostedFileBase>();
}
}
My HTML:
#using (Html.BeginForm("ShowExcelFile", "ShowExcel", FormMethod.Post, new { enctype = "multipart/form-data" }))
{
#Html.TextBoxFor(m => m.Files, new { type = "file", name = "Files" })<br />
<input type="submit" value="Upload file" />
}
But when I use this Excelsheet:
http://cdn.imghack.se/images/1fd5dab2a891c3adda8cd33114ef07c1.png
It works fine and I get all values from column "Amortization" in output. Both files are .xlsx
Can anyone help me with this?
EDIT: I have found the problem (I guess). The sheet that works has values only, the other one has only formulas. Is it possible to read the value instead of the formula?

Related

export file with asp.net

This project is an ASP.Net Api project with Angular. What I'm trying to do is export data from a database table and into an excel file. So far, I've managed to export all the table data into an excel file, but struggle to select 2 or 3 fields in the table to export.
[HttpGet("download")]
public IActionResult DownloadExcel(string field)
{
string dbFileName = "DbTableName.xlsx";
FileInfo file = new FileInfo(dbFileName);
byte[] fileContents;
var stream = new MemoryStream();
using (ExcelPackage package = new ExcelPackage(file))
{
IList<UserTable> userList = _context.UserTable.ToList();
ExcelWorksheet worksheet = package.Workbook.Worksheets.Add("DbTableName");
int totalUserRows = userList.Count();
}
return File(fileContents, fileType, dbFileName);
}
There's no need to write so many if ... else if ... else if ... else if ... to get the related field names.
A nicer way is to
Use a field list (IList<string>)as a parameter.
And then generate a required field list by intersect.
Finally, we could use reflection to retrieve all the related values.
Implementation
public IActionResult DownloadExcel(IList<string> fields)
{
// get the required field list
var userType = typeof(UserTable);
fields = userType.GetProperties().Select(p => p.Name).Intersect(fields).ToList();
if(fields.Count == 0){ return BadRequest(); }
using (ExcelPackage package = new ExcelPackage())
{
IList<UserTable> userList = _context.UserTable.ToList();
ExcelWorksheet worksheet = package.Workbook.Worksheets.Add("DbTableName");
// generate header line
for(var i= 0; i< fields.Count; i++ ){
var fieldName = fields[i];
var pi= userType.GetProperty(fieldName);
var displayName = pi.GetCustomAttribute<DisplayNameAttribute>()?.DisplayName;
worksheet.Cells[1,i+1].Value = string.IsNullOrEmpty(displayName ) ? fieldName : displayName ;
}
// generate row lines
int totalUserRows = userList.Count();
for(var r=0; r< userList.Count(); r++){
var row = userList[r];
for(var c=0 ; c< fields.Count;c++){
var fieldName = fields[c];
var pi = userType.GetProperty(fieldName);
// because the first row is header
worksheet.Cells[r+2, c+1].Value = pi.GetValue(row);
}
}
var stream = new MemoryStream(package.GetAsByteArray());
return new FileStreamResult(stream,"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
}
}
You could configure the display name using the DsiplayNameAttribute:
public class UserTable
{
public int Id{get;set;}
[DisplayName("First Name")]
public string fName { get; set; }
[DisplayName("Last Name")]
public string lName { get; set; }
[DisplayName("Gender")]
public string gender { get; set; }
}
It's possible to add any properties as you like without hard-coding in your DownloadExcel method.
Demo :
passing a field list fields[0]=fName&fields[1]=lName&fields[2]=Non-Exist will generate an excel as below:
[Update]
To export all the fields, we could assume the client will not pass a fields parameter. That means when the fields is null or if the fields.Count==0, we'll export all the fields:
[HttpGet("download")]
public IActionResult DownloadExcel(IList<string> fields)
{
// get the required field list
var userType = typeof(UserTable);
var pis= userType.GetProperties().Select(p => p.Name);
if(fields?.Count >0){
fields = pis.Intersect(fields).ToList();
} else{
fields = pis.ToList();
}
using (ExcelPackage package = new ExcelPackage()){
....
}
}
if you want to use the datatable then we can define which you need to select from the datatable in this way
string[] selectedColumns = new[] { "Column1","Column2"};
DataTable dt= new DataView(fromDataTable).ToTable(false, selectedColumns);
or else if you wanna you list then you can use linq for selection of particular columns
var xyz = from a in prod.Categories
where a.CatName.EndsWith("A")
select new { CatName=a.CatName, CatID=a.CatID, CatQty = a.CatQty};

asp.net return same view but delete url?

I have a project where I report time on diffrent projects, and I am working on so I can delete a report incase I do it wrong, which is working decent.
When I go to the summery page of all my reports it lists all the dates, I click the date and it sends it to the TimeReport view like this:
http://localhost:9061/Reports/TimeReport/b604a74a-2034-4916-9534-57788db1e8e2
And than it checks if the ReportId has a value like this:
#if (Model.ReportId.HasValue)
{
<button type="button" class="btn btn-danger" data-toggle="modal" data-target="#basic">Ta bort!</button>
}
And if it exists a Remove button will appear, I click the remove button and it will remove the report. but the URL is still the same, so if I refresh the site my application will crash because it that ID no longer exists in the database.
if (form != null && form.AllKeys.Contains("delete"))
{
new DatabaseLayer().DeleteTimeReport(Guid.Parse(form["ReportId"]));
LoadDefaultSettings(projectData);
ViewData.Model = projectData;
ViewData["deleted"] = true;
return View();
}
This is the model that check if if the GUID exists.
public void SaveToDatabase(Guid consultantId)
{
using (DatabaseLayer db = new DatabaseLayer())
{
//Time report exists, delete it.
if (ReportId.HasValue)
{
db.DeleteTimeReport(ReportId.Value);
}
//Time report does not exist, create a new one.
else
{
ReportId = Guid.NewGuid();
}
Report report = new Report
{
FK_ConsultantID = consultantId,
FK_UserID = Constants.UserTreetop,
Date = Date,
TimeReportID = ReportId.Value
};
TimeReportData reportData = new TimeReportData
{
Absent = 0,
Description = "",
StartHour = Times.StartHour,
StartMinute = Times.StartMinute,
EndHour = Times.EndHour,
EndMinute = Times.EndMinute,
BreakHour = Times.BreakHour,
BreakMinute = Times.BreakMinute,
FK_TimeReportID = ReportId.Value,
TimeReportDataID = Guid.NewGuid()
};
TimeReportProject[] reportProjects = new TimeReportProject[Projects.Count];
for (int i = 0; i < Projects.Count; i++)
{
reportProjects[i] = new TimeReportProject
{
Description = Projects[i].Description,
FK_ProjectID = Projects[i].ProjectId,
FK_TimeReportID = ReportId.Value,
Hours = Projects[i].Hours.GetValueOrDefault(), //Projects[i].Hours.Value,
HourRate = db.GetProjectHourRate(Projects[i].ProjectId, Date, Projects[i].Hours.GetValueOrDefault()),
TimeReportProjectID = Guid.NewGuid()
};
}
db.InsertTimeReport(report, reportData, reportProjects);
}
}
And as it exists it does this
public void DeleteTimeReport(Guid timeReportId)
{
db.ExecuteStoreCommand(
#" DELETE FROM [Salesweb].[dbo].[TimeReportProject] WHERE FK_TimeReportID = #id;
DELETE FROM [Salesweb].[dbo].[TimeReportData] WHERE FK_TimeReportID = #id;
DELETE FROM [Salesweb].[dbo].[TimeReport] WHERE TimeReportID = #id;"
, new SqlParameter("#id", timeReportId));
db.SaveChanges();
}
This is the view when I pass in the guid I Want to delete as the guid has a value the remove button will appear.
But as I delete the project it will return to the same view. Like we can see the tabs is not showing up, so if I want the to show again I have to go to another view, and than back to the same view. And if I refresh it will crash due the guid dosen't exist in the DB.
And here is the whole controller, it's a bit messy right now.
public ActionResult TimeReport(FormCollection form, Guid? id)
{
ViewDataDictionary vd = new ViewDataDictionary
{
["projects"] = new DatabaseLayer().GetConsultantProjects(Constants.CurrentUser(User.Identity.Name)),
["id"] = 1,
["showDescription"] = true
};
ViewData["vd"] = vd;
NewTimeReportModel projectData = new NewTimeReportModel();
if (form != null && form.AllKeys.Contains("delete"))
{
new DatabaseLayer().DeleteTimeReport(Guid.Parse(form["ReportId"]));
LoadDefaultSettings(projectData);
ViewData.Model = projectData;
ViewData["deleted"] = true;
return RedirectToAction("Index");
}
if (id.HasValue && (form == null || form.AllKeys.Length == 0))
{
using (DatabaseLayer db = new DatabaseLayer())
{
var timeReport = db.GetTimeReport(id.Value);
projectData = new NewTimeReportModel(timeReport);
if (projectData.Projects.Count == 1)
projectData.Projects[0].Hours = null;
}
}
else if (form == null || form.AllKeys.Length == 0)
{
LoadDefaultSettings(projectData);
}
else
{
//Get's all the dates from the view and formates them to look like yy-mm-dd so we can parse it to a datetime.
string[] dates = FormateDate(form["date"]);
//Loops over all the dates and saves the dates to the database.
projectData = ReportDates(form, projectData, dates);
//Loads default settings if all dates been reported.
LoadDefaultSettings(projectData);
}
//Get's and lists all the missing days
ListAllMssingDays();
ViewData.Model = projectData;
return View();
}
Recommended thing to do in such cases is run redirect to some default URL, like the summary page. Guessing that you have Summary action, that should be something like:
if (form != null && form.AllKeys.Contains("delete"))
{
new DatabaseLayer().DeleteTimeReport(Guid.Parse(form["ReportId"]));
return RedirectToAction("Summary", "Reports");
}
Note that this will do a client-side redirect, so to say - this will do a response with code 302 and new URL /Reports/Summary. This is usually a desired behavior though.
The exception you're getting is because your code assumes the item you're deleting will exist.
Change
return db.TimeReports.Where(x => x.TimeReportID == timeReportId).Single();
To
return db.TimeReports.Where(x => x.TimeReportID == timeReportId).SingleOrDefault();
Which will return null if your Where clause returns 0 items.
Then wherever in your code you're calling GetTimeReport() you need to check for and handle null.

Writing CSV files into Sql database using LinqToCsv - .NET MVC 4 - Entity Framework

I am trying to create an application in which a user can upload .CSV files into an SQL database that I have created. I have become a little confused when it comes to actually getting the file path from the view and writing it into the database.
First off, here is the model that I'm working off:
public class OutstandingCreditCsv
{
[CsvColumn(FieldIndex = 1, CanBeNull = false)]
public string PoNumber { get; set; }
[CsvColumn(FieldIndex = 2, OutputFormat = "dd MMM HH:mm:ss")]
public DateTime CreditInvoiceDate { get; set; }
[CsvColumn(FieldIndex = 3)]
public string CreditInvoiceNumber { get; set; }
[CsvColumn(FieldIndex = 4, CanBeNull = false, OutputFormat = "C")]
public decimal CreditInvoiceAmount { get; set; }
}
And here is the controller code so far:
public ActionResult Index()
{
CsvFileDescription inputFileDescription = new CsvFileDescription
{
SeparatorChar = ',',
FirstLineHasColumnNames = true
};
var context = new CsvContext();
IEnumerable<OutstandingCreditCsv> csvList =
context.Read<OutstandingCreditCsv>("C:/Users/BlahBlah/Desktop/CsvUploadTestFile.csv", inputFileDescription);
foreach (OutstandingCreditCsv line in csvList)
{
}
return View();
}
There are two areas where I need a little guidance. I'm not sure how to pass the file from the view to the controller, lets say my view is something simple like this:
<form action="" method="POST" enctype="multipart/form-data">
<table style="margin-top: 150px;">
<tr>
<td>
<label for="file">Filename:</label>
</td>
<td>
<input type="file" name="file" id="file"/>
</td>
<td><input type="submit" value="Upload"/></td>
</tr>
</table>
</form>
I'm also unsure how I would actually loop the csv data into my database. You can see the foreach loop in my controller is empty. Any help would be appreciated. Thanks!
I am editing my post to answer the part of the question I missed. What you are doing here is uploading the csv file to a temporary location, reading it into an object then if you want you can delete the csv file out of the temporary location (not shown).
[HttpPost]
public JsonResult UploadValidationTable(HttpPostedFileBase csvFile)
{
CsvFileDescription inputFileDescription = new CsvFileDescription
{
SeparatorChar = ',',
FirstLineHasColumnNames = true
};
var cc = new CsvContext();
string filePath = uploadFile(csvFile.InputStream);
var model = cc.Read<OutstandingCreditCsv>(filePath, inputFileDescription);
//if your csv has several rows convert it to a list of your model
//var model = cc.Read<List<OutstandingCreditCsv>>(filePath, inputFileDescription);
//then you can loop through and do the same as below
/*foreach(var row in model)
{
var invoice = row.CreditInvoiceNumber;
}*/
try
{
//do what you need here, like save items to database
var invoice = model.CreditInvoiceNumber;
var invoiceTable = yourContext.yourTable
.FirstOrDefault(x => x.yourTableID == passedInId);
invoiceTable.CreditInvoiceNumber = model.CreditInvoiceNumber;
yourContext.SaveChanges();
}
catch(LINQtoCSVException ex)
{
}
return Json(model, "text/json");
}
private string uploadFile(Stream serverFileStream)
{
string directory = "~/Content/CSVUploads";
bool directoryExists = System.IO.Directory.Exists(Server.MapPath(directory));
if (!directoryExists)
{
System.IO.Directory.CreateDirectory(Server.MapPath(directory));
}
string targetFolder = Server.MapPath(directory);
string filename = Path.Combine(targetFolder, Guid.NewGuid().ToString() + ".csv");
try
{
int length = 256; //todo: replace with actual length
int bytesRead = 0;
Byte[] buffer = new Byte[length];
// write the required bytes
using (FileStream fs = new FileStream(filename, FileMode.Create))
{
do
{
bytesRead = serverFileStream.Read(buffer, 0, length);
fs.Write(buffer, 0, bytesRead);
}
while (bytesRead == length);
}
serverFileStream.Dispose();
return filename;
}
catch (Exception ex)
{
return string.Empty;
}
}

Why is my csv file path always returning null?

I am currently working on a ASP.NET MVC project where users can insert csv and excel records in a database and edit it afterwards. I have finished the excel part and it is working splendidly. But on the other hand my csv version does not even manage to take the csv file path and it always return null. I have tried different approaches but none seem to work.
My wish is to keep the excel version and the csv version similar and so I used httpPost on both but for some reason it does not manage to get the csv file path.
Can someone see what I am doing wrong?
This is my home controller view that is supposed to send the file path:
#{
ViewBag.Title = "Index";
}
<h1>Index</h1>
#using (Html.BeginForm("Import", "CSVImport", FormMethod.Post, new {enctype="multipart/form-data" }))
{
#Html.ValidationSummary(true)
<fieldset>
Select a file <input type="file" name="file" />
<input type="submit" value="Upload CSV" />
</fieldset>
}
And this here is my csv importer:
using System.Data.Common;
using System.Text;
using System.Data.ProviderBase;
using System.Data.SqlClient;
using System.Data.SqlTypes;
using System.Data.Sql;
using Microsoft.VisualBasic.FileIO;
namespace BFProj2.Controllers
{
public class CSVImportController : Controller
{
// GET: CSVImport
//public static DataTable GetDataTabletFromCSVFile(string csv_file_path)
[HttpPost]
public ActionResult Import(HttpPostedFileBase csv_file_path)
{
DataTable csvData = new DataTable();
if (csv_file_path != null && Request.Files["csv_file_path"].ContentLength > 0)
{
try
{
using (TextFieldParser csvReader = new TextFieldParser(csv_file_path.InputStream))
{
//Solution to HDR problem: When getting to the checkbox part of the application, just keep the HDR unchecked.
csvReader.SetDelimiters(new string[] { "," });
//Can be true depending on if the csv document rows are enclosed in quotes or not. Delimiters are set as either ; or , depending on weather the csv columns are split with ; or ,
csvReader.HasFieldsEnclosedInQuotes = false;
string[] colFields = csvReader.ReadFields();
foreach (string column in colFields)
{
DataColumn Titel = new DataColumn(column);
Titel.AllowDBNull = true;
csvData.Columns.Add(Titel);
DataColumn LastName = new DataColumn(column);
LastName.AllowDBNull = true;
csvData.Columns.Add(LastName);
DataColumn AbstrNum = new DataColumn(column);
AbstrNum.AllowDBNull = true;
csvData.Columns.Add(AbstrNum);
DataColumn PosterTitel = new DataColumn(column);
PosterTitel.AllowDBNull = true;
csvData.Columns.Add(PosterTitel);
DataColumn Workshop = new DataColumn(column);
Workshop.AllowDBNull = true;
csvData.Columns.Add(Workshop);
DataColumn Keywords = new DataColumn(column);
Keywords.AllowDBNull = true;
csvData.Columns.Add(Keywords);
DataColumn Institution = new DataColumn(column);
Institution.AllowDBNull = true;
csvData.Columns.Add(Institution);
DataColumn CollabEmail = new DataColumn(column);
CollabEmail.AllowDBNull = true;
csvData.Columns.Add(CollabEmail);
}
while (!csvReader.EndOfData)
{
string[] fieldData = csvReader.ReadFields();
//Making empty value as null
for (int i = 0; i < fieldData.Length; i++)
{
if (fieldData[i] == "")
{
fieldData[i] = null;
}
}
csvData.Rows.Add(fieldData);
}
csvData = System.Data.Common.DbProviderFactories.GetFactoryClasses();
ViewData.Model = csvData.AsEnumerable();
}
}
catch (Exception ex)
{
}
}
return View();
}
}
}
I took some source material from this:
http://www.morgantechspace.com/2013/10/import-csv-file-into-sql-server-using.html
I have not yet put in the bulk copy part and it is only a DataTable, but I want to make it so that it does everything in the controller before returning to the homecontroller view again instead of placing it somewhere else.
By the way, I am pretty new to stackoverflow so if there is something else I am doing wrong related to the question asking I would need directions to change it.
When you post a file, the name of the <input> is used as the key for Request.Files and the parameter name for the action method, so these need to match. In your case, you have <input name="file"> but parameter name csv_file_path. So you need to rename one or the other:
<input name="file">
public ActionResult Import(HttpPostedFileBase file)
{
if (file != null && Request.Files["file"].ContentLength > 0)
...
}
or
<input name="csv_file_path">
public ActionResult Import(HttpPostedFileBase csv_file_path)
{
if (csv_file_path != null && Request.Files["csv_file_path"].ContentLength > 0)
...
}
Note you also do not need to use Request.Files - the HttpPostedFileBase has what you need:
if (csv_file_path != null && csv_file_path.ContentLength > 0)

Save HttpPostedFileBase temporary

I have a input of type file that is sending a HttpPostedFileBase (Excel.xlxs) to my controller. This file do I need to save temporary to use in another action. This HttpPostedFileBase am I converting to DataSet using Excel Data Reader. This works fine when I work directly with the file, but not when I have stored it in TempData.
First action:
[HttpPost]
public ActionResult CompareExcel(ExcelModel model, string submitValue, string compareOption)
{
TempData["firstFile"] = model.Files[0];
TempData["secondFile"] = model.Files[1];
if (submitValue == "Compare calculations")
{
var firstFile = TempData["firstFile"];
var secondFile = TempData["secondFile"];
if (firstFile == null || secondFile == null)
return View("CompareExcel");
else
return CompareColumn(compareOption);
}
//Assume submitValue is "Compare calculations"
}
Second action:
[HttpPost]
public ActionResult CompareColumn(string compareOption)
{
List<Calculation> cList = new List<Calculation>();
Calculation calc = new Calculation();
BigModel m = new BigModel();
HttpPostedFileBase firstFile = (HttpPostedFileBase)TempData["firstFile"];
HttpPostedFileBase secondFile = (HttpPostedFileBase)TempData["secondFile"];
DataSet firstSet = ExcelToDataSet.ConvertToDataSet(firstFile);
DataSet secondSet = ExcelToDataSet.ConvertToDataSet(secondFile);
for (var i = 0; i < firstSet.Tables[0].Rows.Count; i++) //Get Object reference not set to an instance of an object
{
DataRow data = firstSet.Tables[0].Rows[i];
calc.PresentValue = Convert.ToDecimal(data.Table.Rows[i]["Capital balance"]);
}
return View();
}
My model:
public partial class GetExcel
{
public List<HttpPostedFileBase> Files { get; set; }
public GetExcel()
{
Files = new List<HttpPostedFileBase>();
}
}
My method to convert my HttpPostedFileBase (Excel.xlxs) to DataSet. The method is working when sending the file directly but not when using TempData:
public static DataSet ConvertToDataSet(HttpPostedFileBase excelFile)
{
DataSet result = null;
if (excelFile != null && excelFile.ContentLength > 0)
{
// .xlsx
IExcelDataReader reader = ExcelReaderFactory.CreateOpenXmlReader(excelFile.InputStream);
// .xls
//IExcelDataReader reader = ExcelReaderFactory.CreateBinaryReader(file.InputStream);
reader.IsFirstRowAsColumnNames = true; // if your first row contains column names
result = reader.AsDataSet();
reader.Close();
}
return result;
}
When I debug and look at firstFile and secondFile I can see there is something there. I can see the filename of the HttpPostedFileBase but when I use ConvertToDataSet it fails in the first if. So obviously it's something wrong with the file saved in TempData. Is it possible to make this work with TempData or can someone suggest some easy way to go around this? Should it work and I'm doing something wrong? The whole thing is that the HttpPostedFileBase somehow needs to be saved temporary (in a easy way).

Categories