views:

81

answers:

4

I have a repeater that displays financial data and prices for various stocks.

On this page, I also have an "export" button that needs to take the data ~on the screen~ and convert it into a CSV for the user.

The problem is, after I databind my list of "Stock" entities:

List<Stock> stocks = GetStocks()
rptStockList.DataSource = stocks;
rptStockList.DataBind();

The data is not persisted on postback.

Also, the data on this page is constantly being updated via an UpdatePanel and a Timer control (being re-databound each time). Every 30 seconds the prices displayed for the various stocks in the repeater control change.

Now, I have a linkbutton that has a click event method in the code-behind that is supposed to export the ~data on the screen~ for the user. I need to grab the current values for the list of stocks last databound to the repeater. I can't simply go grab the latest values from the database because those will have been changed in the time between the last refresh.

protected void lbtnExportStocks_Click(object sender, EventArgs e)
{
    // No longer have the stock data used in the repeater control
    ExportStocksToExcel();
}

I know that ASP.NET doesn't persist the datasource for the repeater on post-back but I need to still be able to either re-construct this list of Stock entities so I can send them the CSV or I need persist it in some way.

I don't want to do anything that is too heavy handed in terms of performance because during certain days of the week this application can have some heavy usage.

What is the proper solution to this type of situation? Should I iterate through the Repeater's "Items" collection and reconstruct the Stock entities?

A: 

It sounds like in your Page_Load method you are doing some sort of binding to your repeater. For your purposes, this is bad, and as you have seen, it will unpersist your data each postback. Can you make sure your Page_Load things are like this? :

Page_Load(...){
  if (! Page.IsPostBack){
    //first time page loads do this block

    //stuff
    //databinding stuff
  }
}
rlb.usa
This is how I have things currently. How does that help that fact that on my call to the export linkbutton, the data isn't persisted?
Darwin
@Darwin The data may be persisted. E.g. if the repeater includes user settable textboxes, listboxes or checkboxes, those values *ARE* persisted on postback unless you call `DataBind`. So your data *may* be in the `ViewState` already. You can check by having a runat="server" button that does nothing but cause a PostBack. If your data *is* persisted, it should still be present after clicking that button.
Mark Hurd
+1  A: 

Could you store stocks in Viewstate, or in Session? e.g.

List<Stock> stocks = GetStocks()
rptStockList.DataSource = stocks;
rptStockList.DataBind();

ViewState.Remove("stocks");
ViewState.Add("stocks", stocks);

private void ExportStocksToExcel
{
    List<Stock> persistedStocks;

    persistedStocks = (List<Stock>)Page.ViewState["stocks"];
    ...
}

Session state may actually be the better choice for storing Stocks since that won't get transmitted to the client in the page (with all the possibilities for 'creative editing' that could entail) - that's probably quite important in an application like this. (Yes, you could encrypt the Viewstate but with an eye on those peak times that's an overhead you might not want.)

PhilPursglove
A: 

I would either take Phil's answer and serialize the whole dataset, OR create some sort of criteria object that is passed to GetStocks() to specify which data to get. Then serialize and store the criteria object in ViewState, so when the user clicks "Export", you can pull the criteria and retrieve the same data.

i.e.,

[Serializable]
public class StockCriteria
{
    public DateTime DateFrom { get; set; }
    public DateTime DateTo { get; set; }
    public string[] Symbols { get; set; }
}

and then GetStocks() has StockCriteria as a parameter and creates its query based on it.

dave thieben
@dave You'd probably want to pass a single point in time, I think, or else a stock's price could change between DateFrom and DateTo...
PhilPursglove
+1  A: 

A simple method is to render the values as input controls (as opposed to, say, <span> or bare <td> elements) - the browser sends input values back to the server when the user posts.

For example:

<ItemTemplate>
    Symbol: <input type="text" readonly="readonly" name="Symbol" value="<%# Container.DataItem("Symbol") %> />
    Quote: <input type="text" readonly="readonly" name="Quote" value="<%# Container.DataItem("Quote") %> />
</ItemTemplate>

The client sends every input value named "Symbol" in an array (and likewise the input values named "Quote"), which you can access in your code-behind like this:

protected void lbtnExportStocks_Click(object sender, EventArgs e) {

    // They come out as comma-delimited strings
    string[] symbols = Request.Form["Symbol"].Split(',');
    string[] quotes  = Request.Form["Quote"].Split(',');

    // ... continue exporting stocks to Excel
}

Of course, at the bottom, this technique is basically writing whatever the client sends you to an Excel file, so you might want to secure or limit the input in some way. That might involve authenticating users and/or throttling the amount of data your method will export. If this is a serious concern in your environment or you can't afford to give it much attention, consider serializing the original data in the user's session instead.

Similarly, if you're intentionally transmitting a large amount of data, you should consider using other approaches for performance reasons. You might use session instead, or or transmit a small key that you can use to reconstruct the data used to build the repeater (for example, if the repeater was bound to the results of a query that doesn't change given its inputs, you can just serialize the input(s) between calls).

Jeff Sternal