8

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?

Darwin
  • 81
  • 1
  • 3
  • 1
    I think you need to post some information on what exactly `ExportStocksToExcel()` does because the repeater should not lose the data that is bound to it (assuming you haven't disabled viewstate on the control or page) because of viewstate. However you can't read the data backwards from the repeater from it's data source if that's what you expected (or atleast in any reasonable way). – Chris Marisic Jul 01 '10 at 18:58
  • @Chris Marisic, so given what I wrote above, how would you personally get the data back from the repeater to output a CSV? – Darwin Jul 01 '10 at 19:02
  • @Chris Marisic, As of right now all ExportStocksToExcel() does is setup the proper header and content type and then I was going to write out a response with the CSV data. However, I'm having difficulty figuring out how to get that data back out. – Darwin Jul 01 '10 at 19:03
  • @Chris I don't think it's as much a case of losing the data that's bound in the Repeater as wanting the original data the Repeater was bound *from*. I'm guessing `Stock` is mostly a ticker symbol and a price so you probably could rebuild `Stocks` but like you say, it'd be a bit fugly :-) – PhilPursglove Jul 01 '10 at 19:05

4 Answers4

1

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
  • 12,511
  • 5
  • 46
  • 68
1

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
  • 47,787
  • 8
  • 93
  • 120
0

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
  • 14,942
  • 16
  • 80
  • 128
  • 1
    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 Jul 01 '10 at 18:36
  • @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 Sep 06 '10 at 10:40
0

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
  • 5,388
  • 2
  • 28
  • 38
  • 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 Jul 05 '10 at 15:50