1. Creating an n-Tier Design
Firstly, create a data layer as a class library that encapsulates your SQL. This layer will typically return DataSets or DataReaders, or indeed your own object that implements IListSource or another bindable interface. If you're using an object such as a data reader, use interfaces as a way to abstract the specific .NET data provider used. The benefits of a data tier:
- You can reuse the data layer across multiple front-ends;
- You can change the data access method (e.g. from Access to SQL Server) late in the development cycle;
- You can centralise all your SQL code into one location.
2. Working with Application Settings
Most people use ConfigurationSettings.AppSettings to get their values from the default <appSettings> section in web.config. But this needs abstraction - what if you change the name of the key, or want to encrypt the setting? In addition, this technique can be slow: every time you read web.config, you're reading one entry at a time.
Instead, why not create your own class that supplies application settings? Use shared / static methods to expose data and then store it either in web.config or even another location (for example, a registry hive - where you can apply ACLs).
You can also create your own configuration class that extends the web.config schema. To do this, you have to implement the IConfigurationSectionHandler interface. There's a area in web.config called <configSections> which allows you to add your own sections. Your class should have a static constructor that calls ConfigurationSettings.GetConfig("MyConfigSection") in order to return the settings, as well as a method that implements IConfigurationSectionHandler.Create that will be used by the ConfigurationSettings class to actually pull the entries out of web.config and expose them to the outside world.
3. Creating Maintainable User Interfaces
Note the difference between ASP.NET user controls and server controls. User controls are great for application-specific user interfaces; server controls are great for repeatable objects that are shared across multiple applications (such as those in the VS.NET toolbox).
For web page furniture such as menus and navigation controls, it can make sense to store the actual data in a database or an XML file and use a data-bound control so that the page itself doesn't need to be amended to modify content.
4. Creating Common Page Code
It can often be useful to override the built-in Page class (for example to add user tracking or exception publishing or even to override the OnLoad or OnError methods). Within an overridden OnLoad method, we obviously need to call the base method - but we can then add additional logic either before or after the base method is called. By default, the ASP.NET designer obviously inherits System.Web.UI.Page, so you need to manually modify this.
5. Tracking Users' Actions
Perhaps you want to add statistics on page visits: you could use a third-party application such as WebTrends, or you could build your own solution. Here we use the overridden Page class as described in the previous section, and add some code after the base OnLoad method that inserts the data into a database. The information stored could include the username, the referrer, the date/time and the browser type.
6. Notifications of Exceptions
The Microsoft Exception Management Block provides a consistent approach to exception management. You can decide to publish exceptions anywhere you want: to the event log, to a SQL Server database, or even to a pager. You can then modify the settings dynamically at runtime without having to make any changes to the application itself. To make this work, you need to add a publisher to web.config and then call ExceptionManager.Publish from within your exception catch block. You can create your own custom publishers by creating a class that implements the IExceptionPublisher interface.
7. Using Session Objects
One problem with session variables is that they are loosely typed: they return System.Object. Furthermore, you normally have to use a string as a key which is perhaps not ideal. Instead, why not create a separate class with static, typed members? To access the session, use HttpContext.Current.Session to reach the current HTTP context.
8. Handling Unexpected Errors Gracefully
So that your users don't see a graceless failure when you get an unhandled exception, override the OnError method using tip #4. Within the override, you can call the Exception Management Block (tip #6) and then call the base OnError method.
Also note that you can modify the <customErrors> section in web.config to redirect all errors to a default page (use the defaultRedirect attribute to set that page).
9. Assigning Roles and Securing Web Pages
Forms-based authentication is a great choice for internet sites. Modify the <authentication> section in web.config to point to a login page. In the login page, we check the credentials and then call FormsAuthentication.RedirectFromLoginPage to set an in-memory cookie to indicate the user is authenticated and redirect them back to the page they were originally attempting to reach.
To limit the scope of forms-based authentication, create a <location path="MySubdir"> element which contains <system.web> and then an <authorization> element. The simplest thing to use here is <deny users="?" /> to deny anonymous users.
Even using forms-based authentication, you can create some code to manage roles. In global.asax, handle the Application_AuthenticateRequest event and create a GenericPrincipal object from the GenericIdentity, which is then assigned to the Context.User object. Now you can simply use User.IsInRole("myrole") to test whether the user is in one of the built roles or not.
10. There Was No #10!
The demos were available for download in both VB.NET and C# formats.