如何在不使用数据库的情况下重新实例化动态 ASP.NET 用户控件?

Posted

技术标签:

【中文标题】如何在不使用数据库的情况下重新实例化动态 ASP.NET 用户控件?【英文标题】:How can I re-instantiate dynamic ASP.NET user controls without using a database? 【发布时间】:2010-11-22 09:07:03 【问题描述】:

我目前正在处理我的应用程序的一部分,它使用动态 Web 用户控件,我在找出使用 ViewState 或其他一些方法重新实例化回发控件的最佳方法时遇到了一些麻烦不需要我在每次回发时查询数据库。

我的页面上基本上是一个用户控件,它包含一个用于容纳可变数量的子用户控件的面板和一个显示“添加控件”的按钮,其功能非常不言自明。

子用户控件非常简单;它只是一个删除按钮、一个下拉菜单和一个排成一行的时间选择器控件。每当用户单击父控件上的“添加控件”按钮时,都会在包含子控件的面板中添加一个新的“行”。

我想做的是能够向这个集合添加和删除控件、修改值以及执行我需要在“内存中”执行的任何操作,而无需对数据库进行任何调用。当我完成添加控件并填充它们的值时,我想单击“保存”以一次将所有数据从控件保存/更新到数据库。目前我发现的唯一解决方案是在每次回发时简单地将数据保存在数据库中,然后使用存储在数据库中的行来重新实例化回发时的控件。显然,这迫使用户违背自己的意愿将更改保存到数据库,如果他们想取消使用控件而不保存数据,则必须做额外的工作以确保删除先前提交的行。

根据我对使用动态控件的了解,我知道最好在生命周期的 Init 阶段将控件添加到页面,然后在加载阶段填充它们的值。我还了解到,确保您可以保持控件的视图状态的唯一方法是确保为每个动态控件提供唯一的 ID,并确保在重新实例化控件时为其分配完全相同的 ID。我还了解到 ViewState 直到生命周期的 Init 阶段之后才真正被加载。这就是我的问题所在。如果我无法使用视图状态并且不想对数据库执行任何调用,如何存储和检索这些控件的名称?使用 ASP.net 甚至可以进行这种内存操作/批量保存值吗?

非常感谢任何帮助,

迈克

【问题讨论】:

有点晚了,但我是这样做的:***.com/a/15346332/52898 【参考方案1】:

您可以在会话中保存的集合中存储重新创建控件所需的最少知识。会话在页面的初始化阶段可用。

这是给你的一个例子。它包括:

默认.aspx, cs - 用于存储用户控件的面板 - “添加控件按钮”,每次点击都会添加一个用户控件

TimeTeller.ascx, cs - 有一个名为 SetTime 的方法,它将控件上的标签设置为指定时间。

默认.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>

默认.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();
      
   
  

如您所见,我仍然需要管理用户控件的内部视图状态,但视图状态包被保存到页面并在回发时交回给控件。我认为重要的是要注意我的解决方案非常接近大卫的解决方案。我的示例中唯一的主要区别是它使用会话而不是视图状态来存储控制信息。这允许在初始化阶段发生一些事情。请务必注意,此解决方案会占用更多服务器资源,因此在某些情况下可能不合适,具体取决于您的扩展策略。

【讨论】:

我喜欢这个主意,因为我真正关心的是我要加载的控件的 ID(也许还有顺序)。我将为该解决方案建立一个快速测试平台并回复您。谢谢! 我希望你能够让它工作。我过去不得不做类似的事情。最好在 init 阶段将控件返回到页面上,因为这是控件生命周期预期发挥作用的方式。我确信大卫的解决方案有效,但我会担心长期可维护性。我也做过类似的事情,最终变成了“与框架作斗争”的场景。如果您在生命周期的后期添加它,您最终可能会遇到一些无法正常工作的东西。 Daniel,我在使用您的会话解决方案方面取得了一些进展,但遇到了两个障碍。一是尽管在 Init() 阶段中读取了控件,但视图状态并未恢复。我的印象是,在读取动态控件时,我只需要设置正确的 ID 即可取回 View State 数据,我是否遗漏了什么?此外,即使我没有使用回发,会话数据也会持续存在。我只会在 !Page.IsPostBack 时将 Session 设置为 null,但是在 Init() 阶段之后才会设置标志。 我认为您仍然需要在内部管理每个或您的用户控件的视图状态。示例即将推出。 我正在尝试做的一件事是学习如何实现 IPostBackdataHandler 接口,因为 Scott Mitchell 将其列为控件在需要回发数据时必须做的事情。我不确定这是否仅适用于服务器控件,或者用户控件是否也需要这样做。【参考方案2】:

我过去曾这样做过。自 .NET 1.1 时代以来,我不必这样做,但主体删除了相同的内容。

我是在 Page_Load 上完成的,而不是 Init 必须重新加载您在上一个页面循环中创建的控件。

首先,您需要跟踪您在每个页面循环中创建的控件。这包括类型、名称等。 .

然后在每个页面加载时您都需要重建它们。

您可以通过重新创建控件、为其分配完全相同的 id、将其添加到页面上的示例位置并最后在 ViewState["LoadedControl"] 中添加到控件类型来做到这一点。

这是我使用的代码,我只使用我创建的用户控件执行此操作。我没有用 ASP.NET 控件尝试过这个,但我认为它会起作用。

在这种情况下,我有一个 Triplets 的 ArrayList(请记住这是 .NET 1.1),第一项是 PageView ID。您的应用程序可能不需要它。

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);


在你的页面上的某些方法

// 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 .. . 

要访问控件以保存信息,您必须执行this.Page.FindControl('');

【讨论】:

大卫,非常感谢您的回复。我还没有机会尝试您的解决方案,但是在查看时我确实有一个问题:我过去曾使用 Page_Load 来加载动态控件,但这通常会给我带来问题,因为 Load 事件发生在 View 之后状态已加载(因此您可以访问控件 ID 数组)。因此,由于在加载视图状态时控件尚未添加到页面中,因此任何用户输入的值在回发时都会丢失。您将这些控件添加到页面的方式是否会手动加载它们的视图状态? Page_Load 应该有权访问视图状态。但是是的,它会在那里加载视图状态。例如,假设您正在动态加载一个文本框。在我的过程中,您告诉页面“嘿,请注意这些控件”,因此在加载它们之后,您可以获得文本框的值或其任何视图状态信息。你必须告诉页面它们是手动创建的,否则页面永远不会知道。【参考方案3】:

我实现了一个与 Daniel 的示例非常相似的页面,但由于技术限制无法使用 Session。但是,我发现通过向页面使用字段,我可以在 Page_Init 事件期间回发并检索其值。

【讨论】:

以上是关于如何在不使用数据库的情况下重新实例化动态 ASP.NET 用户控件?的主要内容,如果未能解决你的问题,请参考以下文章

WCF反序列化如何在不调用构造函数的情况下实例化对象?

如何在不使用 Nib 的情况下实例化和调用 UIView 子类

如何在不实例化整个 Laravel 的情况下加载 Laravel 模型?

如何在不重新编译的情况下在 .NET 中动态切换 Web 服务地址?

如何在不使用父 App.vue 的情况下在 Laravel 中实例化 vue 3 应用程序?

如何在不选择模式配置参数的情况下使用 mongoose 在 MongoDB 模式实例化中的关联数组/对象中执行 foreach?