asp.net - How to update GridView on UI before method ends -
i running small app in sharepoint produces data needed in our company. in short pulls data azure database data-table, , converts data-table excel. can take minute or 2 , sometimes, because in new zealand , using remote server in usa, timeouts , 1 of worksheets won't load.
so when builds excel iterates through list of suppliers, , list of financial weeks getting data each , creating separate worksheet in excel per data-table. ideally, add new row grid view user sees, as report being built, stating whether financial week , supplier added, or not, excel report creating in back-end. allow user more aware of progress, , allow them know if there has been problem rather guessing.
it's lot of code try show relevant parts.
method pulls , creates excel
public void excelthreadcall() { datatable updatedatatable = new datatable(); gridview.datasource = updatedatatable; //payments download chosen financial week using (xlworkbook workbook = new xlworkbook()) { //gradeweek = selectedgradeweek.selectedvalue; foreach (listitem supplier in selectedsuppliers.items) { if (supplier.selected) { foreach (listitem fweek in selectedfweeks.items) { if (fweek.selected) { string checkemptytablesql = @"select count(*) fleshvgraded supplier_code = '" + supplier.value + "' , po_revision = " + fweek.value; int rowcount = convert.toint32(getvariable(checkemptytablesql)); if (rowcount > 0) { foreach (listitem report in selectedreports.items) { //sql strings string sqlintakedate = @"select week_ending_date fiscal_calendar fiscal_week = left(" + fweek + ", 2) , fiscal_year = concat(20, right(" + fweek + ", 2))"; string sqlpo = @"select distinct po_no fvgsummaryall supplier_code = '" + supplier.value + "' , f_week = " + fweek.value; string sqlallserials = "select * fvgdata supplier_code = '" + supplier.value + "' , f_week = " + fweek.value //variables datetime weekending = convert.todatetime(getvariable(sqlintakedate)); datetime weekstarting = weekending.adddays(-5); string fweekstring = fweek.tostring(); string ponostring = getvariable(sqlpo).tostring(); string intakedatestring = weekstarting.day + "/" + weekstarting.month + "/" + weekstarting.year + " " + weekending.day + "/" + weekending.month + "/" + weekending.year; //adds summary variables dictionary dictionary<string, string> summaryvariablesdict = new dictionary<string, string>(); summaryvariablesdict.add("f week", fweekstring); //other values added dict //adds worksheets based on above data if (report.selected && report.value.equals("allserials")) { string worksheetname = supplier.value + " data " + fweek.value; datatable datatable = getdatatable(sqlallserials); createworksheet(workbook, worksheetname, datatable); } //other reports follow **//what hope - need show in grid view immediatley not end of method updatedatatable.rows.add(suppler, fweek, "successful"); gridview.databind();** } } } } } } workbook.saveas(filepath); } }
so exists in class it's no problem me move aspx page, , have taken liberties show need in method. if doesn't make complete sense in respect (i.e. wouldn't declare datasource grid in method normally).
the problem have wait until end of method before updating grid view via postback , user gets @ once. hoping there way update gridview @ each iteration or every few seconds if use timer, can't find way implement this.
so long story short, how can update gridview method results appear on users ui, , not wait until end of method.
i along these lines:
- when page first loads, start background thread start building spreadsheet.
- when page loads, call javascript kicks off callback.
- in method in code-behind that's called callback, check status of building process. have process maintain list of strings, each representing html row in table.
- have page (via javascript) perform callback every few seconds. callback current list of rows. javascript on page receive response , update rendered table include new rows.
- when spreadsheet done (or when error occurs aborts creation process), show success or failure message user.
if helpful, can provide simple callback samples going.
edit: added code sample:
markup:
<%@ page language="c#" autoeventwireup="true" codebehind="default.aspx.cs" inherits="callbackwebform.default" %> <!doctype html> <html xmlns="http://www.w3.org/1999/xhtml"> <head runat="server"> <title></title> <script src="https://ajax.googleapis.com/ajax/libs/jquery/2.2.2/jquery.min.js"></script> <script type="text/javascript"> var callbackfrequency = 2000; // callback javascript // make callback server, call callserver(); // receive response server after callback function receiveserverdata(arg, context) { // parse json got server args = json.parse(arg); // add rows table $.each(args.tablerows, function (index, value) { $('#table1').append(value); }); // if we're done, show message if (args.doneloadingspreadsheet) $('#donediv').show(); // otherwise, start timer call again else window.settimeout(function () { callserver(); }, callbackfrequency); } $(document).ready(function() { // start callback loop window.settimeout(function () { callserver(); }, callbackfrequency); }); </script> </head> <body> <form id="form1" runat="server"> <div> sample page progress-y stuff </div> <table id="table1"> <tr> <th>col 1</th> <th>col 2</th> <th>col 3</th> </tr> <!-- rows inserted javascript go here --> </table> <div id="donediv" style="display: none;"> done! </div> </form> </body> </html>
code-behind:
using system; using system.collections.generic; using system.threading; using system.threading.tasks; using system.web.ui; using newtonsoft.json; namespace callbackwebform { public partial class default : system.web.ui.page, icallbackeventhandler { protected void page_load(object sender, eventargs e) { // setup callback javascript page can initiate callbacks , receive callback responses createclientsidecallbackfunction(); if (!page.ispostback) startbuildingspreadsheettask(); } #region callback private void createclientsidecallbackfunction() { var cm = page.clientscript; // javascript function in markup must match function name entered below (receiveserverdata) var cbreference = cm.getcallbackeventreference(this, "arg", "receiveserverdata", ""); // javascript function placed in markup used initiate callback var callbackscript = "function callserver(arg, context) {" + cbreference + "; }"; cm.registerclientscriptblock(this.gettype(), "callserver", callbackscript, true); } /// <summary> /// called when callserver(arg, context) called in javascript on page /// </summary> /// <param name="eventargument">not used, must passed</param> public void raisecallbackevent(string eventargument) { } /// <summary> /// called @ end of callback; provides response/result client /// </summary> /// <returns>json string representing instance of datatransferclass</returns> public string getcallbackresult() { // serialize datatransferobject, delete tablerows don't send them browser again // note: not thread-safe. should add sort of locking mechanism background thread // doesn't modify tablerows list while we're serializing , deleting it. var dtojson = jsonconvert.serializeobject(datatransferobject); datatransferobject.tablerows.clear(); return dtojson; } public class datatransferclass { public bool doneloadingspreadsheet { get; set; } public list<string> tablerows { get; set; } } #endregion callback #region background task // sessions have unique ids, individual page views don't. so, create 1 page view. private string viewid { { if (string.isnullorempty(viewstate["_viewid"] string)) viewstate["_viewid"] = guid.newguid().tostring(); return viewstate["_viewid"] string; } } // store datatransfer data , token sources in static dictionaries background task can them private static dictionary<string, datatransferclass> datatransferdictionary = new dictionary<string, datatransferclass>(); private static dictionary<string, cancellationtokensource> tokensourcesdictionary = new dictionary<string, cancellationtokensource>(); // make values in dictionaries view accessible via properties private datatransferclass datatransferobject { { if (datatransferdictionary.containskey(viewid)) return datatransferdictionary[viewid]; else return null; } set { if (datatransferdictionary.containskey(viewid)) datatransferdictionary[viewid] = value; else datatransferdictionary.add(viewid, value); } } private cancellationtokensource tokensource { { if (tokensourcesdictionary.containskey(viewid)) return tokensourcesdictionary[viewid]; else return null; } set { if (tokensourcesdictionary.containskey(viewid)) tokensourcesdictionary[viewid] = value; else tokensourcesdictionary.add(viewid, value); } } private void startbuildingspreadsheettask() { datatransferobject = new datatransferclass() { doneloadingspreadsheet = false, tablerows = new list<string>() }; tokensource = new cancellationtokensource(); var token = tokensource.token; (new taskfactory()).startnew(() => buildspreadsheet(viewid, token), token); } private void buildspreadsheet(string viewid, cancellationtoken token) { // simulate work. update datatransferobject every 5 seconds, finish after 30 seconds (6 iterations 5 second wait); (int = 0; < 6; i++) { // work 5 seconds system.threading.thread.sleep(5000); // update datatransferobject new row (don't use 'datatransferobject' property; relies 'viewid' property, in // turn relies upon viewstate, isn't available background thread). datatransferdictionary[viewid].tablerows.add("<tr><td>val " + + "</td><td>val " + (i * 10) + "</td><td>val " + (i * 100) + "</td></tr>"); } // done; update datatransferobject datatransferdictionary[viewid].doneloadingspreadsheet = true; } #endregion background task } }
a couple notes:
- add json.net nuget package (newtonsoft)
- note page class implements
icallbackeventhandler
interface
edit 2: updated suggested process @ top match did in code sample.
Comments
Post a Comment