Come condividere metodi e proprietà tra controlli web personalizzati

7

Sto costruendo alcuni controlli web personalizzati in .NET usando C #. I controlli ereditano dai controlli web standard e aggiungono proprietà e funzionalità aggiuntive (ad es. Sto creando un 'extendedTextBox' e sto aggiungendo una proprietà 'required', che se impostata su 'true' aggiungerà un campo obbligatorio .NET validatore al controllo automaticamente.

Lo sto facendo per una serie di controlli web (ad esempio, RadioButtonList, textArea). Condividono alcune proprietà e metodi comuni, ad esempio, ho un metodo AddRequiredFieldValidator che utilizza alcune delle proprietà estese che ho aggiunto. Mi piacerebbe condividere le proprietà e i metodi comuni. Ho provato ad aggiungere i metodi a una classe separata come metodi di estensione per un controllo web. Per raggiungere questo obiettivo ho implementato un'interfaccia che definisce le proprietà condivise aggiuntive e la sto usando in questo modo:

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

Il problema è che il metodo 'ConfigureAndAddValidator' ora non conosce nulla delle proprietà 'ID' o 'ClientScript' del controllo poiché 'IExtendedControl' definisce solo le proprietà personalizzate, non le proprietà standard di un controllo web. Così ho provato ad aggiungere una classe base che eredita da WebControl e implementa l'interfaccia, in questo modo:

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

Il problema ora è che nella mia classe extendedTextBox non posso lanciare il mio extendedTextBox come un 'BaseExtendedControl' per usare i metodi di estensione, dato che extendedTextBox eredita dalla classe standard di TextBox come questa:

public class ExtendedTextBox : TextBox, IExtendendControl

quindi non esiste una base comune per il cast di ExtendedTextBox come 'BaseExtendedControl' in quanto non eredita da esso. Inoltre, non posso semplicemente passare gli oggetti controllo web estesi come parametro nei metodi condivisi in quanto i controlli estesi sono di tipi diversi (ad es. Ereditano da TextBox, RadioButtonList e così via). Se si specifica che il tipo previsto viene passato nei metodi "standard" come "WebControl", non funziona poiché i controlli estesi hanno le proprietà aggiuntive.

Poiché non posso utilizzare l'ereditarietà multipla reale in C #, come potrei progettarlo per poter condividere i metodi e le proprietà?

    
posta Johnny 21.08.2012 - 01:09
fonte

3 risposte

1

Grazie per le risposte. Fuex - mi hai messo sulla giusta strada, ecco il codice finale che include un controllo TextBox esteso e un controllo esteso di RadioButtonList.

Per usarli, aggiungi questa dichiarazione alla pagina aspx:

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

Quindi aggiungi il controllo alla pagina in questo modo:

Controllo TextBox esteso:

< MYPREFIX: ExtendedTextBox ID="HomePhone" LabelText="Numero di telefono" CssClass="width_normal" LabelCssClass="width_normal" ValidatorCssClass="width_normal"  Obbligatorio="true" RequiredMessage="Il nome è obbligatorio" DataType="Intero" runat="server" > < / MYPREFIX: ExtendedTextBox >

  • Se viene specificato solo CssClass , i controlli label e validator erediteranno questa classe.
  • Le classi css Label e validator possono essere specificate singolarmente utilizzando LabelCssClass e ValidatorCssClass .
  • Puoi aggiungere un RequiredMessage per impostare il messaggio di convalida. Se questo non è specificato, il messaggio di convalida sarà ' LabelText è richiesto'
  • LabelText non è richiesto - se manca, l'asterisco 'richiesto' verrà posizionato accanto alla casella di testo
  • Per impostazione predefinita, viene generato lo script di validazione lato client (quindi non è necessario impostare 'ClientScript' su true).
  • DataType può essere specificato per convalidare un tipo di dati specifico. I valori supportati sono Email e Intero

Controllo RadioButtonList esteso:

< WVNZ: ExtendedRadioButtonList RepeatLayout="Flow" ID="WrittenContactPreference" LabelText="Preferenziale preferenza per i contatti scritti"                     LabelCssClass="boldLabel" runat="server" Richiesto="falso" >
< asp: ListItem > E-mail < / asp: ListItem >
< asp: ListItem > Casa < / asp: ListItem >
< / WVNZ: ExtendedRadioButtonList >

Affinché i controlli funzionino, è necessario incorporare quanto segue in un assembly denominato "MYASSEMBLY" a cui fa riferimento il progetto in cui viene implementato il controllo:

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

Il modulo può quindi essere disposto utilizzando il seguente markup:

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

E quando il seguente foglio di stile viene aggiunto alla pagina dovrebbe funzionare bene. Lo sto utilizzando insieme a JQuery Datepicker, quindi se lo utilizzi, lo styling sarà fatto per il widget datepicker

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

Intendo mettere i dettagli più completi di questa soluzione su un blog o in un progetto di codice da qualche parte quando avrò tempo in modo che possa essere sviluppato open source in un set completo di controlli. Se qualcuno vuole farlo nel frattempo, non esitare a farlo e per favore pubblica un link qui per aiutare altri sviluppatori.

Saluti

    
risposta data 24.08.2012 - 03:48
fonte
2

As I can't use true multiple inheritance in C#, how would I design this to be able to share the methods and properties?

Questo è il modo giusto per condividere i metodi e le proprietà. Ma:

The trouble now is that in my extendedTextBox class I can't cast my extendedTextBox as a 'BaseExtendedControl' to use the extension methods, as the extendedTextBox inherits from the standard TextBox class like this:

public class ExtendedTextBox : TextBox, IExtendedControl

so there's no common base to cast ExtendedTextBox as 'BaseExtendedControl' as it doesn't inherit from it.

Sì, hai ragione: non esiste una classe comune, ma puoi risolvere il problema eseguendo il casting sull'interfaccia IExtentedControl che è comune alle tue classi e contiene i metodi di cui hai bisogno. Che dire:

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

Non penso che sia necessario implementare l'interfaccia IExtendedControl nella classe ExtendedControlHelper . Inoltre ho corretto la grammatica del nome dell'interfaccia da IExtendendControl a IExtendedControl .

EDIT:

Un'altra soluzione potrebbe essere utilizzare la parola chiave dynamic . La parola chiave dinamica è come object ma il tipo viene controllato in fase di esecuzione anziché in fase di compilazione:

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

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

Il tipo dinamico non esiste in fase di esecuzione perché è stato risolto dal compilatore, quindi se scrivi questo: myExtendedTextBox.AddRequiredFieldValidator(); il metodo è risolto come

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

e così via. Quindi il tipo può cambiare continuamente in fase di esecuzione.

Per ulteriori informazioni dai uno sguardo qui .

    
risposta data 21.08.2012 - 03:10
fonte
1

Non sono sicuro che l'intero approccio aggiunga molto di più all'aggiunta di un validatore quando ne vuoi uno, dal momento che ti ritroverai con sempre più controlli per ogni tipo di convalida.

Ma se vuoi davvero fare qualcosa di simile, un modo che puoi fare è con un controllo generico che eredita dal controllo fornito:

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
}
    
risposta data 21.08.2012 - 02:43
fonte

Leggi altre domande sui tag