I am trying to export data as Excel in my C#.Net MVC Application. I have used return file() in Actionresult. The file is returned and downloaded successfully.
But there is error while opening file and the file names gets changed when it is opened.
Downloaded file name is ExportFilterCRMdoctorRequest.xls but after opening it changes to Book1.
code for Exporting file:
public ActionResult ExportFilterCRMdoctorRequest()
{
var stream = new MemoryStream();
var serializer = new XmlSerializer(typeof(List<CDRFilterCRMDoctorRequest>));
//We load the data
List<CDRFilterCRMDoctorRequest> data = (List<CDRFilterCRMDoctorRequest>)Session["filterCRMRequestList"]; //Retriving data from Session
//We turn it into an XML and save it in the memory
serializer.Serialize(stream, data);
stream.Position = 0;
//We return the XML from the memory as a .xls file
return File(stream, "application/vnd.ms-excel", "ExportFilterCRMdoctorRequest.xls");
}
This is called Extension Hardening. Execute steps to avoid this error.
Open your Registry (Start -> Run -> regedit.exe) Navigate to
HKEY_CURRENT_USER\SOFTWARE\MICROSOFT\OFFICE\12.0\EXCEL\SECURITY
Right click in the right window and choose New -> DWORD Type
“ExtensionHardening” as the name (without the quotes)
Verify that the data has the value “0″
NOTE
There is one thing that has to be borne in mind when serializing in
XML. XML is not Excel’s standard format and it has to open the file as
XML data. This means that when opening the file it will issue a couple
of warnings which are more of a nuisance than anything else.
Back to your original Query : Replicated your issue and below is the fix
Sample Class
public class StudentModel
{
public string Name { get; set; }
public string Address { get; set; }
public string Class { get; set; }
public string Section { get; set; }
}
Sample Data
private List<StudentModel> StudentData()
{
List<StudentModel> objstudentmodel = new List<StudentModel>();
objstudentmodel.Add(new StudentModel { Name = "Name1", Class = "1",
Address = "Address1", Section = "A" });
objstudentmodel.Add(new StudentModel { Name = "Name2", Class = "2",
Address = "Address2", Section = "A" });
return objstudentmodel;
}
Action Method
public ActionResult Index()
{
List<StudentModel> objstudent = new List<StudentModel>();
objstudent = StudentData();
StringBuilder sb = new StringBuilder();
sb.Append("<table border='" + "1px" + "'b>");
//code section for creating header column
sb.Append("<tr>");
sb.Append("<td><b><font size=2>NAME</font></b></td>");
sb.Append("<td><b><font size=2>CLASS</font></b></td>");
sb.Append("<td><b><font size=2>ADDRESS</font></b></td>");
sb.Append("<td><b><font size=2>SECTION</font></b></td>");
sb.Append("</tr>");
//code for creating excel data
foreach (StudentModel item in objstudent)
{
sb.Append("<tr>");
sb.Append("<td><font>" + item.Name.ToString() + "</font></td>");
sb.Append("<td><font>" + item.Class.ToString() + "</font></td>");
sb.Append("<td><font>" + item.Address.ToString() + "</font></td>");
sb.Append("<td><font>" + item.Section.ToString() + "</font></td>");
sb.Append("</tr>");
}
sb.Append("</table>");
HttpContext.Response.AddHeader("content-disposition",
"attachment; filename=student_" +
DateTime.Now.Year.ToString() + ".xls");
this.Response.ContentType = "application/vnd.ms-excel";
byte[] buffer = System.Text.Encoding.UTF8.GetBytes(sb.ToString());
return File(buffer, "application/vnd.ms-excel");
}
Related
I'm loading images which are store in the database and I want to upload them to the serverpath which is in wwwroot folder.
byte[] imageData= //Loading image from sql server
string strWebPath = _hostingEnvironment.WebRootPath + "\\img\\+"This is where I want to upload that image"";
How can I upload that image into wwwroot/img folder
Storing image datas in the database is not the best way.
Anyway, if you have your image data in a byte array, you can store it anywhere using a filestream.
You need to add : using System.IO;
byte[] imageData = ...//Loading image from sql server
string strWebPath = _hostingEnvironment.WebRootPath + "\\img\\myImageName.png"; //yourImageName.Extension;
//You may have to change the FileMode.Create according the image names if, unique etc.
using (FileStream fs = new FileStream(strWebPath, FileMode.Create))
{
//Takes a byte array, writes to the disk from the given index until the given length.
fs.Write(imageData, 0, imageData.Length);
fs.Flush();
}
I hope this helps.
In most cases, files are stored in the database as byte[]. So you can use this method to achieve it.
FileModel:
public class AppFile
{
public int Id { get; set; }
public string FileName { get; set; }
public byte[] Content { get; set; }
}
Method:
public async Task Uploadfilefromdb(int id)
{
//select file by id
var result =await _dbcontext.File.FirstOrDefaultAsync(x => x.Id == id);
var fileName = Path.GetFileName(result.FileName);
var filePath = Path.Combine(Directory.GetCurrentDirectory(), #"wwwroot\images",fileName);
using (var fileStream = new FileStream(filePath, FileMode.Create))
{
//convert byte[] to IformFile
var stream = new MemoryStream(result.Content);
IFormFile file = new FormFile(stream, 0, result.Content.Length, "name", fileName);
await file.CopyToAsync(fileStream);
}
}
Demo:
After I use this method, image has been uploaded from database to wwwroot.
I have a string that is data bytes base64EncodedString from iOS which is an extremely long string
let imageStr = imageData.base64EncodedString()
I am calling a .NET Method from my ios that will call a stored procedure to insert these bytes into the database.
Here is my .NET Method, I have the data type set to VarBinary
public string PostLandGradingImages(List<Images> landingCells)
{
try
{
using (connection = new SqlConnection(connectionString))
{
connection.Open();
using (SqlCommand command = new SqlCommand("PostLandGradingImages", connection))
{
command.CommandType = CommandType.StoredProcedure;
for (int i = 0; i < landingCells.Count; i++)
{
command.Parameters.Clear();
SqlParameter parameter1 = new SqlParameter("#Job_No", SqlDbType.VarChar);
parameter1.Value = landingCells[i].jobNo;
parameter1.Direction = ParameterDirection.Input;
command.Parameters.Add(parameter1);
SqlParameter parameter2 = new SqlParameter("#Image", SqlDbType.VarBinary);
parameter2.Value = landingCells[i].imageBytes;
parameter2.Direction = ParameterDirection.Input;
command.Parameters.Add(parameter2);
command.ExecuteNonQuery();
}
}
}
}
catch (Exception e)
{
return e.Message.ToString();
}
return "All Good";
}
Here is my Image Class, notice my imageBytes is defined as a byte[]:
public class Images
{
public string jobNo { get; set; }
public byte[] imageBytes { get; set; }
}
The column I am inserting into is defined as varbinary(MAX)
and here is my stored procedure:
ALTER PROCEDURE [dbo].[PostLandGradingImages]
-- Add the parameters for the stored procedure here
#Job_No varchar(MAX) = NULL,
#Image varbinary(MAX) = NULL
AS
BEGIN
-- SET NOCOUNT ON added to prevent extra result sets from
-- interfering with SELECT statements.
SET NOCOUNT ON;
-- Insert statements for procedure here
INSERT INTO LandGradingImages (Job_No, ImageBytes) VALUES (#Job_No, #Image)
END
My problem is nothing is getting inserted, I am getting this error in my catch:
Object reference not set to an instance of an object.
My question is, what am I doing wrong? Should I not be sending base64EncodedString or am I not setting my class right? or my db column?
I tried this:
byte[] bytes = System.Convert.FromBase64String(landingCells[i].imageBytes);
SqlParameter parameter2 = new SqlParameter("#Image", SqlDbType.VarBinary, 800000);
parameter2.Value = bytes;
parameter2.Direction = ParameterDirection.Input;
command.Parameters.Add(parameter2);
Still does not work :( and I changed imageBytes to string.
I modified your code a little to the method below. It creates a new CommandType.StoredProcedure for every Image. Also the results are returned per image, so you can see which ones failed. In your method, if you have 10 images, and the 9th failed, you would not know that.
public List<Images> PostLandGradingImages(List<Images> landingCells)
{
//create a connection to the database
using (SqlConnection connection = new SqlConnection(Common.connectionString))
{
//loop all the images
for (int i = 0; i < landingCells.Count; i++)
{
//create a fresh sql command for every Image
using (SqlCommand command = new SqlCommand("PostLandGradingImages", connection))
{
command.CommandType = CommandType.StoredProcedure;
//add the parameters
command.Parameters.Add("#Job_No", SqlDbType.VarChar).Value = landingCells[i].jobNo;
command.Parameters.Add("#Image", SqlDbType.VarBinary).Value = landingCells[i].imageBytes;
try
{
//open the connection if closed
if (connection.State == ConnectionState.Closed)
{
connection.Open();
}
//execute the stored procedure
command.ExecuteNonQuery();
//set the save result to the image
landingCells[i].saveResult = true;
}
catch (Exception ex)
{
//handle error per Image
landingCells[i].errorMessage = ex.Message;
}
}
}
}
return landingCells;
}
In order to track the save result per image I've added two properties to the Image class, but this can be done in various other ways as well.
public class Images
{
public string jobNo { get; set; }
public byte[] imageBytes { get; set; }
public bool saveResult { get; set; }
public string errorMessage { get; set; }
}
A simple test was done with the following code. None of them gave a NullReference Error. Even with both properties being null, a database entry was still made.
//create a new list with Images
List<Images> landingCells = new List<Images>();
//add some dummy data
landingCells.Add(new Images() { jobNo = null, imageBytes = null });
landingCells.Add(new Images() { jobNo = "Job 1", imageBytes = null });
landingCells.Add(new Images() { jobNo = null, imageBytes = new byte[10000] });
landingCells.Add(new Images() { jobNo = "Job 2", imageBytes = new byte[10000] });
//send the images to be saved
landingCells = PostLandGradingImages(landingCells);
//loop all the images to check the result
for (int i = 0; i < landingCells.Count; i++)
{
if (landingCells[i].saveResult == false)
{
//display the result for each failed image
Label1.Text += landingCells[i].errorMessage + "<br>";
}
}
If there is still a NullReference error, that means that your List landingCells itself is null, or an Image object within that List is null (in which case it should never have been added to the List in the first place imho). You can change the snippet easily to check for that.
Consider batching the queries in a transaction. Also you should validate the values provided to the method to make sure that you can call the stored procedure correctly.
public int PostLandGradingImages(List<Images> landingCells) {
int count = 0;
using (var connection = new SqlConnection(connectionString)) {
connection.Open();
//Transaction to batch the actions.
using (var transaction = connection.BeginTransaction()) {
foreach (var image in landingCells) {
if (valid(image)) {//validate input properties.
try {
using (SqlCommand command = connection.CreateCommand()) {
command.CommandType = CommandType.StoredProcedure;
command.CommandText = "PostLandGradingImages";
command.Parameters
.Add("#Job_No", SqlDbType.VarChar, image.jobNo.Length)
.Value = image.jobNo;
command.Parameters
.Add("#Image", SqlDbType.VarBinary, image.imageBytes.Length)
.Value = image.imageBytes;
count += command.ExecuteNonQuery();
}
} catch {
//TODO: Log error
}
}
}
if (landingCells.Count == count) {
transaction.Commit();
}
}
}
return count;
}
private bool valid(Images image) {
return image != null && String.IsNullOrWhiteSpace(image.jobNo)
&& image.imageBytes != null && image.imageBytes.Length > 0;
}
public ActionResult Import(HttpPostedFileBase currencyConversionsFile)
{
string filename = "CurrencyConversion Upload_" + DateTime.Now.ToString("dd-MM-yyyy") + ".csv";
string folderPath = Server.MapPath("~/Files/");
string filePath = Server.MapPath("~/Files/" + filename);
currencyConversionsFile.SaveAs(filePath);
string[] csvData = System.IO.File.ReadAllLines(filePath);
//the later code isn't show here
}
I know the usual way to convert httppostedfilebase to String array, which will store the file in the server first, then read the data from the server. Is there anyway to get the string array directly from the httppostedfilebase with out store the file into the server?
Well you can read your file line by line from Stream like this:
List<string> csvData = new List<string>();
using (System.IO.StreamReader reader = new System.IO.StreamReader(currencyConversionsFile.InputStream))
{
while (!reader.EndOfStream)
{
csvData.Add(reader.ReadLine());
}
}
From another thread addressing the same issue, this answer helped me get the posted file to a string -
https://stackoverflow.com/a/40304761/5333178
To quote,
string result = string.Empty;
using (BinaryReader b = new BinaryReader(file.InputStream))
{
byte[] binData = b.ReadBytes(file.ContentLength);
result = System.Text.Encoding.UTF8.GetString(binData);
}
Splitting the string into an array -
string[] csvData = new string[] { };
csvData = result.Split(new string[] { "\r\n", "\n" }, StringSplitOptions.None);
I have a partial result view that takes in the name of the table and a value for a particular column to query. I read the DBContext API and found that Set(Type) should return a DBSet that you can do CRUD operations on. I don't know how exactly to query the DBSet without a PK since the user don't know the PK to look up.
May be using Classic ADO would be easier?
EDIT: I figure out how to use DbSet.SQLQuery function but have no clue to store the results. I inspected the element in debugger and the SQLQuery does work as it found all the rows inside the table.
public class SF1DB : DbContext
{
//List of table names that feeds a DropDownList
public DbSet<tablelist> tables { get; set; }
//Data table
public DbSet<dataTable1> dataTable1 { get; set; }
public DbSet<dataTable2> dataTable2 { get; set; }
//...list of other tables
}
public PartialViewResult GetFeatures(String tablelist, String[] countyfp)
{
String type = "MvcApplication1.Models." + tablelist;
Type dbType = Type.GetType(type);
DbSet set = _db.Set(dbType);
String sql = "select * from " + tablelist;
//How do I store the result in a variable?
set.SqlQuery(sql);
return PartialView();
}
I figured it out by creating a List that have the same type as the DbSet that the user selected. Then I use the SQLQuery's GetEnumerator method and iterate thru the result and add to the new list. Finally, pass the list to the partial view.
public PartialViewResult GetFeatures(String tablelist, String[] countyfp)
{
String type = "MvcApplication1.Models." + tablelist;
Type dbType = Type.GetType(type);
DbSet set = _db.Set(dbType);
String sql = "select * from " + tablelist + " where ";
Type listType = typeof(List<>).MakeGenericType(dbType);
IList list = (IList)Activator.CreateInstance(listType);
for (int i = 0; i < countyfp.Length; i++)
{
sql += "cntyidfp like '%" + countyfp[i] + "'";
if (i < (countyfp.Length - 1))
{
sql += " or ";
}
}
IEnumerator result = set.SqlQuery(sql).GetEnumerator();
while (result.MoveNext())
{
list.Add(result.Current);
}
return PartialView(list);
}
I'm trying to serve a txt file made from the database using an action. The action is the following:
public ActionResult ATxt()
{
var articulos = _articulosService.ObteTotsArticles();
return File(CatalegATxt.ATxt(articulos), "text/plain");
}
and the CatalegATxt class is:
using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using WebDibaelsaMVC.DTOs.Busqueda;
namespace WebDibaelsaMVC.TxtLib
{
public static class CatalegATxt
{
public static Stream ATxt(IEnumerable<ArticuloBusquedaDTO> articles)
{
var stream = new MemoryStream();
var streamWriter = new StreamWriter(stream, Encoding.UTF8);
foreach (ArticuloBusquedaDTO article in articles)
{
streamWriter.WriteLine(article.ToStringFix());
}
stream.Seek(0, SeekOrigin.Begin);
return stream;
}
public static string ToStringFix(this ArticuloBusquedaDTO article)
{
string result = "";
result += article.CodigoArticulo.PadRight(10, ' ').Substring(0, 10);
result += article.EAN.Trim().PadLeft(13, '0').Substring(0, 13);
result += article.NombreArticulo.PadRight(100, ' ').Substring(0, 100);
result += article.Marca.PadRight(100, ' ').Substring(0, 100);
result += article.Familia.PadRight(50, ' ').Substring(0, 50);
result += article.PrecioCesion.ToStringFix();
result += article.PVP.ToStringFix();
return result;
}
private static string ToStringFix(this double numero)
{
var num = (int)Math.Round(numero * 100, 0);
string result = num.ToString().PadLeft(10, '0');
return result;
}
}
}
it just writes the file lines based on the stuff I got from the database. But when I look at the file it looks truncated. The file is about 8Mb. I also tried converting to byte[] before returning from ATxt with the same result.
Any idea?
Thanks,
Carles
Update: I also tried to serve XML from the same content and it also gets truncated. It doesn't get truncated on the data (I thought it might have been an EOF character in it) but it truncates in the middle of a label...
I was having the exact same problem. The text file would always be returned as truncated.
It crossed my mind that it might be a "flushing" problem, and indeed it was. The writer's buffer hasn't been flushed at the end of the operation - since there's no using block, or the Close() call - which would flush automatically.
You need to call:
streamWriter.Flush();
before MVC takes over the stream.
Here's how your method should look like:
public static Stream ATxt(IEnumerable<ArticuloBusquedaDTO> articles)
{
var stream = new MemoryStream();
var streamWriter = new StreamWriter(stream, Encoding.UTF8);
foreach (ArticuloBusquedaDTO article in articles)
{
streamWriter.WriteLine(article.ToStringFix());
}
// Flush the stream writer buffer
streamWriter.Flush();
stream.Seek(0, SeekOrigin.Begin);
return stream;
}
Why are you using an ActionResult?
ASP.NET MVC 1 has a FileStreamResult for just what you are doing. It expects a Stream object, and returns it.
public FileStreamResult Test()
{
return new FileStreamResult(myMemoryStream, "text/plain");
}
Should work fine for what you want to do. No need to do any conversions.
In your case, just change your method to this:
public FileStreamResult ATxt()
{
var articulos = _articulosService.ObteTotsArticles();
return new FileStreamResult(CatalegATxt.ATxt(articulos), "text/plain");
}
You probably want to close the MemoryStream. It could be getting truncated because it expects more data still. Or to make things even simpler, try something like this:
public static byte[] ATxt(IEnumerable<ArticuloBusquedaDTO> articles)
{
using(var stream = new MemoryStream())
{
var streamWriter = new StreamWriter(stream, Encoding.UTF8);
foreach (ArticuloBusquedaDTO article in articles)
{
streamWriter.WriteLine(article.ToStringFix());
}
return stream.ToArray();
}
}