To start, the JavaScript. This isn't the entire script but it's the necessary part:
var values = { selectedFormat: null, selectedGenre: null } // pull values from UI values.selectedGenre = $('#genre').val(); values.selectedFormat = $('#format').val(); // clear hidden form $('#hidden').html(''); // post form back to Web API on the fly $('<form>').attr({ method: 'POST', id: 'hidden', action: 'http://localhost:12345/api/GridApi/ExportData' }).appendTo('body'); $('<input>').attr({ type: 'hidden', id: 'genre', name: 'genre', value: values.selectedGenre }).appendTo('#hidden'); $('<input>').attr({ type: 'hidden', if: 'format', name: 'format', value: values.selectedFormat }); $('#hidden').submit();
One thing to point out, the first time this code runs, the hidden element ($('#hidden')) does not exist. It first comes into existence when we create the form and give it the id of hidden. However, after the page loads, if the user request another search, the line $('#hidden').html('') will clear the form.
Now for the Web API code:
public class GridApiController : ApiController { [HttpPost] public HttpResponseMessage ExportData(JObject values) { string genre = values.GetValue("genre").ToString(); string format = values.GetValue("format").ToString(); SearchParams searchParams = new SearchParams() { SelectedFormat = format, SelectedGenre = genre }; ListviewModels = Search(searchParams); string result = ConvertListToCsv(viewModels); HttpResponseMessage message = new HttpResponseMessage(HttpStatusCode.OK); message.Content = new StringContent(result); message.Content.Headers.ContentType = new MediaTypeHeaderValue("application/octet-stream"); message.Content.Headers.ContentDisposition = new ContentDispositonHeaderValue("attachment"); message.Content.Headers.ContentDisposition.FileName = "Test.csv"); return message; } public List<MusicItemViewModel> Search(SearchParams values) { // code omitted, just filter logic against repository } private string ConvertListToCsv<T>(List<T> list) { if (list == null || list.Count == 0) { throw new HttpResponseException(HttpStatusCode.NotFound); } Type t = typeof(T); string newLine = Environment.NewLine; object obj = Activator.CreateInstance(t); PropertyInfo[] props = obj.GetType().GetProperties(); byte[] carriageReturnBytes = System.Text.Encoding.UTF8.GetBytes("\r"); string text; using (MemoryStream ms = new MemoryStream()) using (StreamReader sr = new StreamReader(ms)) { foreach (PropertyInfo pi in props) { byte[] data = System.Text.Encoding.UTF8.GetBytes(pi.Name.ToString() + ","); ms.Write(data, 0, data.Length); } ms.Write(carriageReturnBytes, 0, carriageReturnBytes.Length); foreach (T item in list) { foreach(PropertyInfo pi in props) { string write = Convert.ToString(item.GetType().GetProperty(pi.Name).GetValue(item, null)).Replace(',', '') + ','; byte[] data = System.Text.Encoding.UTF8.GetBytes(write); ms.Write(data, 0, data.Length); } byte[] writeNewLine = System.Text.Encoding.UTF8.GetBytes(Environment.NewLine); ms.Write(writeNewLine, 0, writeNewLine.Length); } ms.Position = 0; text = sr.ReadToEnd(); return text; } } }
A short note. If you are separating your scripts for a modular purpose, separation of concerns, etc, here is an additional approach. If you have a client side data service to call back to the server, it's only a minor change....
// the original script abbreviated // post form back to Web API on the fly $('<form>').attr({ method: 'POST', id: 'hidden', action: 'http://localhost:12345/api/GridApi/ExportData' }).appendTo('body'); $('<input>').attr({ type: 'hidden', id: 'genre', name: 'genre', value: values.selectedGenre }).appendTo('#hidden'); $('<input>').attr({ type: 'hidden', if: 'format', name: 'format', value: values.selectedFormat }); // commented out //$('#hidden').submit(); // assign hidden form to variable var hiddenForm = $('#hidden'); // call data service dataService.ExportGridData(hiddenForm); // dataService.js var dataService = function () { var exportGridData = function (hiddenForm) { // wrap hidden form in jQuery so we can call submit $(hiddenForm).submit(); }; return { ExportGridData: exportGridData } }
The form we created on the fly contains the URL to call on the controller so everything will work as it should.