Keep Your Template Logic in the Template

Recently I stumbled upon this blog post. Unfortunately, Jonathan Holland has things completely wrong. First and foremost, the article is only speaking of presentation in all senses. And, in modern complex applications, presentation can easily include a little bit of logic to give it that "stickyness" the marketing types like. Your core, application logic should most definitely not live on ASP.NET web pages. Or preferably not even in the web project at all. But your template logic can very easily live in the template, and, if at all possible, should live more in the ASPX template than in the CodeBehind.

As some of Mr. Holland’s commenters point out, his method is fundamentally flawed to begin with because it still relies upon the rather loosey-goosey FindControl() to work it’s magic. Which is kind of self-defeating as reliance upon that method destroys some of the advantages he touts. But there are some other, deeper, more fatal flaws than reliance on that method:

  1. If you rely upon CodeBehind to do everything, you cannot change anything in the repeater without recompiling and redeploying the entire application, or at least just the main DLL. This means that making a minor text change will result in a complete recycle, dumping any existing sessions and forcing users to wait through ASP.NET’s spin-up cycle. This also means emergency fixes require a developer with Visual Studio and access to the whole project’s source. Whereas I have been known to push in an emergency template-level fix using notepad on the production server in a pinch.

    Even at design time, making a small change to output requires a recompilation of the application, completely slowing the most fungible part of the development process down considerably.

  2. All the server controls generate some nasty overhead from several angles. First, they have ViewState. Second, they are server controls so they will be parsed and loaded into the control tree. [EDIT: As Mr. Holland points out below, this is a bit unclear and partially wrong. HtmlControls also do appear in the control tree, but they are alot "lighter" than true WebControls--no event model, no ViewState.]

  3. In my opinion, it is much easier to make sense of the HTML when it lives in an HTML-style template rather than completely disconnected on the server side.

  4. If you are working with a separate design team, they will likely give you HTML for such things anyhow. Why go through the trouble of translating it into compiled server-side code? Moreover, most good designers these days can at least somewhat handle ASPX template-style code—they know not to go into the <%# %> land and generally can "read around" that stuff if need be. The smarter ones can even pick up enough DataBinding syntax to be almost useful.

Now, you might ask—what do I do when I need some conditional logic in the template. I surely must use Codebehind to power this? And, at least for the light sorts of logic one should need in the template, the answer is no, it all can be done inline. Even for heavier sorts of logic, one can make calls directly from the template to deeper application services if the requirements call for it. Let’s use a slightly more complex version of Mr. Holland’s rptrComment tied into a comments class with the following fields:

  • Guid Id
  • string AuthorName
  • Uri AuthorWeb
  • string AuthorEmail
  • string AuthorIpAddress
  • string CommentText
  • DateTime CommentTimeStamp
  • bool IsSuppressed

Now, insofar as the templating logic goes, your requirements state the following:

  • All users shall be able to see the AuthorName, AuthorWeb and CommentText and CommentTimeStamp fields for non-suppressed comments.
  • Article owners and administrators shall see the above fields, as well as AuthorEmail and AuthorIpAddress. Furthermore, they shall see all comments, even the suppressed ones.
  • Administrators shall have the ability to suppress comments.

Now, one could do this with a lot of server controls and codebehind. Or one could just push all the display-layer stuff into the template and call the back-end classes, like your SecurityHelper, from there:


<asp:Repeater runat="server" ID="CommentsRepeater" DataSource='<%# GetComments %>'>
    <HeaderTemplate>
        <ul class="commentcontainer">
    </HeaderTemplate>
    <FooterTemplate>
        </ul>
    </FooterTemplate>
    <ItemTemplate>
        <li runat="server" visible='<%# ((SitePoint.Core.Entities.Content.ArticleComment)Container.DataItem).IsSuppressed && !SitePoint.Core.Security.SecurityHelper.CanSeeCommentInfo(User) ? false : true %>'>
           <p><%# string.IsNullOrEmpty(((SitePoint.Core.Entities.Content.ArticleComment)Container.DataItem).CommentText) ? "No comment . . ." : ((SitePoint.Core.Entities.Content)Container.DataItem).CommentText %></p>
           <p>
               By : <a href='<%# ((SitePoint.Core.Entities.Content.ArticleComment)Container.DataItem).AuthorWeb.ToString() %>'><%# ((SitePoint.Core.Entities.Content.ArticleComment)Container.DataItem).AuthorName %></a> on <%# ((SitePoint.Core.Entities.Content.ArticleComment)Container.DataItem).CommentTimeStamp.ToShortDateString() %> at <%# ((SitePoint.Core.Entities.Content.ArticleComment)Container.DataItem).CommentTimeStamp.ToShortTimeString() %></p>
           <p runat="server" visible='<%# SitePoint.Core.Security.SecurityHelper.CanSeeCommentInfo(User) %>'>
                <a href='mailto:<%#((SitePoint.Core.Entities.Content.ArticleComment)Container.DataItem).AuthorEmail %>'><%# ((SitePoint.Core.Entities.Content.ArticleComment)Container.DataItem).AuthorEmail %></a> 
                • 
                IP: <%# ((SitePoint.Core.Entities.Content.ArticleComment)Container.DataItem).AuthorIpAddress %>
                <asp:LinkButton runat="server" ID="SuppressCommentButton" CommandArgument='<%# ((SitePoint.Core.Entities.Content.ArticleComment)Container.DataItem).Id %>' Text='[Suppress Comment]' Visible='<%# !((SitePoint.Core.Entities.Content.ArticleComment)Container.DataItem).IsSuppressed && SitePoint.Core.Security.CanSuppressComment(User) %>' OnClick="DoSuppressComment" />
           </p>
        </li>
    </ItemTemplate>
</asp:Repeater>

Now, at first glance, that looks hideously complex. But it really is not—90% of the DataBinding code is casting your Container’s [the RepeaterItem] DataItem to it’s natural class: ((SitePoint.Core.Entities.Content.ArticleComment)Container.DataItem). Once one looks past this (or just adds a <% @ Reference %> which I am loath to do), it becomes much more digestible—in all reality a simple ASPX template, nothing more nothing less. Furthermore, it is far more malleable than pushing all this template logic in your codebehind as you can tweak, reload, tweak and reload without rebuilding things.

One question you might be asking is "Why do all that casting instead of calling DataBinder.Eval? Well, because, when one knows what class your DataItem exposes, there are more than a few advantages:

  1. Intellisense generally works with this tactic. I should note here that using this much DataBinding can, in some cases, drive the designer batty and it need not always be listened to.
  2. DataBinder.Eval is much slower as it uses reflection (see the note on this page).
  3. We can call methods on our now stongly-typed reference. DataBinder.Eval() only lets us output strings.

A second question you might ask is "why are you using runat=’server’ on some of those HTML tags?" The reason here is that these have become your containers for parts of the post you wish to suppress. First, on the <li> element, we hide the entire thing if the ArticleComment.IsSuppressed and if the user is does not have the right privileges. Later on we added a <p> to contain the administrative functions on a given post.

In the interest of full disclosure, I should point out the one significant issue with this approach: refactoring. Modern tools, such as Resharper and to a lesser extent Visual Studio 2005+, feature some pretty good tools for automating naming and locational changes in code. Because the code in your ASPX templates is not compiled until runtime, these tools often miss this angle. So, if you have a big refactoring job, you will have to touch each of the template files to ensure they are compatible with the back-end still. Of course, if you have a big refactoring job, you will probably be touching much of the front-end code anyhow, so this is likely not that horrible a downside.

Remember kids, template logic in the template is a good thing. And don’t let the evil man tell you otherwise.

kick it on DotNetKicks.com

Win an Annual Membership to Learnable,

SitePoint's Learning Platform

  • Jonathan Holland

    Perhaps you don’t realize that the entire core comment handling was done in the Comment business object? The entire project follows correct 3 tier style.

    All I was doing was binding the Repeater to the business object. That is definitely presentation layer code. There is no business logic going on here, thats in a totally separate assembly.

    Your example is hideously complex, and still relies on server controls (runat=”server” generates a HTMLControl in the control tree, there is no advantage of this over using Web Controls).

    Perhaps I’m alone in striving for keeping code out of my markup, I don’t care. The code behind event model was created exactly for this reason, its your own fault if you don’t want to take advantage of it.

    Keep code where it belongs, in the code file. You even highlight the weakness’s of your approach (Refactoring???).

    As someone who has written thousands of lines of classic ASP, I understand the strength of the code behind model. I also understand the nightmare that is inline code. I refuse to do it, and I’ve maintained far too much code that is written like your above example.

    And your first point, that making a quick change requires a rebuild is completely worthless. Every large project I’ve worked on has had designated build / test / and deploy dates. Only solo coders ham out a fix and push it to the server without going through the proper channels of Build / QA / Deploy.

    Your article is entirely an opinion piece with a lot of animosity.

    Grow up.

  • Jonathan Holland

    In addition, Tell me how static ASP server controls have view state?

    Do you even understand view state?

  • wwb_99

    First, there is no need to get personally angry about this. I never intended any animosity towards you, only a bit of honest intellectual disagreement. Welcome to the blogosphere.

    Now, with that out of the way . . .

    Your example is hideously complex, and still relies on server controls (runat=”server” generates a HTMLControl in the control tree, there is no advantage of this over using Web Controls).

    Is it really more complex than casting a bunch of controls using FindControl? Especially when some other developer could easily come by, change some ids and break your codebehind bad? Let’s say you were doing a bit more than assigning values to the controls, but rather showing and hiding things? Is the template less understandable? Moreover, could you farm it out to a designer and have them retreat the code and make it pretty?

    Valid point in some ways. Still, HtmlControls are far, far ‘lighter’ to deal with from the server-side than WebControls with fully featured event models. Nor do they have ViewState, which is a good thing in most, if not all, cases.

    As someone who has written thousands of lines of classic ASP, I understand the strength of the code behind model. I also understand the nightmare that is inline code. I refuse to do it, and I’ve maintained far too much code that is written like your above example.

    Oh, I have my fair share of ASP.old and PHP code of various qualities under my belt, so I understand where you are coming from. But there is a big difference between inline code for application logic and inline code for handling local template issues. Funnily enough, one is now seeing many of the PHP template engines turning to using an ASP.NET style codebehind model, where the template uses PHP to call back-end objects to template things inline.

    And your first point, that making a quick change requires a rebuild is completely worthless. Every large project I’ve worked on has had designated build / test / and deploy dates. Only solo coders ham out a fix and push it to the server without going through the proper channels of Build / QA / Deploy.

    Might be the line of business I am in, but telling someone that “no, we cannot make this minor fix to the way the Comments displays because you need to wait on the next build cycle” holds no water. Back-end logic changes, yeah, you can make the argument. But the front-end needs to be very agile. To give you an idea how agile one can be with the front-end here, we have one site that has gone through no less than 6 complete reskinnings/UI refreshes without ever recompiling it. Why? Because the finer bits of the display logic were pushed out to the templates.

  • Jonathan Holland

    First, there is no need to get personally angry about this. I never intended any animosity towards you, only a bit of honest intellectual disagreement. Welcome to the blogosphere.

    Well, perhaps you can work on your execution, because your post came across as both rude and arrogant.

    Especially when some other developer could easily come by, change some ids and break your codebehind bad?

    This is true for any control, only that the site won’t compile if someone changes any other control id. Honestly, a developer should never muck around with the control ID’s without fully understanding how the control is used and with full testing. Even the most amateur developer knows that changing the control ID only changes the name of the control and its reference in the designer file, but does not update the code behind.

    This is a strawman argument, it holds no weight at all. I’m sorry you work with such horrible developers.

    Still, HtmlControls are far, far ‘lighter’ to deal with from the server-side than WebControls with fully featured event models. Nor do they have ViewState, which is a good thing in most, if not all, cases.

    ASP:Label and ASP:Literal controls only have viewstate if you change their attributes on postback. Since this never happens in this scenario, there is no viewstate stored for them.

    BTW, HTMLControls inherit from Control, so they do have viewstate (if enabled).

    Might be the line of business I am in, but telling someone that “no, we cannot make this minor fix to the way the Comments displays because you need to wait on the next build cycle” holds no water. Back-end logic changes, yeah, you can make the argument. But the front-end needs to be very agile. To give you an idea how agile one can be with the front-end here, we have one site that has gone through no less than 6 complete reskinnings/UI refreshes without ever recompiling it. Why? Because the finer bits of the display logic were pushed out to the templates.

    My argument is too have more builds. And honestly, any site that can be reskinned without recompiling will be simple enough that an actual build deployment would take all of what, 5 seconds longer than pushing the aspx pages?

  • marshallpenguin

    Wyatt, having a blog doesn’t give you a right to be rude or arrogant. And calling someone evil because they don’t see eye to eye with you is really a very bad idea.

    It’s ok to post controversial stuff, but next time be sure to be respectfull and make it crystal clear it is only your perspective. Don’t act like you know it all when you don’t.

    I was actually very surprised to see this sort of article on sight point.

    This only a bit of honest intellectual disagreement; welcome to the blogosphere.

  • Betty

    Lets see if I can post here (for some reason I had trouble posting on Jonathan Hollands blog meaning I lost my first comment so I reposted a smaller less critical one).

    Even the most amateur developer knows that changing the control ID only changes the name of the control and its reference in the designer file, but does not update the code behind.

    Yes but the amateur dev will also think that they can find the now broken references to that control by compiling. Alot of the time they won’t look for a reference to the control used by the magic FindControl function. They change the id and it still compiles fine? sweet push it out for QA/Deployment.

    My major new problem with Jonathan Hollands approach was the following line CommentDateTime.Text = currentComment.ShortDateTime;, surely you would consider ShortDateTime to be layout/formatting and it should be in the presentation layer. (although I generally consider the aspx + the aspx.cs the presentation layer and a code library to be the business logic).

    All that casting in the aspx is quite ugly, strongly typed repeaters would be a really nice thing.

    ...

    Pity you can’t nicely use generics Repeater anyone? >?

  • Betty

    Ah the joy of blog formatting, looks like I mananged to mangle the end of my comment there quite nicely.

    All that casting in the aspx is quite ugly, strongly typed repeaters would be a really nice thing.

    ...

    Pity you can’t nicely use generics >?

  • Betty

    Damnit. I’m just gonna let someone clean that up, the idea wasn’t that exciting anyway. I guess if I could be bothered registering I could probably have just edited my comments. <asp:repeater<SitePoint.Core.Entities.Content.ArticleComment>>

  • Jonathan Holland

    Betty:

    Comment.ShortDateTime is just a property that returns a string in the business layer. Instead of returning a DateTime Object and having to cast it to a string, its easier to have an additional property in the BLL. Why repeat yourself? :)

    I find fault with ASP.NET and nested controls. They should be exposed strongly typed, yet they are not, hence the need for FindControl. If you spend any time at all writing custom controls in ASP.NET you will find that you need to use FindControl. It is a necessary evil.

  • goykanok

    The jolly website, more with googleandme.

  • honeymonster

    I would prefer:

    '>

    By : '>
    on
    '>
    '>
    • IP:

    '
    Text='[Suppress Comment]' Visible=''
    OnClick="DoSuppressComment" />

    This would have to be backed by some extension methods:


    public static class SecurityHelper
    {
    public static bool CanSeeCommentInfo(this IPrincipal user) {
    throw new NotImplementedException();
    }

    public static bool CanSuppressComment(this IPrincipal user)
    {
    throw new NotImplementedException();
    }
    }

    In the real MVC, the view goes to the model directly. That is implemented by an object datasource which can be a static facade or another object with default contructor which reads its conf. from the conf file.

    I use Eval since it allows a much shorter notation. I also import the namespaces so that I don’t need wyatt’s long paths.

    Really the only thing needed to be implemented in the codebehind here is the actions, such as DoSuppressComments. This allows you (or designer) to change the page, possibly pulling in more entities using another objectdatasource, without touching the codebehind.

    Wyatt is right that Eval uses reflection internally and thus is slightly (!) slower than direct access. However, with the number of typecasts in your example, I’m not so certain that is would in fact be faster. Anyhow, this is negligible.

    The real problems here is that we are being let down by the MS ASP.NET team. C# got generics in C# 2.0, but the templates of ASP.NET still just assumes dataitems are objects. IMO repeaters and other data aware controls should expose a generic Item property method that – when bound to a statically typed datasource – exposed the item as statically typed.

    Also, I would like to see a generic version of FindControl, enabling me to write FindControl(“Name”) – and use it strongly typed.

  • honeymonster

    Grrrrrrrr. Sitepoint needs to fix that code block quoting. :-(

    Here goes a again:

    ————————————————————————-

    I would prefer:

    <asp:ObjectDataSource id="dsComments" TypeName="SitePoint" SelectMethod="GetArticleComments" runat="server">
    <SelectParameters>
    <asp:QueryStringParameter Type="Int32" Name="articleId" QueryStringField="Id" />
    </SelectParameters>
    </asp:ObjectDataSource>
    <asp:Repeater runat="server" ID="CommentsRepeater" DataSourceID="dsComments">
    <HeaderTemplate>
    <ul class="commentcontainer">
    </HeaderTemplate>
    <ItemTemplate>
    <li id="Li1" runat="server" visible='<%# !(bool)Eval("IsSuppressed") || SecurityHelper.CanSeeCommentInfo(User) %>'>
    <p><%# Eval("CommentText") ?? "no comments . . ." %></p>
    <p>By : <a href='<%# Eval("AuthorWeb") %>'><%# Eval("AuthorName") %></a>
    on <%# Eval("CommentTimeStamp","g") %></p>
    <p id="P1" runat="server" visible='<%# User.CanSeeCommentInfo() %>'>
    <a href='mailto:<%# Eval("AuthorEmail") %>'><%# Eval("AuthorEmail") %></a>
    • IP:
    <%# Eval("AuthorIpAddress") %>
    <asp:LinkButton runat="server" ID="SuppressCommentButton" CommandArgument='<%# Eval("Id") %>'
    Text='[Suppress Comment]' Visible='<%# !(bool)Eval("IsSuppressed") && User.CanSuppressComment() %>'
    OnClick="DoSuppressComment" />
    </p>
    </li>
    </ItemTemplate>
    <FooterTemplate>
    </ul>
    </FooterTemplate>
    </asp:Repeater>

    This would have to be backed by some extension methods (C# 3.0):


    public static class SecurityHelper
    {
    public static bool CanSeeCommentInfo(this IPrincipal user) {
    throw new NotImplementedException();
    }

    public static bool CanSuppressComment(this IPrincipal user)
    {
    throw new NotImplementedException();
    }
    }

    In the real MVC, the view goes to the model directly. That is implemented by an object datasource which can be a static facade or another object with default constructor which reads its conf. from the conf file.

    I use Eval since it allows a much shorter notation. I also import the namespaces so that I don’t need Wyatts long paths.

    Really the only thing needed to be implemented in the codebehind here is the actions, such as DoSuppressComments. This allows you (or designer) to change the page, possibly pulling in more entities using another objectdatasource, without touching the codebehind.

    Wyatt is right that Eval uses reflection internally and thus is slightly (!) slower than direct access. However, with the number of typecasts in your example, I’m not so certain that is would in fact be faster. Anyhow, this is negligible.

    The real problems here is that we are being let down by the MS ASP.NET team. C# got generics in C# 2.0, but the templates of ASP.NET still just assumes dataitems are objects. IMO repeaters and other data aware controls should expose a generic Item property method that – when bound to a statically typed datasource – exposed the item as statically typed.

    Also, I would like to see a generic version of FindControl, enabling me to write FindControl<TextBox>(“Name”) – and use it strongly typed.

  • honeymonster

    Well, just forget it then…..

  • Orbit

    I’m totally with Jonathan on this – Wyatt you should be more humble in your opinions, especially when theyr’e wrong ;-)

  • Dave

    The way I look at it is to use inline when there is not much logic, codebehind when that becomes ugly (as Wyatt’s example did), and a server- or user-control within the template if I am handling too many of the Repeater’s events.

  • Elyzion

    Is it really more complex than casting a bunch of controls using FindControl? Especially when some other developer could easily come by, change some ids and break your codebehind bad?

    If another developer breaks your codebehind, he obviously doesn’t know what hes doing and therefore shouldn’t be developing on your application.

    My major new problem with Jonathan Hollands approach was the following line CommentDateTime.Text = currentComment.ShortDateTime;, surely you would consider ShortDateTime to be layout/formatting and it should be in the presentation layer. (although I generally consider the aspx + the aspx.cs the presentation layer and a code library to be the business logic).

    If you want to create this presentation logic so badly, create your own control with a public property for string formatting. Then you can change the property on the control and not have to recompile.

    I kinda regret reading this page.