More servicesWindows Live
HomeHotmailSpacesOneCare
 
MSN
Sign in
 
 
Spaces home  Microsoft Dynamics CRM &...ProfileFriendsBlogMore Tools Explore the Spaces community

Microsoft Dynamics CRM & Co

Neuigkeiten, Tipps, Trends und Tricks

Microsoft Dynamics CRM 4.0 | Eigene Workflow Assemblies


Die Windows Worflow Foundation in Microsoft Dynamics CRM 4.0 zu integrieren, hat sichtlich gut getan. Viele Aufgaben lassen sich mit Workflows vereinfachen oder gar komplett automatisieren. Und eine weitere herausragende Möglichkeit ist, den Workflows über Custom Assemblies weitere Funktionen "beibringen" zu können. Viele von Euch fragen mich, ob ich hierzu nicht ein paar Beispielcodes hätte oder ob es nicht eine Code-Bibliothek gibt, unter der sich Workflow-Assemblies finden lassen.

Anbei möchte ich Euch daher mit einem Praxis-Beispiel die Workflow Assemblies näher bringen. Zunächst zur Ausgangssituation: In einem Projekt ging es darum, die durch den Import erzeugten bzw. aktualisierten Datensätze einer übergeordneten Firma zuzuweisen, sofern der besagte Datensatz eine Filiale einer Firma war. Um dies zu identifizieren, arbeitet der Kunde mit zwei Nummern: Der Firmennummer - dem von Haus aus bekannten Standard-Feld und einer Filialnummer - einem benutzerdefinierten Attribut "new_branchsiteaccountnumber". Eine Filiale hat dabei exakt die gleiche Firmennummer, wie der Hauptsitz, jedoch ist die Filialnummer unterschiedlich. Am Hauptsitz hingegen ist die Filialnummer immer "0".

Da sich die Daten automatisch über eine zeitgesteuerte Import-Routine aus einem Fremdprogramm aktualisierten, galt es eine Routine zu schaffen, die den Prozess der Zuweisung der "übergeordneten Firma" automatisch übernimmt. Und genau hierfür kommt die Windows Workflow Foundation zum Einsatz.

Das Attribut "Übergeordnete Firma" ist ein Lookup Feld, wie sich schnell ermitteln lässt. Ich benötige zur Aktualisierung des Feldes also gezielt eine DatensatzID - und zwar diejenige, bei dem die Firmennummer = der Firmennummer des aktuellen Datensatzen und die Filialnummer = 0 ist.

Zunächst erweitern wir unsere VisualStudio-Anwendung, sofern noch nicht geschehen, um  die Erweiterungen für .NET Framework 3.0.

Als Resultat haben wir einige neue Projekttypen in VisualStudio 2005 zur Verfügung. Und bevor ich noch weiter in die Tiefe einsteige: Unter http://www.stunnware.com/crm2/topic.aspx?id=CustomWorkflowActivity findet sich für den Einsteiger alles notwendige, für die Entwicklung und Einrichtung.

Zunächst schaffen wir uns also die erforderlichen Verweise im Projekt:

using System;
using System.Workflow.Activities;
using System.Workflow.ComponentModel;
using Microsoft.Crm.Sdk;
using Microsoft.Crm.SdkTypeProxy;
using Microsoft.Crm.Workflow;
using Microsoft.Crm.Sdk.Query;
using System.Workflow.Runtime;
using System.Data;
using System.Xml;
using System.Xml.Linq;

Nun müssen beschäftigen wir uns mit der 1. Anforderung - wir benötigen eine erste Eingabemöglichkeit: Die Firmennummer

namespace FindAccountbyAccountnumber
{
    [CrmWorkflowActivity("Finde Firma zu Firmennummer & Filialnummer")]
 
    public class MatchAccountby :
 
    System.Workflow.Activities.SequenceActivity
    {
        // Input property
        public static DependencyProperty senderProperty = DependencyProperty.Register("sender", typeof(string), typeof(MatchAccountby));
 
        [CrmInput("Firmennummer")]
 
        public string sender
        {
            get
            {
                return (string)base.GetValue(senderProperty);
            }
            set
            {
                base.SetValue(senderProperty, value);
            }
        }


Da wir ebenfalls noch eine Filialnummer übergeben wollen, benötigen wir eine zweite Eingabemöglichkeit: Die Filialnummer

public static DependencyProperty sender2Property = DependencyProperty.Register("sender2", typeof(string), typeof(MatchAccountby));
 
[CrmInput("Filialnummer")]
 
public string sender2
{
    get
    {
        return (string)base.GetValue(sender2Property);
    }
    set
    {
        base.SetValue(sender2Property, value);
    }
}

Als Ausgabe möchten wir die AccountID des gefundenen Datensatzes zurückgeliefert bekommen.

// Output property
 
public static DependencyProperty accountIdProperty = DependencyProperty.Register("accountId", typeof(Lookup), typeof(MatchAccountby));
 
[CrmOutput("accountId")]
[CrmReferenceTarget("account")]
 
public Lookup accountId
{
    get
    {
        return (Lookup)base.GetValue(accountIdProperty);
    }
    set
    {
        base.SetValue(accountIdProperty, value);
    }
}

Jetzt hilft ein Blick in das aktuelle SDK, um die verschiedenen Methodiken abzuwägen, mit denen es möglich ist, die Suche innerhalb der Datenbank durchzuführen. Meine Wahl fiel in diesem Fall auf QueryByAttribute

private Guid MatchAccountbyAccountnumber(ICrmService crmService, string fromAccountnumber, string fromBranchsitenumber)
{
 
    // Retrieve Accounts from Entity with "fromAccountnumber" & "fromBranchsiteaccountnumber"
 
 
    QueryByAttribute query = new QueryByAttribute();
    query.ColumnSet = new AllColumns();
    query.EntityName = EntityName.account.ToString();
    query.Attributes = new string[] { "accountnumber", "new_branchsiteaccountnumber" };
    query.Values = new object[] { fromAccountnumber, fromBranchsitenumber };
 
 
    RetrieveMultipleRequest retrieveMultipleRequest = new RetrieveMultipleRequest();
    retrieveMultipleRequest.Query = query;
    retrieveMultipleRequest.ReturnDynamicEntities = true;
 
    RetrieveMultipleResponse retrieveMultipleResponse = (RetrieveMultipleResponse)crmService.Execute(retrieveMultipleRequest);
    BusinessEntityCollection retrieved = crmService.RetrieveMultiple(query);
 
    Guid accountId = Guid.Empty;
 
    foreach (BusinessEntity busEntity in retrieveMultipleResponse.BusinessEntityCollection.BusinessEntities)
    {
        // Pick the first accountid.
        accountId = ((Key)((DynamicEntity)busEntity)["accountid"]).Value;
        break;
    }
 
    return accountId;
 
}

Soweit also zur Abfrage der Datensätze. Da ich nach einer Übereinstimmung von Firmennummer und Filialnummer suche, wird definitiv nur ein Datensatz zurückgeliefert. Diesen gilt es nunmehr an die Workflow Routine zu übergeben.

protected override System.Workflow.ComponentModel.ActivityExecutionStatus Execute(System.Workflow.ComponentModel.ActivityExecutionContext executionContext)
{
    IContextService contextService = (IContextService)executionContext.GetService(typeof(IContextService));
    IWorkflowContext context = contextService.Context;
    // Obtain IcrmService so we can call into CRM SDK to retrieve accounts
    ICrmService crmService = context.CreateCrmService();
    // this.sender property will have the accountnumber that needs to be matched.
    // this.sender2 property will have the branchsiteaccountnumber that needs to be matched.
    Guid accountId = MatchAccountbyAccountnumber(crmService, this.sender, this.sender2);
    // Set the accountId output property to return this data back to the calling workflow
 
    this.accountId = new Lookup("account", accountId);
    return ActivityExecutionStatus.Closed;
}
    }
}

Soweit zum Quellcode. Diesen gilt es nunmehr zu kompilieren und die resultierende .dll-Datei mit Hilfe des aktuellen Plug-In-Registration-Werkzeuges in der Datenbank zu registrieren.

Im Anschluss an die erfolgreiche Registrierung, steht uns die neue Option als "Schritt" in den Workflows zur Verfügung.

Diesen gilt es nunmehr wie folgt aufzusetzen:

workflow_shot1

Zunächst prüfen wir die Filialnummer dahingehend, dass der Workflow nur ausgeführt wird, wenn die Filialnummer nicht "0" entspricht. Schließlich wollen wir der Hauptfiliale nicht sich selbst zuweisen.

Im Anschluss daran bestimmen wir unsere Firmennummer & Filialnummer.

Die Firmennummer holen wir uns dabei als dynamischen Wert aus dem aktuellen Datensatz und die Filialnummer geben wir als "0" vor. Im Anschluss daran fügen wir im einen weiteren Schritt eine Datensatzaktualisierung durch. Hier aktualisieren wir das Feld "Übergeordnete Firma" mit dem durch unsere Routine zurückgelieferten Wert - der AccountID. workflow_shot2Und fertig...

Wie Ihr den Bildern entnehmen könnt, ist in meinem Beispiel die Entität "Firma" in "Betrieb" umbenannt worden.

Natürlich kann der Workflow auch noch verbessert werden und z.B. vorweg ein kleiner "TimeOut" als "Warte bis"-Schritt hinzugefügt werden, um zu verhindern, dass der Workflow unmittelbar nach Erstellung mit der Arbeit beginnt. Weiterhin kann der Bereich auch auf "Organisation" umgestellt werden. Meine Wahl "Benutzer" hängt lediglich mit meiner Präsentationsmaschine zusammen.

Wer von Euch nun etwas mehr über Workflow Assemblies wissen möchte, dem seien nachfolgende Blog-Beiträge als Ersatz zu einer Bibliothek empfohlen:

- http://www.stunnware.com/crm2/topic.aspx?id=CustomWorkflowActivity

- http://danishmscrm.blogspot.com/2008/03/example-of-custom-workflow-activity-crm.html

- http://blogs.msdn.com/jim_glass/archive/2008/04/30/calling-a-net-assembly-in-mscrm-4-0-workflows.aspx

- http://msdn.microsoft.com/en-us/library/cc151142.aspx

- http://blogs.msdn.com/crm/archive/2008/01/11/part-2-happy-birthday-via-workflow-using-custom-workflow-activity.aspx

- https://community.dynamics.com/blogs/crmteam/archive/2008/02/19/e-mail-to-case-lead-using-crm-4-workflow.aspx

- http://blogs.msdn.com/ukcrm/archive/2008/06/12/e-mail-to-case-using-workflow-update.aspx

- http://blogs.msdn.com/ukcrm/archive/2008/04/27/creating-sharepoint-sites-with-crm-workflow.aspx

Viel Spass und

vielleicht gibt es schon bald eine Bibliothek auf MSDN

 

Microsoft Dynamics CRM 4.0 | Standard-Ansicht für Aktivitäten & historische Aktivitäten


Mittlerweile haben einige Kollegen hier im Netz Wege aufgezeigt, wie sich die Aktivitätenansichten image durch Anpassungen beeinflussen lassen. In meinen Projekten habe ich mich mit zwei Varianten (supported) näher auseinandergesetzt. Der Methode, die Micheal Höhne (stunnware) unter http://www.stunnware.com/crm2/topic.aspx?id=js11 veröffentlicht hat bzw. dem Update des Codes für 4.0 unter http://www.stunnware.com/crm2/topic.aspx?id=JS33, den er von Jonathan Briggs zugesandt bekommen hat und einem weiteren Ansatz von Adi Katz, der unter http://mscrm4ever.blogspot.com/2008/07/changing-activity-history-default-view.html zu finden ist.

Letzteren fand ich insbesondere spannend, da in einer Routine, sowohl die Ansicht der Aktivitäten, als auch die Ansicht der historischen Aktivitäten zu beeinflussen ist. In meinen Projekten, sorgte der Einsatz jedoch für einen Script-Fehler. Und zwar immer dann, wenn ich das Register wechselte. Dies hatte zur Folge, dass beim Verlassen des Datensatzes über Abbruch ohne Speichern oder auch mit Speichern der Anwender eine unschöne Fehlermeldung erhält.

...
var _loadarea = loadArea;
loadArea = function(areaid)
{
//load the iframe 
_loadarea(areaid);
//create the iframe object 
var iframe = document.getElementById(areaid + "Frame");
//wait until the iframe is fully loaded ("complete") 
iframe.onreadystatechange = function()
{
if( iframe.readyState == "complete")
{
...

 

Nachdem ich den Code analysiert hatte, fiel mir auf, das beim Wechsel des Registers die Variable iframe mit einem NULL-Wert belegt werden könnte.
Hier also der komplette Code mit einer Fehler-Abfrage zum NULL-Wert:

   1: //Activity scheduledend options 
   2: var ActivityOptions =
   3: {
   4: Overdue :"Overdue",
   5: Today :"Today",
   6: Tomorrow :"Tomorrow",
   7: Next7Days :"NextXDays;7",
   8: Next30Days :"NextXDays;30",
   9: Next90Days :"NextXDays;90",
  10: Next6Months :"NextXMonths;6"
  11: }
  12: //Activity History actualend options 
  13: var HistoryOptions =
  14: {
  15: Today : "Today",
  16: Yesterday : "Yesterday",
  17: Last7Days : "LastXDays;7",
  18: Last30Days : "LastXDays;30",
  19: Last90Days : "LastXDays;90",
  20: Last6Months : "LastXMonths;6",
  21: Last12Months: "LastXMonths;12"
  22: }
  23:  
  24: var _loadarea = loadArea;
  25: loadArea = function(areaid)
  26: {
  27: //load the iframe 
  28: _loadarea(areaid);
  29: //create the iframe object 
  30: var iframe = document.getElementById(areaid + "Frame");
  31: // added code to avoid NULL value
  32: if( iframe != null)
  33: {
  34: //wait until the iframe is fully loaded ("complete") 
  35: iframe.onreadystatechange = function()
  36: {
  37: if( iframe.readyState == "complete")
  38: {
  39: var picklist,option;
  40: //reference to the iframe document 
  41: var iframeDoc = iframe.contentWindow.document;
  42: switch(areaid)
  43: {
  44: case "areaActivityHistory":
  45: picklist = iframeDoc.all.actualend[0];
  46: /* change to suit your needs */
  47: option = HistoryOptions.Last90Days;
  48: break;
  49: case "areaActivities":
  50: picklist = iframeDoc.all.scheduledend[0];
  51: /* change to suit your needs */
  52: option = ActivityOptions.Next7Days;
  53: break;
  54: default: return;
  55: }
  56: picklist.value = option;
  57: picklist.FireOnChange();
  58: }
  59: }
  60: }
  61: } 

 

Damit gehört der Script-Fehler der Vergangenheit an. Viel Spass !

 

Microsoft Dynamics CRM 4.0 | AutoUpdate Routine des Clients


Um auch administrativ Ordnung beim Patchen von Microsoft Dynamics CRM zu behalten, wurde in den Client eine AutoUpdate Routine eingebaut. Unter http://blogs.msdn.com/crm/archive/2008/05/08/crm-client-autoupdate.aspx steht beschrieben, wie diese Routine einzurichten ist, weshalb ich hier nicht näher auf die Einrichtung eingehen werde. Den CRM Server hingegen gilt es manuell mit den Patchen / Hotfixes zu versorgen. Für die Clients jedoch ist die AutoUpdate Routine eine Erleichterung für den Administrator und noch dazu behält man den Überblick über ausgerollte Patches / Hotfixes.

Ebenfalls ausführlich beschrieben steht der AutoUpdate Einrichtungs-Prozess unter http://blog.powerobjects.com/2008/06/19/dr-strangelove-or-how-i-learned-to-stop-worrying-and-love-the-autoupdate-tool/. Hier sogar mit den Auswirkungen - sprich dem Screenshot des sich am Client öffnenden Fensters mit einem erforderlichen Update. Auch der Verweis auf ein Tool mit dem man die Gruppenrichtlinien erweitern kann, um den für die AutoUpdate Funktion erforderlichen Registry Key jedem Client über eine Gruppen-Regel zur Verfügung zu stellen, ist hier sprichwörtliches "gold" wert. Schließlich hat niemand Lust an n-Rechnern den Registry Key manuell einzutragen.

Ziemlich aufwendig jedoch ist der Prozess, die Konfigurations XML Datei jeweils auf den aktuellen Stand zu bringen. Insbesondere, da man in der XML-Datei die <PatchId> vermerken muss. An diese lässt sich laut Anleitung über die Extraktion der Patch- bzw. Hotfix-Datei (.exe) herankommen. Dabei fragt man sich, warum Microsoft diesen Wert nicht in die Hotfix.txt-Datei schreibt, die ebenfalls jedem Hotfix oder Patch beigefügt ist?!

Ich war schon am überlegen, hierfür ein kleines Tool zu schreiben. Doch momentan fehlt die Zeit für ein solches Projekt. Um es sich als Administrator im Tagesgeschäft jedoch einfacher zu gestalten und nicht jeden Client Patch in der aufwendigen Art und Weise zu entpacken, um an die <PatchId> zu gelangen, hier mein Tipp:

GettingPatchId

Alles was es bedarf, ist ein Client, auf den der Administrator direkten Zugriff hat und die Installation des Patches oder Hotfixes. Im Anschluss lässt sich über die Ausgabe der installierten Upates auf dem System durch einen Klick auf <Klicken sie hier, um Supportinformationen zu erhalten> in dem sich öffnenden Fenster unter Updatekennung die erforderliche <PatchId> ermitteln.

Mit dieser lässt dann die config.xml Datei pflegen und für die Beschreibung des Hotfixes hat man ja die Beschreibung aus dem KB-Artikel (diese in der XML mit aufzuführen lohnt sich - die User danken es einem zu wissen, welches Update denn eingespielt wird).

Hat man sich erst einmal eine config.xml-Datei aufgebaut, so lässt sich diese kontinuierlich pflegen und der Aufwand hält sich somit in Grenzen.

Ich hoffe, damit wieder einen kleinen Teil zur Zeitersparnis in der Administration von Microsoft Dynamics CRM 4.0 beigetragen zu haben und wünsche Euch viel Spass in der Umsetzung.

 

Microsoft Dynamics CRM 4.0 | Anpassungen hochladen?


Manchmal entdeckt man durch Zufall, was sich Programmierer bei einer Routine überlegt haben.

Mir erging es jüngst so bei der Einspielung von Anpassungen. Fehlermeldung_Upload_XML_AnpassungDa war ich doch ziemlich überrascht, als mir folgende Fehlermeldung bei dem Versuch eine Anpassungsdatei hochzuladen präsentiert wurde. Da der Standard-Programmpfad von Microsoft Dynamics CRM (C:\Programme\Microsoft Dynamics CRM\) bereits 36-Zeichen einnimmt kommt man schon bald an dieses Limit. In meinem Fall hatte der Kunde einen Pfad "\Anpassungen\" und darunter in ordentlicher Art und Weise nach Datumsbezeichnung "\2008-07-25\" die ZIP-Dateien abgelegt.

Mein Tipp also: die Sicherungen der Anpassungen direkt im Root-Verzeichnis ablegen, um nicht in die Not zu kommen, den Bezeichnungen der Datei zu kürzen oder gar mehrfach Kopien auf dem Server ablegen zu müssen. Und natürlich auch keine der genannten Sonderzeichen in der Dateibezeichnung.

 

Technorati-Tags: ,,