Monday, July 31, 2006

Overview of Customizing Community Server

I have been learning a lot about customizing CommunityServer lately and it occurs to me that it would have been easier to do more faster if I had had an overview of the way CS is built. To that end, I will try to codify what I have observed.

Structurally, CS can be a little confusing because it appears to be a fully implemented asp.net 2.0 website application. The fact is that it is almost entirely 1.1, but was built by people who were very knowledgeable about the changes coming in 2.0. Whether its the use of master pages and skins, or the appearance of a global IsNullOrEmpty string checking method - the app seems to be 2.0. It isn't; the 2.0 version still uses the home-grown skins/master pages and many other 2.0-seeming features.

In terms of customizing the way your website looks or behaves you have to start with the aspx pages found in the various folders of the website application structure. These pages will point you to the various skins or controls in use. Yet, they will never (besides in controlpanel) point you to the code or visual features of the website. All of the real implementation occurs in the master pages, skins, and views, or in the control code in one of the included assemblies.

Each aspx page will identify some master page (not identifying one explicitly means it will use master.ascx) in its CS:MPContainer control declaration, and potentially one or more control declarations in CS:MPContent controls. The master pages are generally slim, and control overall layout of a page. The actual master pages are simply custom controls (ascx files) located in the /Themes/[current theme]/Masters folder of the website application. The most common base implementation of a master page is of 3 sections called 'lcr' (left side content), 'bcr' (body content, and 'rcr' (right side content). You can either define controls for every descendent page in these sections in the master page, or override master page content by declaring CS:MPContent controls with these ids on your aspx page. Skins should not implement these content controls. For example, if you wanted to understand how each thing is showing up on default.aspx, you would open default.aspx and find the 'ThemeMasterFile' attribute on the page level CS:MPContainer control. If you navigate to the HomeMaster.ascx file you'll see that the only thing being added here are some style includes in a "HeaderRegion", and the 3 content sections. In order to really understand where the content is coming from, you have to look at the controls declared within the various content sections.

A control declaration on a page or skin will carry a custom prefix defined on the page and the name of the control - this is standard asp.net customization. It is important to note such control names because first - along with the prefix declaration on the page - it will point you to the control code in the proper assembly where you can see how it is implemented. Second, the name is almost always identical to the the name of the skin that is used to display the control, with an added prefix of 'Skin-'. By default, all templated controls in CS will load a skin named "Skin-" + [the name of the control] + ".ascx".

While the master pages generally declare overall sections in which the various skins will be displayed, the aspx pages, skins, and views define the HTML and any additional sub-controls along with client or server side script to control display. I believe it is preferable to leave all html out of the aspx pages, and rely on the skins for this implementation. As a simple example, if we look this time at 'login.aspx' we'll see that the master file is not declared (so it is master.ascx) and all content is controlled by the "CS:Login" control. That means this control is declared in the CommunityServer.Controls.Login class, and its layout will be found in /Themes/default/Skins/Skin-Login.ascx. Sure enough, the layout of the login page is on this skin. The functionality (application logic) of this page is found in the class file.

This brings us to an important lesson about how all this comes together in the application: the class files control behavior by "wiring" properly named controls to certain events or operations on the back end. For instance, the DefaultButtonTextBox control for the password on Skin-Login.ascx must be named 'password' in order for the control logic to work properly. This magic takes place in the "AttachChildControls" method of each control which manipulates its members on the back end.

Using this basic knowledge we can then start to change how our website looks and behaves. Each templated or skinned control (those with skins) has a property "SkinName" which it inherits and will consult as the proper skin to apply if it has been supplied. Recall that if this property is null, then the skin named "Skin-[control name]" will be applied. Note that I have run into controls which ignore this property, but it is not the norm. As such, if we want to change how login.aspx looks we should create a new skin, and provide the name as a "SkinName" attribute on the control declaration on login.aspx. I think you should copy and rename the skins rather than alter them because it will save you headaches later if you try to upgrade CS, and clearly shows where you have made changes. When you fill in the "SkinName" attribute you use the full file name of the skin you created. This name may need to include the sub folder when you are dealing with blogs and galleries (i don't really have the nuances of these exceptions mastered but generally the controls from these assemblies automatically determine the folder which contains their skins so try that first. Aspx pages in the Blog subfolder are really an exception to most things I have said so far anyhow and I'll cover that later).

If you want to change the way login.aspx behaves you'll need to modify the Login class. Again, rather than modifying the class provided with CS, you should create a new assembly for your controls and extend the Login class via inheritance. You can change the name to match your modified skin if you have one, or leave the name the same. The only name collision issue I had working in VS 2003 was with the namespace including the CommunityServer.Controls prefix (so don't use MyCompany.CommunityServer.Controls, try CS.Controls) - the controls themselves are all fully prefixed in the aspx and ascx pages so there is no confusion there. I have found that the control classes aren't all designed real well for extension, so I often have to copy base class methods in order to modify behavior, but I am trying to guarantee that an upgrade will still work, and that I know where my code begins and CS provided code stops. Once you have your class built and the assembly included in your web project, you can change or add the TagPrefix declaration on your page and repoint the control declaration to your new custom control.

Blog Skin Exception

Blog aspx pages generally declare which view they are using, rather than a skin. These "Views" are found in /Themes/Blogs/[current blog theme]/Views and have the name "View-[view name].ascx. Views contain layout templates with various blog controls in them. Each control then has a skin named the same way I mentioned above for other controls. However, these controls have their skins in the /Themes/Blogs/[blog theme]/Skins folder, rather than with the other skins.
Submit this story to DotNetKicks