3

I am working on New web application which is Using Web API as Business Layer and Knock out Js as client side frame work to binding. I have a requirement like Pass the certain search criteria to Web API Controller and get the Data from DB and Create and Send the Excel/MS-Word file on the fly as a downloadable content.

I am new to both the Web API and Knock out, I am searching on the Net and get partial solution and I am looking here to get more optimal solution for this use case.

Below is my code:

Client:

 function GetExcelFile() {
    var $downloadForm = $("<form method='POST'>")
      .attr("action", baseUrl + "api/FileHandler/GetExcelFileTest")
              .attr("target", "_blank")
    $("body").append($downloadForm);
    $downloadForm.submit();
    $downloadForm.remove();
}

On Button Click having this code snippet to create a form on the fly and Get response from Web API.

Web API Code:

[HttpPost]
        public HttpResponseMessage GetExcelFileTest()
        {
            var response = new HttpResponseMessage();
            //Create the file in Web App Physical Folder
            string fileName = Guid.NewGuid().ToString() + ".xls";
            string filePath = HttpContext.Current.Server.MapPath(String.Format("~/FileDownload/{0}", fileName));

            StringBuilder fileContent = new StringBuilder();
            //Get Data here
            DataTable dt = GetData();
            if (dt != null)
            {
                string str = string.Empty;
                foreach (DataColumn dtcol in dt.Columns)
                {
                    fileContent.Append(str + dtcol.ColumnName);
                    str = "\t";
                }
                fileContent.Append("\n");
                foreach (DataRow dr in dt.Rows)
                {
                    str = "";
                    for (int j = 0; j < dt.Columns.Count; j++)
                    {
                        fileContent.Append(str + Convert.ToString(dr[j]));
                        str = "\t";
                    }
                    fileContent.Append("\n");
                }
            }
            // write the data into Excel file
            using (StreamWriter sw = new StreamWriter(fileName.ToString(), false))
            {
                sw.Write(fileContent.ToString());
            }
            IFileProvider FileProvider = new FileProvider();
            //Get the File Stream
            FileStream fileStream = FileProvider.Open(filePath);
            //Set response
            response.Content = new StreamContent(fileStream);
            response.Content.Headers.ContentDisposition = new ContentDispositionHeaderValue("attachment");
            response.Content.Headers.ContentDisposition.FileName = fileName;
            response.Content.Headers.ContentType = new MediaTypeHeaderValue("application/ms-excel");
            response.Content.Headers.ContentLength = fileStream.Length;
            //Delete the file

            //if(File.Exists(filePath))
            //{
            //    File.Delete(filePath);
            //}
            return response;
        }

Using this code I am able to download an Excel File. Still I have some more open questions to make this code optimal.

Q1) I need to Pass view model(Search Criteria) to API Controller Using the dynamically create form ? (OR) Any better ways to get Excel file from Web API.

Q2) I am sure it's not a good way to create Excel file in Physical folder and Get FileStream and send as a respone. How to do on the fly ? OR any other optimal ways.

Please suggest me to do better ways.. Thanks

SJ Alex
  • 149
  • 3
  • 12
  • If the form is dynamic, how are you using Knockout? Are you somehow dynamically adding *ad-hoc* observables? – awj Jun 09 '15 at 07:38
  • I have a already rendered form in place. In that form I have Search Criteria on the top and having 4 tabs based on the search criteria I will update my all the view model on tab click like load on demand basis. In the tabs I have a Grids so I have to export my Grid Data into Excel file when user clicks Export to Excel button – SJ Alex Jun 09 '15 at 09:48

1 Answers1

0

Q1) You can quite easily pass the view-model, but it's also similarly easy to pull that information from the posted form.

Passing the view-model

If you want to pass the view-model to a WebAPI method then remember that said method must take as a parameter an object with the same properties. So if the object that you wish to post back always has the same properties then it's trivial to build a server-side class with the same properties and receive an instance of that class.

To post back this client-side object you can do something like this (uses jQuery, which I see you're already using):

$.ajax({
    contentType: "application/json",
    data: my-view-model.toJSON(),
    type: "POST",
    url: baseUrl + "api/FileHandler/GetExcelFileTest" });

I haven't attached any success or error handlers here because the JavaScript isn't concerned with the return, but you might wish to add some handlers in case an exception is thrown in your WebAPI method. I recommend doing that by adding the following to the above $.ajax() call:

statusCode: {
    500: function(jqXhr, textStatus, errorThrown) {
    },
    [other HTTP error codes]
}

[Read the documentation for the $.ajax() call here.]

One additional tip here: when you call my-view-model.toJSON() (or self.toJSON(), if called from within your view-model) Knockout will first of all determine if your view-model contains a toJSON() method. If so, it will use this method; if not then it will call the browser's implementation of this function. However, the browser's implementation of this function will serialise everything, which can be particularly length if you have, for example, long select lists in your view-model. Therefore, if you wish only to send back a subset of the view-model's properties then define your own toJSON function on your view-model like so:

var toJSON = function() {
    return {
        Property1: ...,
        Property2: ...
    };
}

[Read more about converting a view-model to JSON here.]

Posting the form as-is

If you don't wish to expend the effort to do the view-model wiring then you can just post the form exactly like you have in your question. You can then retrieve the values from the form by using

Request.Form["my-field"];

Q2)

You're probably right in pointing out that it's not wise to create the Excel file in the physical folder. However, as far as I'm aware (interested if someone says otherwise) you'll have to use a 3rd-party library for this. Microsoft do offer an Office automation library but I have a suspicion that you also need Office to be installed at the same location.

Creating Excel spreadsheets dynamically is something I've done several times but for the actual creation I use Aspose.Cells, which requires a license. Although I do create a physical version and then delete it, I believe Aspose.Cells may allow you to create it as a stream. But take a look around, there are certainly other libraries which offer Excel automation.

Returning the File from the Server

Calling $.ajax({...}) alone won't allow you to present the user with a "Save as..." dialog. What I do in this situation - and this won't work if you wish to store the generated file only in memory (FileStream, for example) and not on the file system - is to respond to the $.ajax({...}) call with a filename for the generated file.

The next step is to direct the user towards that filename.

So I have something like this in my JavaScript:

$.ajax({
    dataType: "json",
    type: "GET",    // you'll probably want POST in your case
    url: ...,
    success: function(response) {
        if (response && response.Uri && response.Uri.length) {
            window.location.href = [root URL] + response.Uri;
        }
    }
});

But don't be alarmed by this redirect. That window.location.href points directly to a folder on the server, no controller needed. Because the browser then receives a file it presents the "Save as..." dialog while remaining on the same webpage.

awj
  • 7,482
  • 10
  • 66
  • 120
  • Thanks for your time and your suggestions. Q1) I am am familiar with passing view model to API Controller method using Ajax Post. In my case I am creating the Form on the fly which has Post method to call web API then the API will return the Excel file with save dialog itself once the file is return from the browser I will remove the dynamically created form. I have a View Model how to Post that view model in on the fly form? – SJ Alex Jun 08 '15 at 18:10
  • Q2) In my company not allowing to Go for Third Party licensed dll's. – SJ Alex Jun 08 '15 at 18:13
  • If you have to post a changeable, dynamic form then you'll have to use `Request.Form["..."]` as I wrote in my answer. – awj Jun 09 '15 at 07:32
  • To receive the HttpResponseMessage I am using that code to create a form on the fly it will go to the Web API method that will be get downloaded with Open, Save and Cancel Dialog. I am stuck here how to load my application/octet-stream OR application/ms-excel Content type in my html client page. – SJ Alex Jun 09 '15 at 09:52
  • Thanks for your suggestions,finally I get help from this SC thread - [Download a file by jquery ajax](http://stackoverflow.com/questions/4545311/download-a-file-by-jquery-ajax) .I am using this method from that thread - 'ajax_download(url, data)'.I could pass my model to Web API controller in dynamically created form after the request I will get the downloadable file from Web Api method. – SJ Alex Jun 10 '15 at 17:34
  • You don't need a jQuery plugin to present the "Save as..." dialog to the user. I've edited my answer to explain how simply this can be achieved. – awj Jun 11 '15 at 07:23
  • Thanks for your response.Your solution should work perfectly in Physical file. In my case I am creating the Excel/Word doc on the file and get bytes and then passing to client. – SJ Alex Jun 11 '15 at 08:30