Idiot-proof guide to a Templated Control

Here comes the easiest tutorial ever for writing your own ASP.Net templated control. But first, you may be wondering "Why would I want to do that?". God I hate you. So much. You and all your damn questions.

Anyways, these are great when you want a reusable control, with common functionality, but you don't actually want to enforce a certain layout, or limit a developer in what they can actually show on the control. Anything can go inside the templated area - html, pictures of cats, asp controls, more cats, you name it. You like to use the ListView control? That's a templated control. You like to use UpdatePanels? That's a templated control. "Wow Rob, all the cool controls use templates!" Yep. And you want to be cool, don't you? Of course you do. You're not an emo terrorist.....ARE YOU!?!?!

So, here's the basics. This examples provides no use whatsoever to anyone ever. We're going to make a control that doesn't do a damn thing but host a template. So, we'll start by making our control. This is the easy part. You can do this with a user control, or write your own control from scratch. All you need is a class that inherits from the Control class, but I'm going to be inheriting from UserControl for this example.

   1: public class WorthlessControl : System.Web.UI.UserControl
   2:     {
   3:         protected void Page_Load(object sender, EventArgs e)
   4:         {
   5:  
   6:         }
   7:     }

If you're already confused, sorry. No, I'm not saying I'm sorry that you're confused, I'm saying you're sorry, and I loathe you. Or whatever.

Next, we want our control to hold the template, right? That's kinda the whole point. We don't define a specific template class, however - there's an interface called ITemplate, that we'll be using. Lets name our property something useful, like ContentTemplate. I know, I know, you really had your heart set on KittyCatJunction, but maybe next time. So, we'd write something like this:

   1: public class WorthlessControl : System.Web.UI.UserControl
   2:     {
   3:         [Browsable(false)]
   4:         [PersistenceMode(PersistenceMode.InnerProperty)]
   5:         public ITemplate ContentTemplate { get; set; }
   6:  
   7:         protected void Page_Load(object sender, EventArgs e)
   8:         {
   9:  
  10:         }
  11:     }

"HEY WAIT A MINUTE THERE ARE BIG WORDS THERE YOU DIDN'T WARN ME ABOUT AAAAHHHH!"

....my hate for you grows exponentially with every passing second. I don't know why I put up with you, seriously. You're lucky I can't stand to be alone. Alright, so those BIG WORDS are extra attributes We're putting on our template field. The first is "Browsable", and we're setting it to "false". Browsable controls wether or not it shows up in the properties window in the asp.net designer. Obviously, a template has no reason to be in there. The next is "PersistenceMode". It's an enumerated type, and we're setting it to "InnerProperty". What this allows us to do, is to write our asp.net markup now and use this property as a nested tag. Snazzy, eh? We don't want developers to actually have to do this stuff in codebehind. That'd be lamzorz.

"Sweet, we're all done! That was easy!" If you would stop yammering for FIVE SECONDS I could tell you were not done yet at all. Now, first off, our template needs somewhere to live. The template will, more than likely, contain asp.net controls. Now, each control that lives on a page has to have a unique name - else, asp.net has no way of knowing which control fired what event, and so on. For this, there's an interface called INamingContainer. It has no functions or properties you need to implement, but it lets asp.net know that it's going to have it's own naming space, and controls will be uniquely named within it. That's why ListView controls work - your ItemTemplate will get repeated many many times, yet you don't get things blowing up because it has 20 copies of "lblCatAwesomenessRating" defined. So, we'll define a naming container class for our templated property. It will also need to be a control, since it'll live on the page and all. Then, we'll need to tell our property what type of container he (or she, don't sue me) gets. It doesn't need to have anything else. However, if you were doing a databound control (which I will next time), this would be a good spot to put your data object - by convention, a property named DataItem. But we'll get into that later. For now, all we need is:

   1: public class WorthlessControl : System.Web.UI.UserControl
   2:     {
   3:         [Browsable(false)]
   4:         [PersistenceMode(PersistenceMode.InnerProperty)]
   5:         [TemplateContainer(typeof(WorthlessContainer))]
   6:         public ITemplate ContentTemplate { get; set; }
   7:  
   8:         protected void Page_Load(object sender, EventArgs e)
   9:         {
  10:  
  11:         }
  12:     }
  13:  
  14:     public class WorthlessContainer : Control, INamingContainer
  15:     {
  16:         
  17:     }

Hey, now we're getting somewhere. Almost. Kinda. Just *one* issue left - actually putting the consuming developers template to use. In most *real* controls, you'll probably have a PlaceHolder control that you'll attach to, but we'll get into that stuff next time (for reals - this is part 1). So, lets think about what we need to do here, hmm? We need to add controls to the page. What page lifecycle event sounds like a proper place? Why, that's correct! CreateChildControls! Maybe I'll call off that mob hit I put on you after all! So, we know where we need to do it - but what are we going to do? Well, the ITemplate class gives us a function called "InstantiateIn". That'll take our markup, and make it into real controls. It's like Pinnochio, but with developer crap. The function also needs to know, WHERE to instantiate all these wonderful controls defined in our template. That's where our Container comes back into play. We'll fire up an instance of it, and pass it in. We'll finish up by actually then adding our container, with our came-to-life template inside it, to our controls collection. So, here's what we have then:

   1: public class WorthlessControl : System.Web.UI.UserControl
   2:     {
   3:         [Browsable(false)]
   4:         [PersistenceMode(PersistenceMode.InnerProperty)]
   5:         [TemplateContainer(typeof(WorthlessContainer))]
   6:         public ITemplate ContentTemplate { get; set; }
   7:  
   8:         protected void Page_Load(object sender, EventArgs e)
   9:         {
  10:  
  11:         }
  12:  
  13:  protected override void CreateChildControls()
  14:         {
  15:             base.CreateChildControls();
  16:  
  17:             WorthlessContainer cont = new WorthlessContainer ();
  18:             ContentTemplate.InstantiateIn(cont);
  19:  
  20:             this.Controls.Add(cont);
  21:         }
  22:     }
  23:  
  24:     public class WorthlessContainer : Control, INamingContainer
  25:     {
  26:         
  27:     }

TADA! Pretty easy, eh? So, now if you want to use this on some consuming page or other control, your markup will look like this:

   1: <uc1:WorthlessControl ID="myWorthlessControl" runat="server">
   2:     <ContentTemplate>
   3:         Rob is so great!
   4:         <asp:Label ID="lblGreat" runat="server" Text="I love cats because they're awesome!"/>
   5:     </ContentTemplate>
   6: </uc1:WorthlessControl>

Just like you'd expect it to. Now, this control is obviously pointless, since it does nothing but expose a template then mimic it back to the page, but it gets you the basics of what's going on. You may want to have some of your own controls already on your control, like say some labels or something, then the custom area defined in the middle, that you can attach on to with a placeholder control. I'll start with that next time, then move on to your own templated databound control. It'll have MULTIPLE TEMPLATES OMG!!!! It's going to be SO AWESOME. You'll finally be as cool as your mom always said you were!

0 comments:

Designed by Posicionamiento Web | Bloggerized by GosuBlogger