HTML-Seiten via Media Library bereitstellen

Meistens wird die Sitecore Media Library genutzt um Bilder und Dateien, wie PDFs, für den eigenen Webauftritt zu speichern und einzubinden, beziehungsweise zum Downloaden zu verlinken. Allerdings gehen die Möglichkeiten mit der Media Library noch viel weiter – hat man denn Bedarf daran.

In diesem Artikel wird erläutert, wie statische HTML-Seiten inklusive JavaScript und CSS über die Sitecore Media Library eingebunden und bereitgestellt werden können. Und das tolle daran: genau wie andere Items und Dateien lässt sich der Zugriff über die Sitecore Berechtigungen ganz genau festlegen – damit auch nur wirklich das von Ihnen gewünschte Zielpublikum darauf zugreifen kann!

Aus diesem Beispiel lassen sich natürlich auch weitere Use Cases ableiten, zum Beispiel wenn im Webauftritt ein Flashplayer integriert werden soll und die dazugehörigen Flashfilme (.flv Dateien) in der Media Library abgespeichert werden.

(mehr …)

Dynamische Sitemaps

Eine XML-Sitemap ist eine klassische Datei, welche nahezu jede Seite besitzen soll. Da wir unter der Haube ein modernes CMS haben, wollen wir hierbei natürlich auch eine automatisch generierte Sitemap-XML haben. Unsere Sitemap soll jedoch nicht blind, alle Content-Items in ihren Index packen, sondern auch je nach Fall entscheiden können, diese draussen zu lassen.

Damit dies in Sitecore möglich ist, erstellen wir hierfür einen gewöhnlichen ASPX-Handler, welcher sich gegen aussen als www.mywebsite.ch/sitemap.xml outet.

  1. Wir erzeugen also in unserer Solution einen Handler (Add new item…) und geben ihm den Namen sitemapxml_handler.ashx
  2. Wir erstellen eine für uns optimale XML-Sitemap. Hier ein mögliches Beispiel:
    // --------------------------------------------------------------------------------------------------------------------
    // <copyright file="SitemapXmlHandler.cs" company="Namics AG">
    //   (c) Namics AG
    // </copyright>
    // <summary>
    //   The sitemap xml handler.
    // </summary>
    // --------------------------------------------------------------------------------------------------------------------

    namespace Namics.Samples.WebApp.Services
    {
        using System;
        using System.Web;
        using System.Xml;
        using Namics.Framework.Sitecore.Core.Utils;

        using Sitecore.Configuration;
        using Sitecore.Data;
        using Sitecore.Data.Items;
        using Sitecore.Diagnostics;
        using Sitecore.Links;

        /// <summary>
        /// The sitemap xml handler.
        /// </summary>
        public class SitemapXmlHandler : IHttpHandler
        {
            #region Public Properties

            /// <summary>
            /// Gets a value indicating whether IsReusable.
            /// </summary>
            public bool IsReusable
            {
                get
                {
                    return true;
                }
            }

            #endregion

            #region Public Methods and Operators

            /// <summary>
            /// The process request.
            /// </summary>
            /// <param name="pContext">
            /// The context.
            /// </param>
            public void ProcessRequest(HttpContext pContext)
            {
                try
                {
                    pContext.Response.ContentType = "text/xml";
                    pContext.Response.StatusCode = 200;
                   
                    XmlDocument xmlDoc = new XmlDocument();
                    XmlDeclaration xmlDeclaration = xmlDoc.CreateXmlDeclaration("1.0", "utf-8", null);

                    XmlElement rootNode = xmlDoc.CreateElement("urlset");
                    rootNode.SetAttribute("xmlns", "http://www.sitemaps.org/schemas/sitemap/0.9");
                    xmlDoc.InsertBefore(xmlDeclaration, xmlDoc.DocumentElement);
                    xmlDoc.AppendChild(rootNode);

                    string databaseName = Settings.GetSetting("Sitemap.Database");
                    string startItemPath = Settings.GetSetting("Sitemap.StartItemPath");
                    Database database = Factory.GetDatabase(databaseName);
                    UrlOptions urlOptions = UrlOptions.DefaultOptions;
                    urlOptions.AlwaysIncludeServerUrl = true;

                    Item item = database.GetItem(startItemPath);
                    AddUrlEntry(item, xmlDoc, rootNode, urlOptions);
                   
                    pContext.Response.Write(xmlDoc.OuterXml);
                }
                catch (Exception ex)
                {
                    Log.Error("Error at sitemap xml handler.", ex, this);
                    pContext.Response.StatusCode = 500;
                }
            }

            #endregion

            #region Methods

            /// <summary>
            /// The add url entry.
            /// </summary>
            /// <param name="pItem">
            /// The item.
            /// </param>
            /// <param name="pXmlDoc">
            /// The xml doc.
            /// </param>
            /// <param name="pRootNode">
            /// The root node.
            /// </param>
            /// <param name="pUrlOptions">
            /// The url options.
            /// </param>
            private void AddUrlEntry(
                Item pItem, XmlDocument pXmlDoc, XmlElement pRootNode, UrlOptions pUrlOptions)
            {
                if (pItem != null && pItem.Visualization != null && pItem.Visualization.Layout != null && pItem.Name != "*")
                {
                    if (!pItem.Fields.Contains(FieldNames.GetFieldId(FieldNames.EXCLUDEFROMSITEMAP))
                        || pItem.Fields[FieldNames.EXCLUDEFROMSITEMAP].Value != "1")
                    {
                        string url = LinkManager.GetItemUrl(pItem, pUrlOptions);
                        GenerateUrlEntry(pXmlDoc, pRootNode, url);
                    }
                }

                if (pItem != null)
                {
                    foreach (Item childItem in pItem.Children)
                    {
                        AddUrlEntry(childItem, pXmlDoc, pRootNode, pUrlOptions);
                    }
                }
            }

            /// <summary>
            /// The generate url entry.
            /// </summary>
            /// <param name="pXmlDoc">
            /// The xml doc.
            /// </param>
            /// <param name="pRootNode">
            /// The root node.
            /// </param>
            /// <param name="pUrl">
            /// The url.
            /// </param>
            private void GenerateUrlEntry(XmlDocument pXmlDoc, XmlElement pRootNode, string pUrl)
            {
                XmlElement sitemapNode = pXmlDoc.CreateElement("url");
                pRootNode.AppendChild(sitemapNode);

                XmlElement locationNode = pXmlDoc.CreateElement("loc");
                locationNode.AppendChild(pXmlDoc.CreateTextNode(pUrl));
                sitemapNode.AppendChild(locationNode);

                XmlElement changeFrequencyNode = pXmlDoc.CreateElement("changefreq");
                changeFrequencyNode.AppendChild(pXmlDoc.CreateTextNode("weekly"));
                sitemapNode.AppendChild(changeFrequencyNode);
            }

            #endregion
        }
    }
  3. Wir registrieren unseren Handler in der Web.Config unter der Rubrik Handlers:
    <add verb="*" path="sitemapxml_handler.ashx" type="Namics.Samples.WebApp.Services.SitemapXmlHandler, Namics.Samples.WebApp" name="Namics.SitemapXmlHandler" />
  4. Wir registrieren unseren Handler in Sitecore (ebenfalls Web.Config) unter der Rubrik „CustomHandlers“ und geben ihm unseren gewünschten Namen:
    <handler trigger="/sitemap.xml" handler="sitemapxml_handler.ashx" />
  5. Wie im C-Sharp Code ersichtlich, prüfen wir noch, ob ein Item aus der Sitemap excluded werden soll. Hierfür erstellen wir auf dem entsprechenden Page-Template ein Checkbox-Field, welches „Excluded from Sitemap“ lautet und demzufolge für die Steuerung zuständig ist.

Als Resultat haben wir eine vollfunktionsfähige sitemap.xml auf unserer WebPage.

Urwald der Konfigurationen

Wie bereits in unserem Blog-Eintrag Sitecore Web.Config aufteilen beschrieben, ist es möglich die Konfiguration der Sitecore Solution in logisch-trennbare Dateien zu splitten. Dies verbessert zum einen den Überblick massiv und vereinfacht das Austauschen von einzelnen Konfigurations-Bereiche.

Neben der eigentlichen Web.Config und deren Bestandteile, besitzt Sitecore jedoch eine grosse Menge an weiteren Konfigurationsdateien. Im App_Config Verzeichnis verstecken sich nur schon in einer leeren Sitecore-Solution viele weitere Konfigurationsdateien. Zu all dem kann man als Entwickler relativ einfach und schnell, weitere Config’s erzeugen, welche es später dem Administrator der Webseite erlaubt – im vordefinierten Rahmen – die Lösung an die gewünschten Bedürfnisse anzupassen.

Nun ist es jedoch so, dass wir selten direkt auf der schlussendlichen Zielplattform entwickeln und bei jedem Speichern/Kompilieren dies direkt live haben wollen. So entstehen pro Plattform verschiedene Konfigurationen. Als Entwickler möchte ich beispielsweise alle Debug-Informationen im Log, ein visuelles Tracing auf der Dev-Webseite oder eine andere Ansicht für gewisse Informationen haben, welche spätestens in der Live-Webseite nicht mehr erscheinen sollten. Je nach Kritikalität des Projektes gibt es da noch Testing-, Quality-, Content-Plattformen dazwischen. All diese Umgebungen benötigen unter umständen verschiedene Einstellungen.

Damit dies nicht zu einem unübersichtlichem Urwald führt und bei jedem Deployment darauf geachtet werden muss, welche Configs überschrieben, angepasst oder gelöscht werden müssen, haben wir einen kleinen, simplen Skript dazu geschrieben:

 

@ECHO off

REM Title:          Set Environment Configuration

REM Description:    Recursively replaces all master files with the specific environment
REM                 files for a given suffix, starting at the defined path or the default
REM                 path if none given. The format for each environment file has to be as
REM                 follows: myfilename.config_SUFFIX, ex. sitecore.config_Q
REM Parameters:     %1 Environment Suffix, ex. Q (required)
REM                 %2 Start Path, ex. "C:\My Directory" (optional)
REM Example:        set_env_config.bat Q "C:\My Directory"

SETLOCAL ENABLEDELAYEDEXPANSION

ECHO.

REM Error handling - check parameters

IF (%1)==() (
    ECHO Parameter environment suffix missing, ex. Q
    GOTO END
)

IF NOT (%2)==() (
    IF NOT EXIST "%~2" (
        ECHO Specified path not found, check if the directory exists and make sure to use quotes, ex. "C:\My Directory"
        GOTO END
    )
)

REM Looks recursively for files following the pattern *.config_SUFFIX, starting at the

REM specified path or the default path if none given
FOR /r %2 %%f IN (*.config_*%1*) DO (
    REM environment path (with filename) and file
    SET env_path=%%f
    SET env_file=%%~nxf
    REM replace _SUFFIX in specific environment file to get the original filename
    SET orig_path=!env_path:_%1=%!
    REM copy and override the env file with the original one without confirmation
    xcopy "!env_path!" "!orig_path!" /Y
    REM check for errors after copy
    IF errorlevel 0 (
        ECHO -- Successfully copied file !env_file! to !orig_path!
    ) ELSE (
        ECHO -- Error while copying file !env_file! to !orig_path!
    )
    ECHO.
)

:END
REM PAUSE
REM EXIT

 

Dieses Script ersetzt also alle Config-Dateien mit einem bestimmten Dateinamen-Suffix.

So kann ich beispielsweise für die ConnectionsStrings, pro Umgebung eine Konfiguration vorbereiten:

  • ConnectionString.config_DEV
  • ConnectionString.config_TEST
  • ConnectionString.config_QUALITY
  • ConnectionString.config_LIVE

Im Deployment-Prozess kann ich dann lediglich das Batchfile ausführen & meine Umgebung ist bereits richtig konfiguriert:

D:\inetpup\myWebsite\SetEnvironmentConfig.bat QUALITY

 

Dies durchsucht nun alle Config-Dateien (ab Batch-Standort & Subfolder) und ersetzt alle bestehenden Konfigurationen mit den Quality-Einstellungen.

Sitecore Web.Config aufteilen

Da Sitecore zu Grunde liegend eine ASP.NET Applikation ist, welche  auf IIS läuft, basiert die Grundkonfiguration auf der web.config. Diese Datei wird bei der Installation stark erweitert und verliert somit an Übersichtlichkeit.

Divide and conquer

Durch einfaches Splitting, erreicht die web.config wieder eine Grösse die man gut lesen kann. Beim starten des Worker Process sammelt der IIS alle Configs zusammen.

Ein Nachteil beim Splitting ist, wenn es die Projektanforderung verlangt, dass die ConnectionStrings verschlüsselt werden. Dies funktioniert nur, wenn die ConnectionStrings in der Web.Config bleiben.

Und so wirds gemacht:

<configuration>
 <connectionStrings configSource="App_Config\ConnectionStrings.config" />
 <appSettings configSource="App_Config\AppSettings.config" />
 <sitecore configSource="App_Config\Sitecore.config" />
</configuration>