Vous êtes ici Forums
  |  Connexion
 Forums
HomeHomeProjetsProjetsTemplate Olymar...Template Olymar...Limitation de la taille pour le nom des settingsLimitation de la taille pour le nom des settings
Précédente
 
Suivante
Nouveau message
17/03/2007 14:39
 

Bonjour à toutes et à tous,

Je viens de tomber sur une désagréable suprise. La taille du champ SettingName de la table TabModuleSettings est limité à 50, ce qui est très peu pour mon projet de développement d'un template OlyMars pour générer automatiquement des modules DNN basés sur des procédures stockées (annoncé officiellement hier à la réunion de la communauté DotNetNuke à Paris).

Avez-vous une idée pour contourner cette limitation (à part bien sûr utiliser des noms inférieurs à 50 caractères ce qui ne m'arrange pas du tout vu la grande quantité de settings que j'ai à stocker) ?

J'ai pensé à utiliser des GUIDs en lieu et place des noms mais du coup, il sera impossible de changer la localisation par l'interface DNN (les champs GUIDs étant par nature illisible :-) ). Cependant, je vais quand même fournir une interface de localisation directement dans le module. Donc finalement la solution des GUIDs serait-elle acceptable ?

Merci,

Pascal Belaud

Microsoft France

http://www.olymars.net/dnn

http://blogs.msdn.com/Pascal

 

 

 
Nouveau message
17/03/2007 16:13
 

Salut Pascal,

voilà une méthode que nous utilisons parfois pour contourner le problème:

Pour pouvoir gérer un grand nombre de paramètres, nous les encapsulons dans les propriétés d'une classe dédiée ("GeneralSettings" dans les exemples de code ci-dessous).

On utilise ensuite un XMLSerializer pour exposer des fonctions ReadXML / WriteXML (le serialiser est mis en cache pour améliorer les perfs)

 Private Shared Function GetGeneralSettingsSerializer() As XmlSerializer
            Dim toReturn As XmlSerializer
            toReturn = CType(DotNetNuke.Common.Utilities.DataCache.GetCache(ConstCacheGeneralSettingsSerializer), XmlSerializer)
            If toReturn Is Nothing Then
                toReturn = New XmlSerializer(GetType(GeneralSettings), New Type() {GetType(MainMapImageFormat), _
                 GetType(NotificationType), GetType(ModerationSettings), GetType(JavaScriptSupport), GetType(JavaScriptSettings), GetType(Aricie.CG.Data.CoucheInfo), GetType(Aricie.Framework.Component.SerializableDictionary(Of String, Aricie.CG.Data.CoucheInfo))})
                DotNetNuke.Common.Utilities.DataCache.SetCache(ConstCacheGeneralSettingsSerializer, toReturn)
            End If
            Return toReturn
        End Function

        Public Shared Function ReadXML(ByVal data As String) As GeneralSettings

            Dim objSerializer As XmlSerializer = GeneralSettings.GetGeneralSettingsSerializer
            Dim surveySettingsReader As New XmlTextReader(New System.IO.StringReader(data))
            Return CType(objSerializer.Deserialize(surveySettingsReader), GeneralSettings)

        End Function

        Public Function WriteXML() As String
            Dim toReturn As New StringWriter
            Dim objSerializer As XmlSerializer = GeneralSettings.GetGeneralSettingsSerializer
            objSerializer.Serialize(toReturn, Me)
            Dim xmlDoc As New XmlDocument
            xmlDoc.LoadXml(toReturn.ToString())
            Dim xmlDocEl As XmlNode = xmlDoc.DocumentElement
            xmlDocEl.Attributes.Remove(xmlDocEl.Attributes.ItemOf("xmlns:xsd"))
            xmlDocEl.Attributes.Remove(xmlDocEl.Attributes.ItemOf("xmlns:xsi"))
            Return xmlDocEl.OuterXml
        End Function

Un couple de fonctions se charge ensuite d'abstraire l'accès et la sauvegarde des Settings correspondants dans les ModuleSettings / TabModuleSettings selon, en décomposant les chaines de caractères par tranches de 2000 (taille de la colonne Settings Value) et en utilisant à nouveau le cache pour aller plus vite.


  Public Shared Function GetGeneralSettings(ByVal moduleID As Integer, ByRef settingsToHydrate As GeneralSettings, Optional ByVal moduleSettings As Hashtable = Nothing) As Boolean

   Dim toReturn As Boolean = True
   Dim strCache As String = ConstCacheGeneralSettingsPrefix & moduleID
   settingsToHydrate = CType(DataCache.GetCache(strCache), GeneralSettings)
   If settingsToHydrate Is Nothing Then
    Dim strKey As String = ConstModuleSettingsGeneralSettings
    Dim strTerminator As String = ConstXMLGeneralSettingsTerminator

    Dim xmlGeneralSettings As String = FetchFromModuleSettings(moduleID, strKey, strTerminator, moduleSettings)
    If xmlGeneralSettings <> "" Then
     settingsToHydrate = GeneralSettings.ReadXML(xmlGeneralSettings)
     CachingHelper.SetCacheDependant(strCache, settingsToHydrate, moduleID)
    Else
     settingsToHydrate = New GeneralSettings
     'settings not found -> return false
     toReturn = False
    End If
   End If
   Return toReturn
  End Function

Public Shared Sub SetGeneralSettings(ByVal moduleID As Integer, ByVal generalSettings As GeneralSettings, _
    ByVal setCache As Boolean, Optional ByVal moduleController As ModuleController = Nothing)

   Dim strCache As String = ConstCacheGeneralSettingsPrefix & moduleID
   If moduleController Is Nothing Then
    moduleController = New moduleController
   End If
   Dim strMSKey As String = ConstModuleSettingsGeneralSettings
   Dim xmlGeneralSettings As String = generalSettings.WriteXML
   SaveToModuleSettings(moduleID, strMSKey, xmlGeneralSettings)
   If setCache Then
    CachingHelper.SetCacheDependant(strCache, generalSettings, moduleID)
   End If

  End Sub

 

Public Shared Function FetchFromModuleSettings(ByVal moduleID As Integer, ByVal key As String, ByVal terminator As String, Optional ByRef Settings As Hashtable = Nothing) As String

   Dim toReturn As String = ""
   If Settings Is Nothing Then
    Dim mc As New ModuleController
    Settings = mc.GetModuleSettings(moduleID)
   End If
   If Settings.ContainsKey(key) Then
    toReturn = CType(Settings(key), String)

    If toReturn <> "" And Not toReturn.EndsWith(terminator) Then
     Dim j As Integer = 2
     Dim tempStr As String
     Do
      tempStr = ""
      If Settings.ContainsKey(key & "-" & j) Then
       tempStr = CType(Settings(key & "-" & j), String)
       toReturn += tempStr
       j += 1
      End If
     Loop Until tempStr.EndsWith(terminator) Or tempStr = ""
    End If

   End If
   Return toReturn

  End Function

  Public Shared Sub SaveToModuleSettings(ByVal moduleID As Integer, ByVal key As String, ByVal strXmlToSave As String)
   Dim objModules As New Entities.Modules.ModuleController
   If strXmlToSave.Length < 2000 Then

    objModules.UpdateModuleSetting(moduleID, key, strXmlToSave)
   Else
    objModules.UpdateModuleSetting(moduleID, key, strXmlToSave.Substring(0, 2000))
    Dim nbRows As Integer = Convert.ToInt32(Math.Ceiling(strXmlToSave.Length / 2000))
    Dim j As Integer
    Dim tempStrXml As String
    For j = 2 To nbRows
     If j = nbRows Then
      tempStrXml = strXmlToSave.Substring((j - 1) * 2000)
     Else
      tempStrXml = strXmlToSave.Substring((j - 1) * 2000, 2000)
     End If
     objModules.UpdateModuleSetting(moduleID, key & "-" & j, tempStrXml)
    Next
    objModules.UpdateModuleSetting(moduleID, key & "-" & j + 1, "")
   End If
  End Sub

Bon le code est un peu vieux, il doit y avoir moyen de l'améliorer et le nettoyer un peu (utilisation de la methode DeleteModuleSetting dont ils avaient oublié la sproc jusqu'à récemment, utilisation de .Net 2.0 etc...).

Mais je suppose que ca pourra sans doute te donner des idées, l'avantage de cette methode étant que l'ajout de nouveaux paramètres ne nécessite à quelque chose près que de rajouter les membres correspondants dans la classe de paramètres.

A+


Jesse
Société de conseil et de service en 
informatique et systèmes d'information
 
Nouveau message
17/03/2007 16:23
 

Un autre truc pendant que j'y pense.

Hier durant ta démo, j'ai vu que tu traitais les permissions additionnelles dans une permission grid certes, mais déconnectée de la permission grid principale.

Tu peux en fait définir des permissions spécifiques à ton module qui viennent se rajouter à la suite des permissions de base dans la grille de permission principale, et qui te permettent d'utiliser la même API courante par la suite:

voilà un exemple de code permettant d'abstraire tout ça: le code te présente les méthodes pour déclarer les nouvelles permissions à la volée, tester si elles existent via un flag dans les host settings (tu peux sinon les définir dans le script d'install sql du module, ce qui est sans doute plus simple), et enfin fournit des méhodes simplifiant leur accès via une Enum

Public Enum Permission
  View
  Edit
  SubmitHotSpot
  SubmitMap
 End Enum

 Public Class Permissions

  Public Shared ReadOnly Property GetConstHostSettingsKey() As String
   Get
    Return ConstHostSettingsKey + ConstHostSettingsKeySuffix
   End Get
  End Property

  Public Shared ReadOnly Property IsPermissionFlagSet() As Boolean
   Get
    Dim objFlag As String = HostSettings.GetHostSetting(GetConstHostSettingsKey)
    If objFlag Is Nothing Then
     Return False
    Else
     Return objFlag = ConstHostSettingsFlag
    End If
   End Get
  End Property

  Public Shared ReadOnly Property PermissionKey(ByVal objPermission As Permission) As String
   Get
    Select Case objPermission
     Case Permission.View
      Return "VIEW"
     Case Permission.Edit
      Return "EDIT"
     Case Permission.SubmitHotSpot
      Return "SUBMITHOTSPOT"
     Case Permission.SubmitMap
      Return "SUBMITMAP"
     Case Else
      Return ""
    End Select
   End Get
  End Property

  Public Shared ReadOnly Property PermissionEnumFromKey(ByVal permissionKey As String) As Permission
   Get
    Select Case permissionKey
     Case "VIEW"
      Return Permission.View
     Case "EDIT"
      Return Permission.Edit
     Case "SUBMITHOTSPOT"
      Return Permission.SubmitHotSpot
     Case "SUBMITMAP"
      Return Permission.SubmitMap
     Case Else
      Throw New Exception("Not a Permission")
    End Select
   End Get
  End Property

  Public Shared ReadOnly Property PermissionName(ByVal permission As Permission) As String
   Get
    Select Case permission
     Case permission.View
      Return "View"
     Case permission.Edit
      Return "Edit"
     Case permission.SubmitHotSpot
      Return "Submit HotSpot"
     Case permission.SubmitMap
      Return "Submit Map"
     Case Else
      Return ""
    End Select
   End Get
  End Property

  Public Shared ReadOnly Property PermissionInfo(ByVal permission As Permission) As PermissionInfo
   Get
    Dim toReturn As New PermissionInfo
    toReturn.PermissionCode = ConstPermissionCode
    toReturn.PermissionKey = PermissionKey(permission)
    toReturn.PermissionName = PermissionName(permission)
    Return toReturn
   End Get
  End Property

  Public Shared Sub ResetPermission(ByVal moduleID As Integer, Optional ByVal moduleDefID As Integer = -1)
   If moduleDefID = -1 Then
    Dim mc As New ModuleController
    Dim objModuleInfo As ModuleInfo = mc.GetModule(moduleID, Null.NullInteger)
    moduleDefID = objModuleInfo.ModuleDefID
   End If
   Dim hashpermissionSetFlag As New Hashtable
   Dim tempPermissionInfo As PermissionInfo
   Dim objEnumPermission As Aricie.DNN.Modules.HTMLMapMaker.Security.Permissions.Permission
   Dim pc As New PermissionController
   'updating existing permissions
   Dim objPermissions As ArrayList = pc.GetPermissionsByModuleID(moduleID)
   For Each objPermission As PermissionInfo In objPermissions
    If objPermission.PermissionCode <> "SYSTEM_MODULE_DEFINITION" Then
     Try
      objEnumPermission = PermissionEnumFromKey(objPermission.PermissionKey)
      tempPermissionInfo = PermissionInfo(objEnumPermission)
      tempPermissionInfo.PermissionID = objPermission.PermissionID
      tempPermissionInfo.ModuleDefID = moduleDefID
      pc.UpdatePermission(tempPermissionInfo)
      hashpermissionSetFlag.Add(objEnumPermission, True)
     Catch ex As Exception
      pc.DeletePermission(objPermission.PermissionID)
     End Try
    End If
   Next
   'creating new permissions
   For Each strPermission As String In System.Enum.GetNames(GetType(Permission))
    objEnumPermission = CType(System.Enum.Parse(GetType(Permission), strPermission), Permission)
    If objEnumPermission <> Permission.Edit AndAlso objEnumPermission <> Permission.View AndAlso (Not hashpermissionSetFlag.ContainsKey(objEnumPermission)) Then
     tempPermissionInfo = PermissionInfo(objEnumPermission)
     tempPermissionInfo.ModuleDefID = moduleDefID
     pc.AddPermission(tempPermissionInfo)
    End If
   Next
   Dim strHSKey As String = GetConstHostSettingsKey
   Dim objHostSettingsController As New HostSettingsController
   objHostSettingsController.UpdateHostSetting(strHSKey, ConstHostSettingsFlag)
  End Sub


  Public Shared Sub DeletePermissions(ByVal moduleID As Integer)

   Dim pc As New PermissionController
   Dim objPermissions As ArrayList = pc.GetPermissionsByModuleID(moduleID)
   For Each objPermission As PermissionInfo In objPermissions
    If objPermission.PermissionCode <> "SYSTEM_MODULE_DEFINITION" Then
     pc.DeletePermission(objPermission.PermissionID)
    End If
   Next
   Dim strHSKey As String = GetConstHostSettingsKey
   Dim objHostSettingsController As New HostSettingsController
   objHostSettingsController.UpdateHostSetting(strHSKey, "")
  End Sub

 End Class

Au sein d'un Controle, il te suffit ensuite d'appeler quelque chose dans le style:

If Me.HasModulePermission(Permissions.PermissionKey(Permission.SubmitHotSpot)) Then ...


Jesse
Société de conseil et de service en 
informatique et systèmes d'information
 
Nouveau message
17/03/2007 17:10
 

Bon et puis tant qu'a faire maintenant que je suis lancé:

Tu nous a montré l'usage du multiview, qui est bon en soit, mais il me semble qu'il ne s'agit pas d'en faire usage la ou le modèle de clés de définition standard s'applique. Je m'explique:

Les modules dnn sont composés d'un jeu de formulaires (héritant de portalModuleBase) associés à une clé (paramètre http) et à une permission.

Sur le Text/Html pour prendre un exemple simple, tu as le controle principal de visualisation (clé vide, permission "View") et le contrôle d'édition via l'éditeur de texte riche (clé "Edit", permission "Edit").

Depuis le controle de vue, tu peux passer sur le controle d'edtion avec:

Response.Redirect(Me.EditUrl("Edit"))

Mieux, tu as également un système d'actions, matérialisé par le menu déroulant, ainsi que des boutons configurables dans le container graphique, que tu peux également associer à la navigation vers tes formulaires. La définition de ce menu est accessible via l'implementation dans tes formulaires (PortalModuleBase) de l'interface IActionable.

Ex d'utilisation riche:

Public ReadOnly Property ModuleActions() As DotNetNuke.Entities.Modules.Actions.ModuleActionCollection Implements DotNetNuke.Entities.Modules.IActionable.ModuleActions
            Get
                Dim Actions As New DotNetNuke.Entities.Modules.Actions.ModuleActionCollection
                If Me.IsEditable Or (Me.limitOK And (Me.HasModulePermission(Modules.HTMLMapMaker.Security.Permissions.Permissions.PermissionKey(Permission.SubmitHotSpot)) _
                  Or Me.HasModulePermission(Modules.HTMLMapMaker.Security.Permissions.Permissions.PermissionKey(Permission.SubmitHotSpot)))) Then
                    If Me.MapID <> -1 Then
                        Actions.Add(GetNextActionID, Localization.GetString(Entities.Modules.Actions.ModuleActionType.EditContent, LocalResourceFile), Entities.Modules.Actions.ModuleActionType.AddContent, "", "", Me.NavigateModuleURL(HtmlMapMakerForm.Edit, Me.MapID), False, DotNetNuke.Security.SecurityAccessLevel.View, True, False)
                        If Me.IsEditable Or Me.CanPerformAction(Permission.SubmitHotSpot) Then
                            Actions.Add(GetNextActionID, Localization.GetString("AddHotSpot.Action", LocalResourceFile), Entities.Modules.Actions.ModuleActionType.AddContent, "", "", Me.NavigateModuleURL(HtmlMapMakerForm.EditHS, Me.MapID), False, DotNetNuke.Security.SecurityAccessLevel.View, True, False)
                        End If
                    End If
                End If
                If Me.IsEditable Or (Me.limitOK And Me.CanPerformAction(Permission.SubmitMap)) Then
                    Actions.Add(GetNextActionID, Localization.GetString(Entities.Modules.Actions.ModuleActionType.AddContent, LocalResourceFile), Entities.Modules.Actions.ModuleActionType.AddContent, "", "", Me.NavigateModuleURL(HtmlMapMakerForm.Edit, -1), False, DotNetNuke.Security.SecurityAccessLevel.View, True, False)
                End If
                Actions.Add(GetNextActionID, Localization.GetString("ImportMap.Action", LocalResourceFile), Entities.Modules.Actions.ModuleActionType.AddContent, "", "", Me.NavigateModuleURL(HtmlMapMakerForm.Import, -1), False, DotNetNuke.Security.SecurityAccessLevel.Edit, True, False)
                Actions.Add(GetNextActionID, "Importer une couche", Entities.Modules.Actions.ModuleActionType.AddContent, "", "", EditUrl("ImportCouche"), False, DotNetNuke.Security.SecurityAccessLevel.Edit, True, False)
                Return Actions
            End Get
        End Property

Bon bien sûr, tu peux toi même implémenter ta propre gestion de chargement de contrôles dynamiques dans un multiview avec ton paramêtre de querystring perso, et c'est parfois nécessaire de la faire en plus, mais bon l'api est quand même la pour ça donc il me semble que le multiview ne devrait être utilisé que pour enrichir un formulaire complexe parmi les autres, plutôt que pour implémenter la diversité des formulaires (ceux que tu nous a montré semblent se prêter à l'utilisation standard dans une définition multicontroles)

Ensuite, il y a la localisation, je crois que tu as déjà jeté un coup d'oeil la dessus.

Pour rappel, la classe Localization expose l'ensemble des méthodes utiles, principalement GetString(...), que tu peux utiliser à chaud dans un formulaire.

Il y a aussi les attributs Resourcekey dans les ascx (je suppose que c'est ce que tu as utilisé). Leur utilisation prend corps dans le PreRender de la classe Pagebase dont hérite la page en cours: l'ensemble des contrôles de la page sont parcourus, et pour ceux qui possèdent cet attribut, la fonction GetString mentionnée ci-dessus est appellée, et les propriétés correspondantes mises à jour selon le type de controle. Bon c'était juste pour te dire ou ça se passe si tu es passé à côté.

Petite subtilité: la méthode GetString prend en paramètre le nom du resx (sans le suffixe de locale). Celui-ci est accessible via la propriété LocalResourceFile dans PortalModuleBase, ce qui est relativement transparent. Maintenant, si tu souhaites utiliser des UserControles enfant, et conserver des resx spécifiques à ces controles, il est bon de leur attribuer une telle propriété, sinon les fichiers de resources principaux sont mis à contribution ce qui n'est pas top en termes de duplicatats.

En 1.1, j'utilisais ce genre de code:

  Public Property MyFileName() As String
   Get
    If Me._MyFileName = "" Then
     Me.MyFileName = Me.GetType.Name.Replace("_ascx", ".ascx")
    End If
    Return Me._MyFileName
   End Get
   Set(ByVal Value As String)
    Me._MyFileName = Value
   End Set
  End Property

  Public Property LocalResourceFile() As String
   Get
    If Me._localResourceFile = "" Then
     Me._localResourceFile = DotNetNuke.Services.Localization.Localization.GetResourceFile(Me, Me.MyFileName)
    End If
    Return Me._localResourceFile
   End Get
   Set(ByVal Value As String)
    _localResourceFile = Value
   End Set
  End Property

Bon, pour le reste, je pense que tu as du déjà parcourir une partie des espaces de nom. L'espace de nom "Services" est assez riche, encore que certaines fonctions principales sont parfois situées plutôt dans "Common" (ex du cache, ou de certaines fonctions de gestion des fichiers etc...), donc il ne faut pas oublier d'y jeter un coup d'oeil.

Enfin n'oublie pas d'utiliser à bon escient les User Controles DNN (contrôle URL pour la sélection des url ou des fichiers, contrôle RichTextEditor pour l'utilisation du fck/ftb selon le provider etc...), et si tu as du temps de jeter un oeil à

  • l'API cliente (intégration et contrôles AJAX + nombreux helpers d'intégration vb/javascript, dans les dll DotNetNuke.WebUtilities  et DotNetNuke.WebControls et dans le répertoire js pour le framework javascript),
  • aux contrôles dynamiques (PropertyEditor & co, qui correspondent à quelque chose près à des FormView semi-automatisés, mentionés durant ta présentation par Mr Généré de la société SmartFocus)

Voilà, il y a sans doute des chose dans tout ça que tu auras déjà vu, mais bon tu dois pouvoir piocher.

A+

PS: Je te parlerais bien d'OlyMars à la lumière de notre expérience sur LLBLGen, mais ça pourra faire l'objet d'un autre post ou d'un email.


Jesse
Société de conseil et de service en 
informatique et systèmes d'information
 
Nouveau message
18/03/2007 01:44
 
J'adore quand Jean-Sylvain répond...

A découvrir aux abords d'AJAX, un post interessant sur l'utilisation de l'API AJAX avec DNN : Creating A DotNetNuke Module using ASP.NET AJAX
... méthodes utiles accessibles sur l'espace de noms DotNetNuke.Framework.AJAX.

Seb
 
Nouveau message
20/03/2007 00:26
 

En effet, la réponse est fournie ! Va falloir me laisser le temps de déassembler tout ça :-)

Pascal

 
Nouveau message
20/03/2007 00:32
 

Ah ça c'est cool en effet !

Donc pour toi, il vaut mieux avoir la liste des procédures stockées du module apparaître à la suite de View et Edit (qui, du coup, n'ont pas grand chose à faire par rapport à ce type de module soit dit en passant :-) ) ou d'avoir une section à part?

J'aime assez l'idée d'avoir une section "MonModule Settings" dans laquelle tu gères tout, de l'affichage en allant jusqu'aux formats à utiliser pour affichir les datas le tout en passant par les permissions spécifiques par vues du module.

Qu'en pensez-vous ?

 
Nouveau message
20/03/2007 00:35
 

Finalement, j'ai implémenté ce week-end les GUIDs. J'avais peur au départ mais c'est assez élégant en fait. Par contre, cela m'oblige donc à développer un module de localisation spécifique. Mais je ne suis plus à cela près... :-) Je posterai du code très bientôt pour que vous me donniez votre avis.

Pascal

Jesse a dit :

Salut Pascal,

voilà une méthode que nous utilisons parfois pour contourner le problème:

Pour pouvoir gérer un grand nombre de paramètres, nous les encapsulons dans les propriétés d'une classe dédiée ("GeneralSettings" dans les exemples de code ci-dessous).

On utilise ensuite un XMLSerializer pour exposer des fonctions ReadXML / WriteXML (le serialiser est mis en cache pour améliorer les perfs)

...

Bon le code est un peu vieux, il doit y avoir moyen de l'améliorer et le nettoyer un peu (utilisation de la methode DeleteModuleSetting dont ils avaient oublié la sproc jusqu'à récemment, utilisation de .Net 2.0 etc...).

Mais je suppose que ca pourra sans doute te donner des idées, l'avantage de cette methode étant que l'ajout de nouveaux paramètres ne nécessite à quelque chose près que de rajouter les membres correspondants dans la classe de paramètres.

A+

 
Précédente
 
Suivante
HomeHomeProjetsProjetsTemplate Olymar...Template Olymar...Limitation de la taille pour le nom des settingsLimitation de la taille pour le nom des settings