Sometimes I just write code. And sometimes I clean up my code. Most of the times, I focus on the meat of the methods, hacking away at verbose or lengthy flows, re-factoring out common code, trying to untangle overly complex logic etc.
Recently, I noticed that many of the conditional terms I write span very long lines and are a bit cumbersome to read. The reason for that is that many of the variable names are long, or the properties or both and that often the comparison is on a property of an object or member of a collection etc.
So for instance:
if (summerCatalog.Products[0].ProductCategories[0].ParentCategoryID >= 1 && summerCatalog.Products[0].ProductCategories[0].ParentCategoryID <= 4)
{
//...
}
Can become quite long.
Long is not easy to read.
Long is not easy to maintain.
Long is not easy to think through.
What I really wanted to say is if [value] is between [a] and [b].
Of course, one could say "lets just make the variable names shorter". But that flies in the face of self explanatory plain naming. So abbreviating for the sake of short-lineliness (New word! You heard it her first!) is out.
Well, this is just screaming "EXTENSION METHODS" doesn't it?
Here we go then:
/// <summary>
/// Returns whether the value is between the 2 specified boundaries.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="value">The value to compare</param>
/// <param name="minValueInclusive">The min value (inclusive).</param>
/// <param name="maxValueInclusive">The max value (inclusive).</param>
/// <returns>True if the value is equal to or between the boundaries; False otherwise.</returns>
public static bool Between<T>(this T value, T minValueInclusive, T maxValueInclusive) where T : IComparable<T>
{
if (minValueInclusive.CompareTo(maxValueInclusive) > 0)
{
throw new ArgumentException("minimum value must not be greater than maximum value");
}
return (value.CompareTo(minValueInclusive) >= 0 && value.CompareTo(maxValueInclusive) <= 0);
}
The Between function takes any object which supports IComparable<T> and performs syntactic sugar comparison with the inclusive min and max values.
What's more, it adds a basic sanity check for the comparison. How many times do I do that sanity check in my normal code (!)?
Now the conditional is
if (summerCatalog.Products[0].ProductCategories[0].ParentCategoryID.Between(1, 4))
{
///...
}
At least I don't have to refactor this in my brain while reading.
Sure, you might say, but you could have just de-referenced the deep value and then have a shorter conditional, like so:
Category category = summerCatalog.Products[0].ProductCategories[0];
if (category.ParentCategoryID >= 1 && category.ParentCategoryID <= 4)
{
//...
}Yes - of course - but it adds a line of code, places the burden of reading the very common idiom ( x >= a && x <= b) on me, and I constantly stumble on the lest-than-or-equal vs. less-than and it doesn't check for my boundaries being inadvertently swapped.
So there you have it. A simple extension cuts down your lines of code, makes long text shorter and saves lives. Which begs the question: is this already part of the language - and should it be?
a54628f4-2ccd-44e7-b039-ece0c3807dfc|0|.0
In this article we'll explore the use of classic ASP.NET controls in combination with extension methods in the MVC framework in order to facilitate development while remaining true to the MVC pattern and best practices.
A lot of hype around MVC these days. So, of course, yours truly is working on some project utilizing MVC. While brushing the dust off my raw HTML tag memory and designing the obvious: lists, grids, repeated item displays and the such, I thought "why not use the asp.net controls instead?"
For one, most of the ASP.NET data bound controls have a rich site of event post backs to hook up. MVC says: no dice! You can't rely on the event dispatch loop of asp.net. In fact, this is the whole point of going MVC – to create a simple, well separated delineation between the presentation and the BL. If you have all these states and post back logic pieces all in the code behind you really have a tightly coupled application.
What *is* available though, is the rendering of most controls. So instead of "for each" and "if" statements sprinkled in the "presentation" template, you can actually just use the plain old asp.net controls declaratively. I put "presentation" in quotation marks because the more code (branching is code!) you have in the View, the more brittle it becomes, the more tightly coupled to BL it is and the less testable it is.
The main advantages I find in using the old controls is that:
- Familiarity – My developers know them already
- Rich – Theming, localization behavior and layout aspects built in and available
- Well tested – These controls have been around a while, and are generally well behaved
- Modular – The controls isolate the presentation of a particular element and divorce it from the surrounding. In particular, it avoids issues of nesting and other inline logic in the presentation layer.
The main issue to overcome in a DataBoundControl is that with no code behind, how do you specify the data source declaratively? How do you do so with minimum code and maximum flexibility?
One solution is to use a data source object. Yes, they work. In most scenarios you can use them as you did before, but you must note that hooking up events (OnSelecting, OnSelected and the such) would again be challenging to do declaratively.
My approach for my project was to create an extension method for
DataBoundControl has a property "DataSource", to which you assign an object that will be data bound. The most permissive base object System.Object, so we simply create an extension method:
using System.Web.UI.WebControls;
public static class DataboundControlExtensions
{
/// <summary>
/// Extends <typeparamref name="System.Object"/> to bind it to a <typeparamref name="System.Web.UI.WebControls.DataList"/>
/// </summary>
/// <param name="data">The data source object to bind.</param>
/// <param name="control">The control to bind the data source object to.</param>
public static void BindTo(this object data, DataList control)
{
control.DataSource = data;
control.DataBind();
}
/// <summary>
/// Extends <typeparamref name="System.Object"/> to bind it to a <typeparamref name="System.Web.UI.WebControls.ListView"/>
/// </summary>
/// <param name="data">The data source object to bind.</param>
/// <param name="control">The control to bind the data source object to.</param>
public static void BindTo(this object data, ListView control)
{
control.DataSource = data;
control.DataBind();
}
}
Note that the control is passed in as the target and is strongly typed. As it turns out, you get a runtime error if you attempt to specify the control parameter typed as DataBoundControl – the base class for all data bound controls. In other words, this doesn't work:
public static void BindTo(this object data, DataBoundControl control) // Runtime error!
With the extension included in my project, I can now add the control and bind it in one code line:
<% Model.BindTo(dataList); %>
<asp:DataList runat="server" ID="dataList" RepeatColumns="2" RepeatDirection="Horizontal">
<ItemTemplate>
<fieldset>
<legend>
<%# Eval("ID")%></legend>
<p>
Title:
<%#Eval("Title") %>
</p>
<a href='<%#Eval("ImagePath") %>'>
<img src='<%#Eval("ThumbPath") %>' height="64" width="48" alt="pic"/></a>
<p>
Description:
<%#Eval("Description") %>
</p>
</fieldset>
</ItemTemplate>
</asp:DataList>
This is not as clean as completely declarative binding, but is much cleaner than having loop and branch constructs all over your view.
Most of you would note that the current MVC release includes HTML helpers. The Futures release is promising several more HTML helpers, which would provide with single line calls that would render lists, and other data bound presentation we are accustomed to. The reasons I didn't use the futures HTML helper methods are:
- The futures were not in release yet at time
- I don't love the mode in which the HTML helpers work
- The HTML helpers are not as rich as the asp.net components
The reason I don't love the HTML helpers is simple: they are string valued functions. As string valued functions, they must, internally generate strings and throw away memory. Even when you use StringBuilder, the repetitive appendage of strings creates discarded buffers in the underlying byte array. Asp.Net controls, on the other hand, have access to the underlying output stream via a TextWriter. This means that the appended string snippets do not create intermediary buffers but rather get copied more efficiently to the actual byte stream that would be shipped to the browser.
So if I need to add an HTML helper to MVC what I do is write an extension method that extends System.Web.MVC.ViewPage, like so
using System.Web.Mvc;
using System.Web.UI;
public static class HelloKittyHelper
{
public static void Meow(this ViewPage page, string text)
{
HtmlTextWriter writer = new HtmlTextWriter(page.Response.Output);
if (writer != null)
{
writer.RenderBeginTag(HtmlTextWriterTag.Pre);
writer.Write(KITTY_ASCII);
writer.Write(text);
writer.RenderEndTag();
}
}
/// <summary>
/// ASCII art of a kitty - source unknown.
/// </summary>
private const string KITTY_ASCII = @"
.-. __ _ .-.
| ` / \ |
/ '.()--\
| '._/
_| O _ O |_
=\ '-' /=
'-._____.-'
/`/\___/\`\
/\/o o\/\
(_| |_)
|____,____|
(____|____)
";
}
Then pass it the data from the Model or anything you like from the view:
<% 1: this.Meow("purrr.. meow"); %>
With the ambient objects in the page, I just extend "this", and internally utilize the HtmlTextWriter to write directly to the stream – no intermediary StringBuilder and hopefully less wasted immutable objects. The other advantage is that HtmlTextWriter can do things like begin/end tag tracking, add attributes and encode HTML as necessary. Leveraging existing well tested framework again, reducing costs of testing and education to deliver value early and often.
In conclusion, we can use the new MVC concepts and remain true to the separation of view and logic by leveraging existing components and developing minimal custom extensions that help us avoid the pitfalls of "presentation logic".
b21de456-c842-48b0-8ac0-819dbcc347b2|0|.0