La première étape est d'ajouter l'interface IScriptControl (qui se trouve dans la dll System.Web.Extensions) sur notre classe pourcentage.

[DefaultProperty("Value")]
[ToolboxData("<{0}:PercentageControl runat=server></{0}:PercentageControl>")]
[ToolboxBitmap(typeof(PercentageControl), "Percentage.bmp")]
public class PercentageControl : WebControlIPostBackDataHandlerIScriptControl

Nous devons ensuite enregistrer cette classe dans le ScriptManager comme nous avons dû le faire pour enregistrer celle-ci dans le support du PostBack.

protected override void OnPreRender(System.EventArgs e)
{
    if (!this.DesignMode)
    {
        ScriptManager sm = ScriptManager.GetCurrent(Page);
        if (sm == null)
        {
            throw new HttpException("A ScriptManager control must exist on the page.");
        }
        sm.RegisterScriptControl(this);
    }
    base.OnPreRender(e);
    this.Page.RegisterRequiresPostBack(this);
}
protected override void Render(HtmlTextWriter writer)
{
    if (!this.DesignMode)
    {
        ScriptManager sm = ScriptManager.GetCurrent(Page);
        sm.RegisterScriptDescriptors(this);
    }
    base.Render(writer);
}

Il est temps de passer à l'implémentation de l'interface IScriptControl. Il faut donner le nom de la classe JavaScript liée au contrôle C# ainsi que les propriétés que l'on envoie à la classe JavaScript. Cela se passe dans la méthode GetScriptDescriptors. De l'autre côté, il faut spécifier le chemin pour accéder au fichier JavaScript où se trouve ma (future) classe JavaScript. Cela se passe dans la méthode GetScriptReferences.

public IEnumerable<ScriptDescriptor> GetScriptDescriptors()
{
    ScriptControlDescriptor descriptor = new ScriptControlDescriptor("MyCustomControls.PercentageControl"this.ClientID);
    descriptor.AddProperty("errorCssClass"this.ErrorCssClass);
    return new ScriptDescriptor[] { descriptor };
}
public IEnumerable<ScriptReference> GetScriptReferences()
{
    ScriptReference reference = new ScriptReference();
    if (this.Page != null)
    {
        reference.Path = this.Page.ClientScript.GetWebResourceUrl(
                                        this.GetType(),
                                        "MyCustomControls.PercentageControl.js");
    }
    return new[] { reference };
}

Nous pouvons démarrer notre classe JavaScript. Nous décidons de la placer dans une ressource "embedded" de notre dll. Nous ajoutons notre fichier PercentageControl.js et nous l'enregistrons comme ressource dans notre assembly via cet déclaration ci-dessous.

[assemblyWebResource("MyCustomControls.PercentageControl.js""text/javascript")]

La prochaine étape est l'implémentation de celle-ci.

1) Nous enregistrons notre namespace

Type.registerNamespace('MyCustomControls');


2) Nous allons initialiser les valeurs par défauts etc

MyCustomControls.PercentageControl = function (element) {
    MyCustomControls.PercentageControl.initializeBase(this, [element]);
    this._errorCssClass = null;
};


3) Nous implémentons notre classe protoype avec 2 méthodes .... initialize et dispose

MyCustomControls.PercentageControl.prototype = {
    initialize: function () {
        MyCustomControls.PercentageControl.callBaseMethod(this'initialize');
    },
    dispose: function () {
        $clearHandlers(this.get_element());
        MyCustomControls.PercentageControl.callBaseMethod(this'dispose');
    }
};

4) Nous implémentons les propriétés pour le css d'erreur à appliquer en JavaScript

MyCustomControls.PercentageControl.prototype = {
    initialize: function () {
        MyCustomControls.PercentageControl.callBaseMethod(this'initialize');
    },
    dispose: function () {
        $clearHandlers(this.get_element());
        MyCustomControls.PercentageControl.callBaseMethod(this'dispose');
    },
    set_errorCssClass: function (value) {
        if (this._errorCssClass !== value) {
            this._errorCssClass = value;
            this.raisePropertyChanged('errorCssClass');
        }
    },
    get_errorCssClass: function () {
        return this._errorCssClass;
    }
};

5) Nous donnons explicitement les descripteurs (utile pour la sérialisation JSON)

MyCustomControls.PercentageControl.descriptor = {
    properties: [{ name: 'errorCssClass', type: String}]
};

6) Nous enregistrons la classe et nous informons le framework que c'est chargé.

MyCustomControls.PercentageControl.registerClass('MyCustomControls.PercentageControl', Sys.UI.Control);
if (typeof (Sys) !== 'undefined') Sys.Application.notifyScriptLoaded();

7) Nous souhaitons implémenter notre code "business"

L'idée est de mettre un cadre rouge autour des texbox lorsqu'il y a une erreur. Pour ce faire, nous allons nous abonner à l'event change et ajouter notre css d'erreur en cas de donnée incorrecte. Nous obtenons au final le résultat ci-dessous.

Type.registerNamespace('MyCustomControls');
MyCustomControls.PercentageControl = function (element) {
    MyCustomControls.PercentageControl.initializeBase(this, [element]);
    this._errorCssClass = null;
};
MyCustomControls.PercentageControl.prototype = {
    initialize: function () {
        MyCustomControls.PercentageControl.callBaseMethod(this'initialize');
        this._onchangeHandler = Function.createDelegate(thisthis._onchange);
        $addHandlers(this.get_element(), { 'change'this._onchangeHandler }, this);
    },
    dispose: function () {
        $clearHandlers(this.get_element());
        MyCustomControls.PercentageControl.callBaseMethod(this'dispose');
    },
    _onchange: function (e) {
        var value = parseFloat(e.target.value);
        var isIncorrect = false;
        if (isNaN(value) || !isFinite(value)) {
            isIncorrect = true;
        }
        if (isIncorrect || value > 100 || value < 0) {
            if (this._errorCssClass !== null && this._errorCssClass !== '') {
                this.addCssClass(this._errorCssClass);
            }
        }
        else {
            this.removeCssClass(this._errorCssClass); 
        }
    },
    set_errorCssClass: function (value) {
        if (this._errorCssClass !== value) {
            this._errorCssClass = value;
            this.raisePropertyChanged('errorCssClass');
        }
    },
    get_errorCssClass: function () {
        return this._errorCssClass;
    }
};
MyCustomControls.PercentageControl.descriptor = {
    properties: [{ name: 'errorCssClass', type: String}]
};
MyCustomControls.PercentageControl.registerClass('MyCustomControls.PercentageControl', Sys.UI.Control);
if (typeof (Sys) !== 'undefined') Sys.Application.notifyScriptLoaded();

Il ne reste plus qu'à ajouter ma class css dans ceux-ci

<style type="text/css">
    .errorCss
    {
        border-colorRed;
    }
</style>

et de l'ajouter dans notre code asp.net
 
<div>
    <asp:Button ID="Button1" Text="Click" runat="server" />
    <Custom:PercentageControl ID="PercentageControl1" runat="server" 
        ErrorCssClass="errorCss" Value="100" />
</div>
 
Nous pouvons maintenant l'exécuter et nous voyons que l'on retrouve un cadre rouge si on encode 200, -10 ou encore le "é" dans notre textbox. Vous pouvez retrouver la solution en fichier attaché ci-dessous.