mercredi 8 décembre 2010

Silverlight – Astuces – Projet de tests unitaires

Comme pour chacun de mes projets, je fais des tests unitaires.
Il en est de même pour les applications Silverlight.

C’est comme ça que j’ai découvert qu’il existait un projet de type Tests unitaires pour Silverlight.

Par contre, utilisant essentiellement Silverlight avec MOSS, je fais tout le temps appel aux web services de celui-ci.
Il faut donc que je teste l’appel aux services de façon asynchrone.

Donc, je vais rapidement survoler la solution utilisée.

Pour infos, mes applis silverlight utilise MVVM donc les appels sont effectués via un ServiceLocator dans mon Model

Pré-requis : Silverlight toolkit

Création du Projet :

creer_projet_test

On ajoute les références au projet Silverlight à tester

On compléte la clase Test générée:

[TestClass]
public class Tests : Microsoft.Silverlight.Testing.SilverlightTest //pour l'asynchrone
{
   private const String _mossUrlRoot = "http://monMoss";
   private Boolean _httpResult;
   public Tests() : base()
   {
       //http://msdn.microsoft.com/en-us/library/system.net.browser.webrequestcreator.clienthttp(VS.95).aspx
       //WebRequestCreator.ClientHttp : car j'envoi du XML dans mes requests et j'utilise les ws de moss 
       _httpResult = HttpWebRequest.RegisterPrefix(_mossUrlRoot, WebRequestCreator.ClientHttp);
       var request = WebRequest.Create(new Uri(_mossUrlRoot)) as HttpWebRequest;
       request.UseDefaultCredentials = true;
    }
[Asynchronous] //car appel asynchrone
[TestMethod]
 public void MVVM_Model_Get_Items()
{
    if (_httpResult)
    {
      //Initialise mes services
      Model.ServiceLocator.InitializeServicesTest(initParams, _mossUrlRoot);
      //Lance la requete 
      Model.ServiceLocator.MesServices.LaunchRequest();
      
      EnqueueConditional(
        () => Model.ServiceLocator.MesServices.Result != null);
      EnqueueCallback(
        () => Assert.IsTrue(Model.ServiceLocator.MesServices.Result.Count > 0));
      EnqueueTestComplete(); 
    //fin du test, mais le Assert est en attente du resultat
     }
}
}


On lance le teste, via un “View in Browser” sur le projet, voilà le résultat :



tests_unitaires



Le méthode de test bien qu’asynchrone est passée sans souci.



Si nous n’avions pas utilisé les “EnqueueCallBack” et “EnqueueTestComplete”, le test aurait retourné une erreur.



Pour réussir ce test, je me suis appuyé sur cet article : http://code.msdn.microsoft.com/silverlightut

Silverlight – Astuce – Cache du navigateur et mise à jour XAP

Mon contexte :

Une webpart dans mon MOSS héberge une application Silverlight.
A chaque mise à jour, je redéploye ma solution via une wsp.
Pour autant, même si mon XAP à bien changé, rien ne change sur le poste client car le XAP est stocké dans le cache du navigateur.

Le problème :

Il faut que je puisse mettre à jour le XAP sur tous les postes à chaque mise à jour sans perdre l’intérêt du cache du navigateur (et sans demander aux utilisateurs de vider leur cache).

La solution :

Elle est finalement simple.
Dans ma webpart, je gère la version de mon XAP en passant juste un paramètre à l’URL du XAP.

Par exemple :
String _xapPath = "/_layouts/RIA/XAP/MonXAP.xap?version=1.0";

Ici le paramètre s’appelle “version” (mais peu importe le nom) et la version est 1.0

Et je l’insère dans le code html qui appelle le XAP

Ainsi, à chaque fois que je change la version du XAP dans ma webpart et que je redéploye ma solution, l’application est re-téléchargée sur le poste client.

Tant que la version ne change pas, le client utilise la version de son cache.

Astuce très simple mais qui me permet de choisir quand l’application doit être remise à jour.

mardi 7 décembre 2010

Silverlight – Carte vectorielle dynamique grâce à ViewerSVG

Contexte :

Sur notre MOSS, nous avons des actualités classées par pays et continents.
Pour rendre l’affichage plus ludique, nous avons décidé d’afficher les news en fonction des choix d’une carte.

Voici les captures du résultat final :

monde

On clique sur un continent, et on a les news qui s’affichent sur la droite.
Mais pour l’Europe, un zoom est nécessaire afin de filtrer par pays

europe

Donc, chaque continent et chaque pays de l’Europe est une zone sélectionnable.

La solution :

Pour pouvoir faire ces cartes, je suis parti d’un format vectoriel de la carte des continents et de la carte des pays de l’Europe.
Ensuite, il a fallu transformer le tout en XAML.

Pour transformer chaque continent/pays en “Path” Silverlight, j’ai utilisé un logiciel très intéressant : ViewerSVG

Il n’est pas très cher et permet de transformer tout objet SVG en XAML. Parfait pour utiliser des images créées par un designer.

mercredi 17 novembre 2010

Sharepoint/Snippet– Obtenir la liste des valeurs d’un champ de type Choice

Le contexte :
J’ai déployé un Field dans ma collection de site.
Le type de ce Field est Choice déployé via Feature dont voici le code :
<Field ID="{4AF5A3A9-AECF-4237-8AD5-D74315AB2A42}"
Type="Choice" DisplayName="Mon champ"
Required="FALSE" Format="Dropdown"
FillInChoice="FALSE"
Group="Mon groupe de test"
SourceID="http://schemas.microsoft.com/sharepoint/v3/fields"
StaticName="mon_champ" Name="mon_champ">
<CHOICES>
<CHOICE>Choix 1</CHOICE>
<CHOICE>Choix 2</CHOICE>
<CHOICE>Choix 3</CHOICE>
<CHOICE>Choix 4</CHOICE>
</CHOICES>
</Field>
Je veux récupérer les valeurs de cette liste dans une webpart par exemple :
SPField spField = 
SPContext.Current.Fields
.GetFieldByInternalName("mon_champ");
if (spField != null && spField is SPFieldChoice)
{
SPFieldChoice spfc = spField as SPFieldChoice;
foreach (var item in spfc.Choices.Cast<String>().ToArray())
{
this.Controls.Add(new LiteralControl(item + "<BR/>"));
}
On obtiendra donc :
Choix 1
Choix 2
Choix 3
Choix 4

mardi 19 octobre 2010

Sharepoint – Création d’une liste avec un content type spécifique (par code)

Voici le besoin :
Je dois créer une bibliothèque d’images spécifique, donc avec un type de contenu personnalisé.
La solution que j’ai choisi, c’est de faire 2 features:
  • Une pour le déploiement de tous les content types et des webparts avec un scope Site
  • Une autre pour la création de ma bibliothèque dans un sous-site choisi donc scope web

Je ne vais pas présenter le détail de la première feature.
Ce qu’il faut savoir, c’est qu’elle déploie des types de contenus.
Je ne montrerai pas non plus comment créer des solutions wsp pour le déploiement, je pars du principe que tout déploiement dans SP passe par ce biais.
Ma seconde feature, le fichier Feature.xml :
<?xml version="1.0" encoding="utf-8"?>
<Feature  Id="4d60eeca-55a8-4f6a-a49a-b036f616c1cc"
Title="Mon titre de feature"
Description="La description"
Version="1.0.0.0"
Hidden="FALSE"
Scope="Web"
DefaultResourceFile="core"
ReceiverAssembly="MonAssembly, Version=1.0.0.0, Culture=neutral, PublicKeyToken=0a51357f54252cab"
ReceiverClass="MonAssembly.MonReceiver"
xmlns="http://schemas.microsoft.com/sharepoint/">
<ActivationDependencies>
<ActivationDependency FeatureId="510155f1-4eaa-4b63-845f-03718f48fdbf"/>
</ActivationDependencies>
<ElementManifests>  
</ElementManifests>
</Feature>


On remarque 2 choses dans le Feature.xml:

  • On a une ActivationDependency, ici elle permet de vérifier que la Feature 1 est bien activée, sinon, on ne peut activer la feature 2.
    Logique, sinon, la création d’une liste héritant d’un content type inexistant posera un problème.
  • On fait appel à un SPReceiver, en effet, c’est lors de l’activation de la feature que l’on va créer la liste.

Le Receiver :MonReceiver.cs
public class MonReceiver : SPFeatureReceiver
{
private const String LIST_NAME = "maListe";
private const String LIST_DESC = "La desc";
private const String CONTENTTYPE_NAME = "MonContentType";

public override void FeatureActivated(SPFeatureReceiverProperties properties)
{
if (properties.Feature.Parent is SPWeb)
{
SPWeb web = (SPWeb)properties.Feature.Parent;
SPSite site = web.Site;

try { SPList list = web.Lists[LIST_NAME]; }
catch //La liste n'existe pas, on va la creer
{
try
{
//Création de la list
Guid listId = web.Lists.Add(LIST_NAME, LIST_DESC, SPListTemplateType.PictureLibrary);
//on selectionne la lise ajoutée
SPList list = web.Lists[listId];
list.ContentTypesEnabled = true;
list.OnQuickLaunch = false;
list.EnableFolderCreation = false;
list.Update(); //Mise à jour de la liste avec les options

//Ajout du type de contenu souhaité pour la liste
SPContentType ct = web.AvailableContentTypes[CONTENTTYPE_NAME];
list.ContentTypes.Add(ct);

//On sélectionne le contentype précédent comme  l'unique CT de la liste
SPContentType[] ctToDisplay =  new SPContentType[1];
for (int i = 0; i < list.RootFolder.ContentTypeOrder.Count; i++)
{
if (list.RootFolder.ContentTypeOrder[i].Name == CONTENTTYPE_NAME)
{
ctToDisplay[0] = list.RootFolder.ContentTypeOrder[i];
}

}
list.RootFolder.UniqueContentTypeOrder = ctToDisplay;
list.RootFolder.Update();

}
catch (Exception ex) { throw ex; }
}
}
}

public override void FeatureDeactivating(SPFeatureReceiverProperties properties)
{
if (properties.Feature.Parent is SPWeb)
{
SPWeb web = (SPWeb)properties.Feature.Parent;
SPSite site = web.Site;

try
{
SPList list = web.Lists[LIST_NAME];
if (list.ItemCount < 1)
{
list.Delete();
}
}
catch {}
}
}

public override void FeatureInstalled(SPFeatureReceiverProperties properties){ }

public override void FeatureUninstalling(SPFeatureReceiverProperties properties) { }
}


Ce que fait l’activation de la feature :

  • Elle crée la liste si celle-ci n’existe pas (de type PictureLibrary dans notre cas)
  • Elle ajoute le type de contenu souhaité pour la liste
  • Ensuite, on redéfini le type de contenu par défaut de la liste, via le RootFolder.UniqueContentTypeOrder
    Il s’agit d’une collection de SPContentType qui permet de définir l’ordre et le type de contenu par défaut du bouton “Nouveau” de la liste.

Lors de la désactivation de la feature, si la liste est vide, on la supprime.

Voilà, ça n’est pas plus compliqué.

vendredi 15 octobre 2010

Sharepoint – Webpart et ValidationGroup en WCM

Si vous déployez une webpart, contenant un formulaire avec validation, dans une page de publication, au moment de l’archivage de la page, vous obtenez le message suivant :

Cette page contient du contenu ou une mise en forme non valide. Vous trouverez plus d'informations dans les sections concernées.
Options :  Quitter sans enregistrer

La solution est de remplir tous les champs du formulaire pour satisfaire la validation : Bof, pas très productif et pour expliquer ça aux collaborateurs du website !!

L’idée, pour éviter ça, est de ne pas afficher le formulaire lorsque la page est en mode “Edition” :

WebPartManager wp = WebPartManager.GetCurrentWebPartManager(this.Page);
if (wp.DisplayMode == WebPartManager.BrowseDisplayMode)
{
Control control = this.Page.LoadControl(ASCX_PATH);
Controls.Add(control);
}
else
{
Controls.Add(new LiteralControl("Mode édition - Pas d'affichage du formulaire"));
}

Donc en mettant le code ci-dessus dans le “CreateChildControls” de la WebPart, en mode édition, on affiche un message au lieu du formulaire.

Après archivage, on visualise le formulaire.


Donc plus de problème de validation.

Sharepoint – Obtenir le SMTP Host de la config en code-behind

Pour obtenir le SMTP Host configuré dans notre ferme :

if (SPContext.Current.Site.WebApplication.OutboundMailServiceInstance != null)
{
//Obtient le SMTP host de "Outgoing e-mail settings"
smtp.Host = SPContext.Current.Site.WebApplication.OutboundMailServiceInstance.Parent.Name;
}

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

mardi 8 juin 2010

[MOSS 2007]Création de page : Accès refusé

Dernièrement, j'ai créé une web application pour un groupe d'utilisateurs.
Je suis Administrateurs de la collection de sites et certains d'entre eux ont un contrôle total sur le site (propriétaire du site mais pas de la collection de sites).

Pourtant, malgré ce contrôle total, ils ne peuvent pas créer de pages => Accès refusé.

En cherchant, je me suis aperçu qu'ils n'avait aucun droit sur la galerie de page maître.
En soit, cela parait normal, mais ce manque de droits empêche la création de pages.

En affectant aux propriétaires du site l'autorisation "Lecture restreinte" sur la galerie de pages maître, ils peuvent maintenant créer des pages sans pour autant avoir accès à la galerie.

Ouf, ma soirée est sauve ;)

lundi 7 juin 2010

WCF RIA Services : Un tuto complet

Benjamin Devuyst a publié un tuto vraiment très bien sur son blog développez.com.

WCF RIA Services : une approche pragmatique !

Il explique en détail les différentes étapes à l'utilisation de WCF RIA Services.