vendredi 3 avril 2015

Comment récupérer les rôles définis dans DNN

Pour récupérer les rôles de DNN, je pensais devoir utiliser le controller "RoleController", et bien non...

Voici un extrait de code, vous permettant de récupérer l'ensemble des rôles définis dans une instance de portail :
ArrayList roles = RoleProvider.Instance().GetRoles(portalId);

Un ArrayList n'est pas forcément des plus simple à utiliser, voici la même chose mais en initialisant un objet List<RoleInfo> :

List<RoleInfo> roles = RoleProvider.Instance().GetRoles(portalId).Cast<RoleInfo>().ToList();

mardi 31 mars 2015

Utiliser Telerik OpenAccess sans avoir à l'installer

Voici une méthode permettant d'utiliser Telerik OpenAccess sans avoir à l'installer. Cette méthode peut aussi être utilisée pour détacher votre projet de la version de Telerik installé sur votre poste.

L'intérêt ? Vous avez une dizaine de projets sur votre poste utilisant Telerik OpenAccess mais bien sur, tous ne vont pas évoluer de la même façon. A un moment donné vous mettrez forcément à jour votre version d'OpenAccess, mais que se passera-t-il pour vos projets ?

Si vos projets sont détachés de la version installée sur votre poste vous n'aurez pas de soucis, sinon attention aux effets de bords, et généralement on les découvre quelques mois après la mise à jour d'OpenAccess lorsque vous retournez sur un projet.

L'idée est donc d'avoir dans votre solution les fichiers OpenAccess vous permettant de compiler sans soucis de version installée sur votre poste. Vous trouverez toutes les infos dans cet article :

http://docs.telerik.com/data-access/developers-guide/integrating-data-access-in-your-solution/external-tools-howto-integrate-enhancer-msbuild

jeudi 12 février 2015

Erreur 405, lors d'un appel Ajax avec le verb DELETE


Lors d'un appel Ajax à une web API avec le verbe DELETE, j'obtenais en retour une erreur 405 (Method Not Allowed).

Voici un extrait du code que j'utilise :

$.ajax({
    url: "/.../API/.../DeleteForm?" + $.param({ id: form.id }),
    type: "DELETE",
    contentType: 'application/json',
    success: function (result) {
    ...
    },
    error: function (result) {
    ...
    }
})
Après quelques recherches je me suis aperçu que ce problème pouvait provenir de la présence de WebDAV qui semble interférer mon appel.

Une solution consiste à désinstaller WebDAV...rien que ça. Sinon une autre solution consiste à désactiver les modules WebDAV au niveau de votre site. Pour cela vous pouvez utiliser le code suivant dans votre fichier web.config :
<system.webServer>
  <modules>
    <remove name="WebDAVModule" />
  </modules>
  <handlers>
    <remove name="WebDAV" />
  </handlers>
</system.webServer>
Et avec cela plus de soucis.

mardi 10 février 2015

Comment utiliser RequireJS sur un site DNN

Voici un sujet pour lequel je n'ai jamais réussi à trouver d'informations auprès de mon ami "Google". Après pas mal de tâtonnement, j'ai, il me semble, trouvé une solution à peu près convenable.

La notion de "component" sous Knockout

J'ai décidé de me pencher sur ce sujet lorsque j'ai découvert la notion de "component" sous Knockout. Cette notion permet de faire des développements Javascript modulaires, ce qui signifie plus de souplesse, plus facile à maintenir... voici un lien vous présentant un peu le sujet : http://knockoutjs.com/documentation/component-overview.html.
Quel est le lien avec RequireJS ? La modularité. Comme je le disais plus haut, vous développez de façon modulaire, chaque module peut être lié à d'autres modules. Comment gérer ces liens et comment faire en sorte que lorsque vous décidez d'utiliser un "component" tous les "components" liés soient bien récupérer par le client ?
C'est là que la puissance de RequireJS prend toute son ampleur.

Le chainage des "components" avec RequireJS

Sur chaque "component" vous allez définir les autres "components" devant être récupérés pour pouvoir fonctionner. Chacun des "components" récupérés ayant eux-mêmes d'autres "components" de référencés. Il se crée ainsi un chainage entre "components". Vous n'avez plus besoin de vous inquiéter si telle ou telle librarie JS a bien été intégrée dans le "header", tout ce fait automatiquement et seuls les éléments nécessaires sont récupérés.

Chargement de RequireJS dans DNN

RequireJS est donc essentiel pour un développement JS modulaire. Maintenant il ne nous reste plus qu'à le charger sur notre site DNN mais voilà...ce n'est pas si simple que cela : RequireJS ne doit être chargé qu'une seule fois.
Sous DNN nous avons l'habitude travailler avec des modules qui sont indépendants les uns des autres, nous aurions donc pu imaginer utiliser RequireJS dans chaque module...malheureusement cela n'est pas possible.
RequireJS n'est pas une librairie JS comme les autres, en effet, lors de la mise en place de la balise SCRIPT permettant de charger RequireJS il faut faire référence à un fichier de configuration qui sera exécuté une fois RequireJS chargé sur le client. On ne peut définir qu'un seul fichier de configuration par page. Donc imaginer monter une instance de RequireJS par module n'est pas possible (où en tout cas je n'ai pas trouvé comment le faire).

Un SKINOBJECT pour charger RequireJS

Je suis donc parti sur la création d'un SKINOBJECT pour charger sur mes pages ma librairie RequireJS. Ce SKINOBJECT est ensuite déposé sur les SKIN où je souhaite utiliser cette librairie.

Voici donc le code C# de mon SKINOBJECT :

    public partial class View : DotNetNuke.UI.Skins.SkinObjectBase
    {
        protected override void OnInit(EventArgs e)
        {
            LiteralControl javascriptRef = new LiteralControl("<script type='text/javascript' data-main='/PathToConfigFile/default.js' src='/PathToLibrary/require.js'></script>");

            Page.Header.Controls.Add(javascriptRef);
        }
    }
Ici, j'insère une balise script dans le "Header" de ma page. Cette balise script pointe vers deux fichiers : "default.js" et "require.js".
Le premier ("default.js"), correspond au fichier de configuration exécuté par RequireJS dès son chargement. Vous trouverez plus loin un descriptif du contenu de ce fichier.
Le second ("require.js") est ni plus ni moins que la librairie RequireJS.

Voici le contenu du fichier "default.js" :
require.config({
    baseUrl: '/DesktopModules',
    paths: {
        "knockout": 'PathToKnockout/knockout',
    },
});


require(["knockout"], function (ko) {
    $(document).ready(function () {
        $.event.trigger("knockoutReady")

        setTimeout(function () {
            ko.applyBindings();
        }, 10);
    });
});
Ce fichier de configuration permet de définir l'URL de base de l'ensemble des chemins utilisés dans RequireJS par la suite ainsi que le chemin d'accès à la librairie "Knockout" (ce chemin étant relatif à la baseUrl s'il ne commence pas par "/").

La seconde partie de ce fichier, permet de mettre en place le Binding de Knockout. Pour cela j'utilise 2 astuces :
  • je lève un événement "knockoutReady" que j'utiliserais dans mes modules utilisant des "components" Knockout pour charger ces derniers.
  • je fais un "applyBindings" en asynchrone pour "binder" les viewModels de mes futurs modules.
Avec cela je suis fin prêt pour créer mes modules avec des "components" dans tous les sens.

vendredi 12 décembre 2014

Telerik : No primary key fields found for class

Voici donc un message d'erreur obtenu lors de l'exécution d'une requête Lync sur une de mes tables gérées à travers DataAccess ORM de Telerik (anciennement OpenAccess).

A en croire ce message je n'ai pas de clef définie sur cette table. Et pourtant... dans le model, j'ai bien une clef de défini. Pour remédier à ce soucis, j'ai dû supprimer la clef, puis enregistrer mes modifications, j'ai eu droits à quelques messages d'erreur correspondant aux FOREIGN KEY qui ne pouvait plus se résoudre. J'ai remis en place ma clef, resauvegarder, puis exécuter l'application et... problème résolu.

Pour supprimer/créer la clef j'ai juste passé de false à true le propriété Identity de mon champ clef.

ClickOnce

Pas grande chose à voir avec DotNetNuke (sujet principal de ce blog). Mais voici une problématique face à laquelle je me suis retrouvé et qui m'a donné quelques sueurs froides : le renouvellement d'un certificat d'une application ClickOnce.

Lors de la création d'une application ClickOnce, les manifestes sont automatiquement signés. En effet, la case à cocher "Signer les manifestes ClickOnce" se trouvant dans l'onglet "Signature" des propriétés du projet est pré-coché. Cela ne pose aucune problème jusqu'à la date de péremption du certificat c'est à dire 1 an après la première publication.

Après que se passe-t-il ? Plusieurs possibilités :
  • vous créez un nouveau certificat, cela induit que vous acceptiez que vos utilisateurs doivent supprimer votre ancienne version pour installer la nouvelle... pas vraiment pratique,
  • vous avez sous le coude le certificat (fichier .pfx) qui a été créé initialement, et là vous n'avez plus qu'à le prolonger et redéployer et c'est reparti pour un tour,
  • vous n'avez pas le certificat, et là vous pouvez soit pleurer, soit accepter "contraint et forcer" la première solution.
Nous allons nous pencher sur la meilleure des solutions : renouvellement du certificat.

Pour info le certificat généré initialement, se trouve normalement à la racine de votre projet.

Voici l'article que vous permettra de procéder au renouvellement du certificat : http://robindotnet.wordpress.com/2010/01/26/how-to-extend-an-existing-certificate-even-if-it-has-expired/

Pour résumé, il vous faut dans un premier temps récupérer un exécutable permettant de renouveler le certificat, celui-ci a pour nom "renewcert.exe".

Ensuite, exécuter la ligne de commande suivante :
renewcert <FichierPfxOrigine> <FichierPfxDestination>.

Vous obtiendrez alors un fichier certificat pfx dont la date aura été étendue de 5 ans. Il ne vous reste plus qu' remplacer votre ancien certificat en passant par les propriétés du projet onglet "Signature" puis le bouton"A partir d'un fichier...".

Problème réglé.

dimanche 12 octobre 2014

DNN + Bootstrap + DDRMenu : Comment garder ouvert un sous-menu

Voici une petite astuce à utiliser dans un cas très précis. Je travaille actuellement sur un skin DNN, basé sur Bootstrap. Arrive le moment tant redouté de l'intégration du menu DNN (DDRMenu) dans le skin et je rencontre là un problème pour la gestion du menu "Rechercher".

En effet, dans DNN lors de la sélection du menu rechercher, un panel s'ouvre permettant de saisir le texte rechercher. Pour intégrer ce genre de fonctionnalité avec Bootstrap, l'utilisation d'un menu dropdown s'impose. Malheureusement lors de la sélection d'un sous-item la dropdown est automatiquement fermé. Mais dans mon cas cela pose soucis, puisque dès que ma zone de texte de mon contrôle de recherche prend le focus, la dropdown se referme.

A tout problème une solution... Voici tout d'abord la définition de mon menu rechercher "à la Bootstrap" :

<ul class="nav navbar-nav searchMenu">
     <li class="dropdown">
          <a href="#" class="dropdown-toggle" data-toggle="dropdown">Search<b class="caret"></b></a>
               <ul class="dropdown-menu">
                    <li>
                         <div class="searchBox">
                              <dnn:Search id="dnnSearch1" runat="server" showsite="false" showweb="false" cssclass="btn btn-success btn-xs" />
                         </div>
                    </li>
               </ul>
          </li>
     </ul>
Cette définition permet d'avoir une dropdown "Search" qui a pour contenu, lorsqu'on la déplie, le contrôle de recherche DNN constitué d'une zone de texte et d'un bouton "Rechercher". Si l'utilisateur clique dans la zone de texte, la dropdown est automatiquement fermé.

Pour éviter cela, il s'agit dans un premier temps de supprimer l'attribut "data-toggle='dropdown'" définie sur l'élément "A" de la dropdown. En supprimant cet attribut, la dropdown ne s'ouvre plus.

L'étape suivante consiste à remettre en place le fonctionnement de la dropdown avec du javascript tout en excluant les cas où l'on ne souhaite pas que la dropdown se ferme. Voici le code JS à mettre en place pour arriver à cela :
$(document).ready(function () {
    $('.searchMenu li.dropdown a').click(function (event) {
        $(this).parent().toggleClass("open");
    });

    $('body').click(function (e) {
        if (!$('.searchMenu li.dropdown').is(e.target) &&
            $('.searchMenu li.dropdown').has(e.target).length === 0 &&
            $('.open').has(e.target).length === 0) {
            $('.searchMenu li.dropdown').removeClass('open');
        }
    });
});
 La première partie du script permet de mettre en place la classe "open" sur le menu dropdown permettant d'ouvrir la dropdown, et cela est fait lors du click sur la dropdown.

La seconde partie du script permet de refermer la dropdown (en supprimant la classe "open") sauf dans le cas où l'on clique dans la dropdown.

Avec cela mon problème de menu "Rechercher" est réglé...