tags:

views:

177

answers:

3

I am trying to create a csv file of some data. I have wrote a function that successfully does this....

Private Sub CreateCSVFile(ByVal dt As DataTable, ByVal strFilePath As String)
        Dim sw As New StreamWriter(strFilePath, False)
        ''# First we will write the headers. 
        ''#DataTable dt = m_dsProducts.Tables[0]; 
        Dim iColCount As Integer = dt.Columns.Count
        For i As Integer = 0 To iColCount - 1
            sw.Write(dt.Columns(i))
            If i < iColCount - 1 Then
                sw.Write(",")
            End If
        Next
        sw.Write(sw.NewLine)
        ''# Now write all the rows. 
        For Each dr As DataRow In dt.Rows
            For i As Integer = 0 To iColCount - 1
                If Not Convert.IsDBNull(dr(i)) Then
                    sw.Write(dr(i).ToString())
                End If
                If i < iColCount - 1 Then
                    sw.Write(",")
                End If
            Next
            sw.Write(sw.NewLine)
        Next
        sw.Close()

    End Sub

The problem is I am not using the streamwriter object correctly for what I trying to accomplish. Since this is an asp.net I need the user to pick a local filepath to put the file on. If I pass any path to this function its gonna try to write it to the directory specified on the server where the code is. I would like this to popup and let the user select a place on their local machine to put the file....

 Dim exData As Byte() = File.ReadAllBytes(Server.MapPath(eio))
        File.Delete(Server.MapPath(eio))
        Response.AddHeader("content-disposition", String.Format("attachment; filename={0}", fn))
        Response.ContentType = "application/x-msexcel"
        Response.BinaryWrite(exData)
        Response.Flush()
        Response.End()

I am calling the first function in code like this...

 Dim emplTable As DataTable = SiteAccess.DownloadEmployee_H()
  CreateCSVFile(emplTable, "C:\\EmplTable.csv")

Where I dont want to have specify the file loaction (because this will put the file on the server and not on a client machine) but rather let the user select the location on their client machine.

Can someone help me put this together? Thanks in advance.

I have recreated my export function and now it lets the usr select a download path, but one column in the data being downloaded has data in the form of "Doe, John" this column is called "EPLNME" this messes up the output file because its reading the comma in the data and now the data is off by a column in the output file can someone help me stop this specific incident im not sure how I can. Here is the code...

Private Sub ExportCSV(ByVal data As DataTable, ByVal nameOfFile As String)
    Dim context As HttpContext = HttpContext.Current
    context.Response.Clear()
    context.Response.ContentType = "text/csv"
    context.Response.AddHeader("Content-Disposition", "attachment; filename=" + nameOfFile + ".csv")

    ''#Write column header names
    For i = 0 To data.Columns.Count - 1
        If (i > 0) Then
            context.Response.Write(",")
        End If
        context.Response.Write(data.Columns(i).ColumnName)
    Next
    context.Response.Write(Environment.NewLine)

    ''#Write data
    For Each row As DataRow In data.Rows
        For i = 0 To data.Columns.Count - 1
            If (i > 0) Then
                context.Response.Write(",")
            End If
            context.Response.Write(row.Item(i).ToString())
        Next
        context.Response.Write(Environment.NewLine)
    Next
    context.Response.End()

End Sub
A: 

You can use a MemoryStream to hold the binary data on the server, instead of writing them to a file.

1) Write the contents youn want to put in the CSV into the memory stream

2) Read from the MemoryStream into the Response when required.

Hope it helps!

taxalb
+1  A: 

You, don't really nead a streamwriter, that is for creating files on the machine where the code is running. Use a StringBuilder to build up the string that represents the CSV file then do the following:

Response.ContentType = "text/csv";
Response.AddHeader("Content-Disposition", "attachment; filename=NameOfFile");
Response.Write(MyStringBuilder.ToString());

If you do need to create a file because you need to store it on the server and also transmit it to the user. Create the file as you are doing and the replace the last line with

Response.TransmitFile("filePath");
Ben Robinson
+1  A: 

First, you need to overload your function like this, to allow sending your output directly to either a stream or a path:

Private Sub CreateCSVFile(ByVal dt As DataTable, ByVal strFilePath As String)
    Using sw As New StreamWriter(strFilePath)
        CreateCSVFile(dt, sw)
    End Using
End Sub

Private Sub CreateCSVFile(ByVal dt As DataTable, ByVal outStream As TextWriter)
    ''# First we will write the headers.  
    Dim delimiter As String = String.Empty
    For Each col As DataColumn in dt.Columns
         outStream.Write(delimiter)
         outStream.Write(col.ColumnName)
         delimiter = ","
    Next col        
    outStream.Write(outStream.NewLine)

   int flushCount = 0;

    ''# Now write all the rows. 
    For Each dr As DataRow In dt.Rows
        delimiter = String.Empty
        For i As Integer = 0 To dt.Columns.Count -1
            outStream.Write(delimiter)

            If Not Convert.IsDBNull(dr(i)) Then
                outStream.Write("""") ''#Wrap fields in quotes to allow for commas in field data

                ''# Need to escape the quotes as well
                outStream.Write(dr(i).ToString().Replace("""", """"""))

                outStream.Write("""") 
            End If
            delimiter = ","
        Next i
        outStream.Write(outStream.NewLine)

        ''# Flush the buffer periodically
        flushCount += 1
        If flushCount > 100 Then
             outStream.Flush()
             flushCount = 0
        End If
    Next dr
End Sub

Notice that your function works pretty much exactly the same as before, but you can now write to a file or directly to a stream, and you didn't have to re-write a lot of code to make it work. Pretty much anything you write that works with files should be written this way. I made a few other improvements to the code as well, but the main thing is the method that does the actual work should always accept a TextWriter and then just add overloads if you want to be able to accept anything else like a file path.

Now what you can do is take the Content Type and Header from Ben Robinson's answer and use this new method to write directly to the asp.net response buffer:

Response.ContentType = "text/csv";
Response.AddHeader("Content-Disposition", "attachment; filename=NameOfFile");
CreateCSVFile(SiteAccess.DownloadEmployee_H(), Response.Output)
Response.Flush()
Response.End()
Joel Coehoorn
its telling me Response is not a TextWriter object
Nick LaMarca
@Nick - Oops, fixed that function call. I needed `Response.Output` there.
Joel Coehoorn
I think I created the proper function but still have to deal with employee names have data in the format of "Doe, John" this comma is messing up my function. See edited question. Thanks for your help!
Nick LaMarca
@Nick - check my code again. There are a few "improvements" to your original, including accounting for fields with commas. Also note the code to double-escape internal quotations marks. Once you start wrapping fields in quotes (to allow for the commas), you need to do that as well.
Joel Coehoorn
Hey Joel I cant get your functin to work its just printing out names plus I want it to initially open in excel as a .csv file. My function is working for me besides the fields with commas issue. Can you make changes to my function to account for the fields with commas? Thanks a bunch.
Nick LaMarca
@Nick - I found why it was only printing one column, that's fixed now. The code I posted is _already_ an adaption of your function, just like you asked for. I didn't write anything from scratch. Do **not** mix asp.net response code in with the csv generation. That's poor separation of concerns.
Joel Coehoorn
that works but I have to open excel and import it manually. The users want 7 files all at once. I can make 7 buttons to download each file one at a time. Do you have any suggestions on to hook it all up in one button or would that not make much sense. Each datatable will have to be on a different worksheet. May if I could download one zip file with 7 seperate files in it each containing one datatable that would suite my needs. Is that an easy implementation here? I have never created a zip file download before.
Nick LaMarca
@Nick The way the web works is one request (button click/submit), one response (file). That doesn't mean you can't do seven files from one button, but it does mean you'd need to zip the results on the server or use the full Excel format and put each csv result into it's own worksheet in the full workbook. But now you're way outside the scope of the original question.
Joel Coehoorn
Thanks Joel thats what I thought, I would have to use th e full excel format and put each csv file on a worksheet which in turn would be super slow and bog down th server. I already implemented a solution like that and it was super slow hence why I used csv which is much faster. Thanks for all your help on this!
Nick LaMarca
@Nick - Excel interop is slow. You can use SpreadsheetML instead and it works much better.
Joel Coehoorn