【问题标题】:Using a user control in a base page class在基页类中使用用户控件
【发布时间】:2010-10-28 00:38:37
【问题描述】:

我有一个从 Page 继承的基本页面类。我们称之为 SpiffyPage。

我还有一个名为 SpiffyControl 的用户控件。

然后我有一个继承自 SpiffyPage 的 aspx 页面 ViewSpiffyStuff.aspx。

在页面加载时,SpiffyPage 类应该将 SpiffyControl 注入到 aspx 页面中。

问题是 SpiffyControl 是一个用户控件,要在后面的代码中实例化它,必须访问 ASP 命名空间,有点像:

ASP.controls_spiffycontrol_aspx MyControl;

但 SpiffyPage 不是 aspx 页面,它只是一个基本页面,因此我无法访问 ASP 命名空间,因此我无法实例化 SpiffyControl 以注入它。

我怎样才能实现我的目标?

编辑:

重要的一点是我必须引用实际的控件类型,这样我才能将值分配给某些自定义属性。这就是为什么像 LoadControl 这样返回类型 Control 的函数在这种情况下不起作用的原因。

【问题讨论】:

  • 您可以使用 LoadControl,但要强制转换。 SpiffyControl spiffy = (SpiffyControl)LoadControl("usercontrol.ascs");
  • 问题是我不能访问 SpiffyControl 类型,所以我也不能转换它——如果我能转换它,我也可以简单地实例化它。
  • 为什么不能访问 SpiffyControl 类型?
  • 尝试对 SpiffyControl 的显式命名空间引用。

标签: c# .net asp.net vb.net


【解决方案1】:

我将在一个单独的 ASP.NET 服务器控件项目中实现 SpiffyControl,并从 Web 应用程序中引用该项目。这样,控件将不会驻留在 ASP 命名空间中,而是驻留在您选择的命名空间中,并且其行为与任何其他服务器控件一样。

更新:将用户控件放入单独项目的一个很好的副作用是,它们与 Web 应用程序本身更加分离,这反过来又使它们更易于重用,并且根据它们的设计,也更易于测试.

【讨论】:

  • 您是说将用户控件放入单独的 dll 中?这可能吗?
  • 当然;当您创建一个新项目时,您可以在 Web 类别中选择创建一个“ASP.NET 服务器控件”类型的项目,这将为您提供一个可以实现一个(或多个)控件的项目,这将提供一个dll 作为输出。
  • 啊,是的,当然。问题是“服务器控件”与“用户控件”不同。我有一个服务器控件项目,但我几乎可以肯定我无法在 Web 项目之外构建用户控件。
  • 没错,服务器控制项目中的用户控制可能不起作用;我叫错树了。
  • 您不能将 USER 控件放入 DLL 中……但您可以将 WebControl 放入 DLL 中。即从 System.Web.UI.WebControls.WebControl 继承,而不是从 System.Web.UI.UserControl 继承。这种方法的缺点是您不能使用代码隐藏模型。你不能做标记+代码,你得到的只是一个代码文件,所以标签的内容必须通过调用this.Controls.Add()来组装。这需要一点时间来适应,但它确实有效——我在我的项目中到处都在使用它。
【解决方案2】:

我不确定这是否会对您有所帮助,但是有一个名为 LoadControl 的方法可以使用用户控件文件名或用户控件的类型动态加载用户控件。该方法在 System.Web.UI.TemplateControl 类中。

Control userControl= LoadControl("usercontrol.ascx");
panelControl.Controls.Add(userControl);

【讨论】:

    【解决方案3】:

    即使您的基础不是 ASPX 页面,您也可以让它继承自同一个框架类,就好像它是:

    public abstract class MyBasePage : System.Web.UI.Page
    {
    
    }
    

    【讨论】:

    • 这就是我所做的——我不知道细节,但不知何故,除非我在实际的 aspx 页面中编写,否则 ASP 命名空间根本不可用。
    • 您是否在基本页面上拥有所有您需要的参考资料?比如“使用 System.Web.UI.WebControls”
    【解决方案4】:

    为您的 SpiffyControl 添加一个基类,例如:

    public class SpiffyBase : UserControl
    {
        public void DoIt()
        {
            Response.Write("DoIt");
        }
    }
    

    然后你让你的 SpiffyControl 继承那个基类,比如:

    public partial class SpiffyControl : SpiffyBase
    

    那么您应该可以从 Basepage 类中执行此操作:

    public class BasePage : Page
    {
        protected override void OnLoad(EventArgs e)
        {
            var c = Page.LoadControl("SpiffyControl.ascx") as SpiffyBase;
    
            Page.Controls.Add(c);
    
            c.DoIt();
        }
    }
    

    【讨论】:

    • 这确实是解决此问题的最干净/最简单的解决方案。一种变体是使用接口,然后在加载后测试 UserControl: if (control is ISpiffyControl) { ((ISpiffyControl)).DoIt(); }
    • 我遇到了类似的问题,就这样解决了。这种行为的原因是 asp.net 二进制文件被拆分为几个 dll,而 App_Code 看不到 aspx 生成的 dll 中的内容。
    【解决方案5】:

    假设 SpiffyControl 和 SpiffyPage 在同一个命名空间中:

     Control spiffyControl = LoadControl("SpiffyControl.ascx");
     (control as SpiffyControl).SpiffyControlProp1 = true;
     (control as SpiffyControl).SpiffyControlProp2 = "Hello, World!";
    

    【讨论】:

      【解决方案6】:

      您可以将 SpiffyControl 移动到库中 - 这将为其提供更精确的命名空间,并允许您在其他项目中使用它。不过,这有点复杂——您已经制作了一个 UserControl,但它不能在库中工作。 UserControls 使用代码隐藏模型——它们由两个文件组成,一个标记和一个代码。库似乎不支持代码隐藏模型,因此您不能使用 UserControl。您需要将该用户控件转换为 Web 控件。

      创建一个新的类库项目。打开项目的属性并将 AssemblyName 和 Default Namespace 设置为 SpiffyLibrary。

      现在我们需要转换您的 UserControl。首先,在新库中创建一个类。将其称为 SpiffyControl,就像您现有的用户控件一样,但使其继承自 System.Web.UI.WebControls.CompositeControl。

      其次,打开现有用户控件的代码并复制类中的所有内容。然后回到新的类,把它全部粘贴进去。如果您使用任何标准控件事件,您可能需要重命名这些函数以覆盖相应的事件。例如,

      protected void Page_Load(object sender, EventArgs e)
      {
          ...
      }
      

      ...应该变成...

      protected override void OnLoad(EventArgs e)
      {
          ...
      
          base.OnLoad(e);
      }
      

      第三(这是复杂的部分)您需要重现您在 SpiffyControl 的标记文件中拥有的所有标记。您不能只是复制它 - 相反,您需要覆盖从 CompositeControl 继承的 CreateChildControls() 函数。标记中的每个标记都需要像变量一样被实例化并添加到类的 Controls 集合中。例如,如果您现有的标记文件如下所示:

      <%@ Control Language="C#" AutoEventWireup="true" CodeFile="SpiffyControl.ascx.cs" Inherits="Controls_SpiffyControl" %>
      
      <div id="Div1" runat="server">
          <asp:Label ID="TextOutput" runat="server" />
      </div>
      

      ...那么您的新 CreateChildControls 函数应如下所示:

      HtmlGenericControl Div1;
      Label TextLabel;
      
      protected override void CreateChildControls()
      {
          Div1 = new HtmlGenericControl("div");
          this.Controls.Add(Div1);
      
          TextLabel = new Label();
          Div1.Controls.Add(TextLabel);
      
          base.CreateChildControls();
      }
      

      请注意,控件被声明为类字段;这样,它们对所有类方法都是可见的。

      转换标记后,编译库项目并将其添加到网站项目的引用中。然后打开您网站的 web.config 文件并找到 system.web/pages/controls 部分。这部分应该已经有这样的标签:

      <add tagPrefix="asp" namespace="System.Web.UI" assembly="System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35"/>
      

      该标签让您可以使用来自 ASP 核心库的控件。所以我们要为我们的库添加一个类似的内容:

      <add tagPrefix="spiffy" namespace="SpiffyLibrary" assembly="SpiffyLibrary" />
      

      现在在您网站的任何地方,您都可以像这样添加标记:

      <spiffy:SpiffyControl ID="SpiffyInstance" runat="server" />
      

      ...这将从您的自定义库中加载您的 Web 控件。

      【讨论】:

        【解决方案7】:

        您也可以手动定义 SpiffyControl 的命名空间。为此,您需要同时更改代码隐藏和标记。

        在代码隐藏中,将您的类包装在命名空间块中。所以如果你的课是

        public partial class Controls_SpiffyControl : System.Web.UI.UserControl
        {
            ...
        }
        

        ...然后将其更改为...

        namespace Spiffy
        {
            public partial class Controls_SpiffyControl : System.Web.UI.UserControl
            {
                ...
            }
        }
        

        然后在标记中,调整您的 @Control 标头。因此,如果您的 @Control 标头是

        <%@ Control
            Language="C#"
            AutoEventWireup="true"
            CodeFile="SpiffyControl.ascx.cs"
            Inherits="Controls_SpiffyControl" %>
        

        ...然后将其更改为...

        <%@ Control
            Language="C#"
            AutoEventWireup="true"
            CodeFile="SpiffyControl.ascx.cs"
            Inherits="Spiffy.SpiffyControl" %>
        

        【讨论】:

          【解决方案8】:

          假设您将在支持反射的环境中运行,您可以使用这些方法。

          private void SetValue(Control control, string propertyName, object value)
          {
              Type type = control.GetType();
              PropertyInfo property = type.GetProperty(propertyName);
              property.SetValue(control, value, null);
          }
          private object GetValue(Control control, string propertyName)
          {
              Type type = control.GetType();
              PropertyInfo property = type.GetProperty(propertyName);
              return property.GetValue(control, null);
          }
          

          然后这样称呼它:

               Control control = this.LoadControl("~/SpiffyControl.ascx");
              string propertyName = "Custom1";
              object value = "Value";
          
              SetValue(control, propertyName, value);
          

          如果经常调用它,您可能需要缓存类型信息。

          您还可以考虑在 SpiffyPage 所在的程序集中创建一个 ISpiffyControl 接口,然后让您的用户控件实现它。

          【讨论】:

            【解决方案9】:

            你们在开玩笑吗,我知道 ASP.NET 网络表单是一种痛苦的体验,但没有人听说过 ClassName 属性,即

            <%@ Control ClassName="SpiffyControl" Language="C#" AutoEventWireup="true" CodeFile="SpiffyControl.ascx.cs" Inherits="Controls_SpiffyControl"  %>
            

            因此,原来的例子:

            SpiffyControl spiffy = (SpiffyControl)LoadControl("~spiffy.ascx");
            

            【讨论】:

              【解决方案10】:

              它已被触及,但界面应该可以工作。使用任何成员/方法制作 ISpiffyControl,在 SpiffyControl.ascx.vb 中实现它并在 SpiffyPage 中执行此操作:

              Protected Overrides Sub OnLoad(ByVal e As System.EventArgs)
                  MyBase.OnLoad(e)
                  Dim page As New Page
                  Dim path As String = request.ApplicationPath
                  Dim control as UI.Control = page.LoadControl(String.Format _
                      ("{0}/pathToUserControl/{1}.ascx", path, controlName))
                  Dim sc As ISpiffyControl = CType(control, ISpiffyControl)
                  sc.DoSomethingSpecial()
                  Me.Controls.Add(sc)
              End Sub
              

              【讨论】:

                【解决方案11】:

                如果我没看错的话,原发帖人的问题是他无法在他的 UserControl 中访问父页面派生的基类的类型,而不是相反。碰巧我也遇到了同样的问题。

                在我的例子中,我项目中的所有页面都继承自同一个基类,所以我不担心我的用户控件知道页面的基类这一事实。向建筑师道歉。

                但是,我通过以下组合解决了该问题:1) 为我的用户控件使用基类,以及 2) 使用公共命名空间。这启用了我需要的可见性,因为由于某种原因,用户控件的基类仍然看不到页面基类的基类类型。

                无论如何,我只是想我会分享我的结果,以防他们将来帮助其他人。

                HTH。

                【讨论】:

                  猜你喜欢
                  • 1970-01-01
                  • 1970-01-01
                  • 2012-02-19
                  • 1970-01-01
                  • 1970-01-01
                  • 1970-01-01
                  • 1970-01-01
                  • 2012-02-14
                  • 1970-01-01
                  相关资源
                  最近更新 更多