Archiv für die Kategorie ‘Software-Development’

OneNote heiratet CRM/SharePoint-Integrator

Freitag, 12. September 2014

Wedding coupleDer erste Schritt ist getan, denn ab sofort kann unser CRM/SharePoint-Integrator auch OneNote-Abschnitte und OneNote-Seiten erzeugen. Manchmal gehen Wünsche eben doch in Erfüllung, zumindest wenn es die Wünsche unserer Kunden sind.

Wie ist das möglich?

Beschäftigt man sich ein wenig mit der Materie, findet man die vor kurzem noch angekündigte OneNote-API. Jedoch kann diese API im aktuellen Status nur das OneNote-Notizbuch im eigenen OneDrive bearbeiten. Eine Bearbeitung eines im SharePoint liegenden Notizbuchs ist aktuell noch nicht möglich, aber immerhin bereits auf der Wunschliste.

Da aber auch wir nicht so lange warten wollen, haben wir analysiert, welche Alternativen es gibt. Erstaunlicherweise sind es sogar recht viele, die jedoch alle der Reihe nach aus Gründen der Performance oder Kompatibilität ausscheiden. Nur eine Alternative blieb, die Anfangs regelrecht gepflastert mit Stolpersteinen schien, die sich jedoch im Laufe der Zeit eher als Steighilfen erwiesen.

Wie habt ihr das gemacht?

Nun, allzu sehr möchten wir natürlich nicht ins Detail gehen. Soviel sei jedoch gesagt:
Wir nutzen für dieses Feature die Dienste der Office WebApps für SharePoint. Konkret simulieren wir dabei die Nutzung der OneNote WebApp in SharePoint. Das die entsprechende Implementierung natürlich recht komplex ist und viel Zeit in Anspruch nahm, sollte sich fast schon von selbst verstehen.

Wie schaut das Ganze nun aus?

Dazu haben wir wie üblich ein kleines Beispiel erstellt. In den folgenden Abbildungen haben wir eine Beispielkonfiguration für Sie vorbereitet. Zum besseren Verständnis der Möglichkeiten, zeigen wir Ihnen einen zweistufigen Aufbau.

1. OneNote-Abschnitt pro Firma

Mit dieser Konfiguration wird pro Firma ein OneNote-Abschnitt erstellt. Der Name des Abschnitts wird aus der Firmennummer und dem Firmenname zusammengesetzt, wodurch sich bspw. ein Abschnitt mit dem Name “1234 – Musterbau GmbH” ableitet. Dieser Abschnitt ist leer und erhält, wie in der Konfiguration angegeben, eine zufällige Farbe. Das Ergebnis könnte wie folgt aussehen.

2. OneNote-Seite pro Verkaufschance

Diese Konfiguration basiert auf der Annahme, dass bereits eine Konfiguration zur Erstellung von OneNote-Abschnitten pro Firma existiert, welche die ID des Abschnitts in das Feld “new_onenotesection_id” schreibt. Dieses Feld lesen wir in unserer Konfiguration zur Erstellung von OneNote-Seiten pro Verkaufschance wieder aus, wodurch die OneNote-Seite im vorher angelegten OneNote-Abschnitt erscheint. Das Ergebnis sollte dann wie folgt aussehen.

Fazit

Mit dem neuen Feature des CRM/SharePoint-Integrators zur Integration von OneNote, wird die komfortable Verwaltung von Notizen in OneNote mit der Struktur und Verwaltbarkeit des CRM gepaart. Eine Hochzeit ungleicher Produkte mit dem Ziel sich gegenseitig zu ergänzen. Was gibt es schöneres?

Autor: Dirk Sarodnick

Google+

Dynamics CRM 2013: Ihre Verkaufschancen-, Angebote und Produkte stets im Gleichgewicht.

Montag, 14. April 2014

Dynamics CRM_Ihre Verkaufschancen- und Angebotsprodukte im GleichgewichtDynamics CRM ist ein flexibles Produkt von Microsoft, das sich relativ einfach an Ihre Unternehmensanforderungen anpassen lässt. Manch individuelle Prozesse erfordern tiefergehende Anpassungen in Dynamics CRM, wie die kürzlich bei uns eingegangene Kundenanforderung.

Standardmäßig ist im Dynamics CRM folgende Business-Logik vorhanden: Angebote werden aus einer Verkaufschance generiert, jedoch gibt es im Nachgang keine automatisierte Synchronisation der eventuell geänderten Angebotsprodukte in die dazugehörige Verkaufschance. Heißt, passe ich die Preise der Angebotsprodukte an, so ist mein Forecast in der Verkaufschance verzerrt und der Mitarbeiter müsste die Differenz eigentlich beheben; dafür fehlt aber meist die Zeit oder man vergisst es einfach. Diese ineffiziente Vorgehensweise kann man mit Hilfe eines Plug-Ins unterbinden.

Problemanalyse

Auf den ersten Blick schien die Aufgabe einfacher zu sein, als sie dann tatsächlich war. Im Prinzip sollte es doch sehr einfach sein, zwei Entitäten miteinander abzugleichen. Die Werte müssen von einer Entität zur anderen kopiert werden. Aber wie die vorhergehende Analyse zeigte, sind die Angebotsprodukte nicht mit den Verkaufschancenprodukten verdrahtet: Quasi als ob die Kinder (die Angebotsprodukte) nichts von ihren Eltern (den Verkaufschancenprodukten) wissen. Fazit: Out-of-the-box gibt es keine eineindeutige Zuordnung zwischen Angebots- und Verkaufschancenprodukten. Diese benötigen wir jedoch, um die Produkte abgleichen zu können.

Eine Verkaufschance kann zudem mehrere Angebote haben. Hm.. Was nun? Welches Angebot muss denn letztlich aktualisiert werden, wenn ein Produkt in der Verkaufschance hinzugefügt, aktualisiert oder gelöscht wird? Für diesen Fall fanden wir zusammen mit dem Kunden einen Kompromiss: Geht man nach dem Prozess vom Kunden, so kann immer nur ein Angebot pro Verkaufschance zum Zeitpunkt X aktiv sein. Mit dem aktiven Angebot galt es die Produkte zwischen Verkaufschance und Angebot abzugleichen.

Problemlösung und Implementierung

1.      Produkte der Verkaufschance aktualisieren

Das erste Problem war schnell gelöst: Wir haben zur Angebots – (Produkt)-Entität ein zusätzliches Feld „opportunityproductid“ hinzugefügt, in dem die ID (GUID) der Verkaufsschancen -(Produkt)-Entität gespeichert wird. Um diese Speicherung zu realisieren, mussten wir auf ein externes Tool von einem MVP (Microsoft Most Valuable Professional) zurückgreifen: http://crm2011entitymaps.codeplex.com/. Das Tool kann versteckte Mapping-Regeln im Vergleich zum Standard-Interface verwalten. Nun dachten wir es sei gelöst, aber ein GUID-Feld kann vom Admin im Anpassungsbereich nicht erzeugt werden. Somit hinterlegten wir die ID zunächst als Textfeld und erzeugten im Nachgang via Plugin ( Step „Post-Create of opportunityproduct“) eine GUID, die wir für die Synchronisation benötigen.

2.      Produkte in dem Angebot aktualisieren

Wie vereinbart, werden die Angebotsprodukte nur in dem einzigen, aktiven Angebot aktualisiert. Der Ablauf für Create-, Delete-, Update-Operationen ist identisch:

  1. Aktives Angebot finden
  2. Das Angebot löschen
  3. Ein neues Angebot aus der Verkaufschance generieren
  4. Neuerstellte Angebot aktivieren

Dynamics CRM 2013 So synchronisieren Sie Ihre Verkaufschancen- und Angebotsprodukte_2

Hinweis: EntityOperations-Klasse ist unsere Sammlung von Funktionen zur Arbeit mit CRM-Entitäten. Das ist vergleichbar mit „Data Access Object“-Entwurfsmuster, welches die Funktionen zum Zugriff auf die Datenquellen kapselt.

Um ein Angebot aus der Verkaufschance zu erstellen, bietet das CRM SDK schon einige Funktionen:

Dynamics CRM 2013 So synchronisieren Sie Ihre Verkaufschancen- und Angebotsprodukte_3

Die Enums mit sämtlichen States können Sie im SDK unter …\SDK\SampleCode\CS\HelperCode\OptionSets.cs und in den generierten Proxy-Klassen finden. Ein Proxy können Sie mit Hilfe eines SDK-Tools CrmSvcUtil generieren. Auf den MSDN-Seiten finden Sie eine gute Anleitung zu diesem Tool.

Fazit

Der Kunde ist zufrieden, sein individueller Prozess konnte im Dynamics CRM umgesetzt werden und sein Forecast ist immer auf dem aktuellen Stand. Das Plug-In hat zudem Weiterentwicklungspotenzial: Es kann so angepasst werden, dass aus mehreren Angeboten nur eines zur Aktualisierung ausgewählt werden kann. Haben Sie dieselbe Anforderung oder möchten gerne auf diese Anforderung aufbauen? Dann kontaktieren Sie uns.

Autor: Walter Nuss

PT_Blog_VK_WNuss_jpg

SharePoint: Listeneinträge via Metadatenfeld und JavaScript ermitteln

Mittwoch, 19. März 2014

201403_BLOG_SP_ListeneintraegeMetadatenfelder im SharePoint sind eine feine Sache. Globale Erstellung und Verwendung machen einige Dinge sehr einfach. Andere Dinge wiederum werden etwas umständlicher und zum Graus, wenn man nicht mal weiß, wonach man googlen oder yahooen soll. So zum Beispiel das programmatische Auslesen eines Listeneintrags anhand eines Metadatenfeldes über JavaScript.

Die Herausforderung der Aufgabenstellung sah wie folgt aus: Es bestehen zwei Listen. In Liste A gibt es ein Metadatenfeld, welches isteneintraegemit anderer Bezeichnung und anderem Namen in Liste B vorkommt. In Liste A soll nun ein Lookup-Feld auf Liste B verweisen. In diesem Lookup-Feld steht genau der Eintrag, der durch das Metadatenfeld identifiziert wird.

Klingt relativ simpel. Wir entschieden uns über den Weg CAMLQuery. Dazu benötigten wir den Wert des Metadatenfeldes. Dies stellte sich als relativ einfach heraus, wenn man die entsprechende Stelle im HTML gefunden hat. Trotzdem befindet sich hier schon die erste Stolperfalle. Über die Developertools des IE’s erhält man als sichtbaren Teil des Feldes ein div mit dem Text des Metadatenfeldes.

SharePoint Listeneinträge via Metadatenfeld und JavaScript ermitteln_Bild1

Der von uns benötigte Wert befindet sich allerdings etwas weiter oben in der Hierarchie in einem Input-Feld, das da wie folgt aussieht:

SharePoint Listeneinträge via Metadatenfeld und JavaScript ermitteln_Bild2

  • Mk1: Name des Metadatenfeldes
  • Value: Das ist der eigentliche Wert „Wert|GUID“

SharePoint trennt praktisch Datenanzeige und Dateneingabe.

Der Wert im input-Feld wird in „Anzeigewert“ und „GUID“ per | geteilt. Die GUID ist unerlässlich für die Arbeit mit dem Termstore und ist eineindeutig. Nun liegt die Vermutung nahe anhand der GUID in die Liste B zu gehen und dort per CAMLQuery den Eintrag herauszufinden. Im Internet findet man jetzt die verschiedensten Ansätze das CAML zu nutzen – es geht über Lookups zu Integer und Textfeldern. Schlussendlich sollte aber der Typ „TaxonomyFieldTypeMulti“ gewählt werden (bzw. ohne „Multi“), um auf eine Taxonomiefeld zuzugreifen. Unsere zweite und vermeintlich letzte Hürde ist also genommen und wir erstellen folgende Query:

SharePoint Listeneinträge via Metadatenfeld und JavaScript ermitteln_Bild3

Der Wert in der Variablen „userInput“ entspricht dabei der GUID. Wider Erwarten funktioniert die Query nicht. Verschiedene Methoden führen zu keinem Ergebnis. Mit dem SPCAMLQueryHelper von codeplex war der Fehler dann relativ schnell klar. Schlussendlich ist für die Nutzung im CAML nicht die GUID von Relevanz sondern der Wert selbst. In unserem Beispiel muss also „V1“ übergeben werden, um die entsprechenden Items zu bekommen. Die Query ändert sich dabei nicht.

Mit Aufruf der getItems-Funktion der Liste B erhalten wir alle entsprechenden Einträge.

SharePoint Listeneinträge via Metadatenfeld und JavaScript ermitteln_Bild4

Zur Lösung der Aufgabenstellung muss dann nur noch das korrekte Item herausgefiltert werden. Die Voraussetzungen ermöglichten uns die Annahme, dass immer nur genau ein Item gefunden werden wird. Wir holen uns also die ID des Eintrags und schreiben diese in das Lookupfeld.

SharePoint Listeneinträge via Metadatenfeld und JavaScript ermitteln_Bild5

Obgleich wir im SharePoint ein hohes Maß an Eindeutigkeit durch IDs und GUIDs erreichen, scheint es hier nichts zu nützen. Zwar ist die GUID unerlässlich für die Arbeit im Termstore, aber scheinbar irrelevant für den vorliegenden Fall.

Als Alternative zum JavaScript bleibt dann noch die Möglichkeit des EventReceivers in einer Custom Solution. Je nach Anforderung kann das schneller und besser zum Ziel führen.

Autor: Henrik Krumbholz

PT_XING_HKrumbholz_jpg

 

SharePoint (Online) in CRM (Online) integrieren? Go for IT!

Mittwoch, 04. Dezember 2013

SharePoint-CRM-IntegrationKennen Sie das Problem, wenn Dokumente im CRM nicht auch im SharePoint sind? Oder wenn zu einem Kunden jedes mal manuell eine passende SharePoint-Seite angelegt werden muss? Wir nicht mehr, denn wir haben eine Lösung.

[Randnotiz: Das ist unser Einführungs-Blogartikel. Erfahren Sie mehr auf unserer Produktseite.]

Seit Längerem haben wir eine Lösung in Betrieb, die uns so einige Integrationsthemen abnimmt. Anfänglich noch eher spartanisch, kann diese mittlerweile mit den Eigenheiten der neuen Onlinevarianten von CRM 2013 und SharePoint 2013 umgehen. Der eine oder andere Kunde bekam diese Lösung bereits, jedoch musste diese immer aufwändig angepasst und bereitgestellt werden. Doch nun haben wir eine CRM-Lösung, die bequem importiert und konfiguriert werden kann.

Was denn nun für eine Lösung?

Wir nennen ihn liebevoll “CRM-SharePoint-Integrator“. Es ist, wie bereits erwähnt, eine CRM-Lösung (Solution) die ziemlich einfach importiert werden kann. Dazu begibt man sich im CRM in das Menu “Einstellungen” > “Lösungen”. Dort angekommen wählt man “Importieren” und klickt sich durch den sich öffnenden Assistenten.

Import-Solution1

Was kann der CRM-SharePoint-Integrator?

Vereinfacht ausgedrückt, kann er für unterschiedlichste Ereignisse im CRM (bspw. das Anlegen/Ändern einer Firma) ein/mehrere SharePoint-Objekt/e (siehe Produktmatrix hier) im SharePoint anlegen. Im Folgenden möchten wir Ihnen ein paar Antworten auf die typischen Fragen zu, die uns als Microsoft Systemhaus in Kundenprojekten immer wieder begegnen beantworten:

Wie verbinde ich Microsoft SharePoint und Dynamics CRM?

  • Wie erstelle ich automatisch eine SharePoint-Webseite pro Account, Opportunity oder Kampagne im Dynamics CRM?
  • Wie erstelle ich nach Bedarf SharePoint Bibliotheken, – Webseiten etc. für Entitäten im Dynamics CRM?
  • Wie synchronisiere ich Accountdaten aus Dynamics CRM als Listeneintrag in SharePoint?

Die Standard-Integration bietet leider nur wenig Features, um SharePoint mit Dynamics CRM zu verbinden: Dynamische URLs werden erzeugt, eine Dokumentenverwaltung steht zur Verfügung. Mehr ist es leider nicht. Aber die Lösung CRM-SharePoint-Integrator von ProTechnology bietet viele Vorteile, wie Sie anhand der Produktmatrix unten sehen. Neben Dokumenten-Bibliotheken sind auch Standardlisten, benutzerdefinierte Listen (auch nach Vorlage), Teamseiten und vieles mehr möglich automatisiert aus Entitäten (Datensatztyp) im CRM zu erzeugen. Für die Konfiguration steht eine eigene Entität zur Verfügung, in der Ihr Systemadmin schnell und einfach bestimmen kann, in welcher Entität bei einem bestimmten Ereignis eine SharePoint-Seite/-Element benötigt wird. Auch die Online-Versionen von SharePoint und Dynamics CRM werden unterstützt .

Oberflaeche_fuer_die_Konfiguration

Oberfläche für die Konfiguration

Wie kann die Standard-SharePoint-Integration in Dynamics CRM erweitert werden?

Die Standard-Integration kann um die in der hier aufgeführten Produktmatrix genannten Listen/Seiten erweitert werden oder nutzen Sie komplett die Funktionalität, die SharePoint Ihnen bietet, indem Sie sich SharePoint im IFrame in Dynamics CRM anzeigen lassen. Auf diesem Weg können Sie über ein Fenster im CRM-Datensatz in den SharePoint schauen und Features, wie Kopie herunterladen, Eigenschaften bearbeiten, SharePoint-Suche etc. nutzen.

SharePoint_Dokumentenbibliothek_pro_Account

SharePoint Dokumentenbibliothek pro Account

Projektmanagement_mit_Integration_Dynamics_CRM_und_SharePoint_verbessern

Projektmanagement mit Integration Dynamics CRM und SharePoint verbessern

Welche SharePoint Apps und Funktionen sind im Dynamics CRM integrierbar?

Unsere Produktmatrix und unsere Anwendungsfälle erläutert im Detail, welche SharePoint-Elemente, wie im Dynamics CRM konfiguriert werden können. Von Standardlisten, Dokumentenbibliotheken, benutzerdefinierten Listen/Seiten, Teamseiten, Ordnern, benutzerdefinierten Listeneinträgen mit konfigurierbarem Feld-Mapping, OneNote-Notizbücher/bzw. -Abschnitte, Taxonomie-Einträge mit Hierarchie und vieles mehr sind derzeit möglich aus dem CRM im SharePoint zu erstellen.

Wie verbinde ich OneNote mit Dynamics CRM?

Beschäftigt man sich ein wenig mit der Materie, findet man die vor kurzem noch angekündigte OneNote-API. Jedoch kann diese API im aktuellen Status nur das OneNote-Notizbuch im eigenen OneDrive bearbeiten. Eine Bearbeitung eines im SharePoint liegenden Notizbuchs ist aktuell noch nicht möglich, aber immerhin bereits auf der Wunschliste.

Da aber auch wir nicht so lange warten wollen, haben wir analysiert, welche Alternativen es gibt. Erstaunlicherweise sind es sogar recht viele, die jedoch alle der Reihe nach aus Gründen der Performance oder Kompatibilität ausscheiden. Nur eine Alternative blieb, die Anfangs regelrecht gepflastert mit Stolpersteinen schien, die sich jedoch im Laufe der Zeit eher als Steighilfen erwiesen. Im Screenshot sieht man, wie eine mögliche Konfiguration aussehen könnte. Mehr dazu gibt es auf dem folgenden Blog-Beitrag: OneNote heiratet CRM/SharePoint-Integrator

OneNote - Seite

OneNote-Abschnitt/-Seite pro Account/Verkaufschance im Dynamics CRM

Fazit

Der CRM-SharePoint-Integrator kann Ihnen viele wiederkehrende Aufgaben abnehmen und integriert zudem den SharePoint je nach Wunsch in Ihrem CRM. Kompatibel ist der CRM-SharePoint-Integrator mit dem CRM ab Version 2011 in den Varianten On-Premise und Online sowie mit dem SharePoint ab Version 2007 in den Varianten On-Premise und Online. Die Funktionalität können Sie sich auch nochmal im Webcast hier anschauen:

Erfahren Sie mehr auf unserer Produktseite. Haben Sie Fragen zum Produkt, möchten Sie eine Vorführung oder das Produkt beziehen, so sprechen Sie uns an.

Autor: Dirk Sarodnick und Melanie Wolf
Google+

SharePoint 2013 Website mit benutzerdefiniertem WebTemplate per Workflow erstellen

Freitag, 15. November 2013

Artistic EndeavourMein Kollege Herr Weichert erklärte in seinem letzten Blogbeitrag, wie mit einfachen Bordmitteln ein WebTemplate im SharePoint 365 per Workflow zu deployen ist. Dabei setzte das Szenario jedoch voraus, dass der Workflow an einer bestimmten Liste hängt und bei jedem neu hinzugefügten Item ausgelöst wird. Das funktioniert mit Standard-Templates, wie der Teamwebsite, sehr gut. Im Workflow wird einfach „STS#0“ als WebTemplate angegeben. Nun haben selbst angelegte WebTemplates keinen solch einfach strukturierten Template-Namen. Was muss ich also angeben, sodass anstatt der Teamsite meine eigens erstellte Vorlage genutzt wird?

Im SharePoint Designer hat man in der entsprechenden Variable „WebTemplate“ verschiedene Typen zur Auswahl:

–          Boolesch

–          Datum/Uhrzeit

–          Ganze Zahl

–          GUID

–          Wörterbuch

–          Zahl

–          Zeichenfolge

Für die Teamseite nutzten wir den Typ „Zeichenfolge“. Für unsere eigene Vorlage schien der Typ GUID vorerst prädestiniert. Spontan fiel uns die SolutionID aus der Manifest.xml ein. Wenn man diese allerdings einträgt, wird man arg enttäuscht. Es wird keine Subsite angelegt.

Die Lösung ist relativ simpel. Wenn man sich die Zeichenfolge „STS#0“ anschaut, scheint diese Struktur nicht willkürlich. Das Gros der Leser erkennt natürlich, dass das der Standardwert von Microsoft für eine Teamseite ist. Die Werte sind auch relativ schnell mit den IE Developer Tools auffindbar: in der Selektionsbox beim Erstellen der Unterseite als „value“ des entsprechenden Eintrags. Weiterhin tauchen dann in den anderen Auswahlmöglichkeiten auch Dinge auf wie „BLOG#0“ oder „COMMUNITY#0“. Einige von euch werden wissen, worauf es hinausläuft.

Scrennshot1

Im Reiter „Benutzerdefiniert“ befindet sich unsere eigene Vorlage. Mit den IE Developer Tools markiere ich den Eintrag und es wird mir eine relativ kryptische Zeichenkette angezeigt, die im Wesentlichen folgendes Format hat:

–          {GUID}#[TEMPLATENAME]

Ein Beispiel sieht wie folgt aus:

–          {71F841A7-F471-4043-8DC7-FB26D64D0F25}#MyWebTemplate

Achtung: Diese Guid entspricht nicht der SolutionID! Das ist die FeatureID, mit der die Vorlage ausgerollt wurde. Diese ID ist also auch beim Ausrollen auf einer anderen SiteCollection in jedem Fall identisch.

Scrennshot2

Wenn wir diese Zeichenkette als Wert im SharePoint Designer eintragen und den Workflow erneut starten: Voilà. Unser eigenes Template bekommt seine Oberfläche per Workflow.

screenshot4Die FeatureID kann man schlussendlich auf zwei Wege herausfinden. Zum einen ist es möglich, diese einfach im Feature abzulesen, wenn es sich um eine selbsterstellte Lösung handelt. Sollte das nicht der Fall sein, kann man einen aus SharePoint 2010 bekannten Umweg gehen, indem man auf „Deaktivieren“ des jeweiligen Features klickt. In der Adressleiste des Browsers taucht eine URL auf, die die ID des Features enthält. Der Eintrag hinter dem Doppelkreuz definiert dann nur noch das eigentliche Template.

Autor: Henrik Krumbholz

PT_XING_HKrumbholz_jpg

 

Die schwarze Magie mit XML Mapping in Word 2013 als BusinessObject-Export

Donnerstag, 25. Juli 2013

bildteaser_blog_072013Kennen Sie das Problem bei allen üblichen Reporting Suites, dass exportierte Word-Dokumente schlecht bis garnicht bearbeitbar sind? Der Aufbau des Dokumentenlayouts wird mit Tabellen oder gar Frames zusammengehalten und der Benutzer hat seine Mühe, mal eben ein paar Zeilen einzufügen. Doch was ist, wenn der Kunde einen vollständigen Export benötigt, dessen Resultat (also das Word-Dokument) auch gut, wenn nicht sogar sehr gut, bearbeitbar ist? Was ist, wenn die Vorlage dafür auch noch vom Kunden änderbar sein soll? “Back to the roots” ist die Lösung, denn Microsoft bietet hierfür eine einfache, mächtige und kaum bekannte Möglichkeit: XML Mapping

Was ist XML Mapping?

Schwarze Magie! Doch schweifen wir für einen Moment ab. Der Weg zu diesem Lösungsansatz führte über den Gedanken der Word-Vorlagen und Serienbriefe. Es musste doch irgendwie möglich sein, einem Word-Dokument beizubringen, dass es Felder automatisch ausfüllt und die dafür nötigen Daten aus irgendeiner Datenquelle zieht. Grundsätzlich ist dies auch völlig problemlos möglich, wenn man ein paar einfache Daten anbinden will.

Doch in der Realität braucht man eben immer etwas mehr, als das schöne bunte Beispielprojekt einem zeigt. So auch hier, denn es musste auch möglich sein, anhand der Daten einzelne Tabellen oder Tabellenzeilen zu wiederholen. Der Ansatz des Serienbriefs zerschellte also in tausende kleine Teile. Auch die Word-Vorlagen führten nicht zum Ziel. Irgendwie muss das doch aber funktionieren? Vielleicht fügt man dem Dokument einfach ein wenig VBA hinzu? Klar, das würde auch funktionieren, aber wer möchte schon freiwillig die Ausführung von VBA in einem Word-Dokument aktivieren, welches man von einem Internetportal heruntergeladen hat? Und selbst wenn, will man sowas denn veröffentlichen? Eher nicht.

Fast schon aufgegeben, fand sich dann jedoch ein Menüpunkt “XML-Zuordnungsbereich” im Entwicklertools-Menü, welcher vielversprechend klingt und förmlich darauf wartete, dass man ihn klickt.

XML-Zuordnungsbereich

Wie genau funktioniert XML Mapping nun?

XML-Zuordnung
Mit einem Klick auf den genannten Knopf öffnet sich ein Menü “XML-Zuordnung” am rechten Bildschirmrand. In diesem sieht man eine Dropdown-Liste mit allen verfügbaren XMLs, welche im Dokument eingebettet sind, und deren inhaltliche Struktur. Standardmäßig sieht man daher erst einmal die üblichen Dokumenteigenschaften mit den Daten für Autor, Titel, Beschreibung, usw.

Es scheint also, als könne man eine XML-Struktur inkl. Daten in ein Word-Dokument einbetten. Da man .docx-Dateien problemlos mit dem OpenXML SDK bearbeiten kann, war dies also schonmal der erste Lichtblick. Sollte es tatsächlich möglich sein, dass man mit nur wenigen Zeilen C# ein ganzes serialisiertes BusinessObject in Word einbetten kann, welches wiederum im Word-Dokument vorgefertigte Felder ausfüllt? Ja, genau das sollte es.

Doch erst einmal müssen wir eine Vorlage erstellen. Dazu legen wir uns eine XML-Datei mit der gleichen Struktur wie unser zukünftiges serialisiertes BusinessObject an, jedoch erst einmal ohne Daten. Warum? Nun, es ist eben eine Vorlage. Das vorbereitete XML soll ja ersetzt werden. Ein Klick in die Dropdown-Liste zeigt uns einen Menüpunkt, mit welchem wir unsere XML-Struktur dem Word-Dokument bekannt machen können.

XML-Zuordnung - Neue Komponente
In meinem Beispiel verwende ich dazu dieses XML als Vorlage.

Ist das erledigt, haben wir unsere XML-Struktur zur Verfügung und können unsere Felder hinzufügen. Dabei kann man sogar zwischen unterschiedlichen Typen wählen und somit sogar eine Datumsauswahl direkt im Word-Dokument anbinden, was die Bearbeitbarkeit deutlich erhöht. Dazu Klicken wir einfach mit der rechten Maustaste auf das gewünschte XML-Element und es öffnet sich folgendes Menü.
Kleiner Tipp: Bei leeren XML-Feldern wird einem automatisch der Standardtext angezeigt. Diesen kann man ändern, wodurch auch bei leeren Feldern ein beliebiger Text als Fallback stehen kann.

XML-Zuordnung - Steuerelement

Und was ist mit Wiederholungen?

Da sind wir auch schon beim Kern. Bis hier hin ist ja alles schön und gut, wäre aber auch mit einer einfachen Serienbrief-Datenbindung möglich gewesen. Doch wir wollen ja Wiederholungen in Wiederholungen in Wiederholungen in […]. Dazu klickt man einfach mit der rechten Maustaste auf das XML-Element, welches sich wiederholt, und verwendet den erscheinenden Menüpunkt. Der Einfachheit halber kann man sich auch eine paar Absätze, Tabellen und Texte vorbereiten, markieren und mit diesem Menüpunkt eine Wiederholung daraus erzeugen. Diese Wiederholungen lassen sich sogar noch sinnvoll benennen, wodurch der Nutzer nützliche Menüpunkt erhält.

XML-Zuordnung - Wiederholung

Verarbeitung und Beispiel

Nun muss das Dokument nur noch nach den eigenen Vorstellungen gefüllt werden. Das war’s dann auch schon.
Die damit erstellte Vorlage, welche lediglich ein simples Word-Dokument (docx) mit einer vorbereiteten und eingebetteten XML-Struktur ist, muss nun nur noch mit dem OpenXML SDK bearbeitet werden. Es muss also das Dummy-XML, welches wir im obigen Schritt hinzugefügt haben, ersetzt werden. Entgegen der unten verlinkten Quelle, muss diese Ersetzung natürlich nicht mit VBA-Code innerhalb des Dokuments geschehen. In meinem gleich folgenden Beispiel habe ich dafür dieses XML mit Daten verwendet.

Den Rest erledigt Word beim Öffnen des Dokuments von selbst. Es füllt die Felder und erzeugt die einzelnen ausgefüllten Wiederholungen, wie man im Beispiel-Dokument wunderbar sehen kann. Hierfür habe ich natürlich wieder ein Beispiel vorbereitet, welches nur einen Klick entfernt ist.
Kleiner Tipp: Im Bearbeitungsmodus erkennt man die Magie des Ganzen erst richtig.

Fazit

Es ist also sehr einfach möglich, dem guten Microsoft Word beizubringen, wie es ein Dokument mit Daten zu füllen hat. Das enthaltene XML (bzw. das serialisierte BusinessObject) ist natürlich schnell und einfach mit C# und dem OpenXML SDK in das Dokument eingefügt. Ein komplettes Aufbauen des Dokuments mit dem OpenXML SDK oder aufwändigem Code bleibt einem erspart. Dadurch, dass das Dokument in Word selbst erstellt wird, ist es am Ende für den Nutzer genauso einfach zu bearbeiten, wie die Vorlage selbst. Aufgrund der Nützlichkeit der Wiederholungen ist es zudem noch einfacher für den Benutzer.
Und das Beste an dieser Methode ist, dass die geänderten Daten (inkl. der Wiederholungen) in die XML-Struktur zurück geschrieben werden, wenn der Nutzer auch die entsprechenden Felder verwendet. Dadurch wäre sogar ein Re-Import der Daten denkbar. Wenn das mal nicht nach schwarzer Magie klingt!

Quellen/Links: OpenXML SDK, XML Mapping, CustomXML mit dem OpenXML SDK
Autor: Dirk Sarodnick
Google+

Validierung aus der Datenbank in ASP.NET MVC

Dienstag, 19. März 2013

bildteaser_validierungAls Zugabe zu meinem letzten Blog über “Lokalisierung aus der Datenbank in ASP.NET MVC“, geht es diesmal um Validierung. Praktischerweise lässt die nun folgende Implementierung zudem weitere Wege offen, verschiedenste Konfigurationen in diversen Datenquellen zu hinterlegen. Je nach gewählter Datenquelle (Datei, Datenbank, Azure Storage) lassen sich die Konfigurationen problemlos während der Laufzeit verändern. Daher die Wahl der Validierung als Beispiel, um die Mächtigkeit zu demonstrieren. Der Titel müsste korrekterweise also “Konfiguration aus einer beliebigen Datenquelle” heißen, aber das würde den Rahmen deutlich sprengen. Doch genug des Vorgeplänkels: “Developers, start your Environment!

Meistens integriert man die Validierung direkt im Code. Methoden prüfen die Werte der einzelnen Eingaben. Im ASP.NET MVC lässt sich das ja bereits mit Attributen am Model entschlacken. Problem ist hierbei allerdings, dass Attribute nicht ohne weiteres auf unterschiedliche Eigenschaften zugreifen können. So lässt sich bspw. mit einem Attribut an einer StartDate-Eigenschaft schwer prüfen, ob die entsprechende EndDate-Eigenschaft zeitlich später angegeben wurde. Unmöglich ist es nicht, aber bisher fand ich keinen sauberen Weg. Somit wählt man über kurz oder lang zumindest teilweise wieder den Weg der direkten Validierung ohne Attribute, wodurch am Ende ein noch viel schlimmerer Misch-Masch entsteht. Ein weiteres Thema ist außerdem die clientseitige Validierung, die bei Attributen recht praktisch mit erzeugt wird, wenn man die Macht des MVC korrekt nutzt. Doch wie Vereinheitlicht man diese ganzen Validierungen und fügt zudem die Flexibilität einer Konfiguration hinzu, die sich praktischerweise auch noch in einer Oberfläche konfigurieren lässt?

Schön und gut, aber was hilft uns nun?

Da sind wir auch schon beim Kern unseres Themas. Es mag sicherlich andere Möglichkeiten geben. Eventuell sogar bessere. Doch ich konzentriere mich hier auf Erprobtes und Gewohntes:
Die Nutzung der Enterprise Library mit einer eigenen ConfigurationSource. Das ist auch schon fast die gesamte Magie. Die Enterprise Library bringt, neben vielen anderen nützlichen Dingen, die Möglichkeit der konfigurierbaren Validierung mit sich. Dazu gibt es ein kleines Programm, mit welchem man ganz simpel seine Klassen  sowie Validatoren wählt und konfiguriert, plus am Ende eine ganze Validierung einstellt. Alternativ kann man sich auch manuell durch die XML Konfiguration hangeln.

Die Validierung der Enterprise Library funktioniert auch etwas anders, als man es bei Attributen gewohnt ist. Es lassen sich unterschiedliche Validatoren zusammensetzen und sogar And-/Or-Validatoren verwenden. Auch das Validieren von mehreren abhängigen Eigenschaften (StartDate, EndDate) stellt keine Probleme dar. Erweitern kann man diese natürlich auch mit eigenen Validatoren, wodurch auch jede andere denkbare Form der Validierung möglich wird. Das Ganze lässt sich mit etwas Code auch problemlos auf die clientseitige Validierung adaptieren, worauf ich jedoch erst im nächsten Blog eingehen möchte.
Zur Speicherung dieser Konfigurationen bringt die Enterprise Library unterschiedliche ConfigurationSources mit. Beispielsweise eine SqlConfigurationSource oder gar eine Azure BlobConfigurationSource. Doch was ist, wenn man sein Frontend mit einer Serviceschicht von diesen Teilen abgetrennt hat? Genau, man muss sich selbst eine ConfigurationSource bauen.

Unsere eigene ConfigurationSource

Als Erstes müssen wir unsere eigene ConfigurationSource vom Interface IConfigurationSource aus dem Namespace Microsoft.Practices.EnterpriseLibrary.Common.Configuration ableiten. Die im Interface enthaltenen Methoden müssen wir anschließend nicht vollständig implementieren. Uns werden nur die Methoden GetSection, Add und Remove interessieren, wobei genau genommen nur GetSection wirklich relevant für unseren Fall ist, außer man beabsichtigt das Konfigurationsprogramm direkt mit dieser ConfigurationSource zu verwenden. In diesem Blog beschränke ich mich erst einmal auf das Wesentliche, also nur auf GetSection, auch wenn die Implementierung für Add und Remove alles andere als kompliziert ist. In der angehangenen Solution sind diese natürlich beispielhaft mit implementiert.

Die Implementierung von GetSection ist, wie man hier deutlich sieht, ziemlich unspektakulär. Wir fragen lediglich unseren Service nach der gewünschten Section, deserialisieren diese und werfen sie zurück. Zur Deserialisierung benötigen wir allerdings bereits den korrekten Typ unserer Section, den wir uns mit Hilfe einer privaten Methode GetSectionByName holen. Diese sieht dann ganz schlicht wie folgt aus:

Dazu nun unser eigenes ConfigurationElement

Zur Vollständigkeit unseres Gesamtkunstwerks benötigen wir nun noch ein ConfigurationSourceElement, welches wir in unserer ConfigurationSource mit Hilfe des Attributs ConfigurationElementType verlinkt hatten. Dieses enthält eigentlich keinen ernsthaften Code und sieht entsprechend so aus:

Anbinden in der web.config und das wars, oder?

Fast! Aber alles zu seiner Zeit. Die Anbindung in der web.config ist auch keine große Sache. Das Konfigurationsprogramm der Enterprise Library würde dies sogar für uns erledigen. Allerdings achtet dieses nicht so wirklich auf Reihenfolge und Formatierung der web.config, die man sich möglicherweise vorher schön säuberlich aufgeräumt hatte. Wie auch immer, hier der entscheidende Code:


Man sollte meinen, wir sind nun fertig. Doch genau genommen haben wir bisher nur die Validierung der Enterprise Library vorbereitet und eine eigene Quelle für Konfigurationen erstellt. Das ist toll und reicht auch aus, wenn man die Validierung nur “manuell” ansteuern möchte. Im MVC ist es allerdings üblich die Validierung vom ModelValidator verrichten zu lassen, da dieser nicht ohne Grund dafür geschaffen wurde.

See you later, ModelValidator

Mit einem ModelValidator wird unsere Validierung beim Erhalt von Daten in einer Action des Controllers bereits ausgeführt, sodass wir direkt mit dem Ergebnis und dem entsprechenden Model arbeiten können. Zusätzlich wird natürlich auch bei TryUpdateModel und TryValidateModel unser ModelValidator aufgerufen, was uns erneut ein manuelles ansteuern der Enterprise Library Validierung erspart. Das ist fast wie Weihnachten :-)

Unser ModelValidator benötigt zur Einbindung ins MVC allerdings auch noch einen ModelValidatorProvider. Diesen implementieren wir kurz und schmerzlos folgendermaßen:

Der ModelValidator selbst ruft dann ohne Umwege die Validierung der Enterprise Library auf. Im nun folgenden Screenshot habe ich mich dabei auf den wesentlichen Teil beschränkt. In der angehangenen Solution ist im Provider zusätzlich ein Cache eingebaut, den man auch problemlos auf Dependency Injection (die Enterprise Library liefert dafür Unity) ummünzen kann. Zudem ist dort auch bereits die clientseitige Validierung angedeutet, die im nächsten Blog lediglich um die konkreten Adapter erweitert werden muss.

Nun implementieren wir nur noch die Validate-Methode, welche die Validierung der Enterprise Library aufruft und direkt dessen ValidationResults in für das MVC taugliche ModelValidationResults umwandelt.

Zu guter Letzt geben wir dem MVC noch unseren mühsam angelegten ModelValidator auf den Weg, sodass fortan die Validierung von der Enterprise Library übernommen wird.

Fazit

Nun haben wir unser Gerüst fertig gestellt. Die Validierung kann nun komfortabel konfiguriert und natürlich auch außerhalb des ModelValidators vom Code angesteuert werden, wodurch sich die gleiche Validierungslogik in der gesamten Anwendung wiederverwenden lässt. Fehlt eigentlich nur noch die clientseitige Validierung.

Als kleines Sahnehäubchen habe ich diesmal nicht nur den üblichen SourceCode in ein ZIP gepackt, sondern verweise zusätzlich auf ein sehr interessantes Projekt namens ElValWeb. In diesem ist die Validierung der Enterprise Library clientseitig umgesetzt. Etwas angepasst/ aktualisiert lässt es sich wunderbar inkl. CustomValidator-Unterstützung im MVC 4 einsetzen. Das möchte ich mir allerdings, wie bereits erwähnt, für einen späteren Blog aufheben. Dieses ganze Wissen in einen Topf geworfen und gut umgerührt, ergibt dann eine komplette konfigurierbare Validierung aus einer beliebigen Datenquelle (Datenbank, Azure Storage) inkl. clientseitiger Validierung. It’s magic!

Im Übrigen können die Meldungen unserer soeben implementierten Validierung auch, passend zu meinem letzten Blog, aus Resourcendateien und demzufolge auch aus der Datenbank kommen. Man könnte meinen, das ist Absicht. ;-)
Wie versprochen hier die gesamte Solution zum Download.

Quellen: Enterprise Library, MSDN – Configuring Enterprise Library, MSDN – Validation Application Block
Autor: Dirk Sarodnick
Google+

E-Mail-Migration aus einem Nicht-Microsoft-CRM auf Dynamics CRM Online

Dienstag, 12. Februar 2013

Für die Datenmigration innerhalb der Microsoft-Welt, zum Beispiel in eine neuere Version, finden sich zahlreiche Anleitungen, Whitepaper und Blog-Beiträge im Netz. Etwas weniger vorteilhaft sieht es bei der Migration von einem Nicht-Microsoft-System aus. Welche Möglichkeiten bestehen hier, Daten zu übernehmen? Im letzten Jahr wollte beispielsweise ein Kunde unter anderem sein bestehendes CRM-System nach Dynamics CRM Online migrieren.

Hier sollten nicht nur alle Entitäten (Firmendatensätze, Kontakte, Projekte, Service-Tickets, Angebote, etc.) mit zugehörigen Aktivitäten (Anrufe, Aufgaben, Faxe) importiert, sondern auch E-Mail-Altdaten richtig verknüpft übernommen werden. Doch wie sieht eine mögliche Strategie für einen solchen Fall aus?

Ausgangssituation

Im vorherigen CRM-System wurden die E-Mails direkt über die Software selbst versendet, es existierte kein separater Mailserver. In der neuen Umgebung wird CRM Online zusammen mit Exchange Online und Outlook 2010 genutzt. Das heißt, E-Mails werden nur noch über Outlook verschickt und die Mitarbeiter entscheiden selbst, welche davon kundenrelevante Informationen enthalten und demzufolge in das Kundenmanagementsystem gehören. Dafür gibt es das MS CRM-Outlook-Plugin: Bis auf wenige Ausnahmen stehen mit diesem Add-In die gleichen Microsoft Dynamics CRM Online-Funktionen zur Verfügung, wie bei der Verwendung der Weboberfläche. Damit lassen sich kundenrelevante E-Mails einfach per Klick direkt mit dem CRM-Kundendatensatz verknüpfen und für jeden Mitarbeiter jederzeit nachvollziehbar archivieren. Zudem wird auf diese Art und Weise auch die Speicherkapazität von standardmäßig 5GB/User im CRM Online nicht unnötig belastet und nur wesentliche E-Mails befinden sich in der Kundenhistorie.

Zielstellung soll es in diesem Beispiel sein, die bestehenden Entitäten mit zugehörigen E-Mail-Aktivitäten nach CRM Online und den gesamten alten Mailverkehr jedes Mitarbeiters nach Exchange Online zu übertragen und anschließend die Verknüpfungen korrekt wiederherzustellen. Tatsächlich erfordert eine solches Migrationsvorhaben noch weitere Aktivitäten, da der alte Datenbestand in der Regel nicht nur auf die E-Mails beschränkt ist, sondern wie eingangs erwähnt häufig noch Dinge wie Projekte, Angebote oder Service-Tickets hinzukommen. An dieser Stelle soll jedoch der Fokus auf E-Mails liegen.

Allgemeine Vorgehensweise

Neben der Einrichtung von Exchange Online und der Installation aller Outlook Clients samt CRM-Plugin ist vor allem die vollständige Datenübernahme erforderlich. Es müssen die existierende Informationen – also alle regulären Entitäten und vor allem die Kontakt-Entitäten – nach CRM Online und die E-Mails nach Exchange Online migriert sowie anschließend die korrekten Zuordnungen hergestellt werden. Dabei ist zu beachten, dass bestehende E-Mails in beiden Systemen abgelegt werden, da die Verknüpfung dieser zwei Elemente nur dann möglich ist.

Das Prinzip selbst ist nicht kompliziert: Jeder Nutzer benötigt einen CRM-Zugang mit entsprechend hinterlegten Exchange-Daten, anschließend können dann pro Nutzerkonto die E-Mails in Exchange importiert werden. Ist das abgeschlossen, wird das Oulook-CRM Plugin die initiale Synchronisation der E-Mails zwischen den Kontakten im CRM Online und Exchange Online durchführen. Das Mapping erfolgt dabei über die E-Mail-Adressen.

Schritt 1: Migration der regulären Entitäten ins CRM Online

Für die Migration der regulären Entitäten bietet sich ein klassischer ETL-Vorgang an, der allerdings unter Beachtung des Quell- und Zielsystems entsprechend ideal designed werden muss. Dabei bietet sich für den Import in CRM Online entweder die Verwendung des Data Migration Wizards unter Einsatz von Data Import Templates oder die CRM Online API an. Einen guten Überblick über die möglichen Verfahren bietet dieser Artikel. Der Export aus dem Altsystem lässt sich, je nach dem, was unterstützt wird, ebenfalls über verschiedene Wege realisieren. Interessant ist hierbei die Frage nach dem zu betreibenden Aufwand für die Transformation – idealerweise wählt man Im- und Exportvariante so aus, dass dieser minimal ist.

Für unser Anliegen “E-Mail-Altdaten mit Verknüpfung importieren” ist es wichtig, dass die Kontakt-Entität (ohne E-Mails und deren Anhänge – diese werden im nächsten Schritt “ordentlich” importiert) ins CRM Online gelangt. Dafür kommt zum Beispiel die Verwendung der entsprechenden Schnittstelle des Altsystems in Frage. Es wird ein kleines Softwaretool geschrieben, welches den XML-Export abholt und via XSLT in die Data Import Templates eingefügt. Anschließend kann mit dem Wizard importiert werden.

Einen Überblick über den gesamten Vorgang bieten die folgenden Grafiken.

ETL-Prozess bei der E-Mail-Migration nach CRM Online

Schritt 2: Vorbereitung des Exchange Online Servers und Import von E-Mails aus dem alten System

Für jeden CRM Nutzer muss auch im Exchange ein Mailkonto existieren. Der Import setzt voraus, dass die E-Mails und Dateianhänge aus dem alten CRM System in einem Format vorliegen, welches dafür geeignet ist. Auch hier ist, je nach Ausgangssystem, gegebenenfalls wieder eine Formatumwandlung erforderlich.

In unserem Beispiel werden die E-Mails über die bereits angesprochene Schnittstelle aus dem alten CRM System ebenfalls in XML exportiert. Mit ein wenig Code und unter Verwendung der EWS Managed API können die E-Mails an den Exchange Server und in das CRM-System übermittelt werden. Ein entsprechendes Codebeispiel in C# kann hier heruntergeladen werden. Wichtig ist, dass dabei die Verbindung zu den im ersten Schritt importierten Kontakten korrekt hergestellt wird.

Schritt 3: Synchronisierung zwischen Exchange und CRM Online

Sind alle Daten in den zwei Zielsystemen vorhanden, erfolgt die Synchronisation zwischen CRM Online und Exchange Online. Hier kommt das MS CRM-Outlook-Plugin ins Spiel, welches die korrekte Verknüpfung bei der ersten Synchronisation anhand der in Schritt 1 und 2 importieren Daten und deren Verknüpfungen durchführt.

Falls darüber hinaus eine weitere Entität innerhalb des alten CRM-Systems, zum Beispiel Projekte, direkt auf eine E-Mail verwiesen hat, muss das beim Export berücksichtigt werden. Die entsprechenden Verbindungen müssten nun abschließend noch über die CRM Online Web Services wiederhergestellt werden, da erst zu diesem Zeitpunkt alle nötigen Informationen dafür in dem Systemen vorliegen.

Fazit

Trotzdem ein nicht ganz einfaches Unterfangen, da zahlreiche Dinge bedacht werden müssen, lässt sich eine solche Migration anhand der vorgestellten Schritte strukturiert über die Bühne bringen. Entscheidend für Erfolg oder Misserfolg ist auf jeden Fall eine vorherige gründliche Systemanalyse und Prozessplanung.

Quellen: Data Import Templates, Dynamics CRM Migration, MS CRM-Outlook-Plugin
Autor: Tom Halank

Lokalisierung aus der Datenbank in ASP.NET MVC

Mittwoch, 12. Dezember 2012

Es war einmal ein kleines Kind. Dieses lief seit einigen Monaten und erfreute sich großer Beliebtheit. Doch dieses Kind war nicht so einfach zu betreuen, wie der Herr Vater sich das vorstellte. Bei der Korrektur eines Sprachfehlers, musste man es ständig neu gebären, was auch die Frau Mutter nicht sehr erfreute. Also dachte sich der Herr Vater: “Es muss doch eine Möglichkeit geben, die Sprache des Kindes direkt in seinem Kopf zu korrigieren.”  So, oder so ähnlich, muss es wohl gewesen sein, als der Mensch lernte, seine Sprache in seinem Kopf zu speichern. Doch was ist, wenn das Kind ein Projekt ist? Ständig muss man die Anwendung neu deployen, obwohl man doch nur ein paar Texte und/oder Benennungen verändert hat. Oder man lagert Bereiche, welche sich häufiger ändern (könnten), einzeln in die Datenbank aus. Das klingt nicht nur unflexibel, es ist auch unflexibel. Gerade bei längeren Release-Zyklen oder ewig ladenden Azure-Instanzen wird so eine kleine Textänderung schnell zur Geduldsprobe. Also ab mit den Texten in die Datenbank… und zwar zackig!

Meist nutzt man Resourcendateien, um unterschiedliche Sprachen seiner Webanwendung bereitzustellen. So mancher hat dieses Problem sicherlich längst erkannt und greift die einzelnen Sprachelemente direkt aus der Datenbank ab, statt Resourcen zu verwenden. Doch was ist mit Fremdassemblies, die sich möglicherweise nur über Resourcen lokalisieren lassen? Hier kommt der Ansatz mit einer ResourceProviderFactory zum Zuge. Diese gibt einem die Freiheit, die angefragten Resourcenwerte bspw. aus einer Datenbank oder einem Service zu saugen. Ganz nebenbei lässt sich diese natürlich mit der manuellen Variante ansteuern, um ggf. mächtigere Abfragen zu tätigen. Doch bleiben wir beim Thema.

Schritt 1: Wie sieht so eine ResourceProviderFactory aus?

Bis dahin also noch nichts Spektakuläres. Die Factory erstellt in unserem einfachen Beispiel lediglich eine neue Klasse des ResourceProviders. Dieser enthält nun unsere Logik, um die entsprechenden Resourcen, möglichst geschickt, aus der Datenbank zu saugen. Dabei wird entweder lediglich der Klassenname bei globalen Resourcen oder der virtuelle Pfad bei lokalen Resourcen als ResourceSet übergeben, welches wir in unserem ResourceProvider halten werden. Je nach belieben kann dies für globale und lokale Resourcen in einzelne Klassen getrennt und/oder zusätzlich gefiltert werden.

Schritt 2: Der ResourceProvider verrichtet also das eigentliche Werk. Aber wie?

Dazu muss unser ResourceProvider lediglich die Interfaces “IResourceProvider” und “IImplicitResourceProvider” implementieren. Das sieht im Wesentlichen folgendermaßen aus:

Wie so oft, sagt sich das allerdings wesentlich einfacher, als es dann tatsächlich ist. Gleichermaßen sieht der entstandene Code viel komplexer aus, als er dann tatsächlich ist. Schwer, einfach, schwer, einfach, was denn nun? Angesichts des Gesamtkunstwerks, was uns schon bald erwartet, würde ich zu “Schw’einfach” tendieren. Aber sehen Sie selbst.

Zunächst implementieren wir die Methoden “GetObject” beider Interfaces. Auch hier machen wir kein großes Hexenwerk und verweisen auf eine allgemeingültige Methode “GetObjectInternal”. Lediglich die Methode “ConstructFullKey” zerlegt unseren ImplicitResourceKey, um den eigentlichen ResourceKey herauszufiltern.

Diese Methode “GetObjectInternal” hat eine einfache Aufgabe: Sie holt nun endlich unseren Text aus der Datenbank. Dies geschieht im Folgenden über einen “LocalizationService”, welcher dann direkt auf ein typisiertes DataSet zugreift. Für die Abfrage werden hier die drei Werte zur Identifikation eines Resourcevalues übergeben: Culture (bspw. “de-DE”), ResourceSet (bspw. “Errors” oder “/Views/Shared/Site.Master”) und ResourceKey (bspw. “SiteTitle”). Als Ergebnis dieser Methode erhält man den passenden String (bspw. “Startseite”).

Damit haben wir nun einen wichtigen Teil unserer Anwendung fertiggestellt. Doch aufmerksame Leser werden sicherlich bemerkt haben, dass wir noch nicht alles implementiert haben. Uns fehlt noch die Property “ResourceReader” und die Methode “GetImplicitResourceKeys”, welche eine ICollection zurückliefert. Zudem fällt im obigen Bild auf, dass die Methode “GetObjectInternal”, neben der Serviceanfrage in der Datenbank, zusätzlich eine Methode “GetResourceCache” anspricht.

Schritt 3: ResourceReader und ICollection? Das klingt verrückt.

Die nun angerichtete Verwirrung lässt sich ziemlich schnell wieder auflösen. Grundsätzlich unterscheidet man in diesem ganzen ResourceProvider zwischen zwei Varianten, um Resourcen abzuholen: Einen einzelnen Wert oder eine Auflistung von Werten eines bestimmten ResourceSets und/oder einer spezifischen Culture. Erstere haben wir in unserer Methode “GetObjectInternal” gebündelt abgehandelt. Zweitere werden wir nicht ganz so elegant bündeln, nutzen aber im Kern die gleiche Funktionalität. Doch genug des Vorgeplänkels und zurück in die Praxis.

Fangen wir mit der Methode “GetResourceCache” an. Diese holt uns nun die erwähnten Werte aus der Datenbank und sortiert sie fein säuberlich in unseren Cache. Dazu nutzt sie die übergebene Culture und das festgelegte ResourceSet. Wenn erledigt, gibt sie unseren Cache zurück, damit der Aufrufer damit arbeiten kann.

Und weil es gerade so spaßig ist, springen wir direkt zu der Methode “GetImplicitResourceKeys”. Der nun folgende Code mag durchaus Geschmackssache sein, sieht aber komplizierter aus, als er tatsächlich ist. Wir lesen lediglich unseren ResourceReader aus, filtern dabei nach unseren keyPrefix und trennen schlussendlich nur den ResourceKey unter Verwendung unseres keyPrefixes, um damit einen ImplicitResourceKey erstellen zu können. Zurückgegeben wird also letztlich nur eine ICollection von ImplicitResourceKeys. Nähere Infos dazu lassen sich im MSDN nachlesen.

Zu guter Letzt benötigen wir noch unseren ResourceReader, den wir bereits oft genug verwendet, aber bisher nicht implementiert haben. Groß angekündigt, aber weniger spektakulär: Unser herzallerliebster ResourceReader ist im Grunde nämlich nichts weiter, als ein kleiner Wrapper für unser ResourceCache-Dictionary. Dies sieht dann wie folgt aus.


Schritt 4: Und wie binde ich dieses Kunstwerk nun ein?

Das ist wohl mit Abstand der “schwierigste” Teil unseres Abenteuers. Man fügt einfach folgendes Schnipsel in seine web.config und schon kann sich gefreut werden. Kernbestandteil ist hier natürlich der Wert des Attributs “resourceProviderFactoryType” des Elements “globalization”.

Fazit

Ich hoffe, den meisten Lesern ist bis hierhin bereits klar geworden, welche Vorteile die Implementierung des ResourceProviders und der ResourceProviderFactory mit sich bringt. Es ist nun möglich, viele Stellen in gewohnter Weise zu lokalisieren. Selbst die guten alten meta:resourcekey-Angaben funktionieren damit. Zudem kann man, wie in diesem Beispiel, einen eigenen Service, den man vorher ggf. nur manuell angesprochen hat, zur Lokalisierung anbinden.

Trotz der ganzen Vorteile überkommt mich immer ein seltsames Gefühl, wenn ich mir den entstandenen Code anschaue. “Schw’einfach” trifft es, denn es ist schwer und einfach zugleich, was in meinem Fall aber hauptsächlich an der Verworrenheit der beiden Interfaces liegt. Aber es funktioniert, ist nützlich und kann mit ein wenig mehr Arbeit durchaus noch “sauberer” implementiert werden.

Zudem sollte noch erwähnt sein, dass die Auslagerung der Lokalisierung in die Datenbank, einen weiteren Vorteil mit sich bringt: Es kann problemlos eine Administrationsoberfläche (Web-/ Windowsanwendung) aufgesetzt werden. Mit dieser kann man sogar Resourceninhalte komfortabel mit HTML formatieren, was je nach Einbindung der Resource (<%: vs <%=) kodiert oder nicht kodiert wird. Auch denkbar wäre die Möglichkeit für vorhandene Benutzer, mit an der Übersetzung zu arbeiten.

Als kleiner Bonus zur Weihnachtszeit gibt es die gesamte Solution auch noch zum Download.
In diesem Sinne: Viel Spaß damit!

Quellen: MSDN
Autor: Dirk Sarodnick
Google+