0

Using a LINQ query I need to export to Excel when a WebApi method is called. I have built the LINQ query that will return the correct data, now I need it to export to .csv or Excel file format.

I have tried using MemoryStream and StreamWriter but I think I am just chasing my tail now.

[HttpGet]
[Route("Download")]
public Task<IActionResult> Download(int memberId)
{
    var results = (from violations in _db.tblMappViolations
                   where violations.MemberID == memberId
                   select new IncomingViolations
                       {
                           Contact = violations.ContactName,
                           Address = violations.str_Address,
                           City = violations.str_City,
                           State = violations.str_State,
                           Zip = violations.str_Zipcode,
                           Country = violations.str_Country,
                           Phone = violations.str_Phone,
                           Email = violations.str_Email,
                           Website = violations.str_WebSite,
                       }).FirstOrDefault();

    MemoryStream stream = new MemoryStream(results);
    StreamWriter writer = new StreamWriter(stream);    

    writer.Flush();
    stream.Position = 0;

    FileStreamResult response = File(stream, "application/octet-stream"); 
    response.FileDownloadName = "violations.csv";

    return response;
}
d219
  • 2,707
  • 5
  • 31
  • 36
Alex D
  • 788
  • 2
  • 11
  • 33
  • Why would serializing some arbirtrary objects make it something that you expect Excel to read and parse correctly? – Uwe Keim Jul 19 '18 at 13:58
  • 1
    This question is a duplicate of https://stackoverflow.com/questions/11123995/is-there-such-thing-as-a-csv-serializer-similar-to-xmlserializer or https://stackoverflow.com/questions/29948809/web-api-return-csv-file, depending on what you are asking. – Jodrell Jul 19 '18 at 14:13

3 Answers3

1

Here is how you can send CSV file to the user from server.

string attachment = "attachment; filename=MyCsvLol.csv";
HttpContext.Current.Response.Clear();
HttpContext.Current.Response.ClearHeaders();
HttpContext.Current.Response.ClearContent();
HttpContext.Current.Response.AddHeader("content-disposition", attachment);
HttpContext.Current.Response.ContentType = "text/csv";
HttpContext.Current.Response.AddHeader("Pragma", "public");

var sb = new StringBuilder();
// Add your data into stringbuilder
sb.Append(results.Contact);
sb.Append(results.Address);
sb.Append(results.City);
// and so on
HttpContext.Current.Response.Write(sb.ToString());

For Sending it from API

MemoryStream stream = new MemoryStream();
StreamWriter writer = new StreamWriter(stream);
// Write Your data here in writer
writer.Write("Hello, World!");
writer.Flush();
stream.Position = 0;

HttpResponseMessage result = new HttpResponseMessage(HttpStatusCode.OK);
result.Content = new StreamContent(stream);
result.Content.Headers.ContentType = new MediaTypeHeaderValue("text/csv");
result.Content.Headers.ContentDisposition = new ContentDispositionHeaderValue("attachment") { FileName = "Export.csv" };
return result;

Update:-

public HttpResponseMessage Download()
{
    var results = (from violations in _db.tblMappViolations
       where violations.MemberID == memberId
       select new IncomingViolations
           {
               Contact = violations.ContactName,
               Address = violations.str_Address,
               City = violations.str_City,
               State = violations.str_State,
               Zip = violations.str_Zipcode,
               Country = violations.str_Country,
               Phone = violations.str_Phone,
               Email = violations.str_Email,
               Website = violations.str_WebSite,
           });
    var sb = new StringBuilder();
    MemoryStream stream = new MemoryStream();
    StreamWriter writer = new StreamWriter(stream);
    foreach(var tempResult in results)
    {
        sb.Append(tempResult.Contact+",");
        sb.Append(tempResult.Address+",");
        sb.Append(tempResult.City+",");
        sb.Append(tempResult.State+",");
        sb.Append(tempResult.Zip+",");
        sb.Append(tempResult.Country+",");
        sb.Append(tempResult.Phone+",");
        sb.Append(tempResult.Email+",");
        sb.Append(tempResult.Website+",");
        sb.Append(Enviroment.NewLine);
    }
    writer.Write(sb.ToString());
    writer.Flush();
    stream.Position = 0;

    HttpResponseMessage result = new HttpResponseMessage(HttpStatusCode.OK);
    result.Content = new StreamContent(stream);
    result.Content.Headers.ContentType = new MediaTypeHeaderValue("text/csv");
    result.Content.Headers.ContentDisposition = new ContentDispositionHeaderValue("attachment") { FileName = "Export.csv" };

    return result;
}
Mihir Dave
  • 3,954
  • 1
  • 12
  • 28
  • Great, I am sorry but could you blend that in with my code above? Still having trouble understanding. – Alex D Jul 19 '18 at 13:55
  • To me, the actual question is regarding that part that you just left out by putting your `// Add your data into stringbuilder` comment. – Uwe Keim Jul 19 '18 at 14:00
  • @AlexD by "blend" do you mean, "do my job for me so I don't have to understand your answer and can just copy and paste it." – Jodrell Jul 19 '18 at 14:03
  • No, I do not mean that. Just trying to get a better understanding of how it fits in with what I currently have. – Alex D Jul 19 '18 at 14:05
  • @AlexD, you have two problems, 1) how to serialize your object graph into CSV or Excel data, 2) How to return that serialized data so that the client knows its CSV or Excel data. Which is the question you are asking? – Jodrell Jul 19 '18 at 14:08
  • @MihirDave I added my revised code to your solution. Can you take a look at it? – Alex D Jul 19 '18 at 14:49
  • Yes I did, it is returning JSON. @AlexD and that's something you don't want right? – Mihir Dave Jul 19 '18 at 14:50
  • What is Enviroment in sb.Append(Enviroment.NewLine); Because result is already in the scope for the HttpResponseMessage results wouldnt it need to be changed so it doesnt conflict? – Alex D Jul 19 '18 at 15:10
  • it's for specifying new line https://msdn.microsoft.com/en-us/library/system.environment.newline(v=vs.110).aspx – Mihir Dave Jul 19 '18 at 15:15
  • @MihirDave okay got that part. I can create "HttpResponseMessage result" because it already exsists in the foreach. If I change it I still get the JSON response. – Alex D Jul 19 '18 at 15:19
  • var result's context will end with foreach's closing brackets. so don't worry about that – Mihir Dave Jul 19 '18 at 15:31
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/176349/discussion-between-alex-d-and-mihir-dave). – Alex D Jul 19 '18 at 15:36
0

First, to reuse the code in other areas, always create helper classes.

I adopted this method of converting list into a stream with headers as property names, if you want a file from this, essentially, I would just add another step to this:

STEP 1:

public static Stream ConvertToCSVStream<T>(IEnumerable<T> objects)
       {
          Type itemType = typeof(T);
          var properties = itemType.GetProperties();

        var mStream = new MemoryStream();
        StreamWriter sWriter = new StreamWriter(mStream);

        var values = objects.Select(o =>
        {
            return string.Join(",", properties.Select(p =>
            {
                var value = p.GetValue(o).ToString();
                if (!Regex.IsMatch(value, "[,\"\\r\\n]"))
                {
                    return value;
                }

                value = value.Replace("\"", "\"\"");

                return string.Format("\"{0}\"", value);

            })) + sWriter.NewLine;
        });

        var valuesInStrings = values.Aggregate((current, next) => current + next);
        try
        {
            sWriter.Write(string.Join(",", properties.Select(x => x.Name.Replace("_", " "))) + sWriter.NewLine);

            sWriter.Write(valuesInStrings);
        }
        catch (Exception e)
        {
            mStream.Close();
            throw e;
        }

        sWriter.Flush();
        mStream.Position = 0;

        return mStream;
    }

if your data is text, just convert it directly to a file result but if not, you must convert it to binary array and write it to stream, refer to this article for converting it to binary data, in our case, for csv, you could just use the FileStream result that you've implemented in a separate method:

STEP 2:

 public FileStreamResult CreateFile(MemoryStream mStream, string path, string name)
        {
            //set values, names, content type, etc 
            //return filestream
        }

or any other method you find better.

-1

Save your result in a DataTable and then just use this

XLWorkbook workbook = new XLWorkbook();
DataTable table = GetYourTable();
workbook.Worksheets.Add(table);

And you should definitely use stream writer for this if you know which file its going to write to from the start, else stream reader and then stream writer.

  • 1
    Which library should I use for this? XLWorkbook is not included in the .NET standard – b0bi Jul 21 '18 at 16:06