Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can I re-instantiate dynamic ASP.NET user controls without using a database?

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

like image 627
mclark1129 Avatar asked Aug 24 '09 18:08

mclark1129


3 Answers

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.

like image 52
Daniel Auger Avatar answered Oct 12 '22 02:10

Daniel Auger


I have done this in the past. I have not had to do this since the days of .NET 1.1, but the principal removes the same.

I did it on Page_Load not Init have to reload the controls that you created on the last page cycle.

First you need to keep track of the controls you have created on each page cycle. This includes type, name etc. . .

Then on each page load you need to rebuild them.

You do that by re-creating the control, assinging it the exact same id, add it to the sampe place on the page and finally in the ViewState["LoadedControl"] to the control type.

Here is the code I used, I only did this with User Controls that I created. I have not tried this with an ASP.NET control, but I think it would work the same.

In this case I have an ArrayList of Triplets (keep in mind this is .NET 1.1) adn the first item was a PageView ID. You might not need that for your application.

protected void Page_Load(object sender, System.EventArgs e)
{
    //**********************************************************
    //*  dynCtlArray will hold a triplet with the PageViewID,  *
    //*  ControlID, and the Control Name                       *
    //**********************************************************

    ArrayList dynCtlArray = (ArrayList)this.ViewState["dynCtlArray"];
    if (dynCtlArray != null)
    {

        foreach (object obj in dynCtlArray)
        {
            Triplet ctrlInfo = (Triplet)obj;

            DynamicLoadControl(ctrlInfo);
        }
    }
}

private void DynamicLoadControl(Triplet ctrlInfo)
{
    // ERROR HANDLING REMOVED FOR ANSWER BECAUSE IT IS NOT IMPORTANT YOU SHOULD HANDLE ERRORS IN THIS METHOD

    Control ctrl = this.LoadControl(Request.ApplicationPath
        + "/UC/" + (string)ctrlInfo.Third);

    ctrl.ID = (string)ctrlInfo.Second;

    // Create New PageView Item
    Telerik.WebControls.PageView pvItem = this.RadMultiPage1.PageViews[(int)ctrlInfo.First];
    pvItem.Controls.Add(ctrl);

    /******************************************************
     *  The ControlName must be preserved to track the    *
     *  currently loaded control                          *
     * ****************************************************/
    ViewState["LoadedControl"] = (string)ctrlInfo.Third;
}
private void RegisterDynControl(Triplet trip)
{
    ArrayList dynCtlArray = (ArrayList)this.ViewState["dynCtlArray"];

    if (dynCtlArray == null)
    {
        dynCtlArray = new ArrayList();
        this.ViewState.Add("dynCtlArray", dynCtlArray);
    }

    dynCtlArray.Add(trip);

}

In some method on your page

// Create new Control
Control ctrl = Page.LoadControl("../UC/MyUserControl.ascx");

// . . . snip .. . 

// Create Triplet
Triplet ctrlInfo = new Triplet(0, ctrl.ID, "MyUserControl.ascx");
// RegisterDynControl to ViewState
RegisterDynControl(ctrlInfo);

// . . . snip .. . 

To access the controls to save there information you will have to do a this.Page.FindControl('');

like image 39
David Basarab Avatar answered Oct 12 '22 02:10

David Basarab


I implemented a page very similar to Daniel's example, but could not use Session due to technical constraints. However, I found that by using an field to the page, I could post back and retrieve its value during the Page_Init event.

like image 1
John Reiner Avatar answered Oct 12 '22 02:10

John Reiner