One such design decision, which I think is very important in creating ASP.NET applications, is creating a base class (or several base classes) for all the pages in your application to inherit from which captures at the very least a high-level purpose of the page. I think for people who are new to ASP.NET, this might get overlooked from time-to-time, because when you add a new page to your project, it automatically creates the class files for you (which inherit directly from System.Web.UI.Page), and also because application development in ASP.NET tends to be "designer-driven" - a lot of tutorials on the web and a lot of the improvements made to the ASP.NET framework seem to focus on "Look Ma, I didn't need to write any lines of code!". The fact that people tend to miss this design decision is also evidenced by the fact that in the last couple of ASP.NET projects I worked on, none had implemented this design. But the benefits can be really crucial, especially when your project is on a fixed schedule, and above all - it's just good OO practise to do it this way.
I'll give you an example of this design in practise, and how it helped me save a few hours of work. You might find this really trivial, but I was just happy at the simplicity of it all, and if I can save a few people a few hours of work, all the better. So in this application, there are several pages (somewhere between 10 and 20) of data entry, which all follow a similar kind of design, but the specifics of the data entry are distinct between each page. All pages use a GridView control to display currently entered records and to allow the entry of new records, but the columns of data vary between each page. The "action" columns (containing Add, Edit, Update, Delete buttons) are consistent across all pages. So far so good. Now for the conflict - a change request comes along stating that when a certain condition is met, the user is to be denied access to data entry. This can be implemented in several ways, but the one that makes the most sense is to hide the action columns of the GridView control in each data entry page. To do this for one page, you would have to do something like this in your Load or PreRender event:
bool dataEntryCondition = ...;
// assuming that action columns are at the 7th and 8th indexes
myGridView.Columns(7).Visible = dataEntryCondition;
myGridView.Columns(8).Visible = dataEntryCondition;
...
I hate having to write code like this, because as soon as you re-order the columns in your GridView, it breaks. What's scary is, I've seen this kind of thing done a lot. A better solution would be something like this:
bool dataEntryCondition = ...;
for each (CommandField actionColumn in myGridView.Columns)
{
actionColumn.Visible = dataEntryCondition;
}
This is definitely better, but you can't just assume all CommandFields in your GridView are going to have this logic applied to them (well, you could assume that if you were implementing this for a single page, but the whole point is that we're thinking generally here... it will all come together soon). Also, in my case, the columns in question weren't actually instances of CommandField; because they required special formatting, I had to use TemplateFields instead. So, as you probably figured out by now, what I did was create my own class of column which inherits from TemplateField, then create a very lightweight interface for "toggle-able" fields. The reason for creating a custom field class and an interface was because if I ever needed another kind of custom field that extended from CommandField, I would still be able to get my toggle-able functionality. The declarations looked something like this:
interface IToggleField
{
public void toggleVisibility(bool visibleCondition);
}
class MyTemplateField extends TemplateField implements IToggleField
{
public void toggleVisibility(bool visibleCondition) { this.Visible = visibleCondition; }
}
You would then need to modify your GridView in the design view to use this new type of column[1]. And to toggle, just slightly modify the previous attempt to use this new class:
bool dataEntryCondition = ...;
// all columns in a GridView inherit from DataControlField
for each (DataControlField actionColumn in myGridView.Columns)
{
if (actionColumn.GetType() is IToggleField)
((IToggleField) actionColumn).toggleVisibility(dataEntryCondition);
}
Much better, yes? You're probably thinking "What's the big deal?". Well the big deal is, I have to make this change across 10-20 pages. Changing the GridView columns definitions to use this new class has to be done "manually" (because I shamefully did not use a custom GridView class for my data entry grids, but let's just ignore that little fact for now), but this is easy enough because the columns in question have the exact same definition across all pages (they were copy-pasted :P) which makes it easy to do a search-replace. But, (this is where it all comes together), because I made the easy design decision of all my data entry pages inheriting a DataEntryPage class, adding in the logic to toggle the columns visibility all of those pages, which could have been painful, is really simple! All I had to do was create a single function to do the above procedure, and call it in the page event of my choice from within the DataEntryPage class, which effectively registers the call in all implementers' events. And the result is clean, simple code, the way it should be in OO programming.
It's because of situations like this, that if I continue doing ASP.NET projects, I will make sure that whoever my development team is knows about these fundamental design choices. If my current experiences are anything to go by, I would suggest that a fair portion of the ASP.NET developers out there probably would have missed this solution. Obviously, it would had been harder to implement had the project been larger and further down the track in development (requiring more refactoring time), but as they always say - you'll never know whether someone's going to have to look at your code in the future, so in my opinion if you can afford to spend the time to refactor the code, it's definitely worth it.
I hope that this hasn't been too boring! It feels good to write about this though, even if it was a trivial problem. Hopefully, the problems I encounter in the future will be more difficult ;)
- Tokes
[1] - for details on how to use custom fields in your pages, and for a more in-depth look on how to make custom fields that actually do complex things, check out this article at MSDN Magazine: Custom Data Control Fields.