One of the interesting things about ASP.NET is that it’s such a huge
and flexible development system that every team finds their own way of
working with it. Microsoft has their recommended approaches, but they’ve
enabled plenty of alternative ways of skinning a Web site. Recently I was
in a position of helping a somewhat resource-constrained team get a fairly
substantial site up and running with ASP.NET 2.0, and I thought it might
be worth sharing some of the things we did on the way to a successful
launch. Perhaps these ideas – some mainstream, some wild – will give you
some notions for your own online development.
Start With a Known Quantity
As you may already know, ASP.NET 2.0 introduces a new way to think
about organizing the files for your Web site. While the new Web Site
Projects do offer advantages for some situations, they can be problematic
to manage from a source-code control perspective. More importantly,
they can represent a substantial learning curve for developers steeped in
the “old” way of doing things. Fortunately, if you don’t want to go up
that learning curve right now (and with a mix of old a new code to
support, we wanted to avoid the curve as long as possible), there’s an
alternative:
Visual Studio Web Application Projects.
Web Application Projects allow you to use the Visual Studio .NET 2003
Web project model with Visual Studio 2005. They’re supported by Microsoft,
and at the moment the necessary bits are at the RC1 stage. Our team found
them to be quite stable, and the familiarity of the project model more
than made up for the few minor bugs we hit. If you’re happy with the VS
.NET 2003 way of creating Web applications, you should definitely check
this add-in out.
Lightweight Deployment
One of the earliest (and perhaps most unusual) decisions we made was to
combine our deployment process with our source code control process. We
use Subversion for version
control in this particular team. Rather than adopt another tool for
deployment, we simply installed a Subversion client on the Web server
itself. This made deployment a matter of running svn update
on the server to pull down the most recent copy of the files for the site.
This isn’t a completely ideal solution (it does put some extra files on
the Web server), but it’s quick and easy.
Using this approach does require some discipline and coordination
between developers and whoever updates the Web server. I’m a strong
advocate of not checking broken code into the source code repository, but
that becomes even more critical when there’s a chance of broken code being
deployed where the customer can see it. In our case, we required testing
before commits to the repository, and only the project manager updated the
live copy on the Web site.
Managing the Environment
With the same codebase running on developer workstations and the live
server, we were faced with another problem: how could we manage
application settings that should be different between development and
production boxes? For example, we didn’t want our developers mucking about
in the live database, nor did we want the live server using our test SMTP
server to relay e-mails. The answer proved to be somewhere between
“clever” and “disgusting hack,” depending on your point of view, but in
practice it’s working quite well for us.
We keep a Globals
object in our code to access various
useful things, including this AppSettings
method:
// Return an app setting from the web.config file
// uses key_PROD if file _prod exists & key exists
// else uses key_STAGE if file _stage exists & key exists
// else uses key_MACHINENAME based on machine name if key exists
// else uses base key
public static string AppSettings(string SettingName)
{
System.Configuration.Configuration config =
System.Web.Configuration.WebConfigurationManager.
OpenWebConfiguration(HttpContext.Current.Request.ApplicationPath);
string val;
if (File.Exists(HttpContext.Current.Server.MapPath("~/_prod")))
{
try
{
val =
config.AppSettings.Settings[SettingName + "_PROD"].Value;
if (val != String.Empty)
return val;
}
catch (Exception ex) {}
}
if (File.Exists(HttpContext.Current.Server.MapPath("~/_stage")))
{
try
{
val =
config.AppSettings.Settings[SettingName + "_STAGE"].Value;
if (val != String.Empty)
return val;
}
catch (Exception ex) {}
}
try
{
val = config.AppSettings.Settings[SettingName +
"_" + System.Environment.MachineName].Value;
if (val != String.Empty)
return val;
}
catch (Exception ex){}
return config.AppSettings.Settings[SettingName].Value;
}
By using the Globals.AppSettings()
method instead of
calling the built-in AppSettings
collection, we have the
flexibility to use a single web.config
file but still allow
each developer to put their own settings in the file. We can also put in
master overrides for staging and production servers without knowing the
server names. For example, a small snippet of the web.config
file might look like this:
<add key ="SMTPServer" value ="localhost"/>
<add key ="SMTPServer_MELVIN" value ="mail.example.com"/>
<add key ="SMTPServer_PROD" value ="live.example.com"/>
Retrieving the value of Globals.AppSettings("SMTPServer")
will return “mail.example.com” if the code is run on a machine named
MELVIN, “live.example.com” on any production server (production servers
being identified by the presence of a zero-byte file named
_prod
in the application’s root directory) and “localhost” on
any other machine. A similar function sorts out multiple settings in the
<connectionStrings>
section of web.config
the same way.
Conserving Developer Resources
One of our big problems was how to put together a comparatively large
site without sufficient developer resources. On the other hand, we did
have a dedicated project manager on the customer side, and some other
resources who knew basic HTML but not ASP.NET. In designing the site, we
did our best to leverage these other resources by making it possible to
maintain whole pages outside of the Visual Studio environment using a
simple HTML editor, or even Notepad. Within the application, we have
various ASP.NET pages that look something like this:
<%@ Page Language="C#" MasterPageFile="~/Site.Master"
AutoEventWireup="true" CodeBehind="faq.aspx.cs"
Inherits="TheSite._faq" %>
<%@ MasterType TypeName="TheSite.Site" %>
<asp:content id="content1" contentplaceholderid="MainContent"
runat="server" >
<div style="padding: 10px;">
<asp:Literal ID="FAQLiteral" runat="server"
Text="FAQ here">
</asp:Literal>
</div>
</asp:content>
In the corresponding code-behind file there’s a single method:
protected void Page_Load(object sender, EventArgs e)
{
FAQLiteral.Text = File.ReadAllText(Server.MapPath("~/faq.html"));
}
The net result is that all the “meat” of this particular page comes out
of a single HTML file that can be edited without all the ASP.NET
scaffolding by pretty much anyone who understands the business
requirements. We equipped the whole team, developers and non-developers
alike, with TortoiseSVN, an
Explorer-integrated Subversion client that makes version control almost
trivially simple. With minimal training, the non-developers could supply
content and leave our few high-end developers to code the tricky part of
the site.
Design it Once With Master Pages
There are a whole bunch of new features in ASP.NET 2.0, and you have to
balance off learning them against actually turning out billable work. One
that proved to be a big win for us was the new master pages system, which
allows you to divide a page into a region of stuff that doesn’t change
(like your site logo and menu) and a region of stuff that changes from
page to page (the FAQ or product detail or whatever other content the page
is concerned with.
Using master pages is pretty easy. The master page itself is a normal
ASP.NET page:
<%@ Master Language="C#" AutoEventWireup="true"
Codebehind="Site.master.cs" Inherits="TheSite.Site" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html >
<head id="Head1" runat="server">
<title>Page title goes here</title>
</head>
<body>
<form id="form1" runat="server">
<table cellpadding="0" cellspacing="0">
<tr>
<td>
<img src="~/images/header.gif" />
</td>
</tr>
<tr>
<td>
<asp:ContentPlaceHolder ID="MainContent"
runat="server">
</asp:ContentPlaceHolder>
</td>
</tr>
</table>
</form>
</body>
</html>
Of course, in a real site, your designers will make the master page
much fancier than this bear-bones little example. The key difference
between the master page and any other page is the presence of the
ContentPlaceHolder
control. This control (or controls – you
can have more than one on a single master page) provides the rectangular
region that will be filled with the content from individual pages. On each
page, a Content
control, whose contentplaceholderid property
matches the ID property of the ContentPlaceHolder
control,
provides the bits to be poured into the blank. Look back at the FAQ page
earlier in the article to see how the bits fit together.
Once you get the hang of it, master pages are easy to work with, and
Visual Studio provides good design time support for them. In addition, you
have full access to the properties of the master page from code in the
individual content pages. For instance, you can write:
this.Master.Page.Title = "Frequently Asked Questions";
And yes, ASP.NET 2.0 does make it easy to set page titles (at
last!).
Lessons Learned
I could go on, picking out other things we did in developing this
particular site, but I’d like to end by stepping back and talking about
overall development philosophy for a moment. It seems that just about
every team these days is required to turn out more product with fewer
resources than might have been thought reasonable a few years ago. Rapid
development products like ASP.NET 2.0 and Visual Studio 2005 can help make
up part of the deficit, but you also have to work smarter instead of
harder to have any hope of delivering sufficient value to please your
customers. Keep these key points in mind as you plan your technical
strategy for any new project:
- New product features are only worth learning if the time invested
pays off in more functionality in the same time or the same functionality
in less time. No customer ever cared that you used generics instead of
collections just because they’re more modern. - Use the whole team, not just developers. It doesn’t make
sense to make a few people the bottleneck. - A low tech solution that works is better than a fancy solution
that you never quite get the bugs out of. - You’re getting paid to build a site (or other product), not to
prove how smart you are.
Remember, there are a lot of other teams out there who would be happy
to do the same work you’re doing, and probably at a lower price. Your job
is to convince the customer that you’re delivering the best bang for their
bucks. Start there, not from the tools, and you’ll be in good shape.
About the Author
Mike Gunderloy is the author of over 20 books and numerous articles on
development topics, and the Senior Technology Partner for Adaptive Strategy, a
Washington State consulting firm. When
he’s not writing code, Mike putters in the garden on his farm in eastern
Washington state.