Microsoft Content Management Server 2002 Developers Guide

Content Management Server
 

Microsoft Corporation

April 2003

Applies to:
     Microsoft® Content Management Server 2002

Summary: Discover best practices, tips, and other guidance for coding solutions with Microsoft Content Management Server (MCMS) 2002. Learn how to set up multi-developer environments which might need to be shared with operations staff. (40 printed pages)

Contents

Introduction
Introduction to Content Management Server 2002
Setting Up a Shared-Source Multi-Developer Environment
Building an MCMS 2002 Site From Scratch
Debugging MCMS 2002 Code
Using Channel Rendering Script
Images on File System vs. Images in Resource Gallery
Creating Custom Placeholders
Creating a Custom Utility Class
Working with MCMS Modes
Using the WebAuthorContext Class
Writing Workflow Event Handlers
Making Programmatic Changes to the Site
Using the CmsApplicationContext
Using CMS 2002 and Web Services
Customizing the Authoring Connector for Word
Use of Custom Property Searches
Glossary
Appendix A: Sample Application for Bulk Changes
Appendix B: Sample Code For Calendar Placeholder Control
Appendix C: Sample Code for Extending the Web Author Toolbar

Introduction

This document contains best practices, tips, and other guidance for developers who are coding solutions for Microsoft® Content Management Server (MCMS) 2002. It also contains guidance for setting up multi-developer environments which might need to be shared with operations staff.

The document assumes that site development will be done in ASP.NET (MCMS 2002 also has legacy support for ASP-based sites). It also assumes a basic understanding of the development of MCMS 2002 sites. Code samples are provided in Microsoft Visual C#®.

Relevant information has been referenced from MCMS 2002 Help.

Introduction to Content Management Server 2002

As a prerequisite, site developers should understand the basic architecture of MCMS 2002 and be familiar with the capabilities of MCMS 2002 as described in the MCMS 2002 Help. Site developers should pay particular attention to the "Developing Your MCMS Site" section of the documentation.

Setting Up a Shared-Source Multi-Developer Environment

When working with multi-developer teams, an effective developer environment and disciplined developer processes are key ingredients to a smooth development process. This information is intended to supplement the guidelines listed in the "Setting Up a Multi-Developer Environment" topic in MCMS 2002 Help (under Developing Your MCMS Site, then Site Development Using MCMS Functionality).

To begin, there should be a central build server which will host the master copy of the source files. It will run the Visual SourceSafe® server as well as Visual Studio® .NET, in order to build release versions of the site. A shared SQL Server database will also be required for developing MCMS content. In most cases, the central database server can also be the build server that is running Visual SourceSafe/Visual Studio .NET.

All developers should complete the following steps:

  • Install MCMS 2002 Server, Site Manager, and Developer Tools (and all pre-requisites) on your workstation. Configure your server to use the shared development database using the Database Configuration Application (DCA).
  • Install the Visual SourceSafe client components by running the netsetup.exe program from the VSS folder on the build server.

To begin site development, only one developer should carry out the following procedure:

  • Create a new Visual Studio solution on your workstation.
  • Add a new MCMS Web application project in the desired language (Visual C#® or Visual Basic® .NET) to the Visual Studio solution.
  • Optionally, create class library projects (or other appropriate projects such as Web Services) for items that will be managed in a separate project (such as Custom Placeholders or Navigation Server Controls).
  • Check the skeleton projects and solution into Visual SourceSafe by clicking File menu, then Source Control, and then the Add Solution to Source Control command in Visual Studio .NET. Create a new folder in Visual SourceSafe for the solution and projects if necessary.

Once this is complete, the rest of the developers will need to carry out the following procedure:

  • Check out the solution and projects from Visual SourceSafe using SourceSafe by clicking File menu, then Source Control, and then the Open From Source Control command in Visual Studio .NET. Be sure to select the solution file if prompted so that all projects are downloaded. In the process, Visual Studio .NET should prompt you to create a virtual directory to map the MCMS Web application to.

In order for the MCMS Web Author to work properly, the developer will need to add a virtual directory under the root of the newly created Web application called "cms". This can be done using the Internet Services Manager, and the virtual directory should be mapped to <MCMS Install Root>\Server\IIS_CMS. Once the virtual directory is created, remove the application on the cms directory by clicking the Remove button on the Virtual Directory tab of its properties page. If this is not done, the developer will see client-side script errors when trying to use the Web Author.

Note that the developer who checks in the original solution or projects does not need to do the "cms" virtual directory mapping. This is because the mapping is automatically created while creating a new MCMS Web Application project in Visual Studio .NET. Since other developers will be getting the project from Visual SourceSafe, the step must be performed manually.

Developers can now check in or check out new or existing files for the solution using the integrated support in the Visual Studio .NET Solution Explorer. It is important to use the Visual Studio .NET integration with Visual SourceSafe in order to check in and check out projects or solutions, rather than the Visual SourceSafe client program itself. This is because Visual Studio .NET will automatically filter out user-specific files and binaries which should not be source controlled.

Building an MCMS 2002 Site From Scratch

One of the most common issues encountered is how to build an MCMS 2002 site from scratch. The most important element is to have a solid design in place for the site. Gaining a full understanding of the design of the site will often lead to the best implementation plan.

Once you have an understanding of the site design, a general process for building an MCMS 2002 site can be discussed. The following is a high-level plan for building an MCMS 2002 site assuming normal layout and requirements:

  • Build the site's container hierarchies (channels, resource galleries, and template galleries).
  • Implement the chosen security approach. If forms-authentication is chosen, build at least a simple forms login page so that developers can log on to the Web Author.
  • Implement a skeleton base template(s). This should include the designed breakdown of the page (meaning it will include skeleton user controls for shared page layout as well as skeleton server controls for navigation and other dynamic pieces). The base template can also be applied as a custom Channel Rendering Script.

    Developers can then work separately on "filling out" each piece of the base template. Once complete, the basic page framework should be ready.

  • Implement any custom placeholders that have been designed for the site.
  • Create the remaining content and dynamic templates on the site, using the base template(s) as a starting point and adding required elements to the "content area," including custom placeholders if necessary.
  • Implement any custom workflow event or Web Author customizations that are required. Note that this can be started independently of template development, using simple templates for testing.
  • Build any custom integration or application pieces such as Web services or search. These pieces can also be developed independently from the rest of the site to some extent.
  • Perform functionality and performance testing on the site to verify templates and other components.
  • Push site hierarchy and any pages that should not be created by content contributors (like summary pages) to the authoring environment so that content can be populated. Apply appropriate user roles for contributors into the system.
  • Continue testing, developing, and pushing new components until the site is ready for release.

Debugging MCMS 2002 Code

One of the significant advances for this release of MCMS on the Microsoft .NET Framework is much simpler and powerful debugging abilities for the developer. It is now simple to break in the execution of an MCMS page, and detect exactly where errors or other problems are occurring.

Debugging an MCMS template is slightly different than debugging a standard ASP.NET-based page, because you cannot simply press F5 (or Debug, then Start) to have the debugger hit breakpoints in the template. This is because a template without MCMS context will clearly not yield anything meaningful. Therefore, you must hit an MCMS page which will in turn run the template with proper MCMS context. This can be achieved using one of two simple methods.

One method is to build a debug start page in the project, but build it outside of MCMS. This page should contain links to various MCMS pages that use templates that need to be debugged. The page can be set to launch when debugging is started by selecting Set As Start Page from the page's right-click (context) menu in the Visual Studio .NET Solution Explorer. Once this is completed, you can press F5 and then select the link to the page that you want to debug. An example of this page is the DebugStartPage.htm provided in the root of the sample WoodgroveNet Web application.

The second method is to attach to the ASP.NET worker process. This can be achieved by opening Debug, then the Processes window. Select the aspnet_wp.exe process. Visual Studio .NET will enter debug mode. From that point, opening or refreshing any browser window will cause Visual Studio .NET to hit the breakpoint, assuming the underlying template contains at least one breakpoint.

Once a breakpoint has been hit using either method, all Visual Studio .NET debugging tools such as the command window and watch list are available to you.

In general the methods are equivalent, so selection is based on programmer preference. Remember that all other ASP.NET prerequisites for debugging must be adhered to. For example, the assembly must be compiled using Debug configuration and the application or page must have the debug=true attribute set (can be done either with a page-level directive or in the Web.config file).

Using Channel Rendering Script

While MCMS postings are rendered based on their template and content, the behavior when a channel is accessed without specifying a posting (i.e. http://localhost/woodgrovenet) is not as obvious. The equivalent of the Default Document in Microsoft Internet Information Services (IIS) can be configured using the Default Page setting in a channel's properties page (the value can also be accessed using the Channel.DefaultPostingName property in the Publishing API. However, in the case that the channel is empty, it is good to have a page that allows users to create the default page using the Web Author. In MCMS, what the user sees can be controlled in this situation by assigning a Channel Rendering Script to the channel.

By default, if you access a valid channel that contains no postings, the "built-in" channel rendering script will look like the following:

Figure 1. The built-in channel rendering script

This page is built into MCMS and cannot be directly modified. It provides basic Web Author functionality as well as basic navigation to get to child, sibling, and parent objects.

Note that if you see a general Web page that describes MCMS and has a gray header, you are seeing a special Channel Rendering Script (by default installed to the root channel). To remove it, remove the value from the Script URL text box (should be "/MCMS/McmsHomeport/McmsHomeport.aspx") in the properties of the channel. Setting the value to empty will enable the built-in script.

However, while the built-in channel rendering script provides the basic functionality that is required, it can be disorienting, particularly if there are situations where content contributors will be seeing it. A better approach is to use the existing page framework (user controls, server controls) to build a custom channel rendering script that fits in with the look and feel of the rest of the site. The custom page should also have a message to end users indicating that they can "override" the behavior of the channel by creating a page with the appropriate default page name.

Another problem with the standard channel rendering script is that it does not provide a login link. While this is not a problem when using Windows Integrated authentication (auto-login), it can be a problem if using forms-based authentication. Therefore, a custom channel rendering script should include a login link (should be part of the "base" template) if using forms-based authentication.

For more information on creating a forms authentication page and login link, refer to "Forms-Based Authentication" in MCMS 2002 Help (click Performing Administrative Tasks, then Authenticating Users). The Woodgrove Bank sample site also has sample code for setting up forms-based authentication.

Implementing a Channel Rendering Script

To implement a channel rendering script, make a copy of the base template used for the site (or use this development task to create the base template). The ASPX page that implements the channel rendering script should (ideally) live in the root of the Web project, or in a special folder. Once complete, set the path (i.e. /woodgrovenet/ChannelRenderScript.aspx) into the Script URL text box in the channel properties dialog.

Alternatively, you can set the channel rendering script programmatically using the Channel.OuterScriptFile property in the Publishing API. This, along with the Channel.DefaultPostingName property mentioned above can be applied in bulk rather than using the UI by writing a small application to perform the changes. Although the properties will be inherited from the parent when a new channel is created, this might be useful if changes need to be made after the channel hierarchy has been built out. For more information, refer to the "Sample Application for Applying Bulk Changes" topic in this document.

For the most part, the code in the base template should render properly as a channel rendering script. However, one situation that may be problematic is if there is code that assumes that the CmsHttpContext.Current.Posting object is available (for instance. code in a navigation server control). While this can be assumed in template files, since they will always be used to create postings, it cannot be assumed in a channel rendering script since they will have channel context only. Ensure that the code in these controls is flexible enough to handle the case of the current posting context not being found.

Images on File System vs. Images in Resource Gallery

As a best practice, any images used in template files and user/server controls should be referenced directly from the server file system. Rather than have some template graphics retained in the Resource Gallery (those directly in the template) and others on the file system (those in include files), it is simpler to have them all in a single location. This also simplifies the organization of the Resource Gallery, as you can assume that all images located there are content images that can be access-controlled for content contributors. This is also the primary intended use of the Resource Gallery structure as it is integrated into the Web Author for use by content contributors.

There are disadvantages to this approach. There may be an occasional duplication of images between the Resource Gallery and file system, but this is usually limited to generic images such as navigation arrows or bullets. Also, if template images change, you will need to trigger the file deployment into the next environment. This is usually not an issue, as you would need to move files to get any changed template files or other file system assets anyway.

Also, note that it is possible to retrieve MCMS resources from code (i.e. using Searches.GetByPath) and inserting them into templates by setting their URL into the Src property of an HtmlImage control. In this case, you will need to make sure that subscribers have rights to any Resource Galleries that will be used in this way. In general it is much easier to use file system images in templates, but this may be an option if it important for template images to be managed by MCMS Resource Managers.

Creating Custom Placeholders

A common task when customizing the authoring interface is to use custom placeholder controls. These typically persist data into XML so that they can be stored in the MCMS Content Repository using the XmlPlaceholder object.

For more information on building custom placeholder controls, refer to "Creating Custom Placeholders" in MCMS 2002 Help (select Developing Your MCMS Site, and then Extending the Functionality of MCMS).

Also, refer to Appendix B: Sample Code For Calendar Placeholder Control for sample code for an implementation of a calendar control which allows users to select a date using the ASP.NET calendar control and then outputs that date in presentation mode.

Creating a Custom Utility Class

While MCMS 2002 provides a rich set of APIs with which to build your site, it is common that during the development process, several helper utilities will be identified as being useful in several scenarios. As with other applications, it is good practice to put these helper methods into a utility class to be used by your MCMS implementation.

The custom utility class(es) can be created by populating a class in C# using only methods marked as static. In Visual Basic .NET, the same effect can be achieved by creating a Module rather than a Class. In either case, this allows the methods to be accessed without first creating an instance of the class.

Any changes made to the utility class should be discussed and communicated among the development team so that all site coders will be aware of the extended functionality available rather than writing the functionality themselves as private methods to their page or control. The custom utility class can be placed either in the main Web project or in a separate project, depending on how the solution is being organized.

Sample Utility Implementation

As a simple example, we could decide to implement a helper method which tests if a certain posting is the default page in the channel. This could be used in navigation so the page is not shown in the navigation, should it be the default page. In this scenario the helper class (with only the one method for now) could be implemented as follows:

public class CmsHelper
{
   // Checks if this posting is the default page for this channel.
   public static bool IsDefault(Posting thisPosting)
   {
      return (thisPosting.Name == thisPosting.Parent.DefaultPostingName);
   }
}

Once defined, this helper method could be used from other code using the following syntax:

if (CmsHelper.IsDefault(currPosting))
{
   ...
}

Working with MCMS Modes

During typical usage, MCMS operates under several modes. When programming against MCMS, being able to detect these modes can help to customize the user experience as they progress through the various phases of the content creation process. Modes are also essential for understanding how the various APIs in MCMS behave. For example, if you ask for the Channel.Postings collection from the Publishing API, the collection that is returned will automatically be adjusted depending on the current mode. For example, hidden and expired postings will be present in the collection when in Unpublished mode, while they are filtered out when in Published mode.

From the Publishing API (refer the PublishingMode enumeration), it is possible to detect or set the following modes (ignoring staging):

  • Published (PublishingMode.Published) is analogous to when the user is "viewing the live site."
  • Unpublished (PublishingMode.Unpublished) is equivalent to when the user is doing anything in edit mode.
  • Update (PublishingMode.Update) is only used when making changes to the site. This is used behind the scenes by Web Author commands, and can be invoked manually (see Making Programmatic Changes to the Site).

    In Web context, the current publishing mode is available from the CmsHttpContext.Current.Mode property. From an implementation perspective, you'll notice that the current publishing mode is indicated by the NRMODE query string value. The key distinction, however, is that the Publishing mode is also available in application (non-Web) context. Refer to "Making Programmatic Changes to the Site" below for more on how to set the mode in application context.

    However, there are several occasions when Unpublished mode will span too many user scenarios because it encapsulates the entire edit mode experience. For example, the user could be authoring the page, previewing the page, or simply browsing the unpublished version of the site. In these cases, use the WebAuthorContext class in the Microsoft. ContentManagerment.WebControls namespace (refer to the WebAuthorContextMode enumeration). The object can also be used to retrieve specific Web Author mode links (see "Retrieving Specific Web Author Mode Links for a Posting"). The following modes are available:

  • AuthoringNew (WebAuthorContextMode.AuthoringNew) is active when the user is authoring a new page (i.e. the page has not been saved yet).
  • AuthoringPreview (WebAuthorContextMode.AuthoringPreview) is active when the user has launched a preview window from an authoring mode (New or Reedit).
  • AuthoringReedit (WebAuthorContextMode.AuthoringReedit) is active when the user is editing a page that has previously been saved.
  • PresentationPublished (WebAuthorContextMode.PresentationPublished) is active when the user is browsing the "live" site. Equivalent to PublishingMode.Published.
  • PresentationUnpublished (WebAuthorContextMode.PresentationUnpublished) is active when the user is browsing in edit site, but is not authoring or previewing anything.
  • PresentationUnpublishedPreview (WebAuthorContextMode.PresentationUnpublished Preview) is active when the user has launched the preview from PresentationUnpublished mode.
  • TemplatePreview (WebAuthorContextMode.TemplatePreview) is active when the user is previewing the template only (no content).

    In Web context, the current Web Author mode is available using the WebAuthorContext.Current. Mode property. From an implementation perspective, the current Web Author mode is indicated by the WBCMODE query string value. However, unlike the Publishing mode, the Web Author mode is not available in application context as expected.

Using the WebAuthorContext Class

The WebAuthorContext class is useful for several other tasks, in addition to the mode queries discussed above. Note that the WebAuthorContext class is also used to raise errors to the Web Author console, as demonstrated in "Raising Errors in the Web Author" below.

Retrieving Specific Web Author Mode Links for a Posting

The WebAuthorContext class is also useful for being able to create properly formed URLs to postings in specific Web Author modes. This can be useful to customize the Web Author console, or to provide links into Web Author functionality outside of the console.

For example, when outputting a summary page such as a list of press releases in a channel, it is easy to also provide an Edit link beside each of the postings that will take the user straight into authoring that press release. This will save them from having to click on the press release itself and then choosing Edit from the Web Author console.

Assuming currPosting contains the current posting object as they are iterated through in the summary navigation and that the summary page lives in the same channel as the content postings, the edit link for each posting can be generated using the following code snippet:

// Initialize the WebAuthorContext and the server control used to hold the link.
WebAuthorContext webContext = WebAuthorContext.Current;
HtmlAnchor editLink = new HtmlAnchor();

editLink.InnerText = "Edit This Item";

// Retrieve the link to send the user straight into authoring the posting.
editLink.HRef = webContext.GetUrlForMode(currPosting,
   WebAuthorContextMode.AuthoringReedit);

Retrieving New Authoring URLs

A variation on retrieving a posting's URL in a specific Web Author mode is the capability of retrieving a URL which will send the user directly into authoring of a new page. This can be particularly useful when creating custom actions for the Web Author console that are more task-based.

For example, rather than requiring press release authors to navigate to the appropriate channel and use the appropriate template, a custom action could be created that automatically selects the proper channel and template and takes the author directly into editing of a new press release regardless of where they are in the site.

In order to get the proper URL to direct the author into authoring the new press release, code similar to the following could be used:

// Set our preset channel and template.
presetChannel = (Channel)CmsHttpContext.Current.Searches.GetByPath(
   "/Channels/WoodgroveNet/About Us/Press Releases");

presetTemplate = (Template)CmsHttpContext.Current.Searches.GetByPath(
   "/Templates/WoodgroveNet/About Us Net/Press Release ASPX");

string urlCreate = WebAuthorContext.Current.GetAuthoringNewUrl(
   presetTemplate, presetChannel);

For more information on creating custom actions, refer to "Customizing the Web Author Console" in this document.

Writing Workflow Event Handlers

One of the most common and powerful methods of extending MCMS is to write workflow event handlers. This allows the developer to insert custom functionality as pages proceed through the various workflow stages.

The workflow can be extended by placing code into predefined functions in the global.asax, or by writing custom HTTP handlers. In most scenarios, inserting the code into global.asax is simpler and will satisfy requirements. However, if an event handler component will be re-used across multiple Web applications, this can be more easily achieved by writing a custom HTTP handler.

For more information on implementing event-driven customizations, refer to "Customizing the Publishing Workflow" in MCMS 2002 Help (under Developing Your MCMS Site, then Extending the Functionality of MCMS).

Raising Errors in the Web Author

One common requirement when writing workflow event handlers is to be able to return feedback to the user as to what may have happened during the custom workflow logic. In particular, when using the "pre" event handlers, it is possible to cancel the operation on the server. When doing so, it is expected that the user should be able to find out what went wrong and how to correct it. This can be achieved by raising custom errors in the Web Author.

Raising custom errors in the Web Author is achieved using the RaiseErrorEvent method on the WebAuthorContext class. In order to use it, you will need to create an object of type WebAuthorErrorEventArgs which contains an Exception object and title as properties that can be set.

If a custom error is being raised and an action on the server is being canceled, be sure to set the ChangingEventArgs.SuppressExceptionOnCancel to true. This will allow your custom error to be caught by the error console rather than being overridden by the built-in error that is raised upon cancel.

For example, we may want to dictate that if a template has a Keywords custom property, it must be filled in or else we will not allow approval of the page. In order to provide feedback to the user, we can create a custom error message in the Web Author error console as well as canceling the approval on the server. The following sample code uses the global.asax method for handling workflow events:

protected void CmsPosting_Approving( Object sender, ChangingEventArgs e )
{
   // Retrieve the posting that raised the event.
   Posting targetPosting = e.Target as Posting;

   if (targetPosting != null)
   {
      try
      {
         // If this doesn't exist on the template, an exception is thrown.
         CustomProperty keywordsProp = 
            targetPosting.CustomProperties["Keywords"];

         if (keywordsProp.Value.Trim().Length == 0)
         {
            // If the property is present and empty, raise the error.
            WebAuthorContext webContext = 
               WebAuthorContext.Current;

            // Raise an error.
            Exception thisException = new Exception("Please ensure " + 
               the keywords custom property is filled out on the page.");

            WebAuthorErrorEventArgs webErrorArgs = new 
               WebAuthorErrorEventArgs("FailedApprove", 
                  thisException);
               
            webErrorArgs.Title = "Approval Failed";

            webContext.RaiseErrorEvent(webErrorArgs);

            // Cancel the approval.
            e.Cancel = true;
            e.SuppressExceptionOnCancel = true;
         }
      }
      catch
      {
         // Do nothing if the custom property doesn't exist.
      }   
   }
}

Making Programmatic Changes to the Site

A useful capability of the various APIs available from MCMS 2002 is the ability to make changes to the site programmatically, rather than relying on the provided user interfaces. Some sample uses might be making bulk changes to the site, creating content or structure, or creating a site archive.

The first step in making changes to the site is to ensure that MCMS is prepared to commit changes to the site. As mentioned in "Working with MCMS Modes" above, this is achieved by setting the Publishing mode to Update before any changes are made to the site.

In Web context, an MCMS posting can be set into Update mode by redirecting to its Posting.UrlModeUpdate property. However, it is important to note that in this case being in Update mode does not apply only to changes to that posting. In other words, update mode is a site-level switch, so once you are in Update mode, changes can be made to any object on the site (assuming proper privileges). This means that you can also enter Update mode in an ASP.NET page that is not an MCMS template. In order to enter Update mode on this external page, add the QueryStringModeUpdate property of any valid MCMS object (i.e. CmsHttpContext. Current.RootChannel.QueryStringModeUpdate) to the query string of the page.

In application context, the application must be set to Update mode when authenticating to the MCMS server. Notice that all of the authentication methods on the CmsApplicationContext class allow you to pass in a publishing mode as an argument.

In either case, in addition to setting the publishing mode to Update, remember that the application must authenticate itself as a user who can make the changes it is attempting, or else the calls will fail. All MCMS roles and rights apply in code as they would through the Web Author or Site Manager.

Another important item to be aware of when making programmatic changes to the site is the requirement that changes be committed before they are actually saved to the Content Repository. This can be achieved by calling the CommitAll method on the CmsContext class (base class for both the CmsHttpContext and CmsApplicationContext). If the CommitAll call is not made, the changes made during the execution of the page/application (or after the latest CommitAll call) will automatically be rolled back. Changes can be explicitly rolled back in the event of an error using the CmsContext.RollBack call.

Sample Code for Creating a Posting Programmatically

This code snippet demonstrates a simple example for creating a posting on the site, setting some content into placeholders, and committing the changes to the system. The following code sample assumes the following:

The application is authenticated as a user who can create and approve content in the specified channel and using the specified template.

The Sample Template has placeholders named "Title" and "Body".

CmsHttpContext cmsContext = CmsHttpContext.Current;

// Retrieve the template and channel for the new page.
Template newPageTemplate = (Template)cmsContext.Searches.GetByPath(
   "/Templates/Sample Template");
Channel newPageChannel = (Channel)cmsContext.Searches.GetByPath(
   "/Channels/Sample Channel");

// Create the new page, set some content and approve it.
Posting newPosting = newPageChannel.CreatePosting(newPageTemplate);

newPosting.Name = "sampleName";
newPosting.DisplayName = "Sample Name";

HtmlPlaceholder titlePh = newPosting.Placeholders["Title"] as HtmlPlaceholder;
HtmlPlaceholder bodyPh = newPosting.Placeholders["Body"] as HtmlPlaceholder;

titlePh.Html = "<b>Sample Title</b>";
bodyPh.Html = "<p>Here is some sample body text...</p>";

newPosting.Approve();

// Finally commit the changes.
cmsContext.CommitAll();

Using the CmsApplicationContext

A useful new feature in the Publishing API is the ability to program against the MCMS server without being in Web (i.e. IIS) context. This is achieved using the CmsApplicationContext class which supports most of the same functionality as the CmsHttpContext since they are both derived from the CmsContext class. The main restriction on using this is that the application must run on the machine that is running the MCMS Content Server component.

One of the most popular uses for the CmsApplicationContext is to make bulk, programmatic changes to the site. For more information on this, including how to set the CmsApplicationContext in Update mode, refer to the topic "Making Programmatic Changes to the Site" in this document.

In addition, the application context can be used for solutions such as publishing XML Web Services (see Using CMS 2002 and Web Services), or Smart Client (i.e. WinForms) applications. Also, any maintenance tasks can be achieved by writing console applications using the CmsApplicationContext and scheduling the resulting executable using the Windows Task Scheduler.

Using the application context is extremely simple. A new instance of the class can be created by calling its constructor with no arguments. Once the application context is created, you must authenticate the application to MCMS as a particular user. The rights of that user will be carried over into that application when accessing objects or making any changes to the site. This is also where the mode can be set.

Sample Code for Performing Bulk Operations

Refer to Appendix A: Sample Application for Bulk Changes for a sample application that demonstrates using the application context to perform bulk operations. It also demonstrates how to recursively crawl the MCMS hierarchy.

Using CMS 2002 and Web Services

For detailed guidance on exposing or consuming Web Services in CMS 2002, see Using Microsoft Content Management Server 2002 and Web Services.

Customizing the Authoring Connector for Word

For detailed guidance on developing and customizing the Authoring Connector for Word, see Customizing Microsoft Content Management Server 2002 Authoring Connector.

Use of Custom Property Searches

The Searches class in the Microsoft.ContentManagement.Publishing namespace exposes methods to retrieve collections of channels or postings based on custom property values. The methods are named GetPostingsByCustomProperty and GetChannelsByCustomProperty. Because they return site-wide results, these searches can be quite useful, but they also require significant database access upon every request which can in turn have a detrimental effect on performance.

If structured querying is a major requirement of the site, consider using an external meta-data database.

Note that searches that retrieve a specific object such as GetByGuid, GetByPath, or GetByUrl perform well. If possible, use the GetByGuid method, as it is by far the fastest method of object lookup in MCMS.

Glossary

administrator (MCMS)
A predefined role in MCMS 2002. The administrator sets up and manages publishing in the MCMS environment. MCMS administrators have full privileges (including the right to create, edit, and approve pages) in all MCMS 2002 containers. The first administrator created by the Database Configuration Application (DCA) when a database is created or upgraded. Initially, this is the only user who can log on to MCMS through the Site Builder.

authentication page
A Web page presented to unauthenticated users logging on to an MCMS 2002 site with a Web browser. On the authentication page, guests select a valid 2000 domain or an LDAP (Lightweight Directory Access Protocol) Organizational Unit (OU) and enter their user name and password to gain access to the site.

author
A predefined role in Microsoft MCMS 2002. Authors can access the following Site Manager containers through the Web Author: channel, template gallery, and resource gallery.

Authoring Connector
An MCMS 2002 feature that enables users to author, edit, and submit content from a Microsoft Word document to an MCMS Web site.

channel
A virtual storage space used to organize pages. By assigning user rights to channels, administrators or channel managers can specify the pages that can be viewed, edited, and approved by groups of users.

channel manager
A predefined role in MCMS 2002 that allows a user or group to have administrative rights within a particular branch of the channel hierarchy. Within channels in which they have rights, channel managers can create hierarchies, assign rights to containers, and act as an author, editor, moderator, template designer, or resource manager.

Content Repository
The SQL Server database component, including table definitions, stored procedures, and so on, that is used by MCMS to store most of its data. This includes information about the structure of the site and its content, and the content itself, including resources.

Content Server
The MCMS processing engine that receives incoming page requests and then dynamically assembles the corresponding Web pages from its components in the Content Repository (database) and the file system.

Database Configuration Application (DCA)
An MCMS 2002 utility used to select and populate a Microsoft SQL Server database with the data and tables required by the MCMS 2002 server. During a new installation of MCMS 2002, the Database Configuration Application (DCA) is also used to select the virtual Web site (for the MCMS Content Server and the Server Configuration Application), a new MCMS system account, and the initial MCMS administrator. After you have used the DCA for a new installation, you can select a new database.

editor
A predefined role in MCMS 2002 that is responsible for approving or declining pages based on accuracy and overall content suitability, revising pages as required, and managing pages in containers where the editor has editing rights.

gallery
Am MCMS 2002 storage space for either templates or resources (such as images). Templates and resources are stored in separate galleries.

guest user (MCMS)
The account used for anonymous site visitors. Guest users must be granted rights to channels, pages, and resources but are only given read access to the site. All users are given guest access in addition to any explicit rights they may have.

moderator
A predefined role in MCMS 2002, similar to editors.

object package
An MCMS data structure that contains the objects selected for export.

page revision
The version of an MCMS 2002 object (page, template, or resource) that is stored when changes are approved and published. Site managers can preview page revisions and replace current pages with them if necessary. An individual version of an object also maintains links to all dependencies that were in effect when the revision was approved. A newly created and published revision in MCMS Web Author always has one revision in its revision history.

Posting
A Web page created by adding content, such as text or images, to the placeholders on templates. MCMS 2002 pages differ from other Web pages, in that different views of the same content can be generated from a single page, and each view of the content can be posted to a different channel. Because pages can use several views, each one based on a different template, the content of the views can be customized to suit different audiences. MCMS pages are also referred to as postings.

resource
Any file that can be inserted in, or attached to, a page whether it is a shared resource from a gallery or a local resource from a desktop.

resource gallery
A container used within MCMS 2002 to organize and control access to resources. You must have subscriber rights at minimum to a resource gallery to view a resource in the gallery. You must have content creator rights (as an author, editor, moderator, template designer, channel manager or administrator) to a resource gallery to be able to insert resources in, or attach them to, a page.

resource manager
A predefined role in MCMS 2002. Resource managers maintain and update resources in the galleries that they have rights to. Only resource managers and administrators have read and write access to the files in a resource gallery and can add, replace, preview, and delete resources.

rights
The right to access, modify, and approve content for publishing. In MCMS 2002, rights are derived from membership in one or more rights groups, and members of a rights group hold privileges to containers based on the predefined MCMS role the rights group is assigned.

rights group
The group of users assigned to one of the MCMS 2002 predefined roles. Members of a rights group hold privileges to containers based on the role the rights group is assigned. Authors, for example, can create pages in a folder, while editors can both create and approve pages in folders. An MCMS administrator or channel manager specifies access to specific containers (folders, channels, galleries) for a rights group.

Server Configuration Application (SCA)
An MCMS 2002 utility used to configure individual or multiple servers. After a new installation, users can selectively configure, globally or on just one server, activities such as changing the MCMS 2002 system account, adding and removing supported Windows NT domains, and adding or removing Active Directory groups as the network topology changes or grows.

site deployment
The act of exporting objects such as resources, templates, and galleries from a source system and importing them to a destination system. A common application of site deployment is moving data from a development server to a production server. Site deployment can be performed by a site administrator only.

site manager
A predefined role in MCMS 2002, also known as the MCMS administrator. The site manager sets up and manages publishing in the (MCMS) environment. Site managers have full privileges (including the right to create, edit, and approve pages) in all MCMS 2002 containers. This is the first administrative role created by the Database Configuration Application (DCA) when a database is created or upgraded. Initially, this is the only user who can log on to MCMS through the MCMS 2002 Site Manager.

Site Stager
A component of MCMS 2002 that produces static HTML or ASP copies of part or all of your Web site and enables you to host this content from another directory or server. MCMS 2002 Site Stager reduces browser traffic on your MCMS 2002 site by allowing you to host your site on platforms other than Windows 2000. Site Stager will not work with an ASP.NET-based MCMS site. Nestlé does not use MCMS Site Stager.

subscriber
A predefined role in MCMS 2002. The subscriber is assigned the minimum level of rights required before information can be accessed in MCMS container objects. Subscribers must have rights to resources and templates in order to view resources in the Web Author. Also, a minimum level of subscriber rights are required to access channels or galleries (including resource or template galleries).

template
An MCMS object that is stored in the Content Repository and that serves as the primary source of information about a particular set of pages that are said to be based on that template. Templates encapsulate the placeholder and custom property definitions, and identify the template file (which contains the executable code and controls), for their pages.

template file
An ASPX, ASP, or ASCX source file that defines an overall appearance for a set of pages in an MCMS Web site and contains the executable code associated with a template object in MCMS. Certain areas of the template file are predefined for all pages based on the template file; others areas, known as placeholder definitions, are reserved for custom content in each page based on the template file. Templates are implemented as a combination of information in the Content Repository, known as the template object, and an ASPX source file in the file system, known as the template file.

template gallery
A container used to manage access to templates. No template gallery can be accessed by a user without subscriber rights at minimum.

Web Author
A tool that enables authors, editors, and moderators to create and approve pages from a Web browser. The MCMS 2002 Web Author is a Web application that uses server-side and client-side scripting to generate an editable version of existing or new pages.

Appendix A: Sample Application for Bulk Changes

This application crawls through the MCMS hierarchy under a specified path (using command-line argument) and approves all pages within that hierarchy using the MCMS application context. It assumes that the user running the application has rights to approve all of the postings and can easily be modified to use a hard-coded user.

using System;
using Microsoft.ContentManagement.Publishing;

namespace CmsApproveAll
{
   /// <summary>
   /// Summary description for Class1.
   /// </summary>
   class ApproveAll
   {
      private const string START_CHANNEL = "/Channels/";
      private const string usageString = 
         "The application requires the channel to be approved.\n\n" +
         "Usage: CmsApproveAll -ChannelName (i.e. CmsApproveAll -sample)\n";


      /// <summary>
      /// The main entry point for the application.
      /// </summary>
      [STAThread]
      static void Main(string[] args)
      {
         //
         // This application will approve all of the pages in CMS below a 
               certain channel 
         // that are in "Saved", "Waiting for Editor Approval" or "Waiting for 
            Moderator 
         // Approval" state.
         //
         Console.WriteLine("CMS 2002 Approve All Application");
         Console.WriteLine();

         if (args.Length == 0)
         {
            Console.WriteLine(usageString);
         }
         else
         {
            string channelName = START_CHANNEL + 
               GetChannelArg(args);

            // Get the CMS application context.
            CmsApplicationContext cmsContext = new 
               CmsApplicationContext();

            // Use this line to authenticate using current user.
            // Must be in update mode to make changes to the system.
            cmsContext.AuthenticateAsCurrentUser(PublishingMode.Update);

            // Could use this line to authenticate as a specific user.
            // cmsContext.AuthenticateAsUser
               ("WinNT://domain/username", "password", 
            // PublishingMode.Update);

            HierarchyItem startItem = 
               cmsContext.Searches.GetByPath(channelName);

            if ((startItem != null) && (startItem is Channel))
            {
               Channel startChannel = (Channel)startItem;
               Console.WriteLine("Starting in channel: " + 
                  startChannel.Path);

               CrawlChannel(cmsContext, startChannel);
            }
            else
            {
               Console.WriteLine("Error: Could not 
                  retrieve starting channel.");
            }
         }
      }

      private static void CrawlChannel(CmsApplicationContext cmsContext, 
         Channel currChannel)
      {
         // First iterate through the postings and approve 
            the ones in the correct state.
         PostingCollection postingColl = currChannel.Postings;

         foreach (Posting thisPosting in postingColl)
         {
            if ((thisPosting.State == PostingState.Saved) ||
               (thisPosting.State == 
                  PostingState.WaitingForEditorApproval) ||
               (thisPosting.State == 
                  PostingState.WaitingForModeratorApproval))
            {
               thisPosting.Approve();
               cmsContext.CommitAll();
               Console.WriteLine("Approved posting at: " + 
                  thisPosting.Path);
            }
         }

         // Now recurse through subchannels.
         ChannelCollection channelColl = currChannel.Channels;

         foreach (Channel thisChannel in channelColl)
         {
            Console.WriteLine();
            Console.WriteLine("Searching channel: " + 
               thisChannel.Path);
            CrawlChannel(cmsContext, thisChannel);
         }
      }

      private static string GetChannelArg(string[] args)
      {
         // Assumes caller has checked to ensure the 
            existence of at least one argument
         string firstArg = args[0];

         // Allow it to handle the case if the user didn't 
            include the "-" for the 
         // parameter.
         return firstArg.StartsWith("-") ? firstArg.Remove(0, 1) : firstArg;
      }

   }
}

Appendix B: Sample Code For Calendar Placeholder Control

using System;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.ComponentModel;
using System.Xml;
using Microsoft.ContentManagement.WebControls;
using Microsoft.ContentManagement.WebControls.Design;
using Microsoft.ContentManagement.Publishing.Extensions.Placeholders;

namespace CustomPlaceholders
{
   /// <summary>
   /// Summary description for CalendarPlaceholderControl.
   /// </summary>
   [
   DefaultProperty("PlaceholderToBind"),
   ToolboxData("<{0}:CalendarPlaceholderControl 
      runat=server></{0}:CalendarPlaceholderControl>"),
   SupportedPlaceholderDefinitionType(typeof(XmlPlaceholderDefinition))
   ]
   public class CalendarPlaceholderControl : BasePlaceholderControl
   {
      public CalendarPlaceholderControl()
      {
      }

      protected override void CreateAuthoringChildControls(BaseModeContainer 
         authoringContainer)
      {
         // Create the authoring control and add some text above it.
         authoringCalendar = new Calendar();

         authoringContainer.Controls.Add(new LiteralControl(
            "Please select a date:<br>"));
         authoringContainer.Controls.Add(authoringCalendar);
      }

      protected override void 
         CreatePresentationChildControls(BaseModeContainer 
         presentationContainer)
      {
         // We will just be outputting the date selected, 
            so just need a literal.
         presentationLiteral = new LiteralControl();

         presentationContainer.Controls.Add(presentationLiteral);
      }

      protected override void LoadPlaceholderContentForAuthoring(
         PlaceholderControlEventArgs e)
      {
         EnsureChildControls();

         DateTime selectedDate = 
            GetDateFromXml(((XmlPlaceholder)this.BoundPlaceholder).XmlAsString);

         authoringCalendar.SelectedDate = selectedDate;
      }

      protected override void LoadPlaceholderContentForPresentation(
         PlaceholderControlEventArgs e) 
      {
         EnsureChildControls();

         DateTime selectedDate = 
            GetDateFromXml(((XmlPlaceholder)this.BoundPlaceholder).XmlAsString);         

         presentationLiteral.Text = selectedDate.ToLongDateString();
      }

      protected override void 
         SavePlaceholderContent(PlaceholderControlSaveEventArgs e) 
      {
         XmlDocument dateXml = new XmlDocument();

         XmlElement dateElement = dateXml.CreateElement("date");
         XmlElement dayElement = dateXml.CreateElement("day");
         XmlElement monthElement = dateXml.CreateElement("month");
         XmlElement yearElement = dateXml.CreateElement("year");

         // Grab the date from the calendar control.
         DateTime selectedDate = authoringCalendar.SelectedDate;

         dayElement.InnerText = Convert.ToString(selectedDate.Day);
         monthElement.InnerText = Convert.ToString(selectedDate.Month);
         yearElement.InnerText = Convert.ToString(selectedDate.Year);

         dateElement.AppendChild(dayElement);
         dateElement.AppendChild(monthElement);
         dateElement.AppendChild(yearElement);

         dateXml.AppendChild(dateElement);

         // Save to CMS now.
         ((XmlPlaceholder)this.BoundPlaceholder).XmlAsString =
            dateXml.OuterXml;
      }

      // Use this to retrieve a DateTime object from the XML representation.
      private DateTime GetDateFromXml(string dateXmlString)
      {
         XmlDocument dateXml = new XmlDocument();

         dateXml.LoadXml(dateXmlString);

         XmlElement dateElement = dateXml.DocumentElement;
         int year = 0;
         int month = 0;
         int day = 0;

         foreach(XmlNode currNode in dateElement.ChildNodes)
         {
            if (currNode.Name == "day")
            {
               day = Convert.ToInt32(currNode.InnerText);
            }
            else if (currNode.Name == "month")
            {
               month = Convert.ToInt32(currNode.InnerText);
            }
            else if (currNode.Name == "year")
            {
               year = Convert.ToInt32(currNode.InnerText);
            }
         }

         DateTime selectedDate = new DateTime(year, month, day);

         return selectedDate;
      }

      private Calendar authoringCalendar;
      private LiteralControl presentationLiteral;
   }
}

Appendix C: Sample Code for Extending the Web Author Toolbar

ActiveXEditing.vbs

Note that this version is the "shell" used to begin customization. Refer to the topic above on how to use each method.

' *****************************************************************************
'
'  NOTE: This file is client side VBScript. 
'
'  The sample code shows how to create a simple context sensitive button
'
' *****************************************************************************
'

' Suppress errors in case ActiveXEditing.vbs is not available.
On Error Resume Next

Call EnableCompatibleToolbarHooks

'
' -----------------------------------------------------------------------------
'
'   This is the hook function being called when the Toolbar is initialized for the 
'  first time. 
'   The custom code here can add or remove toolbar buttons.
'
'   @pToolbarInterface [in] - Toolbar interface object that can be used to create
'                             button or comboboxes.
'
' sample implementation that creates a new toolbar and adds one button with the id 
' "TestButton" to it:
'
'   Dim pMyTest
'   Set pMyTest = document.ToolbarInterface.Toolbars.CreateToolbar("MyTest" )
'
'   Call pMyTest.AddButton( "TestButton", "", "undo.gif", "TestButton" )
'
'
' -----------------------------------------------------------------------------

Sub OnToolbarInitialize( ByVal pToolbarInterface )
   Dim CustomToolbar
   Set CustomToolbar = document.ToolbarInterface.Toolbars.CreateToolbar("CustomToolbar")
   Call CustomToolbar.AddButton("AcronymButton", "", "undo.gif", "Insert Acronym")
End Sub

' -----------------------------------------------------------------------------
'
'   This hook is called for each placeholder, and is used to initialize the state
'   for that specific placeholder. The toolbar state will already be initialized to
'   its default state.
'
'   @strPlaceholderName [in] - The name of the placeholder we are initializing for
'
'   @pState [in] - The toolbar state of this toolbar. The toolbar state keeps track
'               of the clicked/disabled state for the toolbar for this placeholder.
'
' sample implementation that enables our button only if the InsertTable button is enabled 
' as well:
'
'   If (pState.Item("InsertTable").Allowed = True) Then
'      pState.Item("TestButton").Allowed = True
'   End If
' -----------------------------------------------------------------------------

Sub OnToolbarStateInitialize( ByVal strPlaceholderName, ByVal pState )
   If (pState.Item("Bold").Allowed = True) Then
      pState.Item("AcronymButton").Allowed = True
   Else
      pState.Item("AcronymButton").Allowed = False
   End If
End Sub

' -----------------------------------------------------------------------------
'
'   This is the hook function being called when the toolbar state should be
'   updated because there is a change of state or context in the ActiveX
'   placeholder.
'
'   @pActiveHtmlEditor [in] - The currently active DHTML control
'
'   @bEditingSource [in] - TRUE if we are currently in HTML Source Mode
'
' sample implementation that only enables the test button in non-HTML edit mode
'
'   If bEditingSource Then
'      pActiveHtmlEditor.ToolbarState.Item("TestButton").Enabled = False
'   Else
'      pActiveHtmlEditor.ToolbarState.Item("TestButton").Enabled = True
'   End If
' -----------------------------------------------------------------------------

Sub OnToolbarUpdate( ByVal pActiveHtmlEditor, ByVal bEditingSource )
   If bEditingSource Then
      pActiveHtmlEditor.ToolbarState.Item("AcronymButton").Enabled = False
   Else
      pActiveHtmlEditor.ToolbarState.Item("AcronymButton").Enabled = True
   End If
End Sub

' -----------------------------------------------------------------------------
'
'   This is the hook function being called when a button is clicked in the 
'   ActiveX toolbar.
'
'   @pActiveHtmlEditor [in] - The currently active DHTML edit control
'
'   @bstrId [in] - The ID of the button or combo box that was clicked on
'
'   @pItem [in] - The item that was clicked on
'
'   @bEditingSource [in] - TRUE if we are currently in HTML Source Mode
'
' sample implementation that shows a message box and makes the current text bold:
'
'   Select Case bstrId
'
'      Case "TestButton"
'         Call MsgBox( "hello!" )
'         pActiveHtmlEditor.dom.execCommand "Bold"
'
'   End Select
' -----------------------------------------------------------------------------

Sub OnToolbarEvent( ByVal pActiveHtmlEditor, ByVal bstrId, ByVal pItem, ByVal bEditingSource )
   Select Case bstrId
      Case "AcronymButton"
         Call CreateAcronym(pActiveHtmlEditor.DOM)
   End Select
End Sub

Sample Code for Client Side Manipulation (CustomToolbar.js)

function CreateAcronym(pDOM)
{
   var sel = pDOM.selection;
   
   // check against case that there is no highlighted selection
   if (sel.type=="None")
   {
      alert("There is no selected text to create an acronym.");
      return;
   }
   
   var rng = sel.createRange();
   
   // componseate for IE selecting trailing space as part of the range
   if (sel.type == "Text")
      rng = fixTrailingSpace(rng);
   
   // check if there is an existing acronym element
   var elAcronym = getAncestorAcronym( sel, rng );
   
   // add the title to the argument if it exists
   var args = elAcronym ? (elAcronym.title ? elAcronym.title : "") : "";
   
   // open the dialog and retrieve the value
   var strPath = "/WoodgroveNet/CustomDialogs/AcronymTitle.aspx";
   var strDlgRet = window.showModalDialog( strPath, args, 
                  "dialogWidth:450px;dialogHeight:265px;status:no;help:no" );
   
   pDOM.body.focus();
                  
   if ( typeof(strDlgRet) == "undefined" )
      return;
      
   if (strDlgRet == "AcronymCancel")
      return;
   
   // if an existing acronym was found, replace the title
   if (elAcronym != null)
   {
      if (strDlgRet == "")
      {
         elAcronym.removeNode(false);
      }
      else
      {
         elAcronym.title = strDlgRet;
      }
   }
   // else create a new acronym tag
   else
   {
      if (strDlgRet != "")
      {
         var strNew = "<acronym title=\"" + strDlgRet + "\">" + 
            rng.text + "</acronym>"
         rng.pasteHTML(strNew);
      }
   }
   
   rng.select();      
}

function getAncestorAcronym( sel, rng )
{
   var elAcronym;
   if (sel.type == "Control")
   {
      elAcronym = getEl("ACRONYM",rng.commonParentElement());
   }
   else 
   {
      elAcronym = getEl("ACRONYM",rng.parentElement());
   }
   
   return elAcronym;
}

Show: