CRM 4 : Migration des Callouts depuis la V3
Ca faisait longtemps que je n' avais plus posté mais maintenant je m'y remet. J'ai eu l'occasion de m'intéresser de près aux développements personnalisés sous Dynamics CRM et je vais tacher de vous faire partager mes expériences sur le sujet.
Mon premier post sur le sujet traitera de la migration puisque j'ai eu à en réaliser une sur un projet comportant des Callouts, c'est à dire des événements levés automatiquement en .NET lors de certaines actions : création/modification/suppression d'entité, changement d'état ou de propriétaire, ...
Si on regarde le SDK de la V4, on peut trouver cette phrase sur la migration des Callouts :
"Microsoft Dynamics CRM 3.0 custom extensions such as callouts and workflow assemblies that use the Microsoft Dynamics CRM 3.0 Web services can be seamlessly upgraded to Microsoft Dynamics CRM On-premise without having to recompile."
Bien évidement, cela n'a pas été aussi simple pour moi. J'ai rencontré quelques erreurs que je vais décrire ici ainsi que les solutions que j'y ai trouvé.
1. Erreur déclenchée dès qu'un événement Callout aurait du être levé
Optimiste, je place mon fichier XML ainsi que mes DDLs dans le dossier approprié et je teste : A chaque création d'entité, j'ai un message d'erreur type. En activant le traçage de logs sur le serveur, j'ai pu avoir un message d'erreur un peu plus explicite que le traditionnel "Veuillez contacter votre administrateur CRM". L'exception déclenchée était :
FileNotFoundException: Impossible de charger le fichier ou l'assembly 'Microsoft.Crm.Platform.Callout.Base, Version=3.0.5300.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35' ou une de ses dépendances. Le fichier spécifié est introuvable.
C'est dans cette librairie que l'on trouve les prototypes des méthodes à implémenter pour en faire des Callouts donc il est normal qu'une erreur soit levée si elle n'est pas présente. Après avoir tenté d'ajouter cette assembly partout où je le pouvais, il s'est avéré que la solution était une "simple réparation" du serveur CRM à l'aide du CD d'installation de CRM.
2. Récupération du ColumnSet
Dans la version .NET 1, j'avais une petite méthode qui utilisait la réflexion pour obtenir la liste des attributs par type d'entités pour le paramètre du WebService.
Public Shared Function GetColumnSet(ByVal entityType As Type) As ColumnSet
Dim cols As New ColumnSet
Dim fields As FieldInfo() = entityType.GetFields
Dim colNames(fields.Length - 1) As String
For i As Integer = 0 To fields.Length - 1
colNames(i) = fields(i).Name
Next
cols.Attributes = colNames
Return cols
End Function
Avec le .NET Framework 2 (et donc le 3 aussi), la méthode GetFields ne fonctionne plus, il faut utiliser GetProperties.
Public Shared Function GetColumnSet(ByVal entityType As Type) As ColumnSet
Dim cols As New ColumnSet
Dim pis As PropertyInfo() = entityType.GetProperties
Dim colNames(pis.Length - 1) As String
For i As Integer = 0 To pis.Length - 1
colNames(i) = pis(i).Name
Next
cols.Attributes = colNames
Return cols
End Function
Ce n'est pas un gros changements mais l'erreur est difficile à diagnostiquer puisqu'il faut passer en debug pour voir qu'aucun attribut n'est listé par la méthode GetFields.
3. Distinction du type d'entité à l'aide du paramètre postImageEntityXml
Pour savoir quel type d'entité est crée/modifié/supprimé, on avait recours à un code de ce type (je rappelle qu'on ne peut toujours pas utiliser entityContext.EntityTypeCode à cause du problème des énumérations qui sont numérotées depuis 0 et par ordre alphabétique dans la référence en VB alors que les numéros CRM ne suivent pas la même logique) :
Public Overrides Sub PostCreate(ByVal userContext As CalloutUserContext, ByVal entityContext As CalloutEntityContext, ByVal postImageEntityXml As String)
Dim de As DynamicEntity = CRMHelper.Deserialize(postImageEntityXml, GetType(DynamicEntity))
Select Case de.Name
Case "account"
'Traitement pour les sociétés
Case "contact"
'Traitement pour les contacts
End Select
End Sub
Depuis le passage en version 4, j'ai pu voir en passant en debug dans la méthode que le paramètre postImageEntityXml était null, ce qui peut s'avérer gênant pour dé-sérialiser l'entité et récupérer son type. La solution est donc de faire une classe par type d'entité et de distinguer les classes à appeler au niveau du fichier XML callout.config.xml de cette façon :
<?xml version="1.0" encoding="utf-8" ?>
<callout.config version="2.0">
<callout entity="account" event="PostCreate">
<subscription assembly="MyCallouts.dll" class="MyCallouts.AccountCallouts"></subscription>
</callout>
<callout entity="contact" event="PostCreate">
<subscription assembly="MyCallouts.dll" class="MyCallouts.ContactCallouts"></subscription>
</callout>
</callout.config>
4. Récupération de l'utilisateur connecté : Utilisation du WhoAmI
Ce dernier point sort un peu du contexte des Callouts, puisqu'il s'agit d'un JavaScript permettant de récupérer l'identifiant de l'utilisateur connecté.
En CRM v3, on aurait fait comme ça :
function getUserId()
{
try
{
var command = new RemoteCommand("SystemUser", "WhoAmI", "/MSCRMServices/");
var oResult = command.Execute();
if (oResult.Success)
return oResult.ReturnValue.UserId;
}
catch(e)
{
alert("Error while retrieving userid.");
}
return null;
}
Mais en CRM v4, comme l'objet RemoteCommand n'est plus accessible, on ferait plutôt comme ça (exemple inspiré des Walkthrough du SDK) :
function getUserId()
{
try
{
var serverUrl;
if(IsOnline())
serverUrl = "http://SRVCRM:5555";
else
serverUrl = "http://localhost:2525";
var xmlhttp = new ActiveXObject("Msxml2.XMLHTTP");
xmlhttp.open("POST", serverUrl + "/mscrmservices/2007/crmservice.asmx", false);
xmlhttp.setRequestHeader("Content-Type", "text/xml; charset=utf-8");
xmlhttp.setRequestHeader("SOAPAction", "http://schemas.microsoft.com/crm/2007/WebServices/Execute");
var soapBody = "<soap:Body>"+
"<Execute xmlns='http://schemas.microsoft.com/crm/2007/WebServices'>"+
"<Request xsi:type='WhoAmIRequest' />"+
"</Execute></soap:Body>";
var soapXml = "<soap:Envelope " +
"xmlns:soap='http://schemas.xmlsoap.org/soap/envelope/' "+
"xmlns:xsi='http://www.w3.org/2001/XMLSchema-instance' "+
"xmlns:xsd='http://www.w3.org/2001/XMLSchema'>";
soapXml += GenerateAuthenticationHeader();
soapXml += soapBody;
soapXml += "</soap:Envelope>";
xmlhttp.send(soapXml);
xmlDoc=new ActiveXObject("Microsoft.XMLDOM");
xmlDoc.async=false;
xmlDoc.loadXML(xmlhttp.responseXML.xml);
var userid = xmlDoc.getElementsByTagName("UserId")[0].childNodes[0].nodeValue;
return userid;
}
catch(e)
{
alert("Error while retrieving userid.");
}
return null;
}
NB : N'oubliez pas de distinguer le cas Online et Offline, puisqu'en v4 on peut se servir des WebService du client comme de ceux du serveur.
Enze
Member of Wygteam
www.wygwam.com
Ce post vous a plu ? Ajoutez le dans vos favoris pour ne pas perdre de temps à le retrouver le jour où vous en aurez besoin :