63

我想<button>在 ASP.NET 网站中使用较新的标签,除其他外,它允许 CSS 样式的文本并在按钮内嵌入图形。asp:Button 控件呈现为<input type="button">,有没有办法使预先存在的控件呈现为<button>

从我读过的内容来看,当按钮位于 a 内时,IE 发布按钮的标记而不是 value 属性不兼容<form>,但在 ASP.NET 中它将使用 onclick 事件触发 __doPostBack,所以我不不认为这会是一个问题。

有什么理由我不应该使用它吗?如果没有,您将如何使用 asp:Button 或基于它的新服务器控件来支持它?如果可以避免,我宁愿不编写自己的服务器控件。


起初该<button runat="server">解决方案有效,但我立即遇到了需要具有 CommandName 属性的情况,而 HtmlButton 控件没有该属性。看来我毕竟需要创建一个从 Button 继承的控件。

我需要做什么才能覆盖渲染方法并使其呈现我想要的内容?


更新

DanHerbert 的回复让我有兴趣再次找到解决此问题的方法,因此我花了更多时间来解决这个问题。

首先,重载 TagName 有一种更简单的方法:

public ModernButton() : base(HtmlTextWriterTag.Button)
{
}

Dan 的解决方案的问题在于标签的 innerhtml 被放入 value 属性中,这会导致回发时出现验证错误。一个相关的问题是,即使您正确呈现 value 属性,IE 的<button>标记的脑死实现仍会发布 innerhtml 而不是 value。因此,任何实现都需要重写 AddAttributesToRender 方法以正确呈现 value 属性,并为 IE 提供某种解决方法,这样它就不会完全搞砸回发。

如果您想利用数据绑定控件的 CommandName/CommandArgument 属性,IE 问题可能是无法克服的。希望有人可以为此提出解决方法。

我在渲染方面取得了进展:

现代按钮.cs

这将呈现为具有正确值的正确 html <button>,但它不适用于 ASP.Net PostBack 系统。我已经写了一些我需要提供Command事件的东西,但它不会触发。

当与常规 asp:Button 并排检查此按钮时,除了我需要的差异外,它们看起来相同。所以我不确定Command在这种情况下 ASP.Net 是如何连接事件的。

另一个问题是,嵌套的服务器控件没有被渲染(正如你可以看到的 ParseChildren(false) 属性)。在渲染期间将文字 html 文本注入控件非常容易,但是如何支持嵌套的服务器控件呢?

4

7 回答 7

44

尽管您说使用 [button runat="server"] 不是一个足够好的解决方案,但重要的是要提一下 - 许多 .NET 程序员害怕使用“本机”HTML标签......

采用:

<button id="btnSubmit" runat="server" class="myButton" 
    onserverclick="btnSubmit_Click">Hello</button>

这通常工作得很好,我的团队中的每个人都很高兴。

于 2009-07-23T12:56:32.603 回答
38

这是一个古老的问题,但对于我们这些不幸仍然不得不维护 ASP.NET Web 窗体应用程序的人来说,我在尝试将 Bootstrap 字形包含在内置按钮控件中时自己经历了这个问题。

根据 Bootstrap 文档,所需的标记如下:

<button class="btn btn-default">
    <span class="glyphicon glyphicon-search" aria-hidden="true"></span>
    Search
</button>

我需要这个标记由服务器控件呈现,所以我开始寻找选项。

按钮

这将是第一个合乎逻辑的步骤,但是——正如这个问题所解释的—— Button 呈现一个<input>元素而不是<button>,因此添加内部 HTML 是不可能的。

LinkBut​​ton (归功于Tsvetomir Tsonev 的回答

来源

<asp:LinkButton runat="server" ID="uxSearch" CssClass="btn btn-default">
    <span class="glyphicon glyphicon-search" aria-hidden="true"></span>
    Search
</asp:LinkButton>

输出

<a id="uxSearch" class="btn btn-default" href="javascript:__doPostBack(&#39;uxSearch&#39;,&#39;&#39;)">
    <span class="glyphicon glyphicon-search" aria-hidden="true"></span>
    Search
</a>

优点

  • 看起来不错
  • Command事件; CommandNameCommandArgument属性

缺点

  • 渲染<a>而不是<button>
  • 呈现并依赖于突兀的 JavaScript

HtmlButton(归功于菲利普的回答

来源

<button runat="server" id="uxSearch" class="btn btn-default">
    <span class="glyphicon glyphicon-search" aria-hidden="true"></span>
    Search
</button>

结果

<button onclick="__doPostBack('uxSearch','')" id="uxSearch" class="btn btn-default">
    <span class="glyphicon glyphicon-search" aria-hidden="true"></span>
    Search
</button>

优点

  • 看起来不错
  • 渲染适当的<button>元素

缺点

  • 没有Command事件;没有CommandNameCommandArgument属性
  • 呈现并依赖于突兀的 JavaScript 来处理其ServerClick事件

在这一点上,显然没有一个内置控件看起来合适,因此下一步合乎逻辑的步骤是尝试修改它们以实现所需的功能。

自定义控件(归功于Dan Herbert 的回答

注意:这是基于 Dan 的代码,所以所有功劳归他所有。

using System.Web.UI;
using System.Web.UI.WebControls;

namespace ModernControls
{
    [ParseChildren]
    public class ModernButton : Button
    {
        public new string Text
        {
            get { return (string)ViewState["NewText"] ?? ""; }
            set { ViewState["NewText"] = value; }
        }

        public string Value
        {
            get { return base.Text; }
            set { base.Text = value; }
        }

        protected override HtmlTextWriterTag TagKey
        {
            get { return HtmlTextWriterTag.Button; }
        }

        protected override void AddParsedSubObject(object obj)
        {
            var literal = obj as LiteralControl;
            if (literal == null) return;
            Text = literal.Text;
        }

        protected override void RenderContents(HtmlTextWriter writer)
        {
            writer.Write(Text);
        }
    }
}

我已经将这个类精简到最低限度,并对其进行了重构,以尽可能少的代码实现相同的功能。我还添加了一些改进。即:

  • 删除PersistChildren属性(似乎没有必要)
  • 删除TagName覆盖(似乎没有必要)
  • Text从(基类已处理此)中删除 HTML 解码
  • 保持OnPreRender原状;AddParsedSubObject改为覆盖(更简单)
  • 简化RenderContents覆盖
  • 添加Value属性(见下文)
  • 添加命名空间(以包含@Register指令的示例)
  • 添加必要using的指令

Value属性只是访问旧Text属性。value这是因为本机 Button 控件无论如何都会呈现一个属性(Text以其值作为其值)。由于value<button>元素的有效属性,因此我决定为其包含一个属性。

来源

<%@ Register TagPrefix="mc" Namespace="ModernControls" %>

<mc:ModernButton runat="server" ID="uxSearch" Value="Foo" CssClass="btn btn-default" >
    <span class="glyphicon glyphicon-search" aria-hidden="true"></span>
    Search
</mc:ModernButton>

输出

<button type="submit" name="uxSearch" value="Foo" id="uxSearch" class="btn btn-default">
    <span class="glyphicon glyphicon-search" aria-hidden="true"></span>
    Search
</button>

优点

  • 看起来不错
  • 渲染一个合适的<button>元素
  • Command事件; CommandNameCommandArgument属性
  • 不呈现或依赖于突兀的 JavaScript

缺点

  • 无(除了不是内置控件)
于 2015-08-07T13:36:01.843 回答
29

我偶然发现了你的问题,正在寻找同样的东西。我最终使用Reflector来确定 ASP.NETButton控件的实际呈现方式。事实证明它真的很容易改变。

它实际上只是归结为覆盖类的TagNameTagKey属性Button。完成此操作后,您只需要确保手动渲染按钮的内容,因为原始Button类从来没有要渲染的内容,并且如果您不渲染内容,控件将渲染无文本按钮。

更新:

可以通过继承对 Button 控件进行一些小的修改,并且仍然可以很好地工作。此解决方案消除了为 OnCommand 实现您自己的事件处理程序的需要(尽管如果您想学习如何做到这一点,我可以向您展示如何处理)。它还解决了提交包含标记的值的问题,可能除了 IE。不过,我仍然不确定如何修复 IE 对 Button 标记的不良实施。这可能只是一个无法解决的真正技术限制......

[ParseChildren(false)]
[PersistChildren(true)]
public class ModernButton : Button
{
    protected override string TagName
    {
        get { return "button"; }
    }

    protected override HtmlTextWriterTag TagKey
    {
        get { return HtmlTextWriterTag.Button; }
    }

    // Create a new implementation of the Text property which
    // will be ignored by the parent class, giving us the freedom
    // to use this property as we please.
    public new string Text
    {
        get { return ViewState["NewText"] as string; }
        set { ViewState["NewText"] = HttpUtility.HtmlDecode(value); }
    }

    protected override void OnPreRender(System.EventArgs e)
    {
        base.OnPreRender(e);
        // I wasn't sure what the best way to handle 'Text' would
        // be. Text is treated as another control which gets added
        // to the end of the button's control collection in this 
        //implementation
        LiteralControl lc = new LiteralControl(this.Text);
        Controls.Add(lc);

        // Add a value for base.Text for the parent class
        // If the following line is omitted, the 'value' 
        // attribute will be blank upon rendering
        base.Text = UniqueID;
    }

    protected override void RenderContents(HtmlTextWriter writer)
    {
        RenderChildren(writer);
    }
}

要使用此控件,您有几个选项。一种是将控件直接放入 ASP 标记中。

<uc:ModernButton runat="server" 
        ID="btnLogin" 
        OnClick="btnLogin_Click" 
        Text="Purplemonkeydishwasher">
    <img src="../someUrl/img.gif" alt="img" />
    <asp:Label ID="Label1" runat="server" Text="Login" />
</uc:ModernButton>

您还可以将控件添加到代码隐藏中按钮的控件集合中。

// This code probably won't work too well "as is"
// since there is nothing being defined about these
// controls, but you get the idea.
btnLogin.Controls.Add(new Label());
btnLogin.Controls.Add(new Table());

我不知道这两个选项的组合效果如何,因为我没有测试过。

现在这个控件的唯一缺点是我认为它不会记住你在 PostBacks 中的控件。我没有对此进行测试,所以它可能已经工作了,但我怀疑它是否有效。您需要为子控件添加一些 ViewState 管理代码,以便跨 PostBacks 处理我认为,但这对您来说可能不是问题。添加 ViewState 支持应该不难做到,尽管如果需要可以轻松添加。

于 2009-02-22T00:08:16.807 回答
6

您可以创建一个新控件,从 Button 继承,并覆盖 render 方法,或使用 .browser 文件覆盖站点中的所有 Button,类似于 CSS 友好的东西适用于 TreeView 控件等的方式。

于 2008-10-09T14:41:10.853 回答
6

您可以使用Button.UseSubmitBehavior属性,如本文中关于将 ASP.NET Button 控件呈现为按钮的讨论。


编辑:对不起..这就是我在午休时间略读问题的结果。您是否有理由不只使用 <button runat="server"> 标签或HtmlButton

于 2008-10-09T15:59:18.497 回答
2

我会使用 LinkBut​​ton 并根据需要使用 CSS 对其进行样式设置。

于 2008-10-09T14:28:33.937 回答
1

我整天都在为此苦苦挣扎——我的自定义<button/>生成控件不起作用:

System.Web.HttpRequestValidationException:从客户端检测到潜在危险的 Request.Form 值

发生的事情是 .Net 添加了一个name属性:

<button type="submit" name="ctl02" value="Content" class="btn ">
   <span>Content</span>
</button>

使用 IE 6 和 IE 7 时,该name属性会引发服务器错误。在其他浏览器(Opera、IE 8、Firefox、Safari)中一切正常。

解决方法是砍掉该name属性。还是我还没想通。

于 2009-06-10T13:21:09.907 回答