views:

109

answers:

1

I have a set of custom ASP.NET server controls, most of which derive from CompositeControl. I want to implement a uniform look for "required" fields across all control types by wrapping each control in a specific piece of HTML/CSS markup. For example:

  <div class="requiredInputContainer">
        ...custom control markup...
  </div>

I'd love to abstract this behavior in such a way as to avoid having to do something ugly like this in every custom control, present and future:

  public class MyServerControl : TextBox, IRequirableField { 

          public IRequirableField.IsRequired {get;set;}

          protected override void Render(HtmlTextWriter writer){

            RequiredFieldHelper.RenderBeginTag(this, writer)
            //render custom control markup
            RequiredFieldHelper.RenderEndTag(this, writer)
          }
   }

  public static class RequiredFieldHelper{

      public static void RenderBeginTag(IRequirableField field, HtmlTextWriter writer){
      //check field.IsRequired, render based on its values
      }
      public static void RenderEndTag(IRequirableField field, HtmlTextWriter writer){
       //check field.IsRequired , render based on its values
      }

  }

If I was deriving all of my custom controls from the same base class, I could conceivably use Template Method to enforce the before/after behavior;but I have several base classes and I'd rather not end up with really a convoluted class hierarchy anyway.

It feels like I should be able to design something more elegant (i.e. adheres to DRY and OCP) by leveraging the functional aspects of C#, but I'm drawing a blank.

+1  A: 

You have an optional behavior you want to add to some objects and not others. That means you have a Proxy pattern in your problem. Create a control that is a container for other controls and does your required stuff before and after rendering any of its children.

You are right to not want to inherit to specialize. Inherit to create abstractions and variations. Delegate to specialize.

EDIT

/// <summary>
/// Create one of these panels for each required control
/// then place the required control into it
/// </summary>
public class ContainerForRequiredControls : Panel
{
  protected override void Render(HtmlTextWriter writer)
  {
    writer.Write("<!-- header -->");

    RenderChildren(writer);

    writer.Write("<!-- footer -->");
  }
}
Max,Proxy via delegation seems like the right pattern to apply here. Unfortunately, WebControl.Render, which is the method I want to proxy, is declared "protected internal". Given this, I don't see how I can get straight delegation to work. Thoughts?
Dirk
@Dirk: I believe that rendering the children is part of the base class's (`Control`'s) Render behavior, right? So add the proxied object(s) to the control's `Controls` collection, when you are instantiating. Then in your render method do your header work, call the base render, then do the footer work. Does that not work?
It doesn't appear to work; at least not without reworking the internals of some of my custom controls.
Dirk
@Dirk: I'll come up with a sample tonight.
@Max: Look forward to seeing it
Dirk
@Dirk: Sorry. I got distracted with some things.
Not sure I understand what just happened, there. Did that not work for you? It sure worked for me.
@Dirk: Did that not work for you?
Hmmm...maybe I'm missing something. I don't see how this example demonstrates applying the Proxy pattern to a class deriving from WebControl. If you read my original question, you'll see that I'm already doing precisely what your example does; Namely, overriding the Render() method of each of my derived classes to add headers/footers; I'm in search of a better solution, not the same solution. The proxy pattern looked like a great fit. I just don't see how to implement it given that Render has protected accessibility.
Dirk
@Dirk: There is a huge difference between what is in your question and in my answer. Your example requires you to override each control (_e.g._: TextBox) to get what you want. The example in my answer is a Panel, which allows you to drop controls into it. So, for instance, you can create one of these panels, then drop your textbox into it and still get what you want.
@Dirk: Can I take it from the fact that the votes on this answer have changed, again, that this worked for you?