New Sitefinity 4.4 SDK Sample: Sitemap Module

New Sitefinity 4.4 SDK Sample: Sitemap Module

Posted on December 22, 2011 0 Comments

The content you're reading is getting on in years
This post is on the older side and its content may be out of date.
Be sure to visit our blogs homepage for our latest news, updates and information.

The Sitefinity SDK for version 4.4 is now available for download. Today we'll take a look at one of the new samples: the Sitefinity Sitemap Module. Although the end result of generating an xml sitemap of your website is a simple one, it demonstrates several key concepts that developers might find useful.

The initial release of this module includes all sitemap pages, as well as News, Events, and Blog Posts, as these are the content types that most often have dedicated Urls to be indexed. However, by following the code sample, you should be able to easily extend this to include the remaining native and even custom content Urls.

How It Works

The Sitemap module is made up of a few key components working together, and the goal of this sample is to demonstrate how they can be used in a real-world scenario.

Custom HttpHandler

The job of generating and displaying the xml sitemap is handled by a custom HttpHandler that builds the XML and returns it to the user. It appends all published pages, as well as the supported content items (News, Blogs, Events) using the XmlTextWriter.

// prepare response type
var response = context.Response;
response.ContentType = "text/xml";

// begin xml response
var writer = new XmlTextWriter(response.OutputStream, Encoding.UTF8) { Formatting = Formatting.Indented };
writer.WriteStartDocument();
writer.WriteStartElement("urlset");
writer.WriteAttributeString("xmlns:xsi", "http://www.w3.org/2001/XMLSchema-instance");
writer.WriteAttributeString("xsi:schemaLocation", "http://www.sitemaps.org/schemas/sitemap/0.9 http://www.sitemaps.org/schemas/sitemap/0.9/sitemap.xsd");
writer.WriteAttributeString("xmlns", "http://www.sitemaps.org/schemas/sitemap/0.9");

// … 

 

Custom RouteHandler

Generally, handlers are saved as .ashx (or sometimes .aspx) files so that they can be automatically served by ASP.NET. However, we want to use the path ~/sitemap.xml, which is generally a static file that is served directly by IIS. To do this, we need to tell Sitefinity to register this custom route and assign it to our custom handler.

All that is required to make this work is to inherit from IRouteHandler, then return the handler we defined earlier.

/// <summary>
/// Route Handler for serving the ~/sitemap.xml file path
/// </summary>
public class SiteMapRouteHandler : IRouteHandler
{
    /// <summary>
    /// Provides the object that processes the request.
    /// </summary>
    /// <param name="requestContext">An object that encapsulates information about the request.</param>
    /// <returns>
    /// An object that processes the request.
    /// </returns>
    public IHttpHandler GetHttpHandler(RequestContext requestContext)
    {
        // simply return the SiteMap Handler
        return new SiteMapHttpHandler();
    }

Finally, the route is registered when the module is installed:

/// <summary>
/// Module for generating an XML Sitemap, including all pages and Sitefinity content types (Currently only supports News, Events, Blogs)
/// </summary>
public class SiteMapModule : ModuleBase
{
    /// <summary>
    /// Initializes the service with specified settings.
    /// </summary>
    /// <param name="settings">The settings.</param>
    public override void Initialize(ModuleSettings settings)
    {
        base.Initialize(settings);
        Config.RegisterSection<SiteMapConfig>();

        // handle the sitemap path
        RouteTable.Routes.Add("SiteMap", new Route("sitemap.xml", new SiteMapRouteHandler()));
    }

    //  ...
}

Including Content Items

Although Content items in Sitefinity have unique Urls, these are used dynamically in conjunction with a Content View widget (such as NewsView or BlogsView) to display the list and details pages. Because these widgets can be placed literally anywhere on any page, we need a way to identify and persist the location of the Sitefinity page that contains the Content View control for each content type.

Custom Configuration

Fortunately, Sitefinity 4 has a built-in mechanism for persisting settings such as these: Configuration. This is the component that creates and utilizes the .config files in the App_Data folder to store Sitefinity settings. By inheriting your configuration from these classes, you gain instant access to this feature for your own custom modules, including access via the Advanced Settings of the Sitefinity Administration Dashboard.

Sitefinity-4-Custom-Configuration

We'll explore building this custom Configuration class in more detail in a future post. For now just be aware that this configuration is used to associate each Content Type with a Sitefinity page to build the content url. It also has a boolean flag to determine whether or not to include those content items in the Sitemap.

Locating Default Pages

The slick new Sitefinity 4 Fluent API makes locating pages a breeze, with an intuitive interface to search for pages based on various conditions. In this example, we are searching for all pages that contain a NewsView control.

// retrieve all pages with a NewsView on them
var NewsPages = App.WorkWith().Pages()
    .Where(p => p.Page != null && p.ShowInNavigation && p.Page.Controls.Where(c => c.ObjectType.StartsWith(typeof(NewsView).FullName)).Count() > 0)
    .Get();

Then we can check the control properties to make sure the control matches the module and provider we are looking for.

// retrieve the news view control from the page
var newsView = page.Page.Controls.Where(c => c.ObjectType.StartsWith(typeof(NewsView).FullName)).First();
if (newsView == null) continue;

// determine if there is a modified control definition
var controlDefinition = newsView.Properties.Where(p => p.Name == "ControlDefinition").FirstOrDefault();
if (controlDefinition == null)
    // control uses default provider
    providerName = NewsManager.GetDefaultProviderName();
else
{
    // search for modified provider name in the control
    var providerNameProperty = controlDefinition.ChildProperties.Where(p => p.Name == "ProviderName").FirstOrDefault();
    if (providerNameProperty == null)
        // control is modified, but still uses default provider
        providerName = NewsManager.GetDefaultProviderName();
    else
        // get selected provider name
        providerName = providerNameProperty.Value;
}

// make sure specified provider matches the current provider we are working with
if (providerName != provider.Name) continue;

// make sure the mode of the control is not Details-only, skip this page if it is
var displayModeProperty = newsView.Properties.Where(p => p.Name == "ContentViewDisplayMode").SingleOrDefault();
if (displayModeProperty != null && displayModeProperty.Value == Telerik.Sitefinity.Web.UI.ContentUI.Enums.ContentViewDisplayMode.Detail.ToString()) continue;

Once we find the page we are looking for, we use our new configuration class to store its location so that it can be persisted or modified as needed.

// save default news page for this provider to the config
var newsPage = new ContentPage(newsConfig.Pages);
newsPage.DefaultPageUrl = page.GetFullUrl();
newsPage.Name = providerName;
newsPage.Include = true;
newsPage.ProviderName = providerName;
newsConfig.Pages.Add(newsPage);

This is repeated for each module. Note, however, that the Blogs module doesn't need a search through the pages for a BlogsView control, since this property is part of the module itself.

Sitefinity-4-Blog-Settings

Retrieving Content, Multiple Providers, and the Fluent API

Generating the actual page nodes of the sitemap is also fairly straightforward, thanks to the Fluent API. We simply retrieve a list of published pages, then build the url and append it to the xml.

using (var api = App.WorkWith())
{
    // append pages
    var pages = api.Pages().ThatArePublished().Where(p => p.ShowInNavigation == true && !p.IsBackend && p.NodeType == Telerik.Sitefinity.Pages.Model.NodeType.Standard).Get();
    foreach (var page in pages)
    {
        // build host
        var protocol = page.Page.RequireSsl ? "https://" : "http://";
        port = string.Concat(":", vars["SERVER_PORT"]);
        if (port == "80" || port == "443") port = string.Empty;
        host = protocol + vars["SERVER_NAME"] + port;

        writer.WriteStartElement("url");
        writer.WriteElementString("loc", host + VirtualPathUtility.ToAbsolute(page.GetFullUrl()));
        writer.WriteElementString("lastmod", GetLastMod(page.Page.LastModified));
        writer.WriteElementString("changefreq", GetChangeFreq(page.Page.LastModified));
        writer.WriteElementString("priority", "0.5");
        writer.WriteEndElement();
    }

    // ...
}

Working with Content items is similar, and a snap again with the Fluent API. However, we need to use our configuration to retrieve the default Sitefinity page url and append it to the Content Url.

Notice that here we are supporting multiple providers with the simple use of the SetContentProvider method of the Fluent API.

// append events
foreach (var eventsProvider in EventsManager.ProvidersCollection)
{
    // check index settings for matching provider from configuration
    var eventsConfig = config.ContentTypes[EventsModule.ModuleName].Pages.Elements.Where(e => e.ProviderName == eventsProvider.Name).FirstOrDefault();
    if (eventsConfig == null || !eventsConfig.Include) continue;

    // retrieve events from provider
    var events = App.Prepare().SetContentProvider(eventsProvider.Name).WorkWith().Events().Publihed().Get();
    foreach (var eventItem in events)
    {
        // build url
        var fullUrl = string.Format("{0}{1}{2}", host, VirtualPathUtility.ToAbsolute(eventsConfig.DefaultPageUrl), eventItem.Urls.Where(u => u.RedirectToDefault == false).First().Url);

        // append to sitemap
        writer.WriteStartElement("url");
        writer.WriteElementString("loc", fullUrl);
        writer.WriteElementString("lastmod", eventItem.LastModified.ToString("yyyy-MM-ddThh:mm:sszzzz"));
        writer.WriteElementString("changefreq", "monthly");
        writer.WriteElementString("priority", "0.5");
        writer.WriteEndElement();
    }
}

After including all pages and supported content items, the handler is complete and the sitemap is returned to the user.

Sitefinity-4-Sitemap-Module

Download the SDK

The new Sitemap module sample is just one of many updates to the Sitefinity 4 SDK and demonstrates the use of several Sitefinity components in a real-world scenario.

Take a moment to download the SDK and try it out for yourself! Be sure to send me any feedback, comments or suggestions via the Sitefinity 4 SDK Discussion Forum.

progress-logo

The Progress Team

View all posts from The Progress Team on the Progress blog. Connect with us about all things application development and deployment, data integration and digital business.

Comments

Comments are disabled in preview mode.
Topics

Sitefinity Training and Certification Now Available.

Let our experts teach you how to use Sitefinity's best-in-class features to deliver compelling digital experiences.

Learn More
Latest Stories
in Your Inbox

Subscribe to get all the news, info and tutorials you need to build better business apps and sites

Loading animation