jeudi 17 mars 2011

ASP.NET - Problème d’Ajax sur Chrome et Safari

Sur mon site SharePoint, j’ai des ApplicationPage ou des webparts contenant de l’Ajax de MS.

Parfois, sur certains appels, les navigateurs sous webkit (Safari, Chrome) me renvoient l’erreur suivante :

Uncaught Sys.ScriptLoadFailedException: Sys.ScriptLoadFailedException: Échec du chargement du script 'http://dev2008/ScriptResource.axd?d=UWfXBVZmHMLaD9yazDm3XCZ1zh7gE0VbTVJs-BJ2wUiVXFWLG3ZiCsDINgJQjFMAPY0wxaBY-pp9eZhQNhP0y-I86N1uMrSe_7z7leFAxDedMNEoRb2dmo3NoyO7pbno5N3XRdEOvlAKSmJYeyoOmKNssMKeamFzDiHavM1oTZMoZZCts9toMuUQPcYX99NBuI94Mg2&t=ffffffffdf363b93'. Vérifiez les éléments suivants :
Chemin d'accès inaccessible.
Erreurs de script. (Internet Explorer) Activez 'Afficher une notification de chaque erreur de script' dans les paramètres avancés.
Appel manquant à Sys.Application.notifyScriptLoaded().
Sys$WebForms$PageRequestManager$_scriptIncludesLoadCompleteScriptResource.axd:1266
Sys$WebForms$PageRequestManager$_scriptIncludesLoadFailedScriptResource.axd:1304
(anonymous function)ScriptResource.axd:31
(anonymous function)ScriptResource.axd:47
Sys$_ScriptLoader$_raiseErrorScriptResource.axd:3656
Sys$_ScriptLoader$_scriptLoadedHandlerScriptResource.axd:3677
(anonymous function)ScriptResource.axd:47
(anonymous function)

Après de longues recherches, j’ai trouvé ce post très intéressant qui corrige le problème :

http://forums.asp.net/p/1252014/2392110.aspx

Donc voici rapidement la solution que j’ai mis en place sur mon site SharePoint :

Dans ma masterpage, j’ai ajouté un lien vers un fichier de hack dans le scriptmanager :

<asp:ScriptManager ID="ScriptManager1" runat="server" 
EnablePartialRendering="true"
EnableScriptLocalization="true" EnableScriptGlobalization="true">
<Scripts>
<asp:ScriptReference Path="/_layouts/Extranet/js/WebKitAjaxHack.js" />
</Scripts>
</asp:ScriptManager>



Et voici le contenu de ce fichier WebKitAjaxHack.js :



Sys.Browser.WebKit = {}; //Safari + chrome
if (navigator.userAgent.indexOf('WebKit/') > -1) {
Sys.Browser.agent = Sys.Browser.WebKit;
Sys.Browser.version = parseFloat(navigator.userAgent.match(/WebKit\/(\d+(\.\d+)?)/)[1]);
Sys.Browser.name = 'WebKit';
}


Une fois la masterpage publier ainsi que le fichier js, tous mes appels fonctionnent sans erreur.

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.