Mots clés
Calendrier
<<  janvier 2012  >>
lumamejevesadi
2627282930311
2345678
9101112131415
16171819202122
23242526272829
303112345
Altima
 
Gold Partner
anthony, 25 juillet 2011 09:46
Pour compléter l'article de christophe sur la lecture du champ MarshalledData du Basket (http://commerceserver.altima.fr/post/Voir-le-contenu-du-MarshalledData.aspx), voici comment lire le contenu des champs MarshalledData pour les PurchaseOrders et les LineItems, via une application console.
 
Commerce Server utilise le champ MarshalledData pour ajouter de nouvelles propriétés aux PurchaseOrders et LineItems.
Ces propriétés sont sauvegardées par paires de clé / valeur.
 
Si vous utilisez Reflector pour décompiler l'assembly Microsoft.CommerceServer.Runtime (%COMMERCE_SERVER_ROOT%\Assemblies\Microsoft.CommerceServer.Runtime.dll), vous pouvez constater qu'un objet de type WeaklyTypedPropertyDictionnary est utilisé pour stocker ces nouvelles propriétés.
   
 
Ce dictionnaire d'objets WeaklyTypedProperty, hérite de la classe Hashtable.
 
A partir de cette constatation, on peut donc en déduire que le champ MarshalledData sauvegardé en base de données n'est qu'un Hashtable séréalisé.
 
Pour récupérer les clés / valeurs sauvegardées dans un PurchaseOrder, voici quelques lignes de codes :
 

Hashtable marchal = null; using (SqlConnection cnx = new SqlConnection(

ConfigurationManager.ConnectionStrings["Transactions"].ConnectionString)) { using (SqlCommand cmd = new SqlCommand()) { cmd.Connection = cnx; cmd.CommandText = @"SELECT TOP 1 [MarshalledData]

FROM [dbo].[PurchaseOrders]"; cnx.Open(); using (SqlDataReader reader = cmd.ExecuteReader()) { while (reader.Read()) { SqlBinary binary = (reader.GetSqlBinary(0)); using (MemoryStream m = new MemoryStream(binary.Value)) { BinaryFormatter formatter = new BinaryFormatter(); marchal = (Hashtable)formatter.Deserialize(m); } } } } } if (marchal == null) { Console.WriteLine("No data found !"); } else { foreach (var key in marchal.Keys) { Console.WriteLine("[{0}] : {1}", key, marchal[key]); } }

 

La ConnectionString utilisée est la chaîne de connexion à la base "Transactions".

Ce qui nous donne le résultat suivant :

   
 
La lecture d'un champ MarshalledData provenant d'un LineItem est identique. Il n'y a que la requête SQL qui varie.
 

Actuellement noté 5.0 par 1 personne(s)

  • Currently 5/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5

Si lors d'une extension du PurchaseOrder vous obtenez l'erreur suivante :   

 
Cette erreur vient du fait qu'un de vos champs possède une valeur par défaut dans la base de données SQL.
 
Pour trouver plus facilement le champ en question, executez la procédure suivante dans la base "transactions" :
 
exec SP_HELPCONSTRAINT @objname=N'PurchaseOrders',@nomsg=N'nomsg'
 
Cette procèdure vous listera les différentes contraintes d'intégrités de la table "PurchaseOrders".
 
Exemple de retour de la procédure stockée :
 
 
 
Une fois que vous avez trouvé la valeur par défaut, supprimez la et relancez votre site.

Actuellement noté 5.0 par 1 personne(s)

  • Currently 5/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5
Mathieu, 13 juillet 2011 08:38

Si lors de vos développements, vous êtes amenés à effectuer une suppression de variants massive, voici les étapes à suivre

- 1ère étape : supprimer l'inventaire  associé aux variants

En effet, lors de mes tests, je me suis aperçu qu'à l'appel de la méthode DeleteItems de BaseCatalog, celle-ci ne supprime pas automatiquement l'inventaire associé aux variants et de ce fait, nous sommes dans l'impossibilité de récréer un variant portant le même nom dans CommerceServer.

CommerceServer nous affiche alors une erreur nous indiquant que l'élément a été modifié et nous empêche de le recréer

 -  2ème étape : supprimer la liste des variants

Voici la solution complète :

// construction de l'expression SQL permettant de retrouver la liste complète des variants à partir d'une collection d'objets String 'ChildObjects'

string searchClause, searchInventoryClause;
StringBuilder searchClauseBuilder = new StringBuilder();
ChildIdObjects.ForEach(
    delegate(string childId)
    {
        searchClauseBuilder.AppendFormat("VariantId='{0}' OR ", childId);
    }
);
searchClause = searchClauseBuilder.ToString().TrimEnd(new char[]{' ', 'R', 'O'});
searchInventoryClause = searchClause.Replace("VariantId", InventorySkusDataSetSchema.VariantId); // SkuVariantId

// suppression de l'inventaire
InventoryCatalog inventoryCatalog = CommerceServerContext.CatalogContext.InventoryContext.GetAssociatedInventoryCatalog(CatalogName);
if (inventoryCatalog.GetSkus(searchInventoryClause).Count > 0)
{
    inventoryCatalog.DeleteSkus(searchInventoryClause);
}

// suppression des variants
baseCatalog.DeleteItems(searchClause, CatalogClassTypes.ProductVariantClass);                           
baseCatalog.Save(); 

 

 

 

 

Soyez le premier à noter ce billet

  • Currently 0/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5

Voici un exemple de code qui permet de récupérer ou d'envoyer un fichier via le protocole HTTP GET ou POST. Ce code réalise la même chose qu'un téléchargement ou un envoi de fichier via un formulaire POST classique, à la différence ici qu'on réalise ce traitement dans une application console. Ce code est un exemple, certains paramètres peuvent varier en fonction du type de serveur.

Pour récupérer un fichier en HTTP GET :

/// <summary>
/// Récupère un fichier via une url
/// </summary>
/// <param name="url">Url à appeler</param>
public void DownloadHttpFile(string url)
{
   // Initialisation de la requête http avec identification
   WebRequest webRequest = WebRequest.Create(url);
   NetworkCredential networkCredential = new NetworkCredential("User", "Pass");
   webRequest.Credentials = networkCredential;

   // Récupération de la réponse
   WebResponse webResponse = webRequest.GetResponse();
   Stream stream = webResponse.GetResponseStream();
   Encoding encoding = System.Text.Encoding.GetEncoding("utf-8");
   StreamReader readStream = new StreamReader(stream, encoding);
   Char[] read = new Char[256];

   // Ecriture de la réponse http dans un fichier
   StreamWriter streamWriter = File.CreateText(@"c:\downloadFile.csv");
   while (count > 0)
   {
       streamWriter.Write(new String(read, 0, count));
       count = readStream.Read(read, 0, 256);
   }
   streamWriter.Close();

   // Libération des ressources
   readStream.Close();
   webResponse.Close();
}


Pour envoyer un fichier en HTTP POST :

/// <summary>
/// Envoi un fichier via une url
/// </summary>
/// <param name="url">Url à appeler</param>
/// <param name="pathFile">Chemin du fichier à envoyer</param>
/// <param name=" nameFormPost ">Nom du formulaire post</param>
public void UploadHttpFile(string url, string pathFile, string nameFormPost)
{
   // Initialisation de la requête http avec identification
   HttpWebRequest webRequest = (HttpWebRequest)WebRequest.Create(url);
   NetworkCredential networkCredential = new NetworkCredential("User", "Pass");
   webRequest.Credentials = networkCredential;

   // Ecriture du fichier à envoyer en mémoire
   Stream memStream = new System.IO.MemoryStream();

   // Le Boundary sert à délimiter les champs dans la requête
   string boundary = String.Format("----------------------------{0}", DateTime.Now.Ticks.ToString("x"));
   byte[] boundaryBytes = System.Text.Encoding.UTF8.GetBytes("\r\n--" + boundary + "\r\n");
   memStream.Write(boundaryBytes, 0, boundaryBytes.Length);

   // Entête http pour l'envoi de fichier
   StringBuilder sbHeader = new StringBuilder();
   sbHeader.AppendLine(String.Format("Content-Disposition: form-data; name=\"{0}\"", nameFormPost));
   sbHeader.AppendLine("; filename=\"" + new FileInfo(pathFile).Name + "\"");
   sbHeader.AppendLine("Content-Type: application/octet-stream" + "\r\n");
   byte[] headerBytes = System.Text.Encoding.UTF8.GetBytes(sbHeader.ToString());
   memStream.Write(headerBytes, 0, headerBytes.Length);

   // Gestion du fichier à transmettre
   FileStream fileStream = new FileStream(pathFile, FileMode.Open, FileAccess.Read);
   byte[] buffer = new byte[4096];
   int bytesRead = 0;
   while ((bytesRead = fileStream.Read(buffer, 0, buffer.Length)) != 0)
   {
        memStream.Write(buffer, 0, bytesRead);
   }
   fileStream.Close();

   // On remet le Boundary pour clôturer le champ
   memStream.Write(boundaryBytes, 0, boundaryBytes.Length);

   // Paramètrage de la requête
   webRequest.Method = "POST";
   webRequest.KeepAlive = true;
   webRequest.ContentType = String.Format("multipart/form-data; boundary={0}", boundary);
   webRequest.ServicePoint.Expect100Continue = false;
   webRequest.ContentLength = memStream.Length;

   // Copie du flux post dans un buffer temporaire
   memStream.Position = 0;
   byte[] tempBuffer = new byte[memStream.Length];
   memStream.Read(tempBuffer, 0, tempBuffer.Length);
   memStream.Close();

   // Envoie du flux post
   Stream requestStream = webRequest.GetRequestStream();
   requestStream.Write(tempBuffer, 0, tempBuffer.Length);
   requestStream.Close();

   // Récupération de la réponse
   WebResponse webResponse = webRequest.GetResponse();
   Stream stream = webResponse.GetResponseStream();
   Encoding encoding = System.Text.Encoding.GetEncoding("utf-8");
   StreamReader readStream = new StreamReader(stream, encoding);

   // Ecriture de la réponse http dans un fichier
   StreamWriter streamWriter = File.CreateText(@"c:\downloadResponseFile.csv");
   Char[] read = new Char[256];
   int count = readStream.Read(read, 0, 256);

   while (count > 0)
   {
       streamWriter.Write(new String(read, 0, count));
       count = readStream.Read(read, 0, 256);
   }

   // Libération des ressources
   readStream.Close();
   streamWriter.Close();
   webResponse.Close();
}
 

 

Soyez le premier à noter ce billet

  • Currently 0/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5

Nous avions eu besoin de rafrâichir la catalogue CommerceServer de la PREPROD avec celui de la PROD.

Pour cela, nous avions exporté le catalogue de la PROD, ce qui nous a permis de générer un flux XML d'une taille de 270Mo.

 Cependant, lors de l'import de celui-ci dans le catalogue en PREPROD, CommerceServer nous a affiché un message d'erreur suivant :

The import failed because the import file is larger than the maximum size allowed on your server.  Contact your system administrator.

Pour résoudre ce problème, il suffit d'augmenter la valeur "maxUploadFileSize" dans le Web.config du Webservice Catalogue sur votre site :

Voici un exemple :

<catalogWebService siteName="NOM_SITE" authorizationPolicyPath="CatalogAuthorizationStore_Apa.xml" debugLevel="Production" maxChunkSize="1024" maxUploadFileSize="304800" timeOutHours="24" enableInventorySystem="true" disableAuthorization="false" maxSearchResults="500">
            <cache enable="false" schemaTimeout="5" itemInformationCacheTimeout="5" itemHierarchyCacheTimeout="5" itemRelationshipsCacheTimeout="5" itemAssociationsCacheTimeout="5" catalogCollectionCacheTimeout="5"/>
</catalogWebService>

 En effet, par défaut cette valeur est définie par défaut à 200MB.

Actuellement noté 5.0 par 1 personne(s)

  • Currently 5/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5

Il arrive parfois lorsque l'on souhaite compiler un projet, d'obtenir le message suivant "Références de fichier circulaires ne sont pas autorisés".
Ce message fini par disparaître en relançant plusieurs fois la compilation.

Pour mon cas, j'avais à la racine de mon application des masters pages, ces pages utilisent des contrôles placés dans le dossier "Controls".
Le problème dans cette configuration, c'est que les éléments à la racine du projet vont être compilés en premiers et ensuite tous les dossiers par ordre alphabétique.
Donc tant que tous les contrôles du dossier "Controls" ne sont pas compilés, je vais obtenir l'erreur des références circulaires.

Pour résoudre ce problème, j'ai créé un dossier "Masters" dans lequel j'ai placé toutes les masters pages du site, de ce fait lors de la compilation, les éléments à la racine puis ensuite l'ensemble des dossiers sont générés dans l'ordre alphabétique, une fois arrivé sur le dossier "Masters", l'ensemble des contrôles seront donc compilés, je n'ai donc plus ce message d'erreur dès la première compilation Smile.

Pour plus de précision vous pouvez voir un article sur ce sujet ici :  http://support.microsoft.com/kb/919284/fr.

Autres précisions :

Dans la structure des dossiers du projet, il faut bien séparer les "Contrôles" (ascx), des "Pages" (aspx).
Si vous créé un dossier "Controls", assurez vous d'y mettre uniquement des contrôles ascx.

J'ai rencontré le problème de référence circulaires dans un projet web, j'ai constaté qu'il y avait dans un dossiers "Controls" quelques pages aspx. En déplaçant ces pages aspx vers des dossiers plus appropriés, toutes les erreurs de références circulaires ont disparues. A noter que dans mon cas les pages aspx étaient toutes simples sans aucune référence à des user controls et sans master page...

 

 

Actuellement noté 3.7 par 3 personne(s)

  • Currently 3,666667/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5

Quand on souhaite utiliser le client riche "Commerce Server Customer and Orders Manager", avec toutes les url des web services en http, on rencontre une erreur lors de la configuration qui nous indique qu'on doit utiliser du https pour les web services Orders et Profile.

Deux solutions possibles pour régler ce poblème :

1 - Configurer les deux web service en https (peut être long si vous n'avez pas la main sur les serveurs)
2 - Mettre à jour le fichier de configuration sur la machine où se trouve le client riche.

La deuxième solution a l'avantage d'être rapide et vous permet d'utiliser des url en http.

Voici la démarche à réaliser pour autoriser l'utilisation du http pour Orders et Profile :

- Aller dans le dossier où est installé le client riche "CustomerAndOrdersManager.exe" (exemple "C:\Program Files (x86)\Microsoft Commerce Server 2007\Business User Applications").
- Editer le fichier "CustomerAndOrdersManager.exe.config".
- Quasiment tout en bas du fichier vers la ligne 88 mettre la valeur "True" dans le setting "AllowHTTP".
- Réaliser ensuite la configuration avec les url en http, le client riche accepte désormais la configuration et se lance.

Soyez le premier à noter ce billet

  • Currently 0/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5

Pour s'authentifier avec un login à la place d'un email, voici la démarche à suivre (exemple avec un nouveau champ "login") :

Tout d'abords créer un champ Login (voir post sur la création d'un nouveau champ dans le profile de Commerce Server), ou alors utiliser un champ existant, il faut dans ce cas s'assurer que celui-ci est bien unique pour chaque utilisateur dans la configuration de commerce server (voir le même post pour plus de détails).

Mise à jour du web.config :

Mise à jour de de l'attribut userIdKey="GeneralInfo.login" (GeneralInfo.email_address remplacé par GeneralInfo.login)

<profiles>
<userProfile profileDefinition="UserObject" userIdProperty="GeneralInfo.user_id" organizationIdProperty="AccountInfo.org_id" catalogSetIdProperty="AccountInfo.user_catalog_set" userIdSource="ASP.NET" userIdKey="GeneralInfo.login" userIdTarget="GeneralInfo.user_id"/>
<organizationProfile profileDefintion="Organization" organizationIdProperty="GeneralInfo.org_id" catalogSetIdProperty="GeneralInfo.org_catalog_set"/>
</profiles>

Ajout de l'attribut logonNameProperty="GeneralInfo.login" dans le provider "UpmProvider"

<membership defaultProvider="UpmProvider">
<providers>
<add name="UpmProvider" applicationName="/" enablePasswordReset="true" enablePasswordRetrieval="true" maxInvalidPasswordAttempts="10" minRequiredPasswordLength="6" minRequiredNonalphanumericCharacters="0" passwordAttemptWindow="10" passwordStrengthRegularExpression="" passwordFormat="Clear" requiresQuestionAndAnswer="false" requiresUniqueEmail="true" logonNameProperty="GeneralInfo.login" type="Microsoft.CommerceServer.Runtime.Profiles.UpmMembershipProvider"/>
</providers>
</membership>

L'authentification se fait maintenant avec le champ "Login".

Il reste à vérifier les appels des méthodes Commerce Server pour récupérer un Profile client, il ne faut plus se baser sur le champ mail, mais sur le champ login, le login étant désormais l'identifiant du compte client.

Soyez le premier à noter ce billet

  • Currently 0/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5

Voici un exemple qui permet d'utiliser un nouveau champ "Login", en étendant le profile client de Commerce Server.

1 - Manipulation côté BDD :

Dans la BDD "xxx_profiles" table "UserObject", ajouter la colonne "Login" champ de type texte, avec la longueur souhaitée.

2 - Manipulation côté Serveur :

a) Sur le serveur où est installer Commerce Server, lancer le programme "Commerce Server Manager".
b) Dérouler le menu "Commerce Server Manager" > "Global Resources" > "Profiles" > "Profile Catalog" > "Data Sources" > "ProfileService_SQLSource" > "Data Objects".
c) Sélectionner "User Object".
d) Clic Droit sur "User Object", "New Data Member", indiquer un nom, sélectionner dans la liste le champ "Login" que vous avez crée précédemment, indiquer le type "String", Cocher les cases "Required" et "Indexed"
-> Ceci permet d'utiliser le nouveau champ

e) Ensuite remonter dans l'arborescence sur "Profile Catalog", aller dans "Profile Definitions", Sélectionner "User Object"
f) Cliquer sur "General Information" puis sélectionner un champ (la nouvelle propriété sera créé juste en dessous de la valeur sélectionnée)
g) Tout en bas cliquer sur "Add", cocher "Add a new property" puis sur "OK"
h) Dans la zone de droite indiquer un nom (sans espaces et caractères spéciaux),  un nom d'affichage, le type "String"
i) Cliquer sur "Advanced Attributes", cocher Active, Exported et Searchable, dans le champ "Map to Data" sélectionner le champ "Login" (le nom que vous avez donné à l'étape 1 - d).
j) Dans Key type sélectionner "Unique Key"
k) Cliquer sur "Custom attributes", éditer ou créé les champs existant comme par exemple :
     Name "DWMemberName" Value "Login"
     Name "MaxLength" Value "128" (correspondant à la longueur maximal autorisé en BDD)
     Name "MinLength" Value "1" (la longueur minimal souhaitée)
l) Sélectionner un autre item que "User Object", faîte ok sur la fenêtre qui apparaît pour enregistrer les changements.

-> Cette étape permet de mapper la propriété login avec l'objet profile de Commerce Server.


-> Désormais vous avez maintenant un nouveau champ dans l'objet profile de commerce server, pour que cette modification soit effective, veuillez redémarrer les pools d'application de Commerce Server, voir aussi faire un iisreset.
La propriété sera accessible sous la forme "monProfile["Login"].Value"

 

Soyez le premier à noter ce billet

  • Currently 0/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5

Suite à mes deux posts sur Commerce Server et Power Pivot (Créer des rapports simples et efficaces pour Commerce Server grâce à PowerPivot (aka Gemini) et Comprendre et analyser vos données Commerce Server grâce à Power Pivot), j’ai reçu quelques questions concernant la récupération des données provenant de Commerce Server.

En effet, il n’est pas rare (j’ose difficilement concevoir la chose autrement) que le(s) serveur(s) e-Commerce soi(en)t hébergé(s) sur un data center déconnecté de l’environnement de l’entreprise (du webmaster, du consultant,…), rendant donc impossible une connexion directe à la base de données.

Que faire donc pour récupérer les données et les exploiter?

  • Réplication SQL Server entre l’environnement de production et votre environnement local
    • Avantages:
      • Nombreuses possibilités de synchronisations (temps réels ou différés)
      • Technologie éprouvée et robuste
    • Inconvénients:
      • Nécessite d’avoir un SGBD (oui tous les consultants n’ont pas un SGBD Smile)
      • Exposer sa base de données, qui normalement doit être derrière la DMZ (même si vous filtrez les IPs ce n’est quand même pas génial en terme de sécurité ou passer par une LS)
  • Exporter les données dans un format texte pour le réintégrer dans l’environnement de base de données local
    • Avantages:
      • Possibilité d’importer de nombreux types de fichiers textes formatés dans Excel
      • Ouverture/Import des fichiers textes comme sous Excel
    • Inconvénients:
      • L’import de fichiers dans Excel n’est pas toujours simple à appréhender par un novice
      • Gestion de plusieurs fichiers pouvant être complexes à maintenir et gérer
  • Utilisez la capacité de PowerPivot à consommer un flux de données type RSS (voir la MSDN à ce sujet)
    • Avantages:
    • Inconvénients:
      • Technologie encore récente
      • Nécessite d’utiliser un protocol https ou une LS pour garantir une bonne sécurisation du transfert de données.

Ces 3 solutions ont chacune leurs avantages et inconvénients et le choix dépend fortement de votre architecture et vos besoins (ie pas de règles figées dans le marbre!). Pour ma part, j’ai décidé de vous présenter la dernière solution: à savoir un flux de données de type RSS.

Pour exposer vos données en asp.net, l’idéal est d’utiliser un service WCF qui se chargera de rendre vos données au format désiré (ici l’ATOM). Bien que l’exposition de vos données au travers d’un service WCF n’est pas nouvelle (il en existe de nombreuses façons pour le réaliser), l’exposer nativement en mode REST et au format ATOM (ou json) pour la représentation des données via une URI (autrement dit, l’utilisation du protocol OData) l’est beaucoup moins!

Pour réaliser cela, nous avons besoin d’avoir une application web dans lequel nous allons ajouter une item de type “ADO.Net Entity Data Model” connecté à votre base de données SQL Server.

image

Dans le cas où vos rapports se basent sur les commandes, vous devez choisir la base de données <site commerce server>_Transactions.

 image

Ajoutez ensuite une item de type WCF Data Service

image

Dans le service créé, ajoutez à la méthode InitializeService le code suivant:

config.SetEntitySetAccessRule("DiscountsApplied", EntitySetRights.AllRead);config.SetEntitySetAccessRule("LineItems", EntitySetRights.AllRead);config.SetEntitySetAccessRule("OrderAddresses", EntitySetRights.AllRead);config.SetEntitySetAccessRule("OrderForms", EntitySetRights.AllRead);config.SetEntitySetAccessRule("PurchaseOrderPayments", EntitySetRights.AllRead);config.SetEntitySetAccessRule("PurchaseOrders", EntitySetRights.AllRead);config.SetEntitySetAccessRule("Shipments", EntitySetRights.AllRead);config.SetEntitySetAccessRule("ShippingDiscounts", EntitySetRights.AllRead);

Ceci vous permettant de donner un accès en lecture seule à vos entitées et donc par extension aux tables de la base de données.

Affichez votre service dans votre navigateur et vous obtenez automatiquement un flux XML Atom listant les différentes entités auxquelles vous avez accès.

image

Ajoutez par exemple LineItems à la fin de votre url (ex: http://localhost//CommerceServerOrdersDataService.svc/LineItems)

Votre navigateur affiche alors, une page similaire à n’importe quel flux RSS:

image

Pour afficher le flux XML à la place de l’interface RSS, allez dans les options internet > Contenus > Paramètres des flux RSS et décochez la case d’affichage RSS

image

image 

Et voilà la partie développement est terminée ! Vous pouvez consommer votre flux dans Power Pivot dès à présent:

image image image

image

Je vous montrerez dans un prochain post comment inclure les flux dans la même stratégie de sécurité que les web services Commerce Server.

Retrouvez plus d’informations sur:

Soyez le premier à noter ce billet

  • Currently 0/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5