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