web-dev-qa-db-fra.com

Comment puis-je utiliser la balise bouton avec ASP.NET?

Je souhaite utiliser la nouvelle <button> tag dans un site Web ASP.NET qui, entre autres, permet le texte de style CSS et l'incorporation d'un graphique à l'intérieur du bouton. Le contrôle asp: Button s'affiche comme <input type="button">, existe-t-il un moyen de rendre un contrôle préexistant à <button>?

D'après ce que j'ai lu, il y a une incompatibilité avec IE affichant le balisage du bouton au lieu de l'attribut value lorsque le bouton est situé dans un <form>, mais dans ASP.NET, il utilisera l'événement onclick pour déclencher __doPostBack de toute façon, donc je ne pense pas que ce serait un problème.

Y a-t-il des raisons pour lesquelles je ne devrais pas utiliser cela? Sinon, comment feriez-vous pour le prendre en charge avec asp: Button, ou un nouveau contrôle serveur basé sur lui? Je préférerais ne pas écrire mon propre contrôle serveur si cela peut être évité.


Au début, le <button runat="server"> la solution a fonctionné, mais j'ai immédiatement rencontré une situation dans laquelle elle doit avoir une propriété CommandName, que le contrôle HtmlButton n'a pas. Il semble que je vais avoir besoin de créer un contrôle hérité de Button après tout.

Que dois-je faire pour remplacer la méthode de rendu et lui rendre ce que je veux?


[~ # ~] mise à jour [~ # ~]

La réponse de DanHerbert m'a incité à trouver une solution à cela, j'ai donc passé plus de temps à y travailler.

Tout d'abord, il existe un moyen beaucoup plus simple de surcharger le TagName:

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

Le problème avec la solution de Dan telle qu'elle se présente est que le HTML interne de la balise est placé dans la propriété value, ce qui provoque une erreur de validation lors de la publication. Un problème connexe est que, même si vous restituez correctement la propriété value, l'implémentation braindead d'IE de <button> tag publie le innerhtml au lieu de la valeur de toute façon. Ainsi, toute implémentation de ceci doit remplacer la méthode AddAttributesToRender afin de rendre correctement la propriété value, et également fournir une sorte de solution de contournement pour IE afin qu'il ne visse pas complètement la publication.

Le problème IE peut être insurmontable si vous souhaitez tirer parti des propriétés CommandName/CommandArgument pour un contrôle de databound. J'espère que quelqu'un pourra suggérer une solution pour cela.

J'ai progressé sur le rendu:

ModernButton.cs

Cela se présente comme un html approprié <button> avec la valeur correcte, mais cela ne fonctionne pas avec le système ASP.Net PostBack. J'ai écrit une partie de ce dont j'ai besoin pour fournir l'événement Command, mais il ne se déclenche pas.

Lorsque vous inspectez ce bouton côte à côte avec un bouton asp: Button normal, ils ont la même apparence que les différences dont j'ai besoin. Je ne sais donc pas comment ASP.Net connecte l'événement Command dans ce cas.

Un problème supplémentaire est que les contrôles serveur imbriqués ne sont pas rendus (comme vous pouvez le voir avec l'attribut ParseChildren (false)). Il est assez facile d'injecter du texte HTML littéral dans le contrôle pendant le rendu, mais comment autorisez-vous la prise en charge des contrôles serveur imbriqués?

62
Adam Lassek

C'est une vieille question, mais pour ceux d'entre nous qui n'ont pas la chance d'avoir encore à maintenir des applications ASP.NET Web Forms, je suis passé par moi-même en essayant d'inclure Bootstrap glyphes à l'intérieur du bouton intégré les contrôles.

Selon la documentation Bootstrap, le balisage souhaité est le suivant:

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

J'avais besoin que ce balisage soit rendu par un contrôle serveur, alors je me suis mis à chercher des options.

Bouton

Ce serait la première étape logique, mais, comme l'explique cette question, Button affiche un <input> élément au lieu de <button>, il n'est donc pas possible d'ajouter du code HTML interne.

LinkButton (crédit à réponse de Tsvetomir Tsonev )

Source

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

Sortie

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

Avantages

  • Semble bien
  • Command événement; CommandName et CommandArgument propriétés

Inconvénients

  • Rends <a> au lieu de <button>
  • Rend et s'appuie sur JavaScript intrusif

HtmlButton (crédit à réponse de Philippe )

Source

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

Résultat

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

Avantages

  • Semble bien
  • Rend bon <button> élément

Inconvénients

  • Pas d'événement Command; pas de propriétés CommandName ou CommandArgument
  • Rend et s'appuie sur JavaScript intrusif pour gérer son événement ServerClick

À ce stade, il est clair qu'aucun des contrôles intégrés ne semble approprié, donc la prochaine étape logique est d'essayer de les modifier pour obtenir la fonctionnalité souhaitée.

Contrôle personnalisé (crédit à réponse de Dan Herbert )

REMARQUE: Ceci est basé sur le code de Dan, donc tout le mérite lui revient.

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

J'ai réduit la classe au strict minimum et l'ai remaniée pour obtenir les mêmes fonctionnalités avec le moins de code possible. J'ai également ajouté quelques améliorations. À savoir:

  • Supprimer l'attribut PersistChildren (semble inutile)
  • Supprimer TagName remplacer (semble inutile)
  • Supprimer le décodage HTML de Text (la classe de base gère déjà cela)
  • Laissez OnPreRender intact; remplacer AddParsedSubObject à la place (plus simple)
  • Simplifier RenderContents remplacer
  • Ajoutez une propriété Value (voir ci-dessous)
  • Ajouter un espace de noms (pour inclure un exemple de directive @ Register )
  • Ajouter les directives using nécessaires

La propriété Value accède simplement à l'ancienne propriété Text. Cela est dû au fait que le contrôle Button natif rend de toute façon un attribut value (avec Text comme valeur). Puisque value est un attribut valide de <button> élément, j'ai décidé d'y inclure une propriété.

Source

<%@ 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>

Sortie

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

Avantages

  • Semble bien
  • Rend une bonne <button> élément
  • Command événement; CommandName et CommandArgument propriétés
  • Ne rend pas ou ne s'appuie pas sur du JavaScript intrusif

Inconvénients

  • Aucun (sauf qu'il ne s'agit pas d'un contrôle intégré)
36
Daniel Liuzzi

Bien que vous disiez que l'utilisation du [bouton runat = "serveur"] n'est pas une assez bonne solution, il est important de le mentionner - beaucoup de programmeurs .NET ont peur d'utiliser les balises HTML "natives" ...

Utilisation:

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

Cela fonctionne généralement parfaitement bien et tout le monde est content dans mon équipe.

42
Philippe

Je suis tombé sur votre question en cherchant exactement la même chose. J'ai fini par utiliser Reflector pour comprendre comment le contrôle ASP.NET Button est réellement rendu. Il s'avère vraiment facile de changer.

Cela revient vraiment à remplacer les propriétés TagName et TagKey de la classe Button. Après cela, il vous suffit de vous assurer de rendre le contenu du bouton manuellement car la classe Button d'origine n'a jamais eu de contenu à rendre et le contrôle rendra un bouton sans texte si vous ne le faites pas ' t rendre le contenu.

Mise à jour:

Il est possible d'apporter quelques petites modifications au contrôle Button par héritage et fonctionne toujours assez bien. Cette solution élimine le besoin d'implémenter vos propres gestionnaires d'événements pour OnCommand (bien que si vous voulez apprendre comment faire, je peux vous montrer comment cela est géré). Il résout également le problème de soumission d'une valeur contenant du balisage, à l'exception de IE probablement. Je ne sais toujours pas comment corriger la mauvaise implémentation d'IE de la balise Button. Cela peut simplement être une véritable limitation technique impossible à contourner ...

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

Pour utiliser ce contrôle, vous avez quelques options. L'une consiste à placer les contrôles directement dans le balisage 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>

Vous pouvez également ajouter les contrôles à la collection de contrôles du bouton dans votre code-behind.

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

Je ne sais pas à quel point une combinaison des deux options fonctionne car je n'ai pas testé cela.

Le seul inconvénient de ce contrôle en ce moment est que je ne pense pas qu'il se souviendra de vos contrôles sur PostBacks. Je n'ai pas testé cela, donc cela peut déjà fonctionner, mais je doute que ce soit le cas. Vous devrez ajouter du code de gestion ViewState pour que les sous-contrôles soient traités dans les PostBacks, je pense, mais ce n'est probablement pas un problème pour vous. L'ajout de la prise en charge de ViewState ne devrait pas être très difficile à faire, bien que cela puisse être facilement ajouté si nécessaire.

29
Dan Herbert

Vous pouvez créer un nouveau contrôle, héritant de Button, et remplacer la méthode de rendu, ou utiliser un fichier .browser pour remplacer tous les boutons du site, de la même manière que le truc CSS Friendly fonctionne pour le contrôle TreeView, etc.

6
Steven Robbins

J'irais avec un LinkButton et le style avec CSS comme approprié.

2
Tsvetomir Tsonev

J'ai eu du mal toute la journée avec ça - ma coutume <button/> le contrôle généré ne fonctionne pas:

System.Web.HttpRequestValidationException: une valeur Request.Form potentiellement dangereuse a été détectée par le client

Ce qui se passe, c'est que .Net ajoute un attribut name:

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

L'attribut name renvoie l'erreur du serveur lors de l'utilisation de IE 6 et IE 7. Tout fonctionne correctement dans d'autres navigateurs (Opera, IE 8, Firefox, Safari).

Le remède consiste à pirater cet attribut name. Je ne l'ai toujours pas compris.

1
David Andersson