0

I've created some custom textboxes which are inherited from textbox. For the next step I want to register javascript with a wrapper.

Decorator pattern allow me to do if only I can inherit it from textbox and pass custom textbox as a constructor parameter.

Problem is that how can I use constructor when I add a control to aspx page or basically how can I use decorator pattern for asp.net controls.

EDIT:

Simply this is my validation base class (IField is an validation interface. This can be ignored):

public abstract class ValidationBase : TextBox, IField
{
    private readonly IField _field;
    protected ValidationBase(IField field)
    {
        _field = field;
    }

    public int MinLength
    {
        get { return _field.MinLength; }
        set { _field.MinLength = value; }
    }

    public bool Required
    {
        get { return _field.Required; }
        set { _field.Required = value; }
    }

    // other porperties etc...

    protected override void OnPreRender(EventArgs e)
    {
        // DO SOME STUFF...

        base.OnPreRender(e);
    }
}

And this is my concrete class (EmailField is a concrete impl. of IField ignore...):

public class ValidationEmail : ValidationBase
{
    public ValidationEmail() 
        : base(new EmailField(string.Empty))
    {
    }
}

And finally I want to implement this (I've made up my mind on wordpad this can't be the exact impl.):

public class JsRegisterDecorator : ValidationBase
{
    private readonly ValidationBase _validationObj;

    //I am not quite sure about the constructor but i can handle
    public JsRegisterDecorator(ValidationBase concreteValidationObj) 
        : base(concreteValidationObj)
    {
        _validationObj = concreteValidationObj;
    }

    //Wrap the properties

    protected override void OnPreRender(EventArgs e)
    {
        //Register JS Files...
        _validationObj.OnPreRender(e);
    }
}

The problem is that How can I use this decorator? Because asp.net construct controls automatically:

<vc:ValidationEmail ID="ValidationEmail1" runat="server"/>

I don't know can I use this (where can I put the constructor parameter?):

<vc:JsRegisterDecorator ID="ValidationEmailWithJs1" runat="server"/>
pilavust
  • 538
  • 1
  • 7
  • 20
  • Can you please elaborate on your particular scenario? What do you want to achieve with Decorator pattern applied to control, what have you tried to do or trying? – Alexander Manekovskiy May 05 '13 at 10:03

2 Answers2

1

I don't think Decorator pattern fits well here. In general I saw more applications of Builder and Factory Method for ASP.NET controls.

To partially solve your task you can use ControlBuilder. It will give you ability to change the type of the control from ValidationBase to JsRegisterDecorator or ValidationEmail. You need to decorate ValidationBase class with ControlBuilderAttribute, inherit builder class from ControlBuilder and override Init method.

[ControlBuilder(typeof(ValidationBaseBuilder))]
public abstract class ValidationBase : TextBox, IField { }

public class ValidationBaseBuilder: ControlBuilder
{
    public override void Init(TemplateParser parser, ControlBuilder parentBuilder, Type type, string tagName, string id, System.Collections.IDictionary attribs)
    {
        var newType = typeof(/*here you can put a JsRegisterDecorator type*/);
        base.Init(parser, parentBuilder, t, tagName, id, attribs);
    }
}

But I'm not sure about such approach. ControlBuilder cannot give you easy control over constructor. Surely you can override ProcessGeneratedCode in ControlBuilder and David Ebbo has a blog post worth reading but it would not be an easy task to rewrite constructor for control and make solution simple.

As alternative that will work I can suggest to add an abstract (or virtual) method like RegisterScripts inside ValidationBase and call it in OnPreRender. Every control will know what scripts it needs and the process of new validator control creation will be clean and simple. If you want to separate knowledge of JS scripts from concrete implementations then approach as seen in ASP.NET DynamicData (read MetaTable) could be used.

Another thing that I can see is that your idea is close enough to DynamicControl and maybe it would be possible to get more ideas from ASP.NET DynamicData like Field Templates and IFielTemplateFactory.

Alexander Manekovskiy
  • 3,185
  • 1
  • 25
  • 34
  • what about creating `WebControl` inherited `INamingContainer`? As children it accepts only `ValidationBase` and OnLoad it register javascriptes. – pilavust May 07 '13 at 20:59
  • @pilavust sounds good if it fits into your case. Another idea is that you can create a "one per page/master" `ValidationScriptManager` (like `ScriptManager`) control where validation controls will be registered and which will gather, combine and return necessary scripts during request. – Alexander Manekovskiy May 08 '13 at 15:26
  • I agree with you. I am very thankful for your support. To contribute this question can I answer it with my solution/imlementation? – pilavust May 08 '13 at 17:22
  • @pilavust sure you can. It would be interesting to see how you solved the problem. – Alexander Manekovskiy May 08 '13 at 17:55
1

I solve my problem AlexanderManekovskiy's help and also some other questions:

And here is the solution:

I've made JsRegistererForValidationBase as a WebControl and implemented INamingContaier.

For the children elements I've created Children property which accepts olny list of Validation Base.

And finally OnInit method, I've registered the js.

Here is the code:

[ParseChildren(true)]
[PersistChildren(true)]
[ToolboxData(@"<{0}:JsRegistererForVB runat=""server""></{0}:JsRegistererForVB>")]
public class JsRegistererForValidationBase : WebControl, INamingContainer
{
    private ValidationFieldCollection _children;

    [PersistenceMode(PersistenceMode.InnerProperty)]
    [DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]
    public ValidationFieldCollection Children
    {
        get
        {
            if (_children == null)
                _children = new ValidationFieldCollection();
            return _children;
        }
    }

    protected override void CreateChildControls()
    {
        Controls.Clear();
        foreach (var c in _children)
            Controls.Add(c);
    }

    protected override void OnInit(EventArgs e)
    {
        //DO THE REGISTER STUFF

        base.OnInit(e);
    }

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

public class ValidationFieldCollection : List<ValidationBase> { }

}

And at the aspx side it becomes like this:

<vc:JsRegisterer ID="JsRegisterer1" runat="server">
    <Children>
        <vc:ValidationEmail ID="ValidationEmail1" runat="server"/>
        <vc:ValidationEmail ID="ValidationEmail2" runat="server"/>,
        <!--etc-->
    </Children>
</vc:JsRegisterer>

For the detailed imlementation I added the code to codeplex

Community
  • 1
  • 1
pilavust
  • 538
  • 1
  • 7
  • 20