jeudi 14 octobre 2010

MOSS 2007 – Reverse proxy, site public et MOSS

Voici la configuration :

  • Une webapp étendue MOSS en WCM public sur le port 82 du IIS
  • Une url public externe www.monsite.fr
  • Une url interne intra.monsite.fr:82
  • une reverse proxy pour la publication
  • Les “acces mappings” de MOSS configurés

Jusqu’ici tout va bien.

La page à afficher contient une “Content Query Webpart”, en interne, pas de problème tout fonctionne.

En externe, ça se complique, la webpart ne fonctionne pas.

1ére solution:

La CQWP ne fonctionne pas avec un accès anonyme car le XSLT utilise la variable “SafeLinkUrl”, donc la modification de ItemStyle.xsl paraissait logique.
Voici un exemple de modifications clair : Content Query Web Part (CQWP) with Anonymous Access

Pour autant, ça ne fonctionne pas.

2ème solution

Une petite analyse de la response avec Fiddler et je m’aperçoit que la webpart à comme url : www.monsite.fr:82

Donc en ajoutant un “access mapping” sur la zone correspondante de type
Url interne : www.monsite.fr:82
Url public : www.monsite.fr

La CQWP fonctione.

Infos : Où se trouve les Access Mappings :

  • Ouvrez la console d’administration de Sharepoint
  • Opérations image

 

 

 

 

  • Rubrique Configuration globale
    image
  • Mappages des accès de substitution
  • image

Linq - Juste un exemple de concaténation de texte avec Aggregate

Pour mes problèmes de mémoire, voici un petit exemple de concaténation de texte avec Linq to Object et la méthode Aggregate :

var checkedValues = 
MaCheckBoxList.Items.Cast<ListItem>()
.Where(c => c.Selected)
.Select(c => c.Value);

//On vérifie qu'il y a bien des valeurs sélectionnées
String maString = MaCheckBoxList.Count() > 0
? checkedValue.Aggregate((c, next) => c + ", " + next)
: String.Empty;


lundi 20 septembre 2010

WPF– Application qui plante au démarrage – Erreur CLR20r3

Application en WPF, l’ensemble des tests sur le poste de développement fonctionne.

Parfait, donc je fais le setup et lance l’installation sur un XP SP3.
Ca fonctionne, tous les tests passent, donc prêt pour la production.
Et soudain, c’est le drame, l’appli plante dès le démarrage sous Seven.

Voici l’erreur :

Récipient d’erreurs , type 0
Nom d’événement : CLR20r3
Réponse : Non disponible
ID de CAB : 0
Signature du problème :
P1 : monitoringserver.exe
P2 : 1.0.0.0
P3 : 4c97529a
P4 : PresentationFramework
P5 : 3.0.0.0
P6 : 4a174fbc
P7 : 81d
P8 : 1f
P9 : System.InvalidOperationException
P10 :

Peut être un problème de références? Impossible à savoir.
Après plusieurs recherche sur le web, voici la solution qui m’a permis de repérer l’erreur :

Dans le App.xaml :

<Application 
x:Class="MonitoringServer.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x
="http://schemas.microsoft.com/winfx/2006/xaml"

StartupUri="MainWindow.xaml"
DispatcherUnhandledException="Application_DispatcherUnhandledException" >



Et le code-behind:



private void Application_DispatcherUnhandledException(object sender, System.Windows.Threading.DispatcherUnhandledExceptionEventArgs e)
{
Exception CurrentException = e.Exception;
while (CurrentException != null)
{
MessageBox.Show(CurrentException.Message);
CurrentException = CurrentException.InnerException;
}

}



Ainsi, dès le démarrage de l’application, des boites de dialogues s’affichent avec le détail de l’erreur.



Mon problème est corrigé et l’application fonctionne Sourire

Silverlight–Navigation–Rediriger vers une page

Ca peut paraitre idiot, mais j’ai mis un peu de temps à trouver et je n’est rien trouvé sur le web.

Mon problème, je suis dans une application de navigation Silverlight.
Je veux qu’une de mes vues soit celle par défaut, au démarrage.

Et bien, j’ai eu du mal à trouvé, pourtant, c’est tout bête :

private void ContentFrame_Navigated(object sender, NavigationEventArgs e)
{
if (String.IsNullOrEmpty(e.Uri.OriginalString))
{
this.ContentFrame.Navigate(new Uri("/MaView", UriKind.Relative));
}
}



C’est tout, il suffit juste de naviguer dans la frame Embarrassé

Silverlight–MVVM Light–Un très bon tuto

Faisant mes premiers (vrai) pas avec Silverlight 4, j’ai voulu faire les choses bien, donc MVVM.

Ce premier tuto m’a réellement aidé, il est vraiment très bien fait concernant MVVM : Article- M-V-VM avec Silverlight

L’auteur nous oriente vers MVVM Light pour travailler sur ce design pattern, et j’avoue avoir été conquis.

Il est simple d’utilisation et peu intrusif.
De plus le système de messagerie qu’il propose est vraiment bien.

Pour apprendre à l’utiliser, je conseille le tuto suivant (du même auteur) : Appliquer la pattern MVVM avec MVVM Light
Tout y est et réellement bien expliqué.

Donc un grand merci à Olivier Dahan, ses articles sont vraiment bien.

Voilà son blog :http://e-naxos.com/Blog/

jeudi 8 juillet 2010

[Sharepoint|WCF] Upload de fichiers dans une Library

Laisser la gestion de la publication d’images dans MOSS aux utilisateurs n’est pas toujours une bonne idée.
Ca ne les gène pas de publier une image de 3000 * 4000 même si celle-ci n’apparait dans la page de Publishing qu’en 300 * 400.
De plus, ils ont rarement les outils pour retailler les images. Donc récemment, j’ai fait une petite application WPF permettant le redimensionnement des images et la publication vers une bibliothèque d’images.
image
Dans ce “pense-bête”, je ne présenterai pas l’application WPF, les backgroundworker ou autres contrôles. Je me concentrerai juste sur l’upload de fichier dans MOSS avec WCF (afin de ne pas oublier ;p).

Service Reference

Pour la mise à jour ou l’upload de fichier dans une library, je vais utiliser le webservice Lists.asmx fournit par Sharepoint. Donc, premier point, on va ajouter un “service reference” à notre projet.
image
On appellera ce service : ServiceCartes (cartes mes images sont des cartes ;))
Pour l’authentification, je me base sur le protocole NTLM et voici donc la configuration du service.
Pour les tests et le traçage, je vous mets aussi la configuration de System.Diagnostic. Et merci le “Service Trace Viewer”, outil vraiment très pratique.

<system.serviceModel>
<bindings>
<basicHttpBinding>
<binding name="MossSoapBinding">
<readerQuotas
maxDepth="64"
maxStringContentLength="2147483647"
maxArrayLength="2147483647"
maxBytesPerRead="4096"
maxNameTableCharCount="16384"/>
<security mode="TransportCredentialOnly">
<transport clientCredentialType="Ntlm" proxyCredentialType="Ntlm"
realm="" />
<message clientCredentialType="UserName" algorithmSuite="Default"></message>
</security>
</binding>
</basicHttpBinding>
</bindings>
<behaviors>
<endpointBehaviors>
<behavior name="MossSoapBehavior">
<clientCredentials >
<windows allowedImpersonationLevel="Impersonation" allowNtlm="true" />
</clientCredentials>
<callbackDebug includeExceptionDetailInFaults="true"/> <!-- juste pour mon debuggage -->
<dataContractSerializer maxItemsInObjectGraph="2147483647" />
</behavior>
</endpointBehaviors>
</behaviors>
<client>
<endpoint address="http://monsite/Test/_vti_bin/lists.asmx"
binding="basicHttpBinding" bindingConfiguration="MossSoapBinding"  behaviorConfiguration="MossSoapBehavior"
contract="ServiceCartes.ListsSoap" name="ListsSoap" />
</client>
<diagnostics>
<messageLogging logEntireMessage="true" logMalformedMessages="true"
logMessagesAtTransportLevel="true" /> <!-- pour le traçage -->
</diagnostics>
</system.serviceModel>
<system.diagnostics>
<sources>
<source name="System.ServiceModel.MessageLogging" switchValue="Warning, ActivityTracing" >
<listeners>
<add name="ServiceModelTraceListener"/>
</listeners>
</source>
<source name="System.ServiceModel" switchValue="Verbose,ActivityTracing"
propagateActivity="true">
<listeners>
<add name="ServiceModelTraceListener"/>
</listeners>
</source>
<source name="System.Runtime.Serialization" switchValue="Verbose,ActivityTracing">
<listeners>
<add name="ServiceModelTraceListener"/>
</listeners>
</source>
</sources>
<sharedListeners>
<add initializeData="App_tracelog.svclog"
type="System.Diagnostics.XmlWriterTraceListener, System, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"
name="ServiceModelTraceListener" traceOutputOptions="Timestamp"/>
</sharedListeners>
</system.diagnostics>

Le service est configuré.

La publication des images

Elle se fait en 2 parties :
  • L’upload des fichiers dans la bibliothèque
  • La mise à jours des metadatas des fichiers
Voici comment je lance upload :

//CarteLibHelper est le coeur du programme
using (CartesLibHelper clh = new CartesLibHelper())
{
foreach (var file in di.GetFiles())
{
String listName = Properties.Settings.Default.CarteLib;
String fileUrl = String.Concat(
clh.WebUrl, //url du website
listName, //nom de la lib
"/",// separateur
file.Name);
//Les propriétés du fichier dans Sharepoint - dans mon cas, le titre uniquement
var properties = new List<KeyValuePair<String, object>>();
properties.Add(new KeyValuePair<String, object>("Title", file.Name));
//Objet CarteInfo qui contient les propriétés nécessaires à l'upload
CartesLibHelper.CarteInfo carteInfo = new CartesLibHelper.CarteInfo(fileUrl, file.FullName, listName, properties);
//lancement de l'upload
String xml = clh.Upload(carteInfo);
}
}


Le coeur de programme se trouve donc dans la classe CartesLibHelper que voici :

public class CartesLibHelper: IDisposable
{
private NetworkCredential _credidentials;
private ServiceCartes.ListsSoapClient _proxyList;
public String WebUrl { get; private set; }

public class CarteInfo
{
public String DestUrl;
public String SourcePath;
public String ListName;
public List<KeyValuePair<String, object>> Properties;

public CarteInfo(String destUrl, String sourcePath, String listName, List<KeyValuePair<String, object>> properties)
{
DestUrl = destUrl;
SourcePath = sourcePath;
ListName = listName;
Properties = properties;
}
}

public CartesLibHelper()
{
_credidentials = CredentialCache.DefaultNetworkCredentials; 
_proxyList = new ServiceCartes.ListsSoapClient("ListsSoap");
WebUrl = _proxyList.Endpoint.Address.ToString().Replace("_vti_bin/lists.asmx", String.Empty);
_proxyList.ClientCredentials.Windows.ClientCredential = _credidentials;       
}
#region IDisposable Members
public void Dispose()
{
if (_proxyList.State != CommunicationState.Closed) //s'il n'est pas fermé, on le ferme
{
_proxyList.Close();
}
}
#endregion

/// <summary>
/// Upload le fichier dans la library
/// </summary>
/// <param name="carte"></param>
/// <returns></returns>
public String Upload(CarteInfo carte) 
{
try
{
if (DAVHelper.UploadFile(carte.DestUrl, carte.SourcePath, _credidentials) != "success")
{
throw new Exception(String.Format(Properties.Resources.Erreur_Upload, carte.SourcePath, carte.DestUrl));
}
else
{
// Update les attributs du fichier
String updateRes = UpdateFileAttributes(carte.DestUrl, carte.ListName, carte.Properties);
return updateRes;
}
}
catch (TimeoutException timeout) 
{ _proxyList.Abort();throw timeout;} 
catch (CommunicationException commException) 
{ _proxyList.Abort();throw commException;}
}

/// <summary>
/// Mets à jour les attributs du fichier
/// </summary>
/// <param name="fileUrl"></param>
/// <param name="listName"></param>
/// <param name="properties"></param>
/// <returns></returns>
private  String UpdateFileAttributes(string fileUrl, string listName,List<KeyValuePair<string, object>> properties)
{
//La methode d'update en XML
XElement xMethod = new XElement("Method",
new XAttribute("ID", "1"),
new XAttribute("Cmd", "Update"),
new XElement("Field", new XAttribute("Name", "ID")),
new XElement("Field", new XAttribute("Name", "FileRef"), fileUrl)
);
//Ajoute toutes les propriiétés du fichier
for (int i = 0; i < properties.Count; i++)
{
xMethod.Add(
new XElement("Field",
new XAttribute("Name", properties[i].Key),
Convert.ToString(properties[i].Value)
)
);
} 
//Le batch nécessaire à la mise à jour
XElement batch = new XElement("Batch",
new XAttribute("OnError", "Continue"),
new XAttribute("PreCalc", "TRUE"),
new XAttribute("ListVersion", "0"),
xMethod
);
//update le fichier
return _proxyList.UpdateListItems(listName, batch).ToString();
}       
}


Pour cette classe helper, je me suis basé sur le post suivant : http://geekswithblogs.net/socasanta/archive/2007/07/09/113809.aspx.

elle se décompose en 4 parties :
  • La classe CarteInfo : C’est une classe basique contenant des informations nécessaires pour l’upload, comme : Le nom de la bibliothèque, le chemin source de la carte, son url de destination et ses propriétés dans la bibliothèque (ici son titre)
  • Le constructeur de l’helper, qui instancie le proxy et initialise les credentials
  • La méthode privée UpdateFileAttributes qui mets à jour toutes les infos du fichier dans la bibliothèque via la méthode du service “UpdateListItems”.
    La construction du XML est faite avec XLinq.
  • La méthode public Upload, celle-ci lance l’upload du fichier dans la bibliothèque et en cas de réussite lance la méthode UpdateFileAttributes pour la mise à jour des données.
    La classe DAVHelper provient du post suivant :  http://blogs.msdn.com/rohitpuri/archive/2007/04/10/upload-download-file-to-from-wss-document-library-using-dav.aspx
    J’y ai juste apporté quelques modifications afin d’utiliser le protocole NTLM et le passage des credentials.
    Voici le code de la classe modifiée :
using System;
using System.Collections.Generic;
using System.Text;
using System.IO;
using System.Web;
using System.Net;
using System.Data;
using System.Diagnostics;

namespace DAVWrapper
{
public static class DAVHelper
{
public static string UploadFile(string destUrl, string sourcePath, NetworkCredential creds)
{
try
{
Uri destUri = new Uri(destUrl);
FileStream inStream = File.OpenRead(sourcePath);

CredentialCache cache = new CredentialCache();
Uri prefix = new Uri(destUrl.Replace(destUri.AbsolutePath, String.Empty));
cache.Add(prefix, "NTLM", creds);
CookieContainer cookiesContainer = new CookieContainer();
HttpWebRequest req = (HttpWebRequest )WebRequest.Create(destUri);
req.CookieContainer = cookiesContainer;
req.AllowAutoRedirect = false;
req.PreAuthenticate = true;
req.UseDefaultCredentials = false;
//on remet les credentials
req.Credentials = cache;
//Pour l'auth Ntlm, on met la propriété suivante à true, ainsi la connexion ne sera pas fermée
req.UnsafeAuthenticatedConnectionSharing = true;
req.AllowWriteStreamBuffering = true;

req.UserAgent = "Upload Carte";
req.Method = "PUT";
req.Headers.Add("Overwrite", "F");
req.Timeout = System.Threading.Timeout.Infinite;
req.Credentials = cache;
Stream outStream = req.GetRequestStream();
string status = CopyStream(inStream, outStream);

if (status == "success")
{
outStream.Close();
HttpWebResponse ores = (HttpWebResponse)req.GetResponse();
return "success";
}else { return status;}
}
catch (WebException we)
{ return we.Message;}
catch (System.Exception ee)
{return ee.Message;}
}        
private static string CopyStream(Stream inStream, Stream outStream)
{
try
{
byte[] buffer = new byte[1024];
for (; ; )
{
int numBytesRead = inStream.Read(buffer, 0, buffer.Length);
if (numBytesRead <= 0)
break;
outStream.Write(buffer, 0, numBytesRead);
}
inStream.Close();
return "success";
}
catch (System.Exception ee)
{
return ee.Message;
}
}

}
}

Et c’est terminée, grâce au 2 posts cités précédemment et quelques modifications, vous pouvez voir qu’il est assez simple d’uploader des fichiers dans une bibliothèque Sharepoint et même si vous nétes pas usr le même Domaine grâce au credentials et au protocole NTLM.
Voila le résultat d’upload d’images retaillées dans une ferme hors domaine :
image
En espérant ce tuto suffisamment clair.

mercredi 7 juillet 2010

[SharePoint] Optimisations des performances d’un site Intranet de type Publishing - Fabrice Romelard

Un premier article très intéressant sur l’optimisation du Publishing Sharepoint 2007.

J’attends la suite avec impatience, rien que l’astuce du viewstate sur les menus fait gagner un poids considérable (je l’ai juste testé en pré-prod, mais le résultat est concluant).

SharePoint : Optimisations des performances d’un site Intranet de type Publishing