In this section I'll take the problems described in the first section and discuss how to solve them by devising a technical system design. In practice, you will design and implement the following:
A good-looking graphical template (layout) that appears the same with Internet Explorer and Firefox, and a mechanism to dynamically apply different color schemes and other appearance attributes to it.
A way to easily share the created template to all pages of the site, without physically copying and pasting the entire code to each page.
A navigation system that enables you to easily edit the links shown in the site's menu, and clearly tells users where they currently are in the site map, enabling them to navigate backward.
A way to apply not only a common design to all pages of the site, but also a common behavior, such as counting page views or applying the user's favorite style to the page.
I'll describe how you can utilize some of the new features in ASP.NET 2.0 when implementing your reusability, menu, navigation, and customization requirements. Later, in the "Solution," section, you'll put these powerful new features into action!
When you develop a site design you typically create a mock-up with a graphics application such as Adobe Photoshop or Jasc Paint Shop Pro to show you what the final site may look like before you do any specific layout or coding in HTML. Once you have a mock-up, you can show this around to the various model users, testers, and managers, who can then make a decision to proceed with coding. You might create a simple picture like the one shown in Figure 2-1, in which you show how the content will be laid out in the various areas of the page.
This is a typical three-column layout, with a header and footer. When the layout gets approved, you must recreate it with real graphics and HTML. Do this in the graphics program because, on average, it takes much less time for a web designer to produce these mock-ups as images, rather than real HTML pages. Once the client approves the final mock-up, the web designer can cut the mock-up image into small pieces and use them in an HTML page.
Creating a mock-up is not always easy for those of us who aren't very artistic by nature. I must admit that I'm one of the worst graphic artists I know. For a medium or large-size company, this is not a problem because there is usually someone else, a professional web designer, to create the graphical design, and then the developers (people like you and me) will build the application around it. Sometimes it can be helpful to enlist the assistance of a third-party company if you're faced with creating the graphical design by yourself — you can, in effect, subcontract that one aspect of the site to someone more artistically talented, and they can make you a site template. For the purpose of creating the web site discussed in this book, I used TemplateMonster (www.templatemonster.com) to create a good-looking site design that I could use as a starting point. They provided the design as PSD files (to be opened with Photoshop or Paint Shop Pro), JPEG files, and some HTML pages with the images already cut in slices and positioned using HTML tables. I found that it was not possible to use their pre-built HTML pages verbatim because I wanted to create my own styles and customize my HTML markup, but it was very helpful to have a visual template to start with. This can give your site a professional feel early in the game, which can help you sell your design to the appropriate people.
ASP.NET 2.0 is the overriding technology that makes the site work. This runs on the web server and takes advantage of the functionality provided by the .NET Framework. However, ASP.NET does not run on the user's computer; instead, it dynamically generates the elements a browser uses to render a page. These elements that are sent down to the browser consist of HTML, images, and Cascading Style Sheets (CSS), which provide colors, sizes, and alignments for various items in the HTML. ASP.NET also generates some JavaScript procedural code that is also sent down to the browser to handle data validation and to tell the browser how to interact with the web server.
HTML is defined in several ways. You can use the visual form designer in Visual Studio to drop controls onto the form, and this automatically creates HTML code. Or, you can hand-edit or author your own HTML code in the .aspx files to give it features that aren't easily specified in the form designer. Lastly, HTML can be dynamically generated by your C# code, or by classes in the .NET Framework.
ASP.NET 1.x used a "code behind" model: HTML (and some presentation-oriented C# code) was put in an .aspx file, and implementation C# code would go into a separate file that would be inherited by the .aspx file. We call the .aspx file "the page" because that's where the visual web page is defined. This provided some separation between presentation code and the related implementation code. One problem with this model is that the auto-generated code created by the form designer would be placed in the same files that the developer uses for his code.
ASP.NET 2.0 modifies the code-behind model and uses a new 2.0 feature of the .NET Framework called partial classes. The idea is simple: Allow one class to span more than one file. Visual Studio will auto-generate at runtime the code for declaring the controls and registering events, and then it will combine that with the user-written code; the result is a single class that is inherited by the .aspx page. The @Page directive declared in the .aspx page uses the CodeFile attribute to reference the .cs code-behind file with the user-written code.
Another change in .ASP.NET 2.0 is the elimination of project files. Projects are now determined based on folders on your hard disk. Also, code for all pages of a project was generated into one .dll by ASP.NET 1.x, but now ASP.NET 2.0 generates code separately for each page. Why does this matter? You don't have to re-deploy large amounts of code when changes are made to one page only. You only need to re-deploy the code for the individual page(s) that changed, which gives you more granular control over change management.
It is not possible to give an exhaustive explanation of CSS in this book, but I'll cover some of the general concepts. You should consult other sources for complete details about CSS. The purpose of CSS is to specify how visual HTML tags are to be rendered by specifying various stylistic elements such as font size, color, alignment, and so on. These styles can be included as attributes of HTML tags, or they can be stored separately and referred to by name or ID.
Sometimes HTML files have the styles hard-coded into the HTML tags themselves, such as the following example:
<div style="align: justify; color: red; background-color: yellow; font-size: 12px;">some text</div>
This is bad because it is difficult to modify these stylistic elements without going into all the HTML files and hunting for the CSS attributes. Instead, you should always put the style definitions in a separate stylesheet file with an extension of .css; or if you insist on including styles inside an HTML file, you should at least define them in a <style> section at the top of the HTML file.
When you group CSS styles together, you can create small classes, which syntactically resemble classes or functions in C#. You can assign them a class name, or ID, to allow them to be referenced in the class= attribute of HTML tags.
If you use stylesheet classes and you want to change the font size of all HTML tags that use that class, you only need to find that class's declaration and change that single occurrence in order to change many visual HTML elements of that given type. If the stylesheet is defined in a separate file you will benefit even more from this approach, because you will change a single file and n pages will change their appearance accordingly.
The primary benefits of using CSS are to minimize the administrative effort required to maintain styles and to enforce a common look and feel among many pages. Beyond this, however, CSS also ensures safety for your HTML code and overall site. Let's assume that the client wants to change some styles of a site that's already in production. If you've hard-coded styles into the HTML elements of the page, then you'd have to look in many files to locate the styles to change, and you might not find them all, or you might change something else by mistake — this could break something! However, if you've used style classes stored separately in CSS files, then it's easier to locate the classes that need to be changed, and your HTML code will be untouched and safe.
Furthermore, CSS files can make a site more efficient. The browser will download it once and then cache it. The pages will just link to that cached instance of the .css file and not contain all the styles again, so they will be much smaller, and therefore faster to download. In some cases this can dramatically speed up the loading of web pages in a user's browser.
Here is an example of how you can redefine the style of the DIV object shown above by storing it in a separate file named styles.css:
.mystyle { align: justify; color: red; background-color: yellow; font-size: 12px; }
Then, in the . aspx or .htm page, you will link the CSS file to the HTML as follows:
<head> <link href="/styles.css" text="text/css" rel="stylesheet" /> <!-- other metatags... --> </head>
Finally, you write the HTML division tag and specify which CSS class you want it to use:
<div class="mystyle">some text</div>
Note that when the style was declared, I used the dot (.) prefix for the class name. You have to do this for all of your custom style classes.
If you want to define a style to be applied to all HTML objects of a certain kind (for example, to all <p> paragraphs, or even the page's <body> tag) that don't have another explicit class associated with them, you can write the following specification in the stylesheet file:
body { margin: 0px; font-family: Verdana; font-size: 12px; } p { align: justify; text-size: 10px; }
This sets the default style of all body tags and all <p> (paragraph) tags in one place. However, you could specify a different style for some paragraphs by stating an explicit class name in those tags.
Yet another way to associate a style class to a HTML object is by ID. You define the class name with a # prefix, as follows:
#header { padding: 0px; margin: 0px; width: 100%; height: 184px; background-image: url(images/HeaderSlice.gif); }
Then you could use the id attribute of the HTML tag to link the CSS to the HTML. For example, this is how you could define an HTML division tag and specify that you want it to use the #header style:
<div id="header">some text</div>
You typically use this approach for single objects, such as the header, the footer, the container for the left, right, center column, and so on.
Finally, you can mix the various approaches. Suppose that you want to give a certain style to all links into a container with the sectiontitle class, and some other styles to links into a container with the sectionbody class. You could do it this way:
In the .css file
.sectiontitle a { color: yellow; } .sectionbody a { color: red; }
In the .aspx/.htm file
<div class="sectiontitle"> some text <a href="http://www.wrox.com">Wrox</a> some text </div> <div class="sectionbody"> some other text <a href="http://www.wiley.com">Wiley</a> some other text </div>
Sometimes developers will use HTML tables to control the positioning of other items on a web page. This was considered the standard practice before CSS was developed, but many developers still use this methodology today. Although this is a very common practice, the W3C officially discourages it (www.w3c.org/tr/wai-webcontent), saying "Tables should be used to mark up truly tabular information ("data tables"). Content developers should avoid using them to lay out pages ("layout tables"). Tables for any use also present special problems to users of screen readers." In other words, HTML tables should be used for displaying tabular data on the page, not to build the entire layout of the page. For that, you should use container controls (such as DIVs) and their style attribute, possibly through the use of a separate <style> section or a separate file. This is ideal for a number of reasons:
If you use DIVs and a separate stylesheet file to define appearance and position, you won't need to repeat this definition again and again, for each and every page of your site. This leads to a site that is both faster to develop and easier to maintain.
The site will load much faster for end users! Remember that the stylesheet file will be downloaded by the client only once, and then loaded from the cache for subsequent requests of pages until it changes on the server. If you define the layout inside the HTML file using tables, the client instead will download the table's layout for every page, and thus it will download more bytes, with the result that downloading the whole page will require a longer time. Typically, a CSS-driven layout can trim the downloaded bytes by up to 50%, and the advantage of this approach becomes immediately evident. Furthermore, this savings has a greater impact on a heavily loaded web server — sending fewer bytes to each user can be multiplied by the number of simultaneous users to determine the total savings on the web server side of the communications.
Screen readers, software that can read the text and other content of the page for blind and visually impaired users, have a much more difficult job when tables are used for layout on the page. Therefore, by using a table-free layout, you can increase the accessibility of the site. This is a very important requisite for certain categories of sites, such as those for public administration and government agencies. Few companies are willing to write off entire groups of users over simple matters like this.
CSS styles and DIVs provide greater flexibility than tables. You can, for example, have different stylesheet files that define different appearances and positions for the various objects on the page. By switching the linked stylesheet, you can completely change the appearance of the page, without changing anything in the content pages themselves. With dynamic ASP.NET pages, you can even change the stylesheet at runtime, and thus easily implement a mechanism that enables end users to choose the styles they prefer. And it's not just a matter of colors and fonts — you can also specify positions for objects in CSS files, and thus have a file that places the menu box on the upper-left corner of the page, and another one that puts it on the bottom-right corner. Because we want to allow users to pick their favorite styles from a list of available themes, this is a particularly important point.
CSS enables you to target different classes of devices in some cases without requiring new HTML markup, such as mobile devices like PDAs or smartphones. Due to their constrained screen size, it is necessary to adapt the output for them, so that the content fits the small screen well and is easily readable. You can do this with a specific stylesheet that changes the size and position of some containers (placing them one under the other, rather than in vertical columns), or hide them completely. For example, you might hide the container for the banners, polls, and the header with a big logo. Try to do this if you use tables — it will be much more difficult. You'll have to think about a custom skinning mechanism, and you'll need to write separate pages that define the different layouts available. This is much more work than just writing a new CSS file.
Note |
Note that the discussion above referred to the use of tables for the site's overall layout. However, using tables is acceptable to create input forms with a tabular structure, because otherwise too much CSS code would be required in that case to be easy writeable and maintainable. It's also not very likely that you'll need to dynamically change the layout of the input form, so you don't need all the flexibility of CSS for that, and using HTML tables is more immediate. |
Once you finish creating your beautiful site design, you need to find a way to quickly apply it to n pages, where n could be dozens or even hundreds of pages. In the previous edition of this book for ASP.NET 1.x, we followed the classic approach of isolating common parts of the design into user controls files, to be imported into all pages that needed them. Specifically, we had a user control for the header, and another for the footer. Although this is immensely better than actually replicating all code in all pages, and much better than including files of classic ASP (because of their object-oriented nature), that still wasn't ideal. The problem with this approach was that for each and every page, you would still need to write some lines in .aspx files to import the controls, and other lines to place the controls where you wanted them to appear on the page. Thus, if you place them somewhere on the first page, and somewhere else on the second page, the two pages would appear differently at runtime. You don't want to pay attention to these details every time you create a new content page; instead, you want to focus on the content for that particular page, and have the common layout be applied to all pages consistently and automatically. What you really want is some sort of visual inheritance in practice, where you define a "base" page and have other pages inherit its layout. With ASP.NET 1.x, however, you could apply inheritance just at the code-behind level, and thus affect the behavior of the page (e.g., what to do when the page loads, unloads, or renders), not its appearance. There were partial workarounds for this issue, but I personally didn't find any that really satisfied me with regard to functionally and design-time support. At last, the problem is solved in ASP.NET 2.0.
ASP.NET 2.0 introduces a new "master page" feature that enables you to define common areas that every page will share, such as headers, footers, menus, and so on. A master page enables you to put the common layout code in a single file and have it visually inherited in all the content pages. A master page contains the overall layout for your site. Content pages can inherit the appearance of a master page, and place their own content where the master page has defined a ContentPlaceHolder control. Although this has the effect of providing a form of visual inheritance, it's not really implemented with inheritance in an OOP sense — instead, the underlying implementation of master pages is based on a template model.
An example is worth a thousand words, so let's see how this concept turns into practice. A master page has a .master extension and is similar to a user control under the covers. Following is some code for a master page that contains some text, a header, a footer, and defines a ContentPlaceHolder control between the two:
<%@ Master Language="C#" AutoEventWireup="true" CodeFile="MasterPage.master.cs" Inherits="MasterPage" %> <html> <head id="Head1" runat="server"> <title>TheBeerHouse</title> </head> <body> <form id="Main" runat="server"> <div id="header">The Beer House</div> <asp:ContentPlaceHolder ID="MainContent" runat="server" /> <div id="footer">Copyright 2005 Marco Bellinaso</div> </form> </body> </html>
As you see, it is extremely similar to a standard page, except that it has a @Master directive at the top of the page instead of a @Page directive, and it declares one or more ContentPlaceHolder controls where the .aspx pages will add their own content. The master page and the content page will merge together at runtime — therefore, because the master page defines the <html>, <head>, <body> and <form> tags, you can easily guess that the content pages must not define them again. Content pages will only define the content for the master's ContentPlaceHolder controls, and nothing else. The following extract shows an example of a content page:
<%@ Page Language="C#" MasterPageFile="~/MasterPage.master" AutoEventWireup="true" CodeFile="MyPage.aspx.cs" Inherits="MyPage" Title="The Beer House - My Page" %> <asp:Content ID="MainContent" ContentPlaceHolderID="MainContent" Runat="Server"> My page content goes here... </asp:Content>
The first key point is that the @Page directive sets the MasterPageFile attribute to the virtual path of the master page to use. The content is placed into Content controls whose ContentPlaceHolderID must match the ID of one of the ContentPlaceHolder controls of the master page. In a content page, you can't place anything but Content controls, and other ASP controls that actually define the visual features must be grouped under the outermost Content controls. Another point to note is that the @Page directive has a new attribute, Title, that allows you to override the value specified in the master page's <title> metatag. If you fail to specify a Title attribute for a given content page, then the title specified on the master page will be used instead.
Figure 2-2 provides a graphical representation of the master page feature.
When you edit a content page in Visual Studio, it properly renders both the master page and the content page in the form designer, but the master page content appears to be "grayed out." This is done on purpose as a reminder to you that you can't modify the content provided by the master page when you're editing a content page.
I'd like to point out that your master page also has a code-beside file that could be used to write some C# properties and functions that could be accessed in the .aspx or code-beside files of content pages.
When you define the ContentPlaceHolder in a master page, you can also specify the default content for it, which will be used in the event that a particular content page doesn't have a Content control for that ContentPlaceHolder. Here is a snippet that shows how to provide some default content:
<asp:ContentPlaceHolder ID="MainContent" runat="server"> The default content goes here... </asp:ContentPlaceHolder>
Default content is helpful to handle situations in which you want to add a new section to a number of content pages, but you can't change them all at once. You can set up a new ContentPlaceHolder in the master page, give it some default content, and then take your time in adding the new information to the content pages — the content pages that haven't been modified yet will simply show the default content provided by the master.
The MasterPageFile attribute at the page level may be useful if you want to use different master pages for different sets of content pages. If, however, all pages of the site use the same master page, it's easier to set it once for all pages from the web.config file, by means of the <pages> element, as shown here:
<pages masterPageFile="~/Template.master" />
If you still specify the MasterPageFile attribute at the page level however, that attribute will override the value in web.config for that single page.
You can take this a step forward and have a master page be the content for another master page. In other words, you can have nested master pages, whereby a master page inherits the visual appearance of another master page, and the .aspx content pages inherit from this second master page. The second level master page can look something like the following:
<%@ Master Language="C#" MasterPageFile="~/MasterPage.master" AutoEventWireup="true" CodeFile="MasterPage2.master.cs" Inherits="MasterPage2" %> <asp:Content ID="Content1" ContentPlaceHolderID="MainContent" Runat="Server"> Some other content... <hr style="width: 100%;" /> <asp:ContentPlaceHolder ID="MainContent" runat="server" /> </asp:Content>
Because you can use the same ID for a ContentPlaceHolder control in the base master page and for another ContentPlaceHolder in the inherited master page, you wouldn't need to change anything in the content page but its MasterPageFile attribute, so that it uses the second-level master page.
This possibility has great promise because you can have an outer master page that defines the very common layout (often the companywide layout), and then other master pages that specify the layout for specific areas of the site, such as the online store section, the administration section, and so on. The only problem with nested master pages is that you don't have design-time support from within the Visual Studio IDE (as you do for the first-level master page). When editing content pages, you must code everything from the Source View in the editor, and you can only see the result from the browser when you view the page. This is not much of a problem for developers, like me, who prefer to write most of the code themselves in the Source View, but having the option of using nested master pages is a good thing!
You also have the capability to access the master page from a content page, through the Page's Master property. The returned object is of type MasterPage, which inherits directly from UserControl (remember that I said master pages are similar to user controls) and adds a couple of properties. It exposes a Controls collection, which allows you to access the master page's controls from the content page. This may be necessary if, for example, in a specific page you want to programmatically hide some controls of the master page, such as the login or banner boxes. Accessing the Controls collection directly would work, but would require you to do a manual cast from the generic Control object returned to the right control type, and you would be using the weakly typed approach. A much better and objected-oriented approach is to add custom properties to the master page's code-beside class — in our example, wrap the Visible property of some control. This is what you could write:
public bool LoginBoxIsVisible { get { return LoginBox.Visible; } set { LoginBox.Visible = value; } }
Now in the content page you can add the following line after the @Page directive:
<%@ MasterType VirtualPath="~/MasterPage.master" %>
With this line you specify the path of the master page used by the ASP.NET runtime to dynamically create a strongly typed MasterPage class that exposes the custom properties added to its code-beside class. I know that it seems a duplicate for the MasterPageFile attribute of the @Page directive, but that's how you make the master page properties visible in the content page. You can specify the master type not just by virtual path (as in the example above), but also by name of the master page's class, by means of the TypeName attribute. Once you've added this directive, in the content page's code-beside file (or in a <script runat="server"> section of the .aspx file itself), you can easily access the master page's LoginBoxIsVisible property in a strongly typed fashion, as shown below:
protected void Test_OnClick(object sender, EventArgs e) { this.Master.LoginBoxIsVisible = false; }
When I say "strongly typed" I am implying that you'll have Visual Studio Intellisense on this property, and that's true: Type "this.Master." and when you press that second period, you'll see your new property in the Intellisense list!
This methodology of accessing master objects from content pages is also particularly useful when you want to put common methods in the master page, to be used by all the pages that use it. If we didn't have access to a strongly typed MasterPage object built at runtime by ASP.NET, you'd need to use reflection to access those methods, which is slower and certainly much less immediate to use (in this case, it would have been easier to put the shared methods in a separate class that every page can access).
For those of you who read the first edition of this book, I'd like to point out a difference between using an OOP base page and using a Master Page. In the first edition, we defined a base class called ThePhile that was inherited by all of the "content" pages. This was true OOP inheritance at work, but it was of limited usefulness because we couldn't inherit any kind of visual appearance from it. We still had to create user controls to achieve common visual elements. However, in ASP.NET 2.0, when we define a master page, we are able to get full visual inheritance (but not OOP code inheritance). The lack of code inheritance is not a serious limitation because we can access the code in the master page through a MasterType reference, as explained above.
The last thing I want to describe in this introduction to master pages is the capability to dynamically change the master page used by a content page at runtime! That's right, you can have multiple master pages and pick which one to use after the site is already running. You do this by setting the page's MasterPageFile property from within the page's PreInit event handler, as follows:
protected void Page_PreInit(object sender, EventArgs e) { this.MasterPageFile = "~/OtherMasterPage.master"; }
The PreInit event is new in ASP.NET 2.0, and you can only set the MasterPageFile property in this event handler because the merging of the two pages must happen very early in the page's life cycle (the Load or Init event would be too late).
When changing the master page dynamically, you must make sure that all master pages have the same ID for the ContentPlaceHolder controls, so that the content page's Content controls will always match them, regardless of which master page is being used. This exciting possibility enables you to build multiple master pages that specify completely different layouts, allowing users to pick their favorite one. The downside of this approach is that if you write custom code in the master page's code-beside file, then you will need to replicate it in the code-beside class of any page; otherwise, the content page will not always find it. In addition, you won't be able to use the strongly typed Master property, because you can't dynamically change the master page's type at runtime; you can only set it with the @MasterType directive. For these reasons we will not use different master pages to provide different layouts to the user. We will instead have just one of them, to which we can apply different stylesheet files. Because we've decided to use a table-free layout, we can completely change the appearance of the page (fonts, colors, images, and positions) by applying different styles to it.
Themes are a new feature in ASP.NET 2.0 that enable users to have more control over the look and feel of a web page. A theme can be used to define color schemes, font names, sizes and styles, and even images (square corners vs. round corners, or images with different colors or shades). The new "skin" support in ASP.NET 2.0 is an extension of the idea behind CSS. Individual users can select a theme from various options available to them, and the specific theme they choose determines a "skin" that specifies which visual stylistic settings will be used for their user session. Skins are a server-side relative of a CSS stylesheet. A skin file is similar to a CSS file but, unlike CSS, a skin can override various visual properties that were explicitly set on server controls within a page (a global CSS specification can never override a style set on a particular control). You can store special versions of images with themes, which might be useful if you want several sets of images that use a different color scheme based on the current skin. However, themes do not displace the need to use CSS; you can use both CSS files and skin files to achieve a great deal of flexibility and control. Speaking of stylesheet files, there's nothing new with them in ASP.NET 2.0 other than a few more controls that allow you to specify a CssClass property; and a few more controls have visual designer support to enable you to select a "canned" CSS specification.
A theme is a group of related files stored in a subfolder under the site's /App_Themes folder, which can contain the following items:
Stylesheet .css files that define the appearance of HTML objects.
Skin files — These are files that define the appearance of server-side ASP.NET controls. You can think of them as server-side stylesheet files.
Other resources, such as images.
One cool thing about the way ASP.NET 2.0 implements themes is that when you apply a theme to the page (you'll learn how to do this shortly), ASP.NET automatically creates a <link> metatag in each page for every .css file located in the theme's folder at runtime! This is good because you can rename an existing CSS file or add a new one, and all your pages will still automatically link to all of them. This is especially important because, as you will see, you can dynamically change the theme at runtime (as you can do with the master page) and ASP.NET will link the files in the new theme's folder, thus changing the site's appearance to suit the preferences of individual users. Without this mechanism you would need to manually create all the <link> metatags at runtime according to the theme selected by the user, which would be a pain.
The best new feature in the category of themes is the new server-side stylesheets, called skin files. These are files with a .skin extension that contain a declaration of ASP.NET controls, such as the following one:
<asp:TextBox runat="server" BorderStyle="Dashed" BorderWidth="1px" />
Everything is the same as a normal declaration you would put into an .aspx page, except that in the skin file you don't specify the controls' ID. Once you apply the theme to your page(s), their controls will take the appearance of the definitions written in the skin file(s). For a TextBox control it may not seem such a great thing, because you could do the same by writing a style class for the <input> HTML element in a .css stylesheet file. However, as soon as you realize that you can do the same for more complex controls such as the Calendar or the DataGrid (or the new GridView control), you will see that it makes much more sense, because those controls don't have a one-to-one relationship with an HTML element, and thus you could not easily define their style with a single class in the classic stylesheet file.
Note |
You can have a single .skin file in which you place the definition for controls of any type, or you can create a separate .skin file for every control type, such as TextBox.skin, DataGrid.skin, Calendar.skin, etc. At runtime, these files will be merged together in memory, so it's just a matter of organizing things the way you prefer. |
To apply a theme to a single page, you use the Theme attribute in the @Page directive:
<%@ Page Language="C#" Theme="NiceTheme" MasterPageFile="~/MasterPage.master" ... %>
To apply it to all pages, you can set the theme attribute of the <pages> element in web.config, as follows:
As for master pages, you can also change the theme programmatically, from inside the PreInit event of the Page class. For example, this is how you apply the theme whose name is stored in a Session variable:
protected void Page_PreInit(object sender, EventArgs e) { if (this.Session["CurrentTheme"] != null) this.Theme = this.Session["CurrentTheme"]; }
Note |
In Chapter 4, we will improve this mechanism by replacing the use of Session variables with the new Profile properties. |
Important |
When you use the Theme attribute of the @Page directive (or the theme attribute in web.config), the appearance attributes you specify in the skin file(s) override the same attributes that you may have specified in the .aspx files. If you want themes to work like .css stylesheets — whereby you define the styles in the .skin files but you can override them in the .aspx pages for specific controls — you can do that by linking to a theme with the StylesheetTheme attribute of the @Page directive, or the styleSheetTheme attribute of the <pages> element in web.config. Try not to confuse the Theme attribute with the StylesheetTheme attribute. |
So far, I've described unnamed skins — namely, skins that define the appearance of all the controls of a specific type. However, in some cases you will need to have a control with an appearance that differs from what you've defined in the skin file. You can do this in three different ways:
As described above, you can apply a theme with the StylesheetTheme property (instead of the Theme property), so that the visual properties you write in the .aspx files override what you write in the skin file. However, the default behavior of the theming mechanism ensures that all controls of some type have the same appearance, which was intended for situations in which you have many page developers and you can't ensure that everyone uses attributes in the .aspx pages only when strictly required.
Disable theming for that control only, and apply the appearance attributes as normal, such as in the following code:
<asp:TextBox runat="server" ID="btnSubmit" EnableTheming="False" BorderStyle="Dotted" BorderWidth="2px" />
Use a named skin for a control, which is a skin definition with the addition of the SkinID attribute, as shown below:
<asp:Label runat="server" SkinID="FeedbackOK" ForeColor="green" /> <asp:Label runat="server" SkinID="FeedbackKO" ForeColor="red" />
When you declare the control, you'll need to use a matching value for its SkinID property, such as the following:
<asp:Label runat="server" ID="lblFeedbackOK" Text="Your message has been successfully sent." SkinID="FeedbackOK" Visible="false" /> <asp:Label runat="server" ID="lblFeedbackKO" Text="Sorry, there was a problem sending your message." SkinID="FeedbackKO" Visible="false" />
In my opinion, this is the best way to go, because it enables you to define multiple appearances for the same control type, all in a single file, and then apply them in any page. Additionally, if you keep all style definitions in the skin files instead of in the pages themselves, you'll be able to completely change the look and feel of the site by switching the current theme (which is the intended purpose behind themes). Otherwise, with hard-coded styles, this is only partially possible.
In the "Solution" section of this chapter, you'll use themes to create a few different visual representations for the same master page.
As I said in the "Problem" section, you need to find some way to create a menu system that's easy to maintain and easy for users to understand. You might think that you can just hard-code the menu as HTML, but that's not a great choice because you'd need to copy and paste the code if you want to have the menu in more than one place (in this case, in the header and in the footer), or when you want to add or modify some link. In the first edition of this book, we developed a custom control that took an XML file containing the site map (i.e., the name and URL of the links to display on the menu) and built the HTML to render on the page by applying a XSL file to it. ASP.NET 2.0 introduces some new built-in controls and features that enable you to do more or less the same, but with more functionality that makes things easier for the developer.
The menu options are specified in an XML site map file. The main site map for the whole site is named web.sitemap, with a hierarchical structure of <siteMapPath> nodes that have the title and url attributes. The following extract provides an example:
<?xml version="1.0" encoding="utf-8" ?> <siteMap xmlns="http://schemas.microsoft.com/AspNet/SiteMap-File-1.0" > <siteMapNode title="Home" url="~/Default.aspx" /> <siteMapNode title="About" url="~/About.aspx" /> <siteMapNode title="Contact" url="~/Contact.aspx" /> </siteMap>
In our case we'll have first-level nodes, such as Home, Contacts and About, and also second and maybe third-level nodes such as Store/Shopping Cart. Here is a more complex example showing some second-level entries; it also refers to another child sitemap file that provides the menu entries for the Store:
<?xml version="1.0" encoding="utf-8" ?> <siteMap xmlns="http://schemas.microsoft.com/AspNet/SiteMap-File-1.0" > <siteMapNode title="Books" url="~/Books/Books.aspx> <siteMapNode title="Authors" url="~/Books/Authors.aspx" /> <siteMapNode title="Publishers" url="~/Books/Publishers.aspx" /> </siteMapNode> <siteMapNode title="DVDs" url="~/DVDs/DVDs.aspx> <siteMapNode title="Movies" url="~/DVDs/Movies.aspx" /> <siteMapNode title="Songs" url="~/DVDs/Songs.aspx" /> </siteMapNode> <siteMapNode siteMapFile="~/StoreMenu.sitemap" /> </siteMap>
The child siteMap (StoreMenu.sitemap) has the same format as the web.sitemap, and it has an outer siteMap node.
Once you've defined the sitemap file, you can use it as a data source for new ASP.NET 2.0 controls such as Menu and TreeView. ASP.NET 2.0 also introduces new nonvisual controls called DataSource controls that can link to a database, XML file, or component class. These controls will be used by graphical controls to retrieve the data to be bound and displayed onscreen. In practice, they serve as a bridge between the actual data store and the visual control. The SiteMapDataSource is one of these DataSource controls, specifically designed for the site's web.sitemap file, and defined like this:
<asp:SiteMapDataSource ID="SiteMapDataSource1" runat="server" />
Note that you don't define the path for the web.sitemap file, as there can be only one site map for the whole site and it is named ~/web.sitemap. When creating child site maps for limited subfolders within your site, the binding is handled transparently because they are linked from the web.sitemap.
Note |
If you don't like the way the default SiteMapDataSource control works (because you may want to support multiple sitemap files, or you may want to store the site map in the database rather than in XML files), you need to either write a new DataSource control or create a new provider class that transparently provides content for SiteMapDataSource. |
The menu control creates popular DHTML fly-out menus, with vertical or horizontal orientation. In ASP.NET 1.1 there were no decent standard Menu controls and it seemed like every web component developer company offered their own component to create these menus. However, with ASP.NET 2.0 we have a standard menu control built in for free and it has the capacity to integrate with the various data source controls. To create a Menu control bound to the SiteMapDataSource defined above, you simply need the following line:
<asp:Menu ID="mnuHeader" runat="server" DataSourceID="SiteMapDataSource1" />
Of course, the Menu control exposes many properties that allow you to specify its orientation (the Orientation property), the CSS class to use (CssClass) or the appearance of its various parts, the number of inner map levels that will be displayed in the fly-outs (StaticDisplayLevels), and much more. However, complete coverage of these properties is beyond the scope of this book; you should refer to the official MSDN documentation for details.
Displaying the sitemap with a tree view representation instead of the fly-out menus involves a replacement of the Menu declaration with the new TreeView control, as shown below:
Besides showing the menu, you also want to provide users with a visual clue as to where they are, and some way to allow them to navigate backward from their current location to the home page. This is usually done through the use of breadcrumbs, i.e., a navigation bar that shows links to all pages or sections, starting from the home page, that the user visited to arrive on the current page, such as the following:
Home > Store > Shopping cart
With this navigation system, the user can go back two pages without pressing the browser's Back button (which may not be visible for a number of reasons) and without starting over from the home page and trying to remember the path previously followed. With ASP.NET 2.0, you can add a breadcrumb bar with a single line of code by declaring an instance of the new SiteMapPath control on the page:
<asp:SiteMapPath ID="SiteMapPath1" runat="server" />
As usual, this control has a number of properties that enable you to fully customize its look and feel, as you'll see in practice in the section "Solution."
All ASP.NET 2.0 built-in standard controls render well-formatted XHTML 1.0 Transitional code by default. XHTML code is basically HTML written as XML, and as such it must comply with much stricter syntax rules. For example, all attribute values must be enclosed within double quotes, all tags must have a closing tag or must be explicitly self-closing (e.g., no more <br> and <img src="...">, but <br /> and <img src="..." />), and nested tags must be closed in the right order (e.g., no more <b><p> hello</b> Marco</p> but <p><b>Hello</b> Marco</p>). In addition, many HTML tags meant to format the text, such as <font>, <center>, <s>, etc., are now deprecated and should be replaced by CSS styles (such as font-family: Verdana; text-align: center). The same is true for some attributes of other tags, such as width and align, among others. The reasoning behind this new standard is to attain a greater separation of presentation and content (something I've already explained earlier), and to create cleaner code — code that can be read by programs that work with XML data. The fact that ASP.NET 2.0 automatically renders XHTML code, as long as you use its controls, is a great time saver for the developer, and makes the process of getting used to XHTML smoother. The official W3C documentation about XHTML 1.0 can be found at http://www.w3.org/TR/xhtml1/.
As for accessibility, the W3C defines a set of rules meant to ease the use of the site by users with disabilities. The official page of the Web Content Accessibility Guidelines 1.0 (commonly referred as WCAG) can be found at www.w3.org/TR/WCAG10/. Section 508 guidelines were born from WCAG, and must be followed by U.S. federal agencies' sites. You can read more at www.section508.gov/. For example, you must use the alt attribute in <img> tags to provide an alternate text for visually impaired users, so that screen readers can describe the image, and you must use the <label> tag to associate a label to an input field. Other guidelines are more difficult to implement and are not specifically related to ASP.NET, so you can check out the official documentation for more information. ASP.NET 2.0 makes it easier to follow some of the simpler rules, such as those mentioned above. For example, the Image control has a new GenerateEmptyAlternateText that, when set to true, generates alt="" (setting AlternateText="" would generate nothing instead), and the Label control has the new AssociatedControlID property that is set to the name of an input control, and at runtime generates the <label> control for it (this should be used together with the AccessKey property, to create shortcuts to the input field).
If you want to read more about XHTML, accessibility, and the new ASP.NET 2.0 features that pertain to this subject, you can refer to the following free online articles: Alex Homer's "Accessibility Improvements in ASP.NET 2.0 - Part 1" (www.15seconds.com/issue/040727.htm) and "Accessibility Improvements in ASP.NET 2.0 - Part 2" (www.15seconds.com/issue/040804.htm), or Stephen Walther's "Building ASP.NET 2.0 Web Sites Using Web Standards" (http://msdn.microsoft.com/asp.net/default.aspx?pull=/library/en-us/dnaspp/html/aspnetusstan.asp).
Master pages and themes do a great job of sharing the same design and look and feel among all pages of the site. However, you may also want the pages to share some common behavior, i.e., code to run at a certain point of their life cycle. For example, if you want to log access to all pages so that you can build and show statistics for your site, you have to execute some code when the page loads. Another case where you need to run some code for every page is when you need to set the page's Theme property in the PreInit event handler. It's true that you can isolate the common code in an external function and just add a line of code to execute it from within each page, but this approach has two drawbacks:
You must never forget to insert that line to call the external function when you design a new page. If multiple developers are creating .aspx pages — which is often the case — you will need to make sure that nobody forgets it.
You may want to run some initialization from inside the PreInit event and some other code from the Load event. In this case, you have to write two separate xxxInitialize methods, and add more lines to each page to call the proper method from inside the proper event handler. Therefore, don't rely on the fact that adding a single line to each page is easy, because later you may need to add more and more. When you have hundreds of pages, I'm sure you'll agree that going back and modifying all the pages to add these lines is not a workable solution.
These two disadvantages are enough to make me discard that option. Another option is to write the common code in the master page's code-behind. This may be a very good choice in many situations. Not in our case, however, because we must handle the PreInit event, and the MasterPage class (and its base classes) do not have such an event. You can handle the Init or Load events, for example, but not PreInit, so we must think about something else.
In the previous edition of this book there was a BasePage class from which all the content pages would inherit, instead of inheriting directly from the standard System.Web.UI.Page class. I believe this is still the best option, because you can handle any page event from inside this class by overriding the OnXXX methods, where XXX is the event name.
The snippet that follows is a basic skeleton for such a custom base class that inherits from Page and overrides the OnPreInit and OnLoad methods:
public class BasePage : System.Web.UI.Page { protected override void OnPreInit(EventArgs e) { // add custom code here... base.OnPreInit(e); } protected override void OnLoad(EventArgs e) { // add custom code here... base.OnLoad(e); } }
The classes in the pages' code-beside files will then inherit from your custom BasePage, rather than the standard Page, as shown below:
public partial class Contact : BasePage { // normal page code here... }
You still need to change some code in the code-beside class of every page, but once that's done you can later go back to BasePage, add code to the exiting methods or overload new methods, and you will not need to modify any additional lines in the code-beside classes. If you take this approach initially, you'll modify the code-beside classes one by one as you create them, so this will be easy and it gives you a future-proof design.