Eigene Berechtigungen erstellen

Das Zuweisen von Berechtigungen ist in Sitecore, wie in den meisten anderen Content Management Systemen eine grundlegende Funktion. Enwickler sollten keine redaktionellen Inhalte verändern, SEO Spezialisten lediglich suchmaschinenrelevante Informationen befüllen und Redakteure einen exklusiven Freigabemechanismus besitzen. Von Sitecore 6.6 auf … Weiterlesen

MultilistField in Kommunikation mit WebServices

Die Datenfelder von Sitecore besitzen stets einen Fokus: Items. Nur was ist, wenn ich auf Funktionen eines weiteren Dienstes zugreifen möchte, ohne dies als Itemstruktur im Sitecore zu besitzen?

Dieser Wunschgedanke kommt je nach Anforderung schnell auf. Beispielsweise wenn ich ein explizites YouTube Video aus meinem Channel selektieren möchte oder auf nicht redaktionelle Listen, welche ich später im Code weiterverarbeiten möchte. Die Grenzen hierfür sind unendlich und gehen von einer einfachen XML-Datei bis hin zu SOAP, REST-Services oder andere properitäre Schnittstellen, welche man anbinden möchte.
(mehr …)

Customizing: Öffnen des Content-Editors innerhalb des PageEditors

Es gibt Situationen in denen man gerne innerhalb des Page-Editors auf die Werkzeuge des Content-Editors zugreifen möchte. Für diese Anforderungen kann man in Sitecore den PageEditor-Ribbon mit weiteren Buttons einfach und ohne Programmieranpassungen erweitern: (mehr …)

Custom Gutter für die Anzeige von fehlenden Item Sprach-Versionen

Wir beschreiben hier, wie eine häufige Kundenanforderung gelöst werden kann, um zu sehen ob ein Item in der aktuell aktivierten Sprache vorhanden ist, oder nicht. Out of the Box bietet Sitecore nur den Gutter „Missing Versions“ an, welcher sich jedoch darauf spezialisiert hat alle Items hervorzuheben, welche irgend eine Lücke in einer Sprache oder Version besitzen – unabhängig von der gewählten Sprach-Version. Weiterlesen

Unser Wissen für Euren Erfolg – Namics goes Open Source

Als wir uns vor einigen Jahren dafür entschieden haben, im .Net Umfeld mit dem Content Management System „Sitecore“ zu starten, gingen wir wohl – wie bei jeder Evaluation, ein gewisses Risiko ein. Erfüllt dieses CMS die Anforderungen der Kunden? Lässt … Weiterlesen

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.

Geschäftsrelevante Daten im CMS

Die Welt dreht sich immer schneller. Man könnte schon fast denken, man lebe auf einem kleinen Spielzeug-Globus, welcher von einem Kind immer und immer schneller um seine eigene Achse gedreht wird. Der Name des Kindes? Ja, genau  „IT“ lautet sein Name und er besitzt immer wieder neue spannende Ideen, was er als nächstes tun kann.

Ein Schritt weniger Sinnbildlich, kann man die IT wohl als Treiber für viele neue Ideen bezeichnen. Dies führte mittlerweile zu einer grossen Menge an guten Lösungen – welche es verstehen, die Geschäftsrelevanten Prozesse eines Betriebes ideal zu optimieren. Die Produkte werden immer komplexer, dafür aber angepasster und auf die Bedürfnisse eines jeden Einzelnen zugeschnitten.

Dieser entwickelte „Massanzug“, welches für die Effienzsteigerung und somit die Schaffung von Wettbewerbsvorteilen wichtig ist, beinhaltet jedoch das Risiko des Technologie-Wildwuchses und führte oftmals zu einem administrativen „Overhead“. Alle Daten, Informationen, Rollenkonzepte, etc. mussten mehrmals angewendet werden, da man jedem Produkt seine eigene Firmen-Kultur und Arbeitsweise  in Form von Konfigurationen erlernen musste.

Bei der Erschaffung einer sinnvollen und gut überlegten CMS Lösung gehören solche Themen auch dazu. Ein Content Management System soll dafür da sein „Content“ zu erfassen und diesen präsentieren zu können. Dennoch erreicht man heutige Wettbewerbsvorteile nur damit, wenn man redaktionellen Content mit geschäftsrelevanten Daten kombiniert und somit auch solche „Out-of-the-Box“-Lösungen dazu bringt, so zu funktionieren, wie die Unternehmung lebt. Die Kultur, die Strukturen und die Arbeitsweise eines CMS sollte ebenfalls wie ein „Massanzug“ funktionieren, ohne den Nachteil zu haben, dass man Daten mehrfach pflegen und administrieren muss.

Sitecore und seine Freundlichkeit individuell erweitert zu werden, bietet hierfür alle Möglichkeiten aus einem reifen CMS, ein individuelles, für sich passendes CMS zu erschaffen.

Als Beispiel haben wir zusammen mit der Migros Klubschule eine solche Lösung entworfen. Die Klubschule verwaltet, administriert und koordiniert all ihre Geschäftsdaten mit einem SAP System. Diese Verwaltung geht von den einzelnen Center-Daten über die einzelnen Kurse, bis hin zu jeder einzelnen Diplomdurchführung.
Nun galt es als Aufgabe, die geschäftsrelevanten Daten aus ihrem ERP-System zu verwenden, als Strukturaufbau in das CMS-Konzept einfliessen zu lassen und zusammen mit redaktionellen Texten und Webfunktionalitäten, die Seite anzureichern.

Als Lösung haben wir einen SAP Data-Provider entwickelt, welcher Daten aus dem SAP System in Realtime abruft bzw. zuschickt und diese mit Sitecore synchronisiert.

Als Beispiel oben sieht man eine Angebotsseite zu einem bestimmten Subthema der Migros Klubschule. Diese Unterkategorie wurde automatisch ins Sitecore-System importiert und gleicht diese Daten nun automatisch in einem definierten Intervall ab. Damit zu den Rohdaten wie Titel, Kategorie und die darunter liegenden Kursen auch Inhalt erfasst werden kann, hat der Seitenauthor nun die Möglichkeit, eine solche Spartenseite mit eigenem Inhalt zu erweitern.

Auch bei der Eröffnung eines neuen Centers muss in erster Linie auf die korrekte interne Erfassung im ERP System Acht gegeben werden. Der Abgleich ins CMS funktioniert automatisch und muss lediglich von einem Author überprüft und veröffentlicht werden. Anschliessend ist das Center bereits für Kunden ersichtlich.

Das Business wird also mit denjenigen strategischen Werkzeugen definiert, welche dafür verantwortlich sind  – und das CMS passt sich individuell dabei an.