13

Context:

I've often been in situations where our ASP.NET pages would have to show data to the user on a GridView, let him change it as he pleases (Textbox on cells) and only save it to the database when he actually hits the "Save Button". This data is usually a virtual state of the information on the page, meaning that the user can change everything without really saving it until he hits the "Save Button". In those cases, there's always list of data that needs to be persisted across ASP.NET Postbacks. This data could be an instance of a DataTable or just some List<Someclass>.

I often see people implementing this and persisting the data on Session. On that cases i also usually see problems when it comes to some user navigating with multiple tabs open, some times on the same page. Where the data of two different tabs would get merged and cause problems of information being scrambled.

Example of how Session is often used:

private List<SomeClass> DataList
{
    get
    {
        return Session["SomeKey"] as List<SomeClass>;
    }
    set 
    {
        Session["SomeKey"] = value;
    }
}

People often tries to solve it by doing something like this:

protected void Page_Load(object sender, EventArgs e)
{
    if (!IsPostBack)
    {
        DataList = null
    }
    else
    {
        FillGridView(DataList);
    }
}

But what about when two tabs are already loaded and the user is changing the GridView values and for some weird reason he tries to save the data by hitting the Save button on the other page? I personally dislike this option.

Other ways to do this would be to put the data on ViewState. However, when it comes to persisting substantially big lists, it could impact the page heavily when it's stored on the page (HiddenField).

But, what's the best way to make that work? Once, i thought in using Session together with ViewState where the ViewState would hold an unique identifier which would index the Session saved data. That would prevent sharing the data between tabs on the browser:

private List<SomeClass> DataList
{
    get
    {
        if (ViewState["SomeKey"] == null)
        {
            ViewState["SomeKey"] = Guid.NewGuid().ToString();
        }

        return Session[ViewState["SomeKey"].ToString()] as List<SomeClass>;
    }
    set {

        if (ViewState["SomeKey"] == null)
        {
            ViewState["SomeKey"] = Guid.NewGuid().ToString();
        }

        Session[ViewState["SomeKey"].ToString()] = value;
    }
}

On the other hand it would store a new list of data to the Session every time the user enters the page. Which would impact the server memory. Maybe they could be erased in some way.

Question:

What would be the best way of persisting that kind of data across Postbacks, considering the contexts of multiple tabs on the browser, with the less cost to the server and to the maintenance coding team?

Update:

As @nunespascal nicely posted, one option would be to store the ViewState in the Session using the SessionPageStatePersister. But unfortunately that's not an option on my case. And yet it is not very different from my last example, saving the data on the Session indexed by an UniqueId stored on the ViewState.

Would there be any other options?

Mateus Schneiders
  • 4,853
  • 3
  • 20
  • 40
  • Second approach is workable with a few tweaks: 1) use a hidden field rather than ViewState. This allows you to access it before ViewState is available on the page (useful sometimes). 2) Wrap Session with a manager that limits how many items can be stored at once and throws out the oldest items. Of course, this doesn't completely fix the memory problem, but can go a long way towards letting users work organically in your application without worrying that a busy user will have hundreds of large objects/sets in memory. – Tim M. Apr 24 '13 at 22:02

2 Answers2

2

There is a simple solution to that problem. Store the ViewState in the Session.

For that you need to use the SessionPageStatePersister

Refer: Page State Persister

All you need to do is override the PageStatePersister and make it use SessionPageStatePersister instead of the default HiddenFieldPageStatePersister

protected override PageStatePersister PageStatePersister
{
    get
    {
        return new SessionPageStatePersister(this);
    }
}

This even saves you the headache of maintaining a unique key. A hidden field will be used automatically to keep a unique key per instance of the page.

nunespascal
  • 17,584
  • 2
  • 43
  • 46
  • Yes, it would definitely solve it. Unfortunately its not an option on my case, but thanks for the quick answer. – Mateus Schneiders Feb 14 '13 at 11:46
  • If i store on viewstate, and store the viewstate on the session, how would that be different from the last example i posted? I mean wouldn't it impact the server memory, or how would that be managed if the user keeps refreshing the page and consequentially storing new data to the session each time? – Mateus Schneiders Feb 14 '13 at 11:55
  • Yes, it would impact server memory. But that is a cost you would bear even if you stored the data directly in the session. As for repeated refreshes, those will hog session space. But if you face such problems, with the session you always have the option to move that to a database. Sessions would eventually get purged. – nunespascal Feb 14 '13 at 12:13
0

I've come across a similar situation. The idea is if you allow long sessions for each user to change the grid view, this means you'll also have a concurrency problem because eventually you will accept only one last set of modifications to your data.

So, my solution was, to allow changes on the database but make sure all the users see the same state via SignalR.

Now, the concurrency problem has disappeared but you still need to make the changes on the fly. You might not want to save the changes after all. I've solved this problem by applying the command design pattern. Now any set of changes can either be approved or discarded. Whenever you check the index you will see the last approved gridview. Go to update page and you see the live-updated gridview. Also, go to revisions to see old approved gridview -another advantages of command design pattern-.