2
votes

I have a situation where I need to have two totally different layouts for a Web User Control but it's quite important that the codebehind for the controls remains identical.

Is there any way I can use the same codebehind for two different Web User Controls? I've tried changing the Page directive to use the same class, but occasionally when you build, a .designer.cs file gets generated with duplicate controls so you get a compilation error.

I also tried inheriting from a common base class but you cannot see any of the controls from this class, which makes it very difficult to take this approach.

Or is there a way to prevent the designer.cs class from being regenerated for the "clone" control?

3
Do you have lots of business logic in the codebehind? If so, can this be placed in a separate class and used by the codebehind for your two user controls? - Richard Ev
Not "business logic" as such - but work validating controls and handling input etc. Because this code heavily accesses the controls, and you can't see controls from within a base class, I don't think this would be a very good way to solve the problem :( - NickG
Rather than a base class, you could create a static class containing methods that contain your validation logic. The methods would be passed references to your controls as parameters. - Richard Ev

3 Answers

3
votes

Create base class, and implement common logic there. Then inherit controls from this base class. For accessing controls from base class level make abstract get properties on base class level for every single control you'll need. Implement those properties in child control classes.

public abstract class BaseControl : UserControl
{
  public abstract TextBox FirstName { get; }

  public void SomeLogicExample() 
  {
    FirstName.Text = "Something"; 
  }
}

public class ControlA : BaseControl
{
  public override TextBox FirstName
  {
    // txtFirstNameA is ID of TextBox, so it is defined in ControlA.designer.cs
    get { return txtFirstNameA; }
  }
}

public class ControlB : BaseControl
{
  public override TextBox FirstName
  {
    // txtFirstNameB is ID of TextBox, so it is defined in ControlB.designer.cs
    get { return txtFirstNameB; }
  }
}

Alternatively and a lot less elegant is locating controls at runtime; because you pay for searching the whole control tree and you have to handle control not found scenarios:

public abstract class BaseControl : UserControl
{
  public T GetControlByType<T>(Func<T, bool> predicate = null) where T : Control
  {
    var stack = new Stack<Control>(new Control[] { this });
    while (stack.Count > 0)
    {
      var control = stack.Pop();
      T match = control as T;
      if (match != null)
      {
        if (predicate == null || predicate(match))
        {
          return match;
        }
      }

      foreach (Control childControl in control.Controls)
      {
        stack.Push(childControl);
      }
    }

    return default(T);
  }    

  public TextBox FirstName
  {
    get { return GetControlByType<TextBox>(t => t.ID == "txtFirstName"); }
  }

  public void SomeLogicExample() 
  {
    FirstName.Text = "Something"; 
  }
}
2
votes

I have just now tested this in VS 2012.

  1. I have added 2 user controls
  2. Deleted .ascx.cs and .ascx.Designer.cs file from second control
  3. Modified Page directive of second control for CodeBehind and Inherits attribute. No need to generate Designer file for second control.

And it works. I don't know this is valid approach or not. I have tested it by adding a Label in each control and setting its value in code behind. The value reflects in both controls.

You may face issue when you have a asp.net control in one user control and it doesn't exists in other.

1
votes

In addition to @Ondrej Svejdar 's correct answer I suggest that you take a look at the MVP design pattern and maybe use it with your Web Forms code. It works, I have tried it in a big project and the result is no worse than the separation of concerns in an MVC project. There are frameworks to help you implement it but for some reason I prefer implementing it myself (after all it is a couple of base classes and a base interface)