Cómo compartir métodos y propiedades entre controles web personalizados

7

Estoy creando algunos controles web personalizados en .NET usando C #. Los controles se heredan de los controles web estándar y agregan propiedades y funciones adicionales (por ejemplo, estoy creando un 'ExtendedTextBox' y estoy agregando una propiedad 'requerida', que si se establece en 'verdadero' agregará un campo obligatorio de .NET validador al control automáticamente.

Estoy haciendo esto para varios controles web (por ejemplo, radioButtonList, textArea). Comparten algunas propiedades y métodos comunes, por ejemplo, tengo un método AddRequiredFieldValidator que usa algunas de las propiedades extendidas que he agregado. Me gustaría compartir las propiedades y métodos comunes. He intentado agregar los métodos a una clase separada como métodos de extensión para un control web. Para lograr esto, he implementado una interfaz que define las propiedades compartidas adicionales, y la estoy utilizando de esta manera:

public interface IExtendendControl
{
    string RequiredMessage { get; set; }
    string ValidatorCssClass { get; set; }
    bool ClientScript { get; set; }
    RequiredFieldValidator rfv { get; set; }
}

public static class ExtendedControlExtensions : IExtendendControl
{
    public static void AddRequiredFieldValidator(this IExtendendControl control)
    {
        control.rfv = new RequiredFieldValidator();
        control.rfv.ErrorMessage = control.RequiredMessage;
        ConfigureAndAddValidator(control, control.rfv);
    }

    public static void ConfigureAndAddValidator(this IExtendendControl control, BaseValidator validator)
    {
        validator.ControlToValidate = control.ID;
        validator.Display = ValidatorDisplay.Dynamic;
        validator.CssClass = "validationMessage ";
        validator.CssClass += control.ValidatorCssClass;
        validator.EnableClientScript = control.ClientScript;
        control.Controls.Add(validator);
    }
}

El problema es que el método 'ConfigureAndAddValidator' ahora no sabe nada acerca de las propiedades 'ID' o 'ClientScript' del control, ya que 'IExtendedControl' solo define las propiedades personalizadas, no las propiedades estándar de un control web. Así que intenté agregar una clase base que se hereda de WebControl e implementa la interfaz, como esto:

public interface IExtendendControl
{
    string RequiredMessage { get; set; }
    string ValidatorCssClass { get; set; }
    bool ClientScript { get; set; }
    RequiredFieldValidator rfv { get; set; }
}

public class BaseExtendedControl : WebControl, IExtendendControl
{
    public string RequiredMessage { get; set; }
    public string ValidatorCssClass { get; set; }
    public bool ClientScript
    {
        get
        { return ClientScript = true; }
        set { }
    }
    public RequiredFieldValidator rfv
    {
        get
        { return rfv = new RequiredFieldValidator(); }
        set { }
    }
}

public static class ExtendedControlHelper : IExtendendControl
{
    public static void AddRequiredFieldValidator(this BaseExtendedControl control)
    {
        BaseExtendedControl extendedControl = (BaseExtendedControl)control;
        extendedControl.rfv = new RequiredFieldValidator();
        extendedControl.rfv.ErrorMessage = extendedControl.RequiredMessage;
        ConfigureAndAddValidator(control, extendedControl.rfv);
    }

    public static void ConfigureAndAddValidator(this BaseExtendedControl control, BaseValidator validator)
    {
        validator.ControlToValidate = control.ID;
        validator.Display = ValidatorDisplay.Dynamic;
        validator.CssClass = "validationMessage ";
        validator.CssClass += control.ValidatorCssClass;
        validator.EnableClientScript = control.ClientScript;
        control.Controls.Add(validator);
    }
}

El problema ahora es que en mi clase extendedTextBox no puedo convertir mi extendedTextBox como un 'BaseExtendedControl' para usar los métodos de extensión, ya que extendedTextBox se hereda de la clase TextBox estándar como esta:

public class ExtendedTextBox : TextBox, IExtendendControl

así que no hay una base común para lanzar ExtendedTextBox como 'BaseExtendedControl' ya que no se hereda de él. Tampoco puedo pasar los objetos de control web extendido como un parámetro a los métodos compartidos, ya que los controles extendidos son de diferentes tipos (por ejemplo, heredan de TextBox, RadioButtonList, etc.). Si especifico que el tipo esperado que se pasa a los métodos 'estándar' como 'WebControl' no funciona, ya que los controles extendidos tienen las propiedades adicionales.

Como no puedo usar la verdadera herencia múltiple en C #, ¿cómo diseñaría esto para poder compartir los métodos y las propiedades?

    
pregunta Chris Halcrow 21.08.2012 - 01:09

3 respuestas

1

Gracias por las respuestas. Fuex: me pusiste en el camino correcto, este es el código final que incluye un control TextBox extendido y un control RadioButtonList extendido.

Para usar estos, agregue esta declaración a la página aspx:

<% @ Register TagPrefix="MYPREFIX" Namespace="MyNamespace" Assembly="MYASSEMBLY"% >

Luego agrega el control a la página de esta manera:

Control de cuadro de texto extendido:

< MYPREFIX: ExtendedTextBox ID="HomePhone" LabelText="Número de teléfono" CssClass="width_normal" LabelCssClass="width_normal" ValidatorCssClass="width_normal"  Required="true" RequiredMessage="El nombre es obligatorio" DataType="Integer" runat="server" > < / MYPREFIX: ExtendedTextBox >

  • Si solo se especifica CssClass , los controles de etiqueta y validador heredarán esta clase.
  • Las clases css Label y validator se pueden especificar individualmente utilizando LabelCssClass y ValidatorCssClass .
  • Puedes agregar un RequiredMessage para configurar el mensaje de validación. Si no se especifica, el mensaje de validación será ' LabelText es obligatorio'
  • No se requiere LabelText : si falta, el asterisco "requerido" se colocará al lado del cuadro de texto
  • De forma predeterminada, se genera el script de validación del lado del cliente (por lo que no es necesario que configure 'ClientScript' en verdadero).
  • DataType se puede especificar para validar un tipo de datos específico. Los valores admitidos son Email y Integer

Control extendido RadioButtonList:

< WVNZ: ExtendedRadioButtonList RepeatLayout="Flow" ID="WrittenContactPreference" LabelText="Preferencia de contacto escrita preferida"                     LabelCssClass="boldLabel" runat="server" Required="false" >
< asp: ListItem > Correo electrónico < / asp: ListItem >
< asp: ListItem > Inicio < / asp: ListItem >
< / WVNZ: ExtendedRadioButtonList >

Para que los controles funcionen, se debe integrar lo siguiente en un ensamblaje llamado 'MYASSEMBLY' al que se hace referencia en el proyecto donde se está implementando el control:

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

namespace MyNamespace
{
public interface IExtendedControl
{
    string ID { get; set; }
    bool Required { get; set; }
    string CssClass { get; set; }
    Label lbl { get; set; }
    string LabelText { get; set; }
    string LabelCssClass { get; set; }
    RequiredFieldValidator rfv { get; set; }
    string RequiredMessage { get; set; }
    string ValidatorCssClass { get; set; }
    bool ClientScript { get; set; }
    ControlCollection Controls { get; }
}

public static class ExtendedControlExtensions
{
    public static void AddRequiredFieldValidator(this IExtendedControl control)
    {
        if (!control.RequiredMessage.IsNullOrEmpty())
        {
            control.rfv.ErrorMessage = control.RequiredMessage;
        }
        else
        {
            control.rfv.ErrorMessage = control.LabelText + " is required";
        }

        ConfigureAndAddValidator(control, control.rfv);
    }

    public static void ConfigureAndAddValidator(this IExtendedControl control, BaseValidator validator)
    {
        validator.ControlToValidate = control.ID;
        validator.Display = ValidatorDisplay.Dynamic;
        validator.CssClass = "validationMessage ";
        if (!control.ValidatorCssClass.IsNullOrEmpty())
        {
            validator.CssClass += control.ValidatorCssClass;
        }
        else validator.CssClass += control.CssClass;
        validator.EnableClientScript = control.ClientScript;
        control.Controls.Add(validator);
    }

    public static void AddLabel(this IExtendedControl control, string controlCssClass = "")
    {
        control.lbl = new Label();
        control.lbl.Text = control.LabelText;
        if (control.Required)
        {
            control.lbl.Text += " <span class=\"requiredFieldIndicator\"> *</span>";
        }

        control.lbl.ID = "lbl" + control.ID;
        //TODO: associate label with control - code below needs other fixes to work
        //lbl.AssociatedControlID = this.ID;

        string cssClassToUse;
        if (!control.LabelCssClass.IsNullOrEmpty()) cssClassToUse = control.LabelCssClass;
        else cssClassToUse = control.CssClass;

        if (control.LabelText.IsNullOrEmpty())
        {
            control.lbl.CssClass = cssClassToUse + " form_field_empty_label ";
        }
        else
        {
            control.lbl.CssClass = cssClassToUse + " form_field_label " + controlCssClass;
        }
    }
}

public class ExtendedTextBox : TextBox, IExtendedControl
{
    public string LabelText { get; set; }
    public string LabelCssClass { get; set; }
    public bool Required { get; set; }
    public string RequiredMessage { get; set; }
    public string DataType;
    public string DataInvalidMessage;
    public string ValidatorCssClass { get; set; }
    public bool ClientScript { get; set; }
    public Label lbl { get; set; }
    public RequiredFieldValidator rfv { get; set; }
    private CompareValidator cv;
    private RegularExpressionValidator rev;
    private bool IsRegExpValidator;

    protected override void OnInit(EventArgs e)
    {
        rfv = new RequiredFieldValidator();
        ClientScript = true;

        this.AddLabel();

        if (Required)
        {
            this.AddRequiredFieldValidator();
        }

        if (!DataType.IsNullOrEmpty())
        {
            switch (DataType)
            {
                case "Email":
                    IsRegExpValidator = true;
                    AddRegExValidator(DataType);
                    break;
                default:
                    AddCompareValidator(DataType);
                    break;
            }
        }
    }

    private void AddCompareValidator(string DataType)
    {
        cv = new CompareValidator();
        cv.Operator = ValidationCompareOperator.DataTypeCheck;

        switch (DataType)
        {
            case "Integer":
                cv.Type = ValidationDataType.Integer;
                cv.ErrorMessage = "Please enter a numeric value";
                break;
            default:
                break;
        }

        if (!this.DataInvalidMessage.IsNullOrEmpty())
        {
            cv.ErrorMessage = this.DataInvalidMessage;
        }

        this.ConfigureAndAddValidator(cv);
    }

    private void AddRegExValidator(string DataType)
    {
        // for regex explanation see http://www.codeproject.com/Articles/22777/Email-Address-Validation-Using-Regular-Expression
        const string MatchEmailPattern =
            @"^(([\w-]+\.)+[\w-]+|([a-zA-Z]{1}|[\w-]{2,}))@"
             + @"((([0-1]?[0-9]{1,2}|25[0-5]|2[0-4][0-9])\.([0-1]?
                        [0-9]{1,2}|25[0-5]|2[0-4][0-9])\."
             + @"([0-1]?[0-9]{1,2}|25[0-5]|2[0-4][0-9])\.([0-1]?
                        [0-9]{1,2}|25[0-5]|2[0-4][0-9])){1}|"
             + @"([a-zA-Z]+[\w-]+\.)+[a-zA-Z]{2,4})$";

        rev = new RegularExpressionValidator();

        switch (DataType)
        {
            case "Email":
                rev.ValidationExpression = MatchEmailPattern;
                rev.ErrorMessage = "Please enter a valid email address";
                break;
            default:
                break;
        }

        if (!this.DataInvalidMessage.IsNullOrEmpty())
        {
            rev.ErrorMessage = this.DataInvalidMessage;
        }

        this.ConfigureAndAddValidator(rev);
    }

    protected override void Render(HtmlTextWriter w)
    {
        lbl.RenderControl(w);
        base.Render(w);
        if (Required)
        {
            rfv.RenderControl(w);
        }
        if (!DataType.IsNullOrEmpty())
        {
            if (IsRegExpValidator)
                rev.RenderControl(w);
            else
                cv.RenderControl(w);
        }
    }
}

public class ExtendedRadioButtonList : RadioButtonList, IExtendedControl
{
    public string LabelText { get; set; }
    public string LabelCssClass { get; set; }
    public bool Required { get; set; }
    public string RequiredMessage { get; set; }
    public string ValidatorCssClass { get; set; }
    public bool ClientScript { get; set; }
    public Label lbl { get; set; }
    public RequiredFieldValidator rfv { get; set; }

    protected override void OnInit(EventArgs e)
    {
        rfv = new RequiredFieldValidator();
        ClientScript = true;

        this.CssClass = "radioButtonList";

        this.AddLabel(this.CssClass);

        if (Required)
        {
            this.AddRequiredFieldValidator();
        }
    }

    protected override void Render(HtmlTextWriter w)
    {
        lbl.RenderControl(w);
        base.Render(w);
        if (Required)
        {
            rfv.RenderControl(w);
        }
    }
}
}

El formulario se puede distribuir utilizando el siguiente marcado:

<fieldset id="PersonalDetails" >
    <legend>Personal Details</legend>
    <div class="form_row">
        <p>
            <strong>Name</strong></p>
        <div class="form_field">
            <MYPREFIX:ExtendedTextBox ID="PreferredTitle" LabelText="Title" CssClass="width_x-small"
                LabelCssClass="width_x-small" ValidatorCssClass="width_x-small" runat="server"
                Required="false">
            </MYPREFIX:ExtendedTextBox>
        </div>
        <div class="form_field">
            <MYPREFIX:ExtendedTextBox ID="FirstName" LabelText="First" CssClass="required width_small"
                ValidatorCssClass="width_small" runat="server" Required="true" RequiredMessage="First name is required">
            </MYPREFIX:ExtendedTextBox>
        </div>
        <div class="form_field">
            <MYPREFIX:ExtendedTextBox ID="LastName" LabelText="Last" CssClass="width_small" runat="server"
                Required="true" RequiredMessage="Last name is required">
            </MYPREFIX:ExtendedTextBox>
        </div>
    </div>
</fieldset>

Y cuando la siguiente hoja de estilo se agrega a la página, debería funcionar bien. Estoy usando esto junto con el JQuery Datepicker, por lo que si se usa, se hará un estilo para el widget del selector de fecha

fieldset legend
{
    font-size: 20px;
    padding: 16px 0px;
}

.form_row
{
    margin-bottom: 10px;
}

.form_field
{
    display: inline-block;
    vertical-align: top;
}

.form_field input, textarea
{
    font-weight: normal !important;
    display: block;
    margin-bottom: 2px;
}

.form_field .radioButtonList
{
    display: block;
}

.form_field .radioButtonList input
{
    display: inline;
    vertical-align: middle;
    margin-bottom: 5px;
}

.form_field .radioButtonList label
{
    display: inline;
    vertical-align: middle;
    margin-left: 5px;
}

.form_field .radioButtonList .validationMessage
{
    float: right;
    clear: both;
    vertical-align: middle;
    margin-left: 5px;
}

.form_field.datepicker input
{
    display: inline;
    float: left;
}

.form_field .datepicker
{
    display: inline;
    float: left;
    vertical-align: middle;
}

.form_field .ui-datepicker-trigger
{
    display: inline;
    float: left;
    margin-left: 5px;
    vertical-align: middle;
}

.form_field.datepicker .validationMessage
{
    clear: both;
}

.ui-datepicker .ui-datepicker-title select
{
    font-size: 0.9em;
    min-width: 0;
}

thead th.ui-datepicker-week-end:first-child
{
    font-size: 1em;
}

table.ui-datepicker-calendar tr:first-child td
{
    font-size: 1em;
}

.form_field .form_field_label
{
    color: #777777;
    display: block;
    font-size: 11px;
    font-weight: normal;
    line-height: inherit;
    float: none;
}

.form_field_label.radioButtonList
{
    margin-bottom: 8px;
}

.form_field_empty_label .requiredFieldIndicator
{
    display: inline;
    font-size: 11px;
    vertical-align: middle;
}

.form_field label
{
    color: #777777;
    display: block;
    font-size: 11px;
    float: none;
}

.boldLabel
{
    font-size: 12px;
    font-weight: bold;
    color: #333333;
    margin-top: 8px;
    margin-bottom: 12px;
}

.requiredFieldIndicator
{
    color: Red;
}

/* general widths */
.width_x-small
{
    width: 25px !important;
}
.width_small
{
    width: 75px !important;
}
.width_normal
{
    width: 150px !important;
}
.width_large
{
    width: 200px !important;
}
.width_x-large
{
    width: 275px !important;
}
.width_xx-large
{
    width: 350px !important;
}
.validationMessage
{
    color: Red;
    font-size: 11px;
    float: left;
}

#body-copy input.ui-formwizard-button
{
    cursor: pointer;
    font-size: 1.5em;
}

Tengo la intención de poner detalles más completos de esta solución en un blog o en un proyecto de código en algún lugar cuando tenga tiempo para que pueda desarrollarse el código abierto en un conjunto completo de controles. Si alguien quiere hacer esto mientras tanto, siéntase libre de hacerlo, y publique un enlace aquí para ayudar a otros desarrolladores.

Saludos

    
respondido por el Chris Halcrow 24.08.2012 - 03:48
2
  

Como no puedo usar la verdadera herencia múltiple en C #, ¿cómo diseñaría?   ¿Esto para poder compartir los métodos y propiedades?

Esta es la forma correcta de compartir los métodos y las propiedades. Pero:

  

El problema ahora es que en mi clase extendedTextBox no puedo lanzar mi   extendedTextBox como 'BaseExtendedControl' para usar la extensión   métodos, como el extendedTextBox se hereda del TextBox estándar   clase como esta:

public class ExtendedTextBox : TextBox, IExtendedControl
     

así que no hay una base común para lanzar ExtendedTextBox como   'BaseExtendedControl', ya que no se hereda de él.

Sí, tienes razón, no hay una clase común, pero puedes resolver el problema enviando a la interfaz IExtentedControl que es común a tus clases y contiene los métodos que necesitas. Entonces, ¿qué pasa con:

public static class ExtendedControlHelper
{
    public static void AddRequiredFieldValidator(this IExtendedControl control)
    {
        control.rfv = new RequiredFieldValidator();
        control.rfv.ErrorMessage = control.RequiredMessage;
        ConfigureAndAddValidator(control, control.rfv);
    }

    public static void ConfigureAndAddValidator(this IExtendedControl control, BaseValidator validator)
    {
        validator.ControlToValidate = control.ID;
        validator.Display = ValidatorDisplay.Dynamic;
        validator.CssClass = "validationMessage ";
        validator.CssClass += control.ValidatorCssClass;
        validator.EnableClientScript = control.ClientScript;
        control.Controls.Add(validator);
    }
}

No creo que necesites implementar la interfaz IExtendedControl en la clase ExtendedControlHelper . Además, corregí la gramática del nombre de la interfaz de IExtendendControl a IExtendedControl .

EDITAR:

Otra solución podría ser usar la palabra clave dynamic . La palabra clave dinámica es como object pero el tipo se verifica en tiempo de ejecución en lugar de compilar:

public static class ExtendedControlHelper
{
    public static void AddRequiredFieldValidator(this dynamic control)
    {
        //...
    }

    public static void ConfigureAndAddValidator(this dynamic control, BaseValidator validator)
    {
         //...
    }
}

El tipo dinámico no existe en el tiempo de ejecución porque el compilador lo resuelve, por lo que si escribe esto: myExtendedTextBox.AddRequiredFieldValidator(); el método se resuelve como

//...
public static void AddRequiredFieldValidator(this ExtendedTextBox control)
{
    //...
}

y así sucesivamente. Así que el tipo puede cambiar continuamente en tiempo de ejecución.

Para obtener más información, eche un vistazo a aquí .

    
respondido por el Omar 21.08.2012 - 03:10
1

No estoy seguro de que todo este enfoque agregue mucho más que solo agregar un validador cuando lo desee, ya que terminará con más y más controles para cada tipo de validación.

Pero si realmente quieres hacer algo como esto, una forma de hacerlo es con un control genérico que se hereda del control suministrado:

public class RequiredField<ControlType>: ControlType where ControlType:WebControl
{
    //In here you can access all of the functionality of a WebControl, 
    //as well as add additional properties
}
    
respondido por el Chris Pitman 21.08.2012 - 02:43

Lea otras preguntas en las etiquetas