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).
Related
How to pass below action to another action in order to export it in excel sheet
,I need to pass VL list to another action
public ActionResult Details(int S)
{
SLMEntitiesDB dbContext = new SLMEntitiesDB();
var VL = (from U in dbContext.Users
join P in dbContext.Products
on U.PID equals P.PID
where P.PID == U.PID
select new UP()
{
UserO = U,
ProductO = P
}).Where(U => U.UserO.LID == S).ToList();
TempData["Exc"] = VL;
return View(VL);
}
and the other action within the same controller, but it's not working
public void ExportToExcel()
{
var V = TempData["Exc"] as List;
ExcelPackage pck = new ExcelPackage();
ExcelWorksheet ws = pck.Workbook.Worksheets.Add("Report");
ws.Cells["A1"].Value = "UserName";
int rowStart = 1;
foreach (var item in V)// here is the error
{
ws.Cells[string.Format("A{0}", rowStart)].Value = item.UserO.CN;
you'll create another Function\Action on your controller and pass the data from your view to it. Depending on the size of the data, you could do that as a QueryString (note: type .ToString() considerations, html encoding in that approach), but it's more likely you'll store the instance server-side, using TempData[key] or something similar.
The other aspect of what you're attempting to do (export an Excel File) will be handled by the added Action; However, instead of you handling the entire Response writing in the scope of your action, I recommend you define a type that inherits the FileResult type - where you handle HttpResponseBase in the overloaded WriteFile function (example below).
XLSFileResult
This sample type will actually be writing Comma Separate Value (CSV) content, but the Content-Type (coupled with Content-Disposition) will 'indicate' to the client that it is an excel file - the Response will respond with an ".xls" file.
Note: the filename and extension in this sample are actually defined at it's initialization - in the action of the controller.
public class XLSFileResult : FileResult
{
public XLSFileResult() : base(#"application/vnd.ms-excel")
{
Data = new List<UP>();
}
public IEnumerable<UP> Data { get; set; }
protected override void WriteFile(HttpResponseBase response)
{
// note: you'll want to handle this better; I'm just choosing a property of each complex type.
string[] lines = Data.Select(d => string.Join(", ", d.UserO.UserName , d.ProductO.PName)).ToArray();
byte[] buffer = response.ContentEncoding.GetBytes(string.Join(Environment.NewLine, lines));
response.BinaryWrite(buffer);
}
}
Sample Action\Function on the Controller
public ActionResult Details(int S)
{
SLMEntitiesDB dbContext = new SLMEntitiesDB();
var VL = (from U in dbContext.Users
join P in dbContext.Products
on U.PID equals P.PID
where P.PID == U.PID
select new UP()
{
UserO = U,
ProductO = P
}).Where(U => U.UserO.LID == S).ToList();
return View(VL);
}
protected FileResult HandleDataToFileResult(IEnumerable<UP> data)
{
return new XLSFileResult()
{
Data = data,
FileDownloadName = "MyFile.xls" //by virtue of this assignment, a 'Content-Disposition' Response.Header is added to HttpResponseBase
};
}
public FileResult GenerateFile()
{
var data = (IEnumerable<UP>)TempData["GenerateFile"];
return HandleDataToFileResult(data);
}
Razor Page
In this sample of the razor page, we'll use a ActionLink...
#using SLMDemo0.Models
#model IEnumerable<UP>
#{
ViewBag.Title = "Details";
//Review TempData, it's session data that clears at the end of the next request
TempData["GenerateFile"] = Model.ToArray();
}
...
#Html.ActionLink("GenerateFile", "GenerateFile");
I have this class, which takes the selected file in the grid
[HttpPost]
public ActionResult ExportXml(string apontamentos)
{
try
{
string[] arrApontamentos = apontamentos.Split(';');
var listApontamentoId = arrApontamentos.Select(x => new Guid(x)).ToList();
var apontamentosViewModel = this._apontamentoAppService.ObterTodos(listApontamentoId);
List<ApontamentoExportarViewModel> listXml = new List<ApontamentoExportarViewModel>();
int item = 1;
foreach (var informacaoApontamentoVM in apontamentosViewModel)
{
listXml.Add(new ApontamentoExportarViewModel
{
Item = item,
Equipamento = informacaoApontamentoVM.Barco.SapId,
Atendimento = informacaoApontamentoVM.Atendimento,
Escala = informacaoApontamentoVM.LocalDaOperacao.Abreviacao,
DescricaoDaOperacao = informacaoApontamentoVM.CodigosDeOperacao.Descricao,
//GrupoDeCodigo = "xxx",
CodigoOperacao = informacaoApontamentoVM.CodigosDeOperacao.Codigo,
DataInicial = string.Format("{0:dd.MM.yyyy}", informacaoApontamentoVM.DataInicio),
HoraInicial = string.Format("{0:HH.mm.ss}", informacaoApontamentoVM.DataInicio),
DataFinal = string.Format("{0:dd:MM:yyyy}", informacaoApontamentoVM.DataTermino),
HoraFinal = string.Format("{0:HH:mm:ss}", informacaoApontamentoVM.DataTermino),
Observacoes = informacaoApontamentoVM.Observacao
});
item++;
}
var status = this._apontamentoAppService.ObterDescricaoStatusApontamento(Domain.Apontamentos.StatusApontamento.Exportado);
this._apontamentoAppService.AtualizarStatus(apontamentosViewModel.Select(x => x.Id).ToList(), status);
return new XmlActionResult<ApontamentoExportarViewModel>(listXml);
}
catch (Exception ex)
{
throw ex;
}
}
And a i have that other one, which does export and xml format and filename
public class XmlActionResult<T> : ActionResult
{
public XmlActionResult(List<T> data)
{
Data = data;
}
public List<T> Data { get; private set; }
public override void ExecuteResult(ControllerContext context)
{
context.HttpContext.Response.ContentType = "text/xml";
// TODO: Use your preferred xml serializer
// to serialize the model to the response stream :
// context.HttpContext.Response.OutputStream
ApontamentoExportarViewModel apontamentoExportarViewModel = new ApontamentoExportarViewModel();
var cd = new System.Net.Mime.ContentDisposition
{
// for example foo.bak
FileName = string.Format("MA_"+ "Equipamento" + "_{0:dd-MM-yyyy_HH-mm-ss}.xml", DateTime.Now),
// always prompt the user for downloading, set to true if you want
// the browser to try to show the file inline
Inline = false,
};
var root = new XmlRootAttribute("meadinkent");
XmlSerializer x = new XmlSerializer(Data.GetType(), root);
context.HttpContext.Response.AppendHeader("Content-Disposition", cd.ToString());
x.Serialize(context.HttpContext.Response.OutputStream, Data);
}
}
}
Basically, I need to get the "Equipamento" attribute and insert it into the file name.
The information from the "ApontamentoExportarViewModel" class is coming from the "Data" attribute, but how do you find the information inside that list? Remembering that I only need the information of the attribute "Equipment"
How would I bring the value of this attribute to the XmlActionResult class?
Well, you've got it in Data in the XmlActionResult, but because it's a List<T>, it could be anything. In this case, it's your ApontamentoExportarViewModel view model.
Should this XmlActionResult method be able to work with various kinds of objects, or only with ApontamentoExportarViewModel? If with various types, then not every one of those types will have a Equipamento property. In that case, you would have to do something like:
var fileName = "default";
if(Data is List<ApontamentoExportarViewModel>)
{
var record = (Data as List<ApontamentoExportarViewModel>).FirstOrDefault(); // what should happen if there's more than one?
if (record != null)
fileName = record.Equipamento;
}
and then later:
FileName = string.Format("MA_"+ fileName + "_{0:dd-MM-yyyy_HH-mm-ss}.xml", DateTime.Now);
Or something like that.
If you are sure that every object that comes in to your method will have a Equipamento property, then you could create a base class from which you derive your view model classes that has Equipamento, or an interface, and then alter your method to not accept any type (<T>), but rather only classes that derive from the base class / implement the interface.
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};
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.
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?