Kürzlich in New Orleans – Rückblick Sitecore Symposium 2016

Sitecore Symposium 2016 New Orleans

Ich hatte das Glück und durfte dieses Jahr zum Sitecore Symposium nach New Orleans reisen. Mit allen, die nicht dabei sein konnten, möchte ich hier meine Highlights teilen.
Es war wieder mal ein Spitzen Event – und Sitecore ein großartiger Gastgeber. Zunächst zur Location: was für eine tolle Stadt, so lebendig, offen und musisch. Insbesondere das French Quarter ist faszinierend.

French Quarter in New Orleans beim Sitecore Symposium 2016
French Quarter in New Orleans

Diese Atmosphäre hat uns durch das ganze Event begleitet. 2000 Menschen aus aller Welt waren da, eine tolle Gelegenheit die Sitecore Community kennenzulernen und sich mit anderen Experten auszutauschen. Inhaltlich war für alle Zielgruppen etwas dabei, es gab Sessions mit den Schwerpunkten Marketing, Business, Development und diesmal auch eine für Sitecore-Neulinge. Die Entscheidung für eine Session war manchmal schwer, für Entwickler gab es bis zu 4 gleichzeitige Sessions.

Pre-Seminar:  “Habitat Masterclass”

Bevor es mit dem offiziellen Symposium losging, hat Sitecore dieses Jahr zum ersten Mal (zumindest in Europa), einige Intensivworkshops angeboten. Ich habe mich für einen eintägiges Seminar bei Thomas Eldblom von Sitecore zu Habitat entschieden. Es ging im Wesentlichen darum, die Architektur hinter Habitat und damit Helix zu verstehen:

“Helix is the official Guidelines and Recommended Practices for Sitecore development.”

Anders Laub hat einen guten Artikel für den schnellen Einstieg in modulare Architekturen geschrieben. Wenn ihr gleich selbst mal ein Habitat Modul entwickeln wollt, dann findet ihr hier die Schritt-für-Schritt Anleitung von Thomas.

Die Aussage war ganz klar: Sitecore sieht Habitat nicht als Framework oder Starterkit, es ist ein Demo-Projekt, umgesetzt nach Sitecore Best-Practices.

Anschließend gab es das Willkommensgetränk im Partner-Pavillon mit Musik aus der Preservation Hall. Die Stimmung stimmte, ein toller Einstieg in die Konferenz.

Mein Symposium Recap

Opening Keynote

Sitecore Symposium 2016 Opening Keynote Michael Seifert

Die Eröffnungskeynote wurde von Michael Seifert persönlich gehalten. Auf Experience (Experience, Experience) folgt DEMAND MORE

  • from Commerce
  • from Mobile
  • from Automation
  • from Context
  • from Engagement

Die Themen, welche uns dann natürlich auch durch das Symposium begleitet haben, waren: Embedded Commerce, Sitecore Experience Accelerator, New Path Analyzer Features, Azure.

Innovation & Technology

In der anschließenden Guest Keynote ging es um Technology & Innovation aus Sicht des Futuristen Jason Silva. Es war beeindruckend und inspirierend, ICH kann es nicht mit Worten beschreiben. Daher hier ein paar Zitate:

“Nature and the technology are one
in the same”
“The next Billionaire will be the one who affects a billion people in a positive way.”
“We have a responsibility to awe.”

Neugierig geworden? Dann schaut euch doch mal bei YouTube um.

Developer Keynote

Lars Fløe Nielsen hat einige Features der 8.2 vorgestellt:

  • Support Dependency Injection (einen guten Artikel von Kam Figy, Sitecore MVP, findet ihr hier)
  • Sitecore NuGet
  • Express Migration Tool (zunächst von 7.2 auf 8.2)
  • Helix Guidelines

Er hat die Wichtigkeit der Sitecore Entwickler (also uns) hervorgehoben.

Sein Schwerpunkt: Die Sitecore Community und das Feedback ist für Sitecore und die Produktentwicklung enorm wichtig. Ein Auszug:

xConnect

Zum Thema Sitecore xConnect gab es unterschiedliche Sessions. Was ist xConnect? Laut Todd Mitchell von Sitecore:

“Single, unified API that allows you to collect and act on any individual and their behavior”

Letztlich können mit dieser API Daten aus der xDB gelesen und in die xDB geschrieben werden, mit dem Ziel, ein Experience Profile über alle Channel, aber auch aus der On- und Offline-Welt, erstellen zu können. Ein gern herangezogenes Beispiel ist hier das Tracking des Besuchs eines Ladengeschäfts mittels iBeacon. Die Persistierung am xProfile erfolgt dann zukünftig mittels xConnect API. Sitecore xConnect kommt mit Sitecore 8.3.

Commerce vNext

Das ist mein persönliches Highlight:

Sitecore Symposium 2016 Commerce vNext

Sitecore hat eine eigene Commerce Engine basierend auf .NET Core von Microsoft entwickelt. Das Modul ist nahtlos in Sitecore integriert und es ist flexibel erweiterbar. Das initiale Release umfasst: Customer Management, Order Management und Dynamic Pricing.

Die gute Nachricht: Das im letzten Jahr veröffentlichte Sitecore Commerce Connect Modul als Layer zwischen Sitecore und einem externen Shop bleibt bestehen.

Das Modul wird mit Sitecore 8.3 veröffentlicht – ich bin wirklich gespannt.

House of Blues

Zwischen den Tagen gab es eine stilechte Symposium-Party im House of Blues. Dieses liegt mitten im French Quarter, niemals hätte ich dahinter eine Location für so viele Menschen vermutet. Es gab einen Shuttlebus, lecker Essen & Trinken, chillige Ecken, aber auch mehrere Bühnen mit Livemusik. Und wir haben gerockt!

Sitecore Symposium Afterparty House Of Blues
House of Blues – New Orleans

Schön, dass es am nächsten Morgen erst um 9 Uhr losging 😉

Why Games Make Us Better

Spieleentwicklerin Jane McGonigal hat uns den Start in den Tag 2 leicht gemacht. Am Beispiel Pokémon Go hat sie uns gezeigt, was ein gutes Spiel ausmacht, aber auch vorgeführt wie viel wir spielen: 1,75 Millarden Minuten pro Tag wird Candy Crush gespielt, das sind 3,5 Millionen Vollzeit-Stellen…
Außerdem sie hat uns ein Alibi gegeben:

Jane McGonigal - Why Games Make Us Better

Und wenn wir alle ein bisschen wie Uber denken, ist spielen dann unsere Zukunft?

Und sonst?

Unit Testing

Sitecore MVP Dan Solovay hat seine Begeisterung für Test Driven Development mit uns geteilt. Sitecore 8.2 macht es uns durch abstrakte Klassen, virtuelle Methoden und integrierter Dependency Injection in der Entwicklung viel leichter automatisiert zu testen. Er hat uns seine Tools vorgestellt und in Teilen live angewendet. xUnit, NSubstitute und NCrunch sind jedenfalls auf meiner Merkliste gelandet.

Multisites

Liz Spranzani und Trevor Campbell haben die seit Sitecore 8.1 in Sitecore integrierten Language-Fallback-Möglichkeiten vorgestellt, inkl. Stolpersteine und Best-Practices. Vor allem aber haben sie Anhaltspunkte und Fragenkataloge als Entscheidungsgrundlage für eine Multilanguage Strategie in Sitecore mitgebracht.

Turbocharged Publishing

Steven Pope hat in seiner Session anschaulich erläutert, warum Publizierung in Sitecore so teuer ist: Wenn 1 Item in 3 Sprachen mit 2 Publizierungszielen publiziert wird, mussten bisher 8753 Items angefasst werden (ich sage nur: Publishing Restrictions, Fields, Base Templates, Workflows, Languages, Publishing Targets, Related Items, Ancestors etc.)

Mit Sitecore 8.2 hat Sitecore die Publizierung auf Basis .NET Core reimplementiert. Damit dauert die Republizierung einer Webseite, die vorher mehr als 1 Tag gedauert hat, nur noch knapp 20 Minuten.

Aktuell ist die Verwendung des neuen Publishing Service optional konfigurierbar.

Mit der 8.3 kommen weitere UX Verbesserungen: Es gibt einen Publishing Manager in welchem die Redakteure OOTB die aktuellen Publizierungen einsehen können. Die für Redakteure nur irreführenden unterschiedlichen Publishing Modi wird es nicht mehr geben (sondern nur noch einen stablien und performanten) und “Publish Site” wird es nur noch für Admins geben.

Die Präsentation hatte echt Unterhaltungswert!

Closing Keynote

Es gab ein Schauspiel zum Thema xConnect in Perfektion. Vom Handy auf dem Sofa, über den Webshop zur persönlichen Ansprache im Ladengeschäft.

Außerdem hat Lars Fløe Nielsen einen Einblick in die Vision des Sitecore “Labors” gegeben: Redakteure bewegen sich in Sitecore ohne bewussten Applikationswechsel, es öffnet sich was gebraucht wird, dort wo es gebraucht wird, Bedienung in einem Fluss.

Das war der offizielle Teil. Für den Abend sind wir einem Tipp gefolgt und sind in kleiner Runde in die Frenchman Street, haben erst lokale Küche genossen und anschließend den Abend in einer Bluesbar ausklingen lassen. Das war ein wunderbarer Abschluss. Ich danke euch.

Ach ja, das nächste Symposium ist 2017 in Las Vegas. Ich freu mich auf euch 🙂

SUGCON 2016 in Kopenhagen – Rückblick auf Tag 2

Über den ersten Tag der SUGCON habe ich hier bereits berichtet. Am zweiten Tag ging es mit den Sessions genau so intensiv weiter.

Active Commerce: How we use SIM, PowerShell, Octopus Deploy, and Azure to automate product builds

Nick Wesselman hat zunächst kurz das Active Commerce E-Commerce System bzw. das darunterliegende Framework, basierend auf dem Sitecore E-Commerce Service (SES) vorgestellt. Das SES wird nicht mehr von Sitecore supported, ist aber Open Source und wurde auf GitHub veröffentlicht.

Anschließend hat Nick die intern etablierten Build- und Deployment-Prozesse und Tools vorgestellt. Meine Erkenntnis: PowerShell, PowerShell, PowerShell 🙂

BuildAndDeploymentProcess

Deep Dive – You, me and Sitecore MVC

Kern Herskind Nightingale hat mit Begeisterung einen Einstieg in MVC gegeben. Ist aber auch auf sehr konkrete Probleme und Lösungsansätze eingegangen:

WebForms sind Vergangenheit, MVC ist die Zukunft. Sein klarer Wunsch: Annäherung von Sitecore MVC an ASP.NET MVC.

Hedgehog Development – Sponsor Technical Highlights

Hedgehog als Sponsor hat kurz das TDS vorgestellt, was natürlich mehr kann als nur Items zu synchronisieren 😉

Wenn ihr interesssiert seid, findet ihr in diesem Blog-Beitrag von mir mehr Infos zum TDS.

Super Charge your Continuous Integration Deployments

Nikola Gotsev hat über CI-Prozesse im Allgemeinen und Best Practises zum Thema Branches, Frontend-Integration, Testing und den dafür erforderlichen verschiedenen Tools im Speziellen gesprochen.

Seine Tipps für Continuous Deployment:

  1. So wenig Content wie möglich in Source Control Systemen.
  2. Keinen Content in höhere Umgebungen ausliefern, dafür sind Packages zu verwenden.
  3. Vor der Auslieferung Datenbank Backup machen (Es gibt kein Undo).

Und so sieht dann sein perfekter Deploy-Prozess aus:

Perfect Scenario

Practical Habitat: Embrace the Architecture

Anders Laub hat weniger über Habitad gesprochen, sondern eher über Softwarearchitektur im Allgemeinen. Die Softwarearchitektur bildet die Grundlage und ist der langlebigste Aspekt eines Entwicklungsprojektes. Anders hat das wie folgt skizziert.

ArchitectureMethodsTools

Er war mit viel Herz und Verstand dabei und hat zum Nachdenken und Diskussionen angeregt. Ein paar Twitter-Zitate:

Ladies and Gentlemen, Start your Testing!

Alistair Deneys hat über Unit Tests in der Sitecore Entwicklung gesprochen. Klar gibt es Herausforderungen, die zu meistern sind: die Items in Sitecore, die Konfigurationsdateien, veraltete/ nicht “mockable” API…

Alistair hat dies gleich auf mehreren Wegen live demonstriert und gesteigert:

  1. Sitecore als Application installieren und die Tests als Page Test Runner laufen lassen
  2. Echte NUnit-Tests, indem Sitecore .configs und .dlls in das Test-Projekt kopiert werden.
  3. Mocken von Sitecore Items mittels Sitecore.FakeDb.

Schlussendlich hat er dann noch gezeigt, wie mittels Sitecore.LiveTesting eine Sitecore Infrastruktur außerhalb des IIS simuliert werden und Tests dagegen gefahren werden können.

Es gab auch schon eine kleine Vorschau auf das was kommt. Alles wird besser 😉 Statisches fliegt raus und es wird viele abstrakte Basisklassen geben.

Mein Mitbringsel für Euer Bücherregal 😉

UniTesting

I refactored doc.sitecore.net and all I got was this experience

Der letzte Vortrag der Konferenz kam von Martina Welander: Ein lebendiger und interessanter Vortrag über den Werdegang des Refactorings von doc.sitecore.net. Nicht immer auf dem direkten Weg, aber sehr reflektiert und selbstkritisch.

Und so hat sie den Prozess selbst dargestellt: https://twitter.com/mhwelander/status/725307365273288704

Irgendwie ein sprechendes Bild für Softwareentwicklung 😉

Auf Wiedersehen

Es war ein rundum gelungenes Event. Ich komme bestimmt nächstes Jahr wieder. Vorher sehen wir uns vielleicht auf einem der nächsten Usergroup Treffen in Deutschland? Ich würde mich freuen.

Highlights der SUGCON 2016 in Kopenhagen

Letzte Woche war ich zum ersten Mal auf der europäischen Sitecore User Group Conference, diesmal zu Gast in Kopenhagen. Die SUGCON ist eine Konferenz der Sitecore Community, von Entwicklern für Entwickler. Mir hat es richtig gut gefallen, die Vorträge waren Spitze. Ich habe Neues gehört, Wissen vertieft, Denkanstöße bekommen und mit vielen tollen Leuten gesprochen.

Tag 1

Am ersten Tag ging es um 12 Uhr mit einem gemeinsamen Lunch los. Es war lecker und ein guter Auftakt, auch um zu sehen, wer alles so da ist: Entwickler aus aller Welt.

Die Keynote haben sich Michael Seifert und Lars Fløe Nielsen geteilt. Das fing mit einer Vorschau auf die nächsten Sitecore Versionen schon spannend an.

Sitecore 8.2 legt den Fokus auf die UX des Experience Editors (Pre-Release!):

Preview ExperienceEditor

In 8.3 liegt der Schwerpunkt auf der Verbesserung der Marketing-Werkzeuge (Pre-Release!):

Preview Experience Editor AutomationPlans

Zukünftig sind deutlich weniger Applikationssprünge nötig. Beispielsweise werden Formulare und Engagement Pläne direkt im Experience Editor gepflegt.

Und sonst:

  • Es wird ein Express Upgrade Tool geben. Perspektivisch ist dann keine Migration mehr von Version zu Version erforderlich, sondern größere Sprünge können einfach durchgeführt werden.
  • Enterprise Publizierung: Es wird eine triggerbasierte Publizierung geben und die Performance im Allgemeinen wird verbessert.
  • ASP.NET WebForms werden nicht mehr weiter entwickelt. Vorerst natürlich aber weiter supported.
  • Das Silverlight Plugin wird zukünftig nicht mehr benötigt. Schade 😉
  • Und mein Highlight: WFFM wird abgelöst (kann man auch in obigem Screen schon sehen)

Es folgten viele weitere Sessions. Teilweise fanden drei parallel statt und es war nicht immer einfach sich zu entscheiden. Ein Auszug meiner Agenda des ersten Tages habe ich hier mal zusammengefasst:

Serialize all the things with Unicorn

Kam Figy selbst hat Unicorn vorgestellt. Unicorn ist ein Tool für den Austausch von Items zwischen unterschiedlichen Sitecore Umgebungen. Hervorzuheben ist, dass die serialisierten Items für Entwickler gut lesbar und damit auch Merge-Konflikte einfacher zu handhaben sind (als die serialisierten Items von Sitecore selbst). Neu ist: Mit der Version 3.2 können auch Benutzer und Rollen synchronisiert werden.

Commerce Nerdvada for Sitecore with uCommerce

Anschließend hat Søren Spelling Lund die Architektur von uCommerce vorgestellt – modular und erweiterbar, nach dem SOLID Prinzip.

SOLID

uCommerce ist ein .NET basiertes E-Commerce System, für welches es bereits einen Sitecore Commerce Connector gibt. Interesse geweckt? Es gibt Online-Trainings. Das nächste beginnt am 23. Mai.

Sitecore Pathfinder – Another look at the developer experience

Dmitry Kostenko von Sitecore hat den Sitecore.Pathfinder als „Experimental open-source toolchain for Sitecore that looks end feels like a compiler“ vorgestellt. Die Idee / das Ziel:

  • Alle Bestandteile der Sitecore Entwicklung (Templating, Items, Design, Quellcode) an einer Stelle verortet
  • Entwicklung in einer beliebigen IDE
  • Einfaches Deployment (ein Shortcut für Build und Deploy)
  • CI: Lokal und auf den Servern die gleichen Tools verwenden

Templating könnte dann in Zukunft so aussehen:

Templating

Mir hat die Idee zu denken gegeben. Ich bin neugierig geworden und ich werde es mir bestimmt näher ansehen.

Sitecore might be secure, but your site isn’t

In Bas Lijten’s Vortrag ging es weniger um Sitecore als um die Absicherung von Webseiten im Allgemeinen. Er hat potentielle Sicherheitslücken anschaulich demonstriert und Lösungen aufgezeigt. Seine Folien findet ihr bereits auf Slideshare.

Sitecore and NuGet: Sitecore development done right

Sean Holmesby hat uns in seiner Session gezeigt, wie NuGet effektiv in der Sitecore- Entwicklung und im Build-Prozess eingesetzt werden kann, um die „DLL-Hell“ zu umgehen. Außerdem hat er mögliche Standards / Konventionen aufgezeigt. Den gesamten Vortrag könnt ihr bei YouTube sehen. Seine Tools und Ideen in Kürze findet ihr hier in der Sitecore Communiy.

GetTogether

Um 18 Uhr waren dann die Sessions durch. Bei einem geselligen Getränk wurden die Sitecore MVP’s 2016 gekürt. Herzlichen Glückwunsch an die Gewinner. Später gab es noch eine Vorstellung des Gewinners des diesjährigen Hackaton. Zum zweiten Mal ist es das Team Uniques von Unic geworden. Wow.

Und zuletzt gab es noch ein lustiges Quiz für alle, in dem historisches Sitecore-Wissen gefragt war. Hättet ihr gewusst, welche der folgenden Datenbanken nie von Sitecore supported wurde: MS SQL, MySQL, MongoDB oder Tamino?

Dinner

Gegen 21 Uhr war das offizielle GetTogether durch, wir aber noch lange nicht 😉

Weiter mit Tag 2 der SUGCON 2016

Sitecore Commerce Connect – Teil 5: Warenkorb verwalten

Bisher ging es in dieser Serie um die Verwaltung und Darstellung von Produkten und Preisen aus Hybris. Das Ziel dieses Artikels ist es auch erste Prozesse rund um den Warenkorb mittels Sitecore Commerce Connect abbilden zu können.

Warenkorb

Die zentrale Überlegung am Anfang eines Projektes ist, welches System zu welcher Zeit die Zuständigkeit für den Warenkorb hat. Wird der Warenkorb immer auf beiden Seiten synchron gehalten, das heißt jede Änderung am Warenkorb wird unmittelbar an das externe Shopsystem (kurz ECS) gemeldet, oder wird der Warenkorb im ECS nur zu definierten Zeitpunkten synchronisiert, also z.B. beim Checkout? Letzteres bedingt weniger Requests an das ECS, aber Sitecore-seitig ist dann z.B. eine Warenkorbwertermittlung erforderlich, die im schlechtesten Fall nicht mit der im ECS synchron ist.

Warenkorb-Verwaltung

Im Rahmen unserer Evaluierung ist das ECS für den Warenkorb zuständig und Sitecore meldet alle Änderungen unmittelbar an das ECS. Für die Darstellung auf der Webseite werden die Daten rund um den Warenkorb wiederum immer online vom ECS abgefragt.
Außerdem haben wir uns entschieden, den mitgelieferten Engagement Plan ‘Abandoned Carts’ gleich mit zu nutzen, zumal dafür scheinbar keine weitere Implementierung erforderlich ist.

Warenkorb anlegen/ abfragen

Für alle Funktionen rund um den Warenkorb ist der Service Provider ‚CartServiceProvider‘ des Cart Service Layers zuständig.

Beispiel Verwendung CartServiceProvider.CreateOrResumeCart

[csharp]
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using Comspace.Sitecore.CommerceConnect.Services.Prices;
using Sitecore;
using Sitecore.Analytics;
using Sitecore.Commerce.Entities.Carts;
using Sitecore.Commerce.Services.Carts;
using Sitecore.Commerce.Services.Prices;
using Sitecore.Data;

namespace Comspace.Sitecore.CommerceConnect.Services.Carts
{
/// <summary>
/// Sitecore.Commerce.Carts.config
/// </summary>
public class CartManager
{
public const string TrackerAttachmentKeyCart = "Comspace.Sitecore.CommerceConnect_Cart";

public static Cart GetCart()
{
if (!Tracker.Current.Contact.Attachments.ContainsKey(TrackerAttachmentKeyCart))
{
//create cart
var createOrResumeCartRequest = new CreateOrResumeCartRequest(
"Website",
Context.User.Profile.Email,
"Default",
Context.User.Profile.Email); //NOTE FindCartInEaState: c.CustomerId == request.CustomerId
var cart = new CartServiceProvider().CreateOrResumeCart(createOrResumeCartRequest).Cart;

//persist cart
Tracker.Current.Contact.Attachments[TrackerAttachmentKeyCart] = cart;
}
return Tracker.Current.Contact.Attachments[TrackerAttachmentKeyCart] as Cart;
}
}

//…
}
[/csharp]

Pipeline loadCart

Die Anbindung des ECS erfolgt mittels Erweiterung der Pipeline ‚loadCart‘ der Sitecore.Commerce.Carts.config.
.config

[xml]
<!– LOAD CART
Gets the cart object that matches the specified criteria.
This pipeline is responsible for reading data for a specific cart that is managed by the commerce system.
This pipeline reads the cart data from the commerce system and converts that data into the Commerce format.
–>
<commerce.carts.loadCart>
<processor type="Comspace.Sitecore.CommerceConnect.Hybris.Pipelines.Carts.LoadCart, Comspace.Sitecore.CommerceConnect.Hybris" patch:after="processor[@type=’Sitecore.Commerce.Pipelines.Carts.LoadCart.LoadCartFromEaState, Sitecore.Commerce‘]"/>
</commerce.carts.loadCart>
[/xml]

Processor LoadCart

[csharp]
using Comspace.Sitecore.CommerceConnect.Hybris.Connector;
using Comspace.Sitecore.CommerceConnect.Hybris.Connector.Model.Cart;
using Comspace.Sitecore.CommerceConnect.Services.Carts;
using Sitecore.Commerce.Entities.Carts;
using Sitecore.Commerce.Pipelines;
using Sitecore.Commerce.Pipelines.Carts.Common;
using Sitecore.Commerce.Services.Carts;

namespace Comspace.Sitecore.CommerceConnect.Hybris.Pipelines.Carts
{
/// <summary>
/// commerce.carts.loadCart
/// </summary>
public class LoadCart : CartPipelineProcessor
{
public override void Process(ServicePipelineArgs args)
{
var loadCartRequest = args.Request as LoadCartRequest;

//get from ECS
ExternalCart externalCart = CartConnector.Load(global::Sitecore.Context.Language.Name, loadCartRequest.UserId);

//convert to commerce connect cart
Cart cart = new Cart();
cart.ExternalId = externalCart.Code;
cart.ShopName = loadCartRequest.ShopName;
cart.UserId = loadCartRequest.UserId;

//init totals
cart = CartManager.UpdateCartTotals(cart); //NOTE prevent multiple request

//persist for next processor
((CartResult) args.Result).Cart = cart;
}
}
}
[/csharp]

Produkt zum Warenkorb hinzufügen

Beispiel Verwendung CartServiceProvider.AddCartLines

[csharp]
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using Comspace.Sitecore.CommerceConnect.Services.Prices;
using Sitecore;
using Sitecore.Analytics;
using Sitecore.Commerce.Entities.Carts;
using Sitecore.Commerce.Services.Carts;
using Sitecore.Commerce.Services.Prices;
using Sitecore.Data;

namespace Comspace.Sitecore.CommerceConnect.Services.Carts
{
/// <summary>
/// Sitecore.Commerce.Carts.config
/// </summary>
public class CartManager
{
//…

public static Cart AddLine(string externalId, string sitecoreItemId, uint quantitiy)
{
Cart cart = GetCart();

//create cartlines
List<CartLine> lines = new List<CartLine>
{
new CartLine
{
Product = new CartProduct
{
ProductId = externalId,
SitecoreProductItemId = new ID(sitecoreItemId),
Price = PriceManager.GetPrice(externalId, Context.Language)
},
Quantity = quantitiy,
LineNumber = (uint) (cart.Lines.Count + 1)
}
};

//add cart lines
AddCartLinesRequest addCartLinerequest = new AddCartLinesRequest(cart, new ReadOnlyCollection<CartLine>(lines));
CartResult cartResult = new CartServiceProvider().AddCartLines(addCartLinerequest);

return cartResult.Cart; //result contains the updated cart
}
}
}
[/csharp]

Pipeline addCartLines

.config

[xml]
<!– ADD CART LINES
This pipeline is responsible for adding a new line to the shopping cart and recording a corresponding page event in DMS.
This happens when a product is added to the cart.
–>
<commerce.carts.addCartLines>
<processor type="Comspace.Sitecore.CommerceConnect.Hybris.Pipelines.Carts.AddLineToCart, Comspace.Sitecore.CommerceConnect.Hybris" patch:after="processor[@type=’Sitecore.Commerce.Pipelines.Carts.AddCartLines.AddLinesToCart, Sitecore.Commerce‘]"/>
</commerce.carts.addCartLines>
[/xml]

Processor AddCartLines

[csharp]
using System.Linq;
using Comspace.Sitecore.CommerceConnect.Hybris.Connector;
using Sitecore.Commerce.Entities.Carts;
using Sitecore.Commerce.Pipelines;
using Sitecore.Commerce.Services.Carts;

namespace Comspace.Sitecore.CommerceConnect.Hybris.Pipelines.Carts
{
/// <summary>
/// commerce.carts.addCartLines
/// </summary>
public class AddLineToCart : PipelineProcessor<ServicePipelineArgs>
{
public override void Process(ServicePipelineArgs args)
{
var cartLinesRequest = args.Request as CartLinesRequest;

//add to ECS
Cart cart = cartLinesRequest.Cart;
CartLine cartline = cartLinesRequest.Lines.First();
CartConnector.AddLineToCard(global::Sitecore.Context.Language.Name, cart, cartline);
}
}
}
[/csharp]

Warenkorb Summen

Damit die Summen im Warenkorb immer korrekt sind und keine Sitecore-seitige Berechnung stattfinden muss, werden auch die Warenkorb Summen immer online aus dem ECS geholt. Dafür muss die Pipeline ‘saveCart’ erweitert werden:

Pipeline saveCart

.config

[xml]
<!– SAVE CART
Saves the cart object to the external system and in Sitecore EA state.
–>
<commerce.carts.saveCart>
<processor type="Comspace.Sitecore.CommerceConnect.Pipelines.Carts.UpdateCartTotals, Comspace.Sitecore.CommerceConnect" patch:after="processor[@type=’Sitecore.Commerce.Pipelines.Carts.Common.SaveCartToEaState, Sitecore.Commerce‘]"/>
</commerce.carts.saveCart>
[/xml]

Processor UpdateCartTotals

[csharp]
using Comspace.Sitecore.CommerceConnect.Services.Carts;
using Sitecore.Commerce.Entities.Carts;
using Sitecore.Commerce.Pipelines;
using Sitecore.Commerce.Services.Carts;

namespace Comspace.Sitecore.CommerceConnect.Pipelines.Carts
{
/// <summary>
/// commerce.carts.saveCart
/// </summary>
public class UpdateCartTotals : PipelineProcessor<ServicePipelineArgs>
{
public override void Process(ServicePipelineArgs args)
{
var cartRequest = (CartRequestWithCart) args.Request;

//get from ECS
Cart cart = CartManager.UpdateCartTotals(cartRequest.Cart);

//persist for next processor
((CartResult) args.Result).Cart = cart;
}
}
}
[/csharp]

Beispiel Verwendung PricingServiceProvider.GetCartTotal

[csharp]
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using Comspace.Sitecore.CommerceConnect.Services.Prices;
using Sitecore;
using Sitecore.Analytics;
using Sitecore.Commerce.Entities.Carts;
using Sitecore.Commerce.Services.Carts;
using Sitecore.Commerce.Services.Prices;
using Sitecore.Data;

namespace Comspace.Sitecore.CommerceConnect.Services.Carts
{
/// <summary>
/// Sitecore.Commerce.Carts.config
/// </summary>
public class CartManager
{
//…

public static Cart UpdateCartTotals(Cart cart)
{
var request = new GetCartTotalRequest();
request.Cart = cart;

//get from ECS
var cartTotalResult = new PricingServiceProvider().GetCartTotal(request); //result contains the updated cart

Tracker.Current.Contact.Attachments[TrackerAttachmentKeyCart] = cartTotalResult.Cart; //refresh cache
return cartTotalResult.Cart;
}
}
}
[/csharp]

Stolpersteine

Der Processor ‘FindCartInEaState’ benötigt eine CustomerId, damit der Contact ermittelt werden kann. Dafür muss die eigentlich optionale CustomerId mit an den ‘CreateOrResumeCartRequest’ übergeben werden (siehe oben). Anderenfalls wird immer wieder ein neuer Warenkorb angelegt, obwohl für den Contact bereits einer existiert.

Resumé

Und auch diesen letzten Beitrag möchte ich mit ein paar Eindrücken und Erkenntnissen abschließen:

  • Der Cart Sercice Layer ist sehr umfangreich und im Rahmen der Evaluierung konnte nur ein sehr kleiner Ausschnitt evaluiert werden.
  • Eine Herausforderung in einem echten Projekt sind die Zuständigkeit und Synchronisation zwischen dem Sitecore Shop und dem ECS. Was wird wo zwischengespeichert und wann an wen gemeldet?
  • Wie auch schon unter Stolpersteine beschrieben, war der Zugriff auf die Contacts im Engangement Plan (automatisch im Hintergrund) immer wieder holprig. Eine der Ursachen war sicherlich die Entwicklungsumgebung: wechselnde Browser, plötzliche Neustarts etc.  Hier müsste in einem echten Projekt eine kritische Analyse erfolgen.
  • Auch dem Umgang mit Fehlern in der Verarbeitung muss in einem echten Projekt eine hohe Aufmerksamkeit gewidmet werden – Stichwort Transaktionssicherheit.

So, das war’s erstmal von meiner Seite zum Thema Commerce Connect. Ich freue mich über euer Feedback 🙂

Alle voran gegangenen Artikel dieser Serie findet ihr hier:
Teil 1: Ein Überblick
Teil 2: Produkte synchronisieren
Teil 3: Klassifizierungen synchronisieren
Teil 4: Preise darstellen

Sitecore Commerce Connect – Teil 4: Preise darstellen

In den letzten beiden Teilen dieser Serie ging es um die Synchronisation von Produkten und Klassifizierungen. Das Ziel dieses Artikels ist es auch die Preise aus dem externen Shopsystem, in unserem Fall Hybris, auf der Webseite anzeigen zu können.

ProduktDemo

Der Pricing Layer ist ein “klassischer” Layer im Sitecore Commerce Connect, d.h. die Preise werden online abgerufen und nicht synchronisiert. Der Grund: Preise ändern sich häufiger und die Preisfindung ist sehr komplex, hier wird die Hoheit im externen Shopsystem (kurz ECS) belassen.

Preisabfragen

Für die Abfrage von Preisen ist der Service Provider ‚PricingServiceProvider‘ zuständig.

Eine Beispiel:

[csharp]using System.Linq;
using Sitecore.Commerce.Entities.Prices;
using Sitecore.Commerce.Services.Prices;
using Sitecore.Globalization;

namespace Comspace.Sitecore.CommerceConnect.Services.Prices
{
/// <summary>
/// Sitecore.Commerce.Prices.config
/// </summary>
public class PriceManager
{
public static Price GetPrice(string productId, Language language)
{
CustomGetProductPriceRequest request = new CustomGetProductPriceRequest(language.Name, productId);
GetProductPricesResult result = new PricingServiceProvider().GetProductPrices(request);

return result.Prices.Any() ? result.Prices.First().Value : null; //simplified
}
}
}[/csharp]

Um die Preise aus dem ECS abzurufen, muss die Pipeline ‚getProductPrices‘ der Sitecore.Commerce.Prices.config erweitert werden:

Pipeline getProductPrices

.config

[xml]<!– GET PRODUCT PRICES Gets the price object that matches the specified criteria.
This pipeline is responsible for reading pricing data from a commerce system.
This pipeline requests product pricing information from the commerce system and
then converts the output into the proper Commerce format.
–>
<commerce.prices.getProductPrices>
<processor type="Sitecore.Commerce.Pipelines.Prices.GetProductPrices.GetProductPrices, Sitecore.Commerce">
<patch:delete />
</processor>
<processor type="Comspace.Sitecore.CommerceConnect.Hybris.Pipelines.Prices.GetProductPrices, Comspace.Sitecore.CommerceConnect.Hybris" />
</commerce.prices.getProductPrices>[/xml]

Processor GetProductPrices

[csharp]using Comspace.Sitecore.CommerceConnect.Hybris.Connector;
using Comspace.Sitecore.CommerceConnect.Hybris.Connector.Model;
using Comspace.Sitecore.CommerceConnect.Services.Prices;
using Sitecore.Commerce.Entities.Prices;
using Sitecore.Commerce.Pipelines;
using Sitecore.Commerce.Services.Prices;

namespace Comspace.Sitecore.CommerceConnect.Hybris.Pipelines.Prices
{
/// <summary>
/// commerce.prices.getProductPrices
/// </summary>
public class GetProductPrices : PipelineProcessor<ServicePipelineArgs>
{
public override void Process(ServicePipelineArgs args)
{
var request = args.Request as CustomGetProductPriceRequest;

//get from ECS
ExternalProduct productSource = ProductConnector.Load(request.Language, request.ProductId);
if (productSource != null && productSource.Price != null)
{
//convert to commerce connect price
Price price = new Price
{
Amount = productSource.Price.Value,
CurrencyCode = productSource.Price.CurrencyIso
};

//persist for next processor
((GetProductPricesResult) args.Result).Prices.Add("List price", price); //simplified
}
}
}
}[/csharp]

Stolpersteine

Der ‚GetProductPricesRequest‘ enthielt überraschenderweise kein Attribut für die aktuell zu synchronisierende Sprache. Daher musste dieser überschrieben werden:

[csharp]using Sitecore.Commerce.Services.Prices;

namespace Comspace.Sitecore.CommerceConnect.Services.Prices
{
/// <summary>
/// Extendended GetProductPricesRequest.
/// </summary>
public class CustomGetProductPriceRequest : GetProductPricesRequest
{
public string Language { get; set; }

public CustomGetProductPriceRequest(string language, string productId, params string[] priceTypeIds)
: base(productId, priceTypeIds)
{
Language = language;
}
}
}[/csharp]

Resumé

Zuletzt möchte ich wieder ein paar Eindrücke und Erkenntnisse zusammenfassen:

  • Der Service Layer ist eher klein und schnell erfasst.
  • Was mir wirklich gut gefällt ist, dass die Preishoheit im ECS verbleibt.
  • In umfangreicheren Produklisten sollte zwecks Performance die Nutzung der Methode/Pipeline ‚getProductBulkPrices‘ bevorzugt werden.

Wenn ihr mehr zum Sitecore Commerce Connect wissen wollt, dann schaut mal hier:

Sitecore Commerce Connect – Teil 3: Klassifizierungen synchronisieren

Im letzten Teil dieser Serie ging es um die Synchronisation von Produkten. Das Ziel dieses Blog-Artikels ist es auch Klassifizierungen unidirektional aus dem Sitecore heraus mit dem externen Commerce System (kurz ECS, in unserem Fall Hybris) zu synchronisieren und den Produkten zuzuordnen.

Klassifizierungen sind im Projekt individuell definierbare Spezifikationen zur Beschreibung von Produkten. Die sog. Classifications in Sitecore können über eine Parent-Verknüpfung (ExternalParentID) beliebig geschachtelt werden, die erste Ebene ist aber immer eine Gruppe, eine sog. ClassificationGroup.
ClassificationRepo

Synchronisation Klassifizierungen

Um Klassifizierungen zu synchronisieren muss zunächst die Pipeline ’synchronizeClassifications‘ der Sitecore.Commerce.Products.config erweitert werden:

Pipeline synchronizeClassifications

.config

[xml]
<!– SYNCHRONIZE CLASSIFICATIONS –>
<commerce.synchronizeProducts.synchronizeClassifications>
<processor type="Sitecore.Commerce.Pipelines.Products.SynchronizeClassifications.ReadSitecoreClassifications, Sitecore.Commerce">
<patch:delete />
</processor>
<processor type="Sitecore.Commerce.Pipelines.Products.SynchronizeClassifications.ResolveClassificationsChanges, Sitecore.Commerce">
<patch:delete />
</processor>
<processor type="Sitecore.Commerce.Pipelines.Products.SynchronizeClassifications.SaveClassificationsToExternalCommerceSystem, Sitecore.Commerce" >
<patch:delete />
</processor>
<processor type="Comspace.Sitecore.CommerceConnect.Hybris.Pipelines.Classifications.ReadClassifications, Comspace.Sitecore.CommerceConnect.Hybris" patch:after="processor[@type=’Sitecore.Commerce.Pipelines.Products.SynchronizeClassifications.ReadExternalCommerceSystemClassifications, Sitecore.Commerce‘]" />
<processor type="Comspace.Sitecore.CommerceConnect.Hybris.Pipelines.Classifications.ReadClassificationGroups, Comspace.Sitecore.CommerceConnect.Hybris" patch:after="processor[@type=’Sitecore.Commerce.Pipelines.Products.SynchronizeClassifications.ReadExternalCommerceSystemClassifications, Sitecore.Commerce‘]" />
</commerce.synchronizeProducts.synchronizeClassifications>
[/xml]

Processor ReadClassificationGroups

[csharp]using System.Collections.Generic;
using System.Linq;
using Comspace.Sitecore.CommerceConnect.Hybris.Connector;
using Comspace.Sitecore.CommerceConnect.Hybris.Connector.Model;
using Sitecore.Commerce.Entities.Products;
using Sitecore.Commerce.Pipelines;
using Sitecore.Commerce.Services.Products;

namespace Comspace.Sitecore.CommerceConnect.Hybris.Pipelines.Classifications
{
/// <summary>
/// commerce.synchronizeProducts.synchronizeClassifications
/// </summary>
public class ReadClassificationGroups : PipelineProcessor<ServicePipelineArgs>
{
public override void Process(ServicePipelineArgs args)
{
var request = args.Request as SynchronizationRequest;
var groups = request.Properties["ClassificationGroups"] as List<ClassificationGroup>; //integration guide (page 9)
groups = groups ?? new List<ClassificationGroup>();

//get groups from ECS
IEnumerable<ExternalCategoryGroup> categoryGroups = ClassificationConnector.Load(request.Language);

//convert and add to commerce connect list
groups.AddRange(from @group in categoryGroups
select new ClassificationGroup
{
ExternalId = @group.Code,
Name = @group.Name
});

//persist for next processor
request.Properties["ClassificationGroups"] = groups;
}
}
}[/csharp]

Processor ReadClassifications

[csharp]using System.Collections.Generic;
using System.Collections.ObjectModel;
using Comspace.Sitecore.CommerceConnect.Hybris.Connector;
using Comspace.Sitecore.CommerceConnect.Hybris.Connector.Model;
using Sitecore.Commerce.Entities.Products;
using Sitecore.Commerce.Pipelines;
using Sitecore.Commerce.Services.Products;

namespace Comspace.Sitecore.CommerceConnect.Hybris.Pipelines.Classifications
{
/// <summary>
/// commerce.synchronizeProducts.synchronizeClassifications
/// </summary>
public class ReadClassifications : PipelineProcessor<ServicePipelineArgs>
{
public override void Process(ServicePipelineArgs args)
{
var request = args.Request as SynchronizationRequest;
var groups = request.Properties["ClassificationGroups"] as List<ClassificationGroup>; ////integration guide (page 9)
groups = groups != null ? groups : new List<ClassificationGroup>();

foreach (ClassificationGroup group in groups)
{
var classifications = new List<Classification>();

//get categories from ECS
foreach (ExternalCategory externalCategory in ClassificationConnector.Load(group.ExternalId, request.Language)) //TBD
{
//convert to commerce connect classification
var classification = new Classification();
classification.ExternalId = externalCategory.Code;
classification.Name = externalCategory.Name;
classifications.Add(classification);

//…with sub-classifications
HandleSubCategories(externalCategory, classifications);
}
group.Classifications = new ReadOnlyCollection<Classification>(classifications);
}

//persist for next processor
request.Properties["ClassificationGroups"] = groups;
}

private static void HandleSubCategories(ExternalCategory parentCategory, List<Classification> allClassifications)
{
if (parentCategory.Categories != null)
{
foreach (var category in parentCategory.Categories)
{
var classification = new Classification();
classification.ExternalId = category.Code;
classification.Name = category.Name;
classification.ExternalParentId = parentCategory.Code;

allClassifications.Add(classification);
HandleSubCategories(category, allClassifications);
}
}
}
}
}
[/csharp]

Klassifizierungen Produkten zuordnen

Anschließend müssen die Klassifizierungen nur noch den Produkten zugeordnet werden. Und auch dafür muss wieder eine Pipeline der Sitecore.Commerce.Products.config erweitert werden:

Pipeline synchronizeProductClassifications

.config

[xml]<!– SYNCHRONIZE PRODUCT CLASSIFICATIONS
This pipeline is responsible for synchronizing and updating the references between a given product and associated classifications and categories within.
It’s assumed that classifications are already synchronized and present in CMS.
The references to categories are stored directly on the main product item. –>
<commerce.synchronizeProducts.synchronizeProductClassifications>
<processor type="Comspace.Sitecore.CommerceConnect.Hybris.Pipelines.Products.ReadProductClassifications, Comspace.Sitecore.CommerceConnect.Hybris" patch:after="processor[@type=’Sitecore.Commerce.Pipelines.Products.SynchronizeProductClassifications.ReadExternalCommerceSystemProductClassifications, Sitecore.Commerce‘]" />
</commerce.synchronizeProducts.synchronizeProductClassifications>[/xml]

Processor ReadProductClassifications

[csharp]using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using Comspace.Sitecore.CommerceConnect.Hybris.Connector;
using Comspace.Sitecore.CommerceConnect.Hybris.Connector.Model;
using Sitecore.Commerce.Entities.Products;
using Sitecore.Commerce.Pipelines;
using Sitecore.Commerce.Services.Products;

namespace Comspace.Sitecore.CommerceConnect.Hybris.Pipelines.Products
{
/// <summary>
/// commerce.synchronizeProducts.synchronizeProductClassifications
/// </summary>
public class ReadProductClassifications : ReadProduct
{
public override void Process(ServicePipelineArgs args)
{
var request = args.Request as SynchronizeProductRequest;
var product = request.Properties["Product"] as Product;
var groups = request.Properties["ClassificationGroups"] as List<ClassificationGroup>;
groups = groups != null ? groups : new List<ClassificationGroup>();

//get product from ECS
ExternalProduct externalProduct = ProductConnector.Load(request.Language, product.ExternalId); //tbd
if (externalProduct != null && IsProductValidForImport(externalProduct))
{
//convert to commerce connect product classification groups and classifications
foreach (ExternalCategoryGroup externalCategoryGroup in externalProduct.CategoryGroups)
{
ClassificationGroup group = new ClassificationGroup();
group.ExternalId = externalCategoryGroup.Code;
group.Name = externalCategoryGroup.Name;

IEnumerable<Classification> classifications = from externalCategory in externalCategoryGroup.Categories
select new Classification
{
ExternalId = externalCategory.Code,
Name = externalCategory.Name
};
group.Classifications = new ReadOnlyCollection<Classification>(classifications.ToList());

groups.Add(group);
}
}

//persist for next processor
request.Properties["ClassificationGroups"] = groups;
}
}
}[/csharp]

Resumé

Mein erster Eindruck:

  • Es gibt viel Dokumentation, aber z.B. zum Thema Zuordnung von Klassifikationen zu Produkten konnte ich nichts finden. Da habt ihr es jetzt ja besser 😉 Häufig war dann doch dotPeek mein bester Freund.
  • Der Differenzabgleich funktionierte problemlos, Mehrsprachigkeit war im Kontext der Zuordnung von Klassifizierungen zu Produkten noch etwas holprig, da sollte ein zweiter Blick erfolgen.

Wenn ihr mehr zum Sitecore Commerce Connect wissen wollt, dann schaut mal hier:

Sitecore Commerce Connect – Teil 2: Produkte synchronisieren

Im ersten Teil dieser Serie ging es darum einen kurzen Überblick über das Sitecore Commerce Connect Modul zu verschaffen. In diesem Beitrag geht es nun um die Synchronisation von Produkten zwischen einem externen Shopsystem (kurz ECS, in unserem PoC Hybris) und Sitecore.

Zunächst ist es wichtig zu verstehen, dass Preise und Bestand im Commerce Connect nicht als Produktstammdaten gelten. Der Bestand ändert sich ständig und DEN Preis gibt es ja auch nicht, Preisfindung ist ein sehr komplexes Thema. Sitecore geht davon aus, dass diese Informationen online vom ECS ermittelt und geliefert werden. Im Teil 4 dieser Serie wird dann beschrieben, wie ihr auch an diese Informationen kommt.

image00

Die Hoheit für die Produktdaten liegt sicherlich im ECS. Für die Synchronisation der Produktdaten schlägt Sitecore folgende Varianten vor:

  1. Produkte als Items in Sitecore Datenbank (optional bidirektional)
  2. Produkte in ECS, Zugriff über Sitecore Data Provider
  3. Produkte in ECS, Zugriff über Sitecore Index

Wir haben uns im Verlauf des PoC für die Variante 1 entschieden, da diese standardmäßig vom Sitecore so vorgesehen ist und den höchsten Komfort bietet. In der Sitecore Commerce Components Overview im Kapitel “Products” sind einige Argumente für oder gegen die unterschiedlichen Variante aufgeführt.

So, jetzt aber Butter bei die Fische 🙂

Installation Modul

Falls ihr das Modul noch nicht installiert habt, findet ihr wie gewohnt eine gute Installationsanleitung auf dev.sitecore.net.

Synchronisation Produkte

Das Ziel: Titel, Beschreibung und Bilder der Produkte aus dem externen Shopsystem mit Sitecore zu synchronisieren.

image01

In folgende Pipelines der Sitecore.Commerce.Products.config muss für die unidirektionale Synchronisation der Produkte eingegriffen werden:

Pipeline getExternalCommerceSystemProductList

.config

[xml]
&lt;!– GET EXTERNAL COMMERCE SYSTEM PRODUCT LIST
This pipeline is responsible for obtaining the list of product Ids to be synchronized from the external commerce system.–&gt;
&lt;commerce.synchronizeProducts.getExternalCommerceSystemProductList&gt;
&lt;processor type="Sitecore.Commerce.Pipelines.Products.GetExternalCommerceSystemProductList.GetExternalCommerceSystemProductList, Sitecore.Commerce"&gt;
&lt;patch:delete /&gt;
&lt;/processor&gt;
&lt;processor type="Comspace.Sitecore.CommerceConnect.Hybris.Pipelines.Products.ReadProducts, Comspace.Sitecore.CommerceConnect.Hybris" /&gt;
&lt;/commerce.synchronizeProducts.getExternalCommerceSystemProductList&gt;
[/xml]

Processor ReadProducts

[csharp]
using System.Collections.Generic;
using System.Linq;
using Comspace.Sitecore.CommerceConnect.Hybris.Connector;
using Comspace.Sitecore.CommerceConnect.Hybris.Connector.Model;
using Sitecore.Commerce.Pipelines;
using Sitecore.Commerce.Services.Products;

namespace Comspace.Sitecore.CommerceConnect.Hybris.Pipelines.Products
{
/// &lt;summary&gt;
/// commerce.synchronizeProducts.getExternalCommerceSystemProductList
/// &lt;/summary&gt;
public class ReadProducts : PipelineProcessor&lt;ServicePipelineArgs&gt;
{
public override void Process(ServicePipelineArgs args)
{
var request = args.Request as SynchronizeProductsRequest;
var productIds = args.Request.Properties["ExternalCommerceSystemProductIds"] as List&lt;string&gt;; //integration guide (page 12)
productIds = productIds ?? new List&lt;string&gt;();

//get products from ECS
IEnumerable&lt;ExternalProduct&gt; externalProducts = ProductConnector.Load(request.Language);

//convert and add to commerce connect list
productIds.AddRange(from product in externalProducts
where product.Code != null
select product.Code);

//persist for next processor
args.Request.Properties["ExternalCommerceSystemProductIds"] = productIds;
}
}
}
[/csharp]

Pipeline synchronizeProductEntity

.config

[xml]
&lt;!– SYNCHRONIZE PRODUCT ITEM –&gt;
&lt;commerce.synchronizeProducts.synchronizeProductEntity&gt;
&lt;processor type="Sitecore.Commerce.Pipelines.Products.SynchronizeProductEntity.ReadProductFromSitecore, Sitecore.Commerce"&gt;
&lt;patch:delete /&gt;
&lt;/processor&gt;
&lt;processor type="Sitecore.Commerce.Pipelines.Products.SynchronizeProductEntity.SaveProductToExternalCommerceSystem, Sitecore.Commerce" &gt;
&lt;patch:delete /&gt;
&lt;/processor&gt;
&lt;processor type="Comspace.Sitecore.CommerceConnect.Hybris.Pipelines.Products.ReadProduct, Comspace.Sitecore.CommerceConnect.Hybris" patch:after="processor[@type=’Sitecore.Commerce.Pipelines.Products.SynchronizeProductEntity.ReadExternalCommerceSystemProduct, Sitecore.Commerce‘]" /&gt;
&lt;/commerce.synchronizeProducts.synchronizeProductEntity&gt;
[/xml]

Processor ReadProduct

[csharp]
using Comspace.Sitecore.CommerceConnect.Entities.Products;
using Comspace.Sitecore.CommerceConnect.Hybris.Connector;
using Comspace.Sitecore.CommerceConnect.Hybris.Connector.Model;
using Sitecore.Commerce.Pipelines;
using Sitecore.Commerce.Services.Products;
using Sitecore.Diagnostics;

namespace Comspace.Sitecore.CommerceConnect.Hybris.Pipelines.Products
{
/// &lt;summary&gt;
/// commerce.synchronizeProducts.synchronizeProductEntity
/// &lt;/summary&gt;
public class ReadProduct : PipelineProcessor&lt;ServicePipelineArgs&gt;
{
public override void Process(ServicePipelineArgs args)
{
SynchronizeProductRequest syncProdRequest = args.Request as SynchronizeProductRequest;

//get product from ECS
ExternalProduct externalProduct = ProductConnector.Load(syncProdRequest.Language, syncProdRequest.ProductId); //TBD
if (externalProduct != null &amp;&amp; IsProductValidForImport(externalProduct))
{
CustomProduct product = new CustomProduct();
product.ExternalId = externalProduct.Code;
product.Name = externalProduct.Name;
product.FullDescription = externalProduct.Summary;
product.ImageUrl = HybrisSettings.Url + externalProduct.ImageUrl; //custom property

//persist for next processor
args.Request.Properties["Product"] = product; //integration guide (page 12)
}
}

protected bool IsProductValidForImport(ExternalProduct product)
{
var result = true;
if (string.IsNullOrEmpty(product.Name)) // = item.Name
{
result = false;
Log.Info("Skip product ‚" + product.Code + "‘: Name not valid.", product); //NOTE System.Messages
}
return result;
}
}
}
[/csharp]

CustomProduct

Wenn auch die Bilder synchronisiert werden sollen, muss das Produkt-Template des Commerce Connect erweitert werden. Konkret müssen dafür folgende Anpassungen vorgenommen werden:

Template
Neues Template “CustomProduct” anlegen, welches vom Standard Commerce Connect Template „Product“ erbt.

Klassen
Erweiterte Produkt-Klasse „CustomProduct“ definieren:

[csharp]
using Sitecore.Commerce.Entities.Products;
using Sitecore.Data.Items;

namespace Comspace.Sitecore.CommerceConnect.Entities.Products
{
/// &lt;summary&gt;
/// Custom product including image field.
/// &lt;/summary&gt;
public class CustomProduct : Product
{
public string ImageUrl { get; set; }

public MediaItem Image { get; set; }
}
}
[/csharp]

Die Klasse „CustomProductRepository“ definiert dann wie das erweiterte Produkt gelesen und geschrieben wird:

[csharp]
using Comspace.Sitecore.CommerceConnect.Entities.Products;
using Comspace.Sitecore.CommerceConnect.Model.sitecore.templates.User_Defined.Comspace.CommerceConnect;
using Sitecore.Commerce.Data.Products;
using Sitecore.Data.Fields;
using Sitecore.Data.Items;
using Product = Sitecore.Commerce.Entities.Products.Product;

namespace Comspace.Sitecore.CommerceConnect.Data.Products
{
/// &lt;summary&gt;
/// Custom product repository including image field.
/// &lt;/summary&gt;
public class CustomProductRepository : ProductRepository
{
/// &lt;summary&gt;
/// Save product data to related item.
/// &lt;/summary&gt;
/// &lt;param name="entityItem"&gt;&lt;/param&gt;
/// &lt;param name="product"&gt;&lt;/param&gt;
protected override void UpdateEntityItem(Item entityItem, Product product)
{
base.UpdateEntityItem(entityItem, product);

using (new EditContext(entityItem))
{
var url = (product as CustomProduct).ImageUrl;

var image = GetMediaItem(url, product); //TBD
ImageField imagefield = entityItem.Fields[IProductConstants.ImageFieldName];
imagefield.Alt = image.Alt;
imagefield.MediaID = image.ID;
}
}

/// &lt;summary&gt;
/// Read product data from related item.
/// &lt;/summary&gt;
/// &lt;param name="entityItem"&gt;&lt;/param&gt;
/// &lt;param name="product"&gt;&lt;/param&gt;
protected override void PopulateEntity(Item entityItem, Product product)
{
base.PopulateEntity(entityItem, product);

ImageField imageField = entityItem.Fields[IProductConstants.ImageFieldName];
(product as CustomProduct).Image = imageField == null ? null : imageField.MediaItem;
}

#region Handle MediaItem
//…
#endregion
}
}
[/csharp]

.config
Angepasste Templates und Klassen in Sitecore.Commerce.Products.config registrieren:

[xml]
&lt;!– PRODUCT REPOSITORY –&gt;
&lt;productRepository type="Sitecore.Commerce.Data.Products.ProductRepository, Sitecore.Commerce"&gt;
&lt;patch:delete /&gt;
&lt;/productRepository&gt;
&lt;productRepository type="Comspace.Sitecore.CommerceConnect.Data.Products.CustomProductRepository, Comspace.Sitecore.CommerceConnect" singleInstance="true"&gt;
&lt;template&gt;{0C589D66-A119-435A-907F-43481CD5199F}&lt;/template&gt;
&lt;branch&gt;{0C589D66-A119-435A-907F-43481CD5199F}&lt;/branch&gt;
&lt;path ref="paths/products" /&gt;
&lt;Prefix&gt;Product_&lt;/Prefix&gt;
&lt;ProductsIndex&gt;commerce_products_master_index&lt;/ProductsIndex&gt;
&lt;ManufacturerRepository ref="productManufacturerRepository" /&gt;
&lt;DivisionRepository ref="productDivisionRepository" /&gt;
&lt;TypeRepository ref="productTypeRepository" /&gt;
&lt;ClassificationsRepository ref="productClassificationsFieldRepository" /&gt;
&lt;ResourcesRepository ref="productResourcesRepository" /&gt;
&lt;RelationsRepository ref="productRelationsRepository" /&gt;
&lt;GlobalSpecificationsRepository ref="productGlobalSpecificationsRepository" /&gt;
&lt;ClassificationsSpecificationsRepository ref="productClassificationsSpecificationsRepository" /&gt;
&lt;TypeSpecificationsRepository ref="productTypeSpecificationsRepository" /&gt;
&lt;/productRepository&gt;

&lt;includeTemplates&gt;
&lt;ProductTemplateId&gt;{0C589D66-A119-435A-907F-43481CD5199F}&lt;/ProductTemplateId&gt;
&lt;/includeTemplates&gt;

&lt;!– Commerce ENTITIES
Contains all the Commerce cart entities.
The configuration can be used to substitute the default entity implementation with extended one. –&gt;
&lt;commerce.Entities&gt;
&lt;Product type="Sitecore.Commerce.Entities.Products.Product, Sitecore.Commerce" &gt;
&lt;patch:delete /&gt;
&lt;/Product&gt;
&lt;Product type="Comspace.Sitecore.CommerceConnect.Entities.Products.CustomProduct, Comspace.Sitecore.CommerceConnect" /&gt;
&lt;/commerce.Entities&gt;
[/xml]

Stolpersteine

In der Klasse „Sitecore.Commerce.Templates“ existiert eine Konstante „ProductTemplateId“, deren Id fest verdrahtet ist und nicht auf die zuvor eingerichteten Konfigurationsdateien verweist. Die Konstante wird u.a. in der Klasse „ItemClassificationService“ verwendet, daher ist dieser auszutauschen:

[xml]
&lt;itemClassificationService type="Sitecore.Commerce.Products.ItemClassificationService, Sitecore.Commerce"&gt;
&lt;patch:delete /&gt;
&lt;/itemClassificationService&gt;
&lt;itemClassificationService type="Comspace.Sitecore.CommerceConnect.Entities.ItemClassificationService, Comspace.Sitecore.CommerceConnect" /&gt;
[/xml]

Resumé

Zuletzt möchte ich ein paar erste Eindrücke und Erkenntnisse zusammenfassen:

  • Der erste Einstieg durch Pipelines in Pipelines in Pipelines ist herausfordernd.
  • Es gibt umfangreiche Dokumentationen.
  • Einige hilfreiche Klassen aus Beispielen im Internet verweisen leider auf die CommerceServer API, z.B. ProductsSearchResult und CommerceConstants. Auch in dem Dynamics AX Demoshop von Sitecore (Commerce.Dynamics.Storefront) sind Verweise auf die CommerceServer API enthalten. Die Trennung bzw. Abstraktion ist m.E. noch nicht ganz sauber erfolgt.
  • Verwunderlich fand ich zunächst, dass die Synchronisation von Bildern nicht im Standard enthalten ist. Aber dem von Sitecore definierten Prinzip “kleinster gemeinsamer Nenner” folgend, ist es verständlich, denn Bild-Quelle und -Ziel sind doch sehr projektspezifisch.
  • Der “Merchandising Manager” ist NICHT Bestandteil des Commerce Connect, sondern des CommerceServer Connect.
  • In einem echten Projekt mit einem umfangreichen Datenmodell wird der größte Aufwand in das Verständnis und das Mapping der Datenmodelle gehen.

Wenn ihr mehr zum Sitecore Commerce Connect wissen wollt, dann schaut mal hier:

Sitecore Commerce Connect – Teil 1: Ein Überblick am Beispiel Hybris

Wie in meinem Vortrag zum Thema “Evaluierung einer Hybris-Anbindung an Sitecore” auf der letzten Sitecore Usergroup in Bielefeld versprochen, möchte ich mit dieser Beitragsserie unsere ersten Eindrücke von der Anbindung eines externen Shopsystems (in diesem Beispiel Hybris) an Sitecore mittels des Commerce Connect Moduls mit euch teilen.

In diesem Artikel geht es mir darum, euch einen ersten Überblick über das Marketing-Potenzial und Funktionsumfang des Moduls zu verschaffen. In den nächsten Beträgen wird es dann technischer mit konkreten Ausschnitten aus Konfigurationsdateien und Quellcode.

Warum sollten WebCMS und E-Commerce Platform verbunden werden?

Bevor wir an die technischen Details gehen, erlaube ich mir zunächst einige Gedanken aus Sicht von Produkt-Management, Marketing und Vertrieb auf Betreiberseite:

Webnutzer erwarten immer mehr eine einheitliche User-Experience, die sich über alle Kanäle und Plattformen eines Unternehmens erstrecken soll. Zudem  bringen auch Online-Shop Systeme kleinere CMS-Funktionalitäten mit und damit wird auch die Darstellung von Inhalten immer besser. Warum sollten dann CMS und Shop miteinander verbunden werden?

Sitecore bietet bereits in seiner eigenen Suite ein umfangreiches Bundle an Funktionen, um verschiedenste Touchpoints (Webseite, Newsletter, Social Media, Offline-Kanäle am POS usw.) zu einem Nutzererlebnis aus einem Guss zu verschmelzen.

Mit diesem Beispiel möchte ich Möglichkeiten aufzeigen, mit denen Besuchern auch auf Unternehmenswebseiten die Kernfunktionalitäten eines Online-Shops angeboten werden können (wie bspw. Produkte in den Warenkorb legen) und wie diese kombinierten Webseiten- und E-Commerce-Daten dann von der Unternehmenswebseite automatisch an das Shopsystem übergeben werden.

Ein Beispiel für eine Customer Journey mit Optimierungspotenzial sähe beispielsweise so aus:

Der Kunde schaut auf einer Webseite nach einem Produkt, muss sich das Produkt merken und im separaten Online-Shop danach suchen um zu bestellen. 

Das bedeutet:
Durch die zwei separaten Technologien Webseite und Shop entsteht für den Kunden Fehler- und Frustrations-Potenzial. Für Unternehmen und Shop-Anbieter kann das bedeuten: Erhöhte Abbruchgefahr und damit niedrigere Conversion-Rate.

Mit Lösungen wie dem Commerce Connect arbeitet die Technologie im Hintergrund für den Kunden, nimmt ihm Arbeitsschritte ab und erhöht damit die Wahrscheinlichkeit, dass es zu einem Verkauf kommt.

Der Endkunde bemerkt so den Technologiewechsel aus dem WebCMS in den Online-Shop überhaupt nicht und hat ein nahtloses Nutzererlebnis im Bestellprozess.

Kurzvorstellung Sitecore Commerce Connect

Sitecore Commerce Connect sieht sich als Framework und API zur nahtlosen Integration von 3rd-Party Commerce Systemen in Sitecore. Das Ziel ist die Überführung der Experience Marketing Features in die Commerce Welt:

  • Einheitliche Darstellung:  Produktkataloge, “normale” Inhalte und Bilder aus einem Guss
  • Bearbeitung von Produktkatalogen, Seiteninhalten und Bildern in einen System
  • Nutzung der Sitecore Facettensuche über ALLE Inhalte.
  • Personalisierung auch über das Kaufverhalten der Besucher
  • Tracking von Conversions und Values rund um den Einkauf
  • Identifizierung von und Einblick in die wertvollsten Kunden
  • Reaktion auf Änderungen im Geschäft, in Echtzeit

Konkreter Umfang des Commerce Connect Moduls

Das Sitecore Modul bietet die Möglichkeit Produktkataloge mit dem externen Commerce System zu synchronisieren, d.h. es stellt entsprechende Templates, Klassen, Repositories, Import-Pipelines etc. zur Verfügung:

Sitecore-Commerce-Connect-Modul

Erste Engagement-Pläne zu Warenkorb-Abbrüchen, neuen Aufträgen und Wiederverfügbarkeit von Produktbeständen werden mitgeliefert und genutzt:

Sitecore-Commerce-Connect-Engagement-Plan

Selbstverständlich kommen auch Erweiterungen für die Sitecore Rule Engine mit:

Sitecore-Commerce-Connect-Rule-Engine

Das Framework des Sitecore Commerce Connect 

Die Architektur wird von Sitecore wie folgt skizziert, wobei der Commerce Server Connector und der Dynamics AX Connector fertige Connectoren von Sitecore selbst sind und der Hybris Connector von uns im Rahmen der Evaluierung entwickelt wurde.

Service Layer:

Folgende Bereiche deckt das Modul ab:

  • Warenkorb
  • Preise
  • Kunden und Benutzer
  • Bestände
  • Produkte
  • Aufträge
  • Geschenkkarten
  • Wunschlisten
  • Bezahlung
  • Versand
  • Treueprogramme

Die einzelnen Layer sind optional und unabhängig voneinander. Ein Layer umfasst ein Datenmodell und Pipelines mit ggf. zugehörigen Service Providern. Die farblich markierten Service Layer wurden im Rahmen des PoC näher beleuchtet. Wenn ihr einen Eindruck haben möchtet was sich dahinter verbirgt, habt etwas Geduld – die zugehörigen Artikel folgen.

Prinzipien

Weil sie mir so gut gefallen haben, hier eine kurze Zusammenfassung und Übersetzung der Prinzipien welche dem Modul von Sitecore selbst auferlegt wurden:

  • Simplicity – kleinster gemeinsamer Nenner
  • Extensibility – Domain Model erweiterbar
  • Independently – keine Abhängigkeit zwischen Service Layern
  • Abstraction – keine Sitecore oder Shopsystem Abhängigkeit in Service Layern
  • Pipelines – jeder Service Layer benutzt Sitecore Pipelines
  • Fallback – fehlende Implementierungen im Shopsystem können überbrückt werden

Erstes Resumé

Mein Eindruck nach der Evaluierung des Commerce Connect anhand einer Hybris-Anbindung:

  • Der erste Einstieg ist herausfordernd, kurz stand ich davor wie “der Ochs vorm Berg”. Auf dem Partnertag in Frankfurt wurde von Sitecore eine Schulung zu dem Modul angekündigt. Die kann ich dann nur empfehlen.
  • Das bringt mich zu den zur Verfügung stehenden Materialien:
    • Es gibt umfangreiche Dokumentationen über das Modul im Developer Netzwerk.
    • Cookbooks gibt es leider keine, diese helfen sonst häufig die erste Hürde zu nehmen.
    • Wenn die Dokumentation nicht weiter hilft, gibt es im Internet aktuell leider noch wenig Material zum Commerce Connect.
    • Geholfen haben immer wieder die herunterladbaren Beispiel-Implementierungen für AX und CommerceServer und natürlich dotPeek.
  • Customizing gehört zum Grundkonzept. Durch eine gute Abstraktion ist fast alles flexibel anpassbar. Das gefällt, auch wenn es manchmal nicht einfach ist, im Pipeline-Dschungel den Überblick zu bewahren.
  • Für eine solide und benutzerfreundliche Integration ist eCommerce-KnowHow unumgänglich. Die dahinterliegenden Modelle und Prozesse müssen durchdrungen werden.
  • Die Komplexität einer Commerce Integration bringt auch höhere Konzeptions- und Abstimmungsaufwände mit sich als vielleicht sonst im CMS-Umfeld üblich.
  • Das Modul umfasst keine Layout-Komponenten und keine speziellen Applikationen.
  • Der “Merchandising Manager” ist  NICHT Bestandteil des Commerce Connect, sondern des CommerceServer Connect.
  • Eine gute Einsatzmöglichkeit für das Modul ist m.E. eine PIM-Integration.
  • Zu guter Letzt: Die Arbeit mit dem Modul hat mir Spaß gemacht!

Die Präsentation zum Vortrag:

In den kommenden Tagen werde ich diese Serie mit den folgenden Artikeln fortführen:

 

 

Ottobock HealthCare: erfolgreiches internes E-Mail-Marketing mit Inxmail

Newsletter Ottobock France

Als unser langjähriger Kunde Ottobock vor einiger Zeit ein Tool zur unternehmensinternen Mitarbeiterinformation- und Kommunikation suchte, schlug ihm unser Projektleiter das Inxmail E-Mail-Marketing System vor. Die Idee zur Verwendung eines E-Mail Marketing Instruments wurde aus der Anforderung geboren, die News aus dem Ottobock Produktmanagement zielgruppenorientiert mit weiterführenden Downloads-Links und Responsemöglichkeit im Unternehmen zu versenden.

Mit der Ausarbeitung eines Newsletter-Layouts im Corporate Design von Ottobock und der Entwicklung und Programmierung eines Templates haben wir das E-Mail-Marketing Konzept mit Inxmail erfolgreich umgesetzt. Inzwischen existieren rund 13 verschiedene Newsletter in mehreren Sprachversionen, darunter auch in Kyrillisch und auf Arabisch – mit entsprechend anderer Laufrichtung des Textes und der Bedienelemente. In einem zweiten Schritt haben wir die Redakteure umfassend in der Erstellung und Auswertung der Newsletter und der Empfängerverwaltung geschult.

Newsletter Ottobock France
Newsletter Ottobock France

Newsletter zur internen Kommunikation: Was war die Ausgangslage?

Das Produktmanagement der Mobility Solutions – dem Unternehmensbereich, der hochwertige Rollstühle und Reha-Bedarf herstellt – versandte in unregelmäßigen Intervallen umfangreiche Präsentationen an alle Vertriebsbeteiligten der über 50 Standorte weltweit per MS-Outlook®.  Nicht nur, dass die inhaltlich wichtigen Infos Postfächer verstopften – es gab auch keine Übersicht für die Versender, was eigentlich von Interesse war. Ein attraktiver Newsletter, der die Neuigkeiten anteasert und bei Bedarf auf weitere Infos und viel häufiger auf wichtige Dokumente verlinkt, stellte die passende Lösung dar. Mit den Inxmail Webspaces lassen sich gerade die Download-Dokumente bestens verwalten und bereitstellen.

Besonders wichtig für die Produktmanager: der direkte Response. Zu den allermeisten News gibt es einen konkreten Ansprechpartner, der per Mail aus dem Newsletter heraus direkt angeschrieben werden kann – ohne jeden Medienbruch. Und: Newsletter eignen sich auch dazu kurzfristig definierte Zielgruppen zu erreichen.

Ausweitung der Newsletter auf den B2B-Bereich

Da sich die Kommunikationsform innerhalb des Unternehmens bewährt hat, wurde das Angebot für die B2B Kommunikation ausgebaut. Inzwischen erhält auch der ausgewählte Fachhandel, die Sanitätshäuser, in Deutschland und Österreich regelmäßig Updates aus dem Produktsegment Mobility.

Damit nicht genug: Die Lösung, per Newsletter intern zu informieren, überzeugte weitere Abteilungen der Otto Bock HealthCare, die gerne auf die guten Erfahrungen mit der Inxmail-Lösung zurückgriffen.

Die unterschiedlichen Unternehmensbereiche und mehr als 60 Redakteure können auf Basis der bestehenden Listen nun sehr gezielt Artikel verfassen, die dann in einem weiteren Schritt durch die Chefredaktion freigegeben und als Newsletter versendet werden.

Durch die einfache Texteingabe ohne HTML-Kenntnisse erweist sich die Erstellung der Mailings für die Redakteure als sehr komfortabel und einfach. So wird die Lösung auch im Produktmanagement sehr gut angenommen.

Inzwischen haben wir auf eigenen Vorschlag die Templates aller Listen in einer Vorlage konsolidiert. Da sich die Newsletter über Header und – besonders wichtig – in der Anbieterkennung des Footers unterscheiden, sind variable Teile der Templates als Bausteine hinterlegt. Einerseits können Anpassungen, z.B. an einen neuen Markenauftritt wie aktuell nach dem Website-Relaunch, einfacher umgesetzt werden, und gleichzeitig ist bei der Erstellung eines Mailings für die Redakteure sichergestellt, dass die verwendete Vorlage auch die richtige ist.

Newsletter Ottobock Prothetik
Newsletter Ottobock Prothetik

Mit Inxmail ließ sich das interne und inzwischen auf ausgewählte Partner ausgeweitete E-Mail Marketing gerade deshalb gut einführen, weil mit einzelnen Funktionen und kleinen Volumen in der Mietvariante (ASP-Service) mit sehr überschaubaren Kosten begonnen werden konnte. Die Weiterentwicklung des Tools haben wir stets in enger Abstimmung mit dem Auftraggeber durchgeführt.

Learnings und Anforderungen konnten alle Beteiligten am Projekt schnell zur Umsetzung bringen. Inzwischen gibt es auch über Inxmail erzeugte An- und Abmeldeseiten, die problemlos in das bestehende CMS integriert werden können.

Durch das Newsletter-Template im Ottobock-einheitlichen Layout können wir die korrekte Optik in allen Mailings gewährleisten, obwohl die Newsletter dezentral in verschiedenen Abteilungen erstellt werden. Die zahlreichen Auswertungsmöglichkeiten wie Öffnungs- und Klickraten liefern uns gute Hinweise für die Mailing-Optimierung. Ein weiterer Vorteil von Inxmail Professional ist die einfache Realisierung von Zielgruppen-spezifischen Mailings, indem wir die Empfänger durch die Vergabe von Attributen bestimmten Zielgruppen zuordnen können.

Janett Klaus, Head of Digital Marketing | Marketing Communications

Fazit zum internen Newsletter-Mailing

Mit einem effizienten E-Mail Marketing Tool wie dem Inxmail System können innerhalb einer großen Organisation schnell notwendige Informationen transportiert werden. Die Anfangsinvestitionen bleiben dabei moderat. Das Tool kann laufend und gezielt – entsprechend der sich entwickelnden Businessprozesse – ausgebaut und angepasst werden.

Wenn auch Sie eine Lösung für Ihr E-Mail Marketing suchen: Fragen Sie uns einfach! Wir stellen Ihnen gerne die E-Mail-Marketing Lösung von Inxmail vor. Als Inxmail Solution-Partner sind wir ein erfahrener Dienstleister und unterstützen Sie bei der Umsetzung Ihres individuellen Newsletter-Konzeptes.

Schnelleinstieg in das Team Development for Sitecore (TDS)

Test TDS

Ihr habt noch nicht mit dem TDS gearbeitet, möchtet es aber mal ausprobieren? Dann seid ihr hier richtig. Mit diesem Blog-Beitrag möchte ich euch einen schnellen Einstieg in das TDS vermitteln. Wenn ihr wissen wollt was das TDS ist und wie es grundsätzlich funktioniert, dann könnt ihr das in diesem Beitrag nachlesen.

Installation des TDS

Auf der Webseite von Hedgehog könnt ihr das TDS herunterladen und 30 Tage lang kostenlos testen. Anschließend müsst ihr euch nur noch den zu eurem Visual Studio passenden Windows Installer heraussuchen, z.B. den HedgehogDevelopmentTDS_VS2013.msi und euch durch den Assistenten klicken.

Konfiguration von TDS Projekten

Zunächst setzt ihr wie gewohnt ein neues Projekt auf, d.h. ihr installiert Sitecore und richtet eine Visual Studio Projektmappe/ Solution ein.

Anschließend werden die TDS-Projekte eingerichtet. Eine erste TDS-Projektstruktur könnte wie folgt aussehen:

  • Tds.Core – enthält Client-Anpassungen
  • Tds.Master.Templates – enthält Templates
  • Tds.Master.Layouts – enthält Layouts, Sublayouts etc.
  • Tds.Master.Content – Beispielcontent, den jeder Entwickler braucht.

Je Projekt führt ihr folgende Schritte durch, wie hier am Beispiel des Tds.Master.Template-Projektes visualisiert:

1. TDS-Projekt anlegen

Rechtsklick auf der Projektmappe > Hinzufügen > Neues Projekt:

TDS Project

2. TDS-Projekt konfigurieren

Rechtsklick auf dem TDS-Projekt > Eigenschaften:

General Tab

  • Datenbank eintragen, hier “master”
  • Hinweis: Ein Projekt, z.B. das Tds.Master.Template-Projekt sollte mit dem Web-Projekt verbunden werden, dann wird das Web-Projekt auch automatisch mit deployed (Pfad siehe Build Tab).

General Tab TDS

Build Tab

  • Sitecore Web Url: Url unter der das Projekt zu erreichen ist
  • Sitecore Deploy Folder: Root-Verzeichnis der Webseite
  • Den Sitecore Connector installieren und die Verbindung testen.
  • Hinweis: Die erste generierte Guid in ALLE Projekte kopieren, diese muss innerhalb der Projektmappe identisch sein.

Test TDS

3. Item Synchronisation einrichten

Um die zu synchronisierenden Items einzurichten, klickt ihr mit der rechten Maustaste auf TDS-Projekt > “Get Sitecore Items” und wählt die Items aus:

Get Sitecore Items TDS

Anschließend könnt ihr einfach mal ein Item in Sitecore anlegen und im Visual Studio das TDS-Projekt mit der rechten Maustaste > “Sync with Sitecore” synchronisieren:

Sync TDS

Das war’s schon. Das erste TDS-Projekt ist fertig eingerichtet.

Einrichtung Code Generierung

Als nächsten Schritt möchte ich mit euch noch die automatische Generierung von Template-Klassen mittels Glass.Mapper einrichten.

1. Installation Glass.Mapper.Sc

Bevor wir damit starten, solltet ihr sicher stellen, dass im Web-Projekt

  • das NuGet-Paket “Microsoft ASP.NET MVC” installiert ist und
  • die Sitecore.Kernel-Bibliothek als Verweis zur Verfügung steht.

Anschließend müsst ihr einfach das NuGet-Paket “Glass.Mapper.Sc“ von nuget.org installieren:

Glass.Mapper.Sc TDS

2. Generierung einrichten

Hierzu bedarf es folgender Schritte:

  • In den Projekteigenschaften des TDS-Projekts, das die Templates synchronisiert (hier Tds.Master.Template), auf dem Tab “Code Generation” die Code Generierung aktivieren.
  • T4-Templates für den Glass.Mapper hier herunterladen.
  • Folgende tt-Dateien im Tds.Master.Templates Projekt im Folder “Code Generation Templates” ablegen und zum TDS-Projekt hinzufügen:
    • Helpers.tt
    • GlassV3Header.tt
    • GlassV3Item.tt
    • GeneralExtensions.tt
    • Inflector.tt
    • StringExtensions.ttt
  • Die Generierung konfigurieren
    • Target Project:  Web-Projekt
    • Code Generation Target File:  z.B. “Model/GeneratedClasses.cs”
    • Base Namespace: z.B. Modle
    • Header Transformation File: GlassV3Header.tt
    • Base Project Transformation File: GlassV3Item.tt

Generation TDS

3. Generierung testen

Entweder ihr klickt mit der rechten Maustaste auf das TDS-Projekt > “Re-Generate Code for all Items” oder ihr fügt im Sitecore ein neues Item hinzu und synchronisiert dieses mit dem TDS-Projekt.

Wenn ihr keine Fehlermeldung erhaltet, dann findet ihr eure Templates als Klassen in der “GeneratedClasses.cs” wieder. Andernfalls schaut mal im nächsten Absatz.

Mögliche Stolpersteine

Fehlender Namspace Glass.Mapper.Sc.Mvc

Es ist wichtig, dass ihr erst ASP.NET MVC installiert und dann das Glass.Mapper.Sc-Paket, andernfalls wird der Verweis auf “Glass.Mapper.Sc.Mvc” nicht mit installiert.

T4-Templates gesperrt

Wenn ihr die Meldung ”ErrorGeneratingOutput” wiederholt in der generierten Klasse findet, dann liegt es häufig daran, dass ihr die T4-Templates von einer externen Quelle geladen habt und diese noch entsperrt werden müssen:

Stolpersteine TDS

Überflüssige Leerzeile

Wenn ihr in der generierten Klasse untenstehende Meldung seht, dann schaut mal im “GlassV3Item.tt”. Dort ist vermutlich in der letzten Zeile eine Leerzeile, die zu entfernen ist.

*********************************************
 An error occured while generating code for item '/sitecore/templates'.
T4 Template: F:\SitecoreDEV\Projekte\TdsDemo\TdsDemo.Tds.Master\Code Generation Templates\glassv3item.tt
Errors:
Kompilierte Transformation: Ungültiges Token 'this' in Klasse, Struktur oder Schnittstellenmemberdeklaration.
Kompilierte Transformation: Die Methode muss einen Rückgabetyp besitzen.
Kompilierte Transformation: Typ erwartet.
*********************************************

Wie geht es weiter?

Die erste Einrichtung habt ihr jetzt hinter euch und damit habt ihr auch einen ersten Eindruck vom TDS gewonnen. Wenn ihr das Thema vertiefen wollt, empfehle ich euch z.B. das Manual von Hedgehog. Zum Thema automatische Builds und Deployment-Strategien findet ihr im Build Extensions Whitepaper Ansätze.

Ich freue mich über Kommentare zu euren Erfahrungen mit dem TDS!