R – How to re-instantiate dynamic ASP.NET user controls without using a database

asp.netcontrolsdynamicuser-controlsviewstate

I'm currently working with a part of my application that uses Dynamic Web User Controls and I'm having a bit of trouble figuring out the best way to re-instantiate the controls on postback by using ViewState or some other method that doesn't require me to query a database on each postback.

Basically what I have on my page is a user control that contains a panel for holding a variable amount of child user controls and a button that says "Add Control" whose function is pretty self explanatory.

The child user control is pretty simple; it's just a delete button, a drop down, and a time picker control arranged in a row. Whenever the user clicks the Add Control button on the parent control, a new 'row' is added to the panel containing the child controls.

What I would like to do is to be able to add and remove controls to this collection, modify values, and perform whatever operations I need to do 'in-memory' without having to do any calls to a database. When I am done adding controls and populating their values, I'd like to click "save" to save/update all the data from the controls to a database at once. Currently the only solution I have found is to simply save the data in the database each post back and then use the rows stored in the db to re-instantiate the controls on postback. Obviously, this forces the user to save changes to the DB against their will and in the event that they want to cancel working with the controls without saving their data, extra work must be done to ensure that the rows previously committed are deleted.

From what I've learned about using dynamic controls, I know it's best to add the controls to the page during the Init stage of the lifecycle and then populate their values in the load stage. I've also learned that the only way to make sure you can persist the control's viewstate is to make sure you give each dynamic control a unique ID and be sure to assign it the exact same ID when re instantiating the control. I've also learned that the ViewState doesn't actually get loaded until after the Init stage in the life cycle. This is where my problem lies. How do I store and retrieve the names of these controls if I am unable to use the viewstate and I do not want to perform any calls to a database? Is this sort of in-memory manipulation / batch saving of values even possible using ASP.net?

Any help is greatly appreciated,

Mike

Best Answer

You could store the bare minimum of what you need to know to recreate the controls in a collection held in session. Session is available during the init phases of the page.

Here is an example for you. It consists of:

Default.aspx, cs
- panel to store user controls
- "Add Control Button" which will add a user control each time it is clicked

TimeTeller.ascx, cs
- has a method called SetTime which sets a label on the control to a specified time.

Default.aspx

<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="Default.aspx.cs" Inherits="DynamicControlTest._Default" %>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<html xmlns="http://www.w3.org/1999/xhtml" >
<head runat="server">
    <title></title>
</head>
<body>
    <form id="form1" runat="server">
    <div>
        <asp:Panel ID="pnlDynamicControls" runat="server">
        </asp:Panel>
        <br />
        <asp:Button ID="btnAddControl" runat="server" Text="Add User Control" 
            onclick="btnAddControl_Click" />
    </div>
    </form>
</body>
</html>

Default.aspx.cs:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;

namespace DynamicControlTest
{
   public partial class _Default : System.Web.UI.Page
   {
      Dictionary<string, string> myControlList; // ID, Control ascx path

      protected void Page_Load(object sender, EventArgs e)
      {

      }

      protected override void OnInit(EventArgs e)
      {
         base.OnInit(e);

         if (!IsPostBack)
         {
            myControlList = new Dictionary<string, string>();
            Session["myControlList"] = myControlList;
         }
         else 
         {
            myControlList = (Dictionary<string, string>)Session["myControlList"];

            foreach (var registeredControlID in myControlList.Keys) 
            {
               UserControl controlToAdd = new UserControl();
               controlToAdd = (UserControl)controlToAdd.LoadControl(myControlList[registeredControlID]);
               controlToAdd.ID = registeredControlID;

               pnlDynamicControls.Controls.Add(controlToAdd);
            }
         }
      }

      protected void btnAddControl_Click(object sender, EventArgs e)
      {
         UserControl controlToAdd = new UserControl();
         controlToAdd = (UserControl)controlToAdd.LoadControl("TimeTeller.ascx");

         // Set a value to prove viewstate is working
         ((TimeTeller)controlToAdd).SetTime(DateTime.Now);
         controlToAdd.ID = Guid.NewGuid().ToString(); // does not have to be a guid, just something unique to avoid name collision.

         pnlDynamicControls.Controls.Add(controlToAdd);

         myControlList.Add(controlToAdd.ID, controlToAdd.AppRelativeVirtualPath);
      }
   }
}

TimeTeller.ascx

<%@ Control Language="C#" AutoEventWireup="true" CodeBehind="TimeTeller.ascx.cs" Inherits="DynamicControlTest.TimeTeller" %>
<asp:Label ID="lblTime" runat="server"/>

TimeTeller.ascx.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;

namespace DynamicControlTest
{
   public partial class TimeTeller : System.Web.UI.UserControl
   {
      protected void Page_Load(object sender, EventArgs e)
      {

      }

      public void SetTime(DateTime time) 
      {
         lblTime.Text = time.ToString();
      }

      protected override void LoadViewState(object savedState)
      {
         base.LoadViewState(savedState);
         lblTime.Text = (string)ViewState["lblTime"];
      }

      protected override object SaveViewState()
      {
         ViewState["lblTime"] = lblTime.Text;
         return base.SaveViewState();
      }
   }
}  

As you can see, I still have to manage the internal viewstate of my user control, but the viewstate bag is being saved to the page and handed back to the control on postback. I think it is important to note that my solution is very close to David's. The only major difference in my example is that it's using session instead of viewstate to store the control info. This allows things to happen during the initialization phase. It is important to note that this solution takes up more server resources, therefore it may not be appropriate in some situations depending on your scaling strategy.

Related Topic