2010-10-07 3 views
2

J'ai découvert les profils ASP.NET il y a quelques années et j'ai trouvé qu'il était facile et rapide d'implémenter de puissantes fonctionnalités dans mon site web. J'ai fini par utiliser exclusivement des propriétés de profil "anonymes" car je n'utilise PAS d'abonnement ASP.NET et je voulais suivre les informations et fournir une personnalisation pour les utilisateurs de mon site même s'ils n'étaient pas connectés. J'ai du mal à garder la taille de ma base de données aspnetdb dans la limite de 3 Go de mon fournisseur d'hébergement. J'ai décidé il y a un an que je ne conserverais que 6 mois de données de profil et que tous les mois je dois maintenant lancer la procédure aspnet_Profile_DeleteInactiveProfiles suivie d'une demande de support à mon hôte pour tronquer le fichier de base de données. Le vrai inconvénient est maintenant que j'ai cette fonctionnalité mise en œuvre, le site Web s'appuie sur cette base de données et il y a des temps d'arrêt chaque fois que j'ai besoin de tronquer. Aujourd'hui, le site a été bloqué pendant plus de 12 heures en faisant de la maintenance sur aspnetdb et j'ai fini par créer une copie de reprise de la base de données aspnetdb juste pour pouvoir mettre le site en ligne (la base de données aspnetdb comme j'écris ceci).Existe-t-il des alternatives plus efficaces aux profils anonymes ASP.NET 2.0?

L'année dernière, j'ai réécrit certaines des fonctionnalités car il stockait une petite collection d'objets binaires et je l'ai converti pour stocker une chaîne délimitée par des virgules d'ID, ce qui le rend plus petit pour le stockage. J'ai cherché à obtenir plus d'espace disque, mais la réalité est qu'il ne vaut pas la peine d'avoir une base de données qui se développe de 4 Go chaque année juste pour faire des choses simples comme garder une liste des derniers utilisateurs consultés les produits, gardez une trace de la catégorie dans laquelle ils se trouvaient pour un bouton «continuer à magasiner» pour les reprendre, et stockez le montant total (monnaie seulement) qui se trouve dans leur panier. De manière réaliste, moins de 5% des données sont réutilisées par les visiteurs réguliers du site, mais il est impossible de savoir à l'avance lequel de ces utilisateurs reviendra pour accéder aux informations de son profil. Donc, ma question est de savoir s'il existe un moyen plus efficace de stocker les données de profil pour ne pas prendre trop de place ou s'il existe des alternatives à la création d'enregistrements de base de données de profil pour chaque utilisateur pour supporter cette fonctionnalité?

J'ai jeté un coup d'oeil au table profile provider, mais quelqu'un peut-il vérifier si son utilisation permettra de stocker les données en utilisant moins d'espace disque que le fournisseur par défaut?

Mise à jour:

Je l'ai fait quelques recherches et il semble que le champ nvarchar (max) est un remplacement direct pour les champs ntext qui sont utilisés par l'implémentation par défaut du aspnetdb, mais ne nécessite que la moitié de l'espace disque selon this article et this one. Cela signifie que je devrais être capable de créer une nouvelle copie de aspnetdb, de modifier le type de données dans son schéma et son code, et de transférer les données dans la nouvelle base de données. Cela devrait corriger à la fois la façon dont les données sont stockées et toute fragmentation qui s'est produite lors de l'opération de réduction.

J'ai maintenant terminé les tests et déplacé cette solution en production. Au début, je pensais qu'il y aurait un problème car Microsoft a codé en dur le type de données NText dans l'appel de procédure stockée à l'intérieur du SqlProfileProvider. Cependant, il s'avère que SQL Server convertira implicitement un paramètre NText en nvarchar (MAX). En bref, je n'avais rien d'autre à changer que les types des colonnes PropertyNames et PropertyValuesString de la table aspnet_Profiles.

Les données ont dû être réécrites sur le disque pour libérer de l'espace, mais j'ai fini par économiser environ 30% en changeant simplement le type de données.

Une autre mise à jour:

J'ai aussi découvert que bien que je reçois environ 700 visiteurs uniques chaque jour, le nombre moyen de quotidiens « utilisateurs » dans les moyennes de table aspnet_Users sur 4000-5000. J'ai théorisé que cela était dû au fait que certains utilisateurs naviguent sans cookies. J'ai effectué quelques tests et découvert qu'un utilisateur ne sera pas créé si le profil n'est pas mis à jour, mais si vous écrivez dans le profil, un utilisateur (et un profil) sera créé à chaque requête si les cookies ne sont pas activés.

Je travaille sur une solution de contournement pour cela. J'essaye d'écrire le javascript pour un appel d'AJAX à un webmethod. En théorie, la deuxième requête (l'appel AJAX) devrait avoir le cookie .ASPXANONYMOUS présent si les cookies sont activés, et donc je peux écrire en toute sécurité dans le profil. Le javascript ne sera injecté dans la page que s'il s'agit du début de la session (déterminé par la propriété Session.IsNewSession). L'utilisateur ne devrait jamais être au courant de l'appel AJAX - il est seulement là pour dire à la page si les cookies sont activés afin qu'il puisse mettre à jour le profil.

Bien sûr, chaque demande subséquente peut simplement vérifier la collection de cookies pour s'assurer que le cookie .ASPXANONYMOUS est présent. Très probablement, je vais créer une fonction partagée qui peut être appelée à la fois par un AJAX appelé webmethod et par l'événement Page_Load directement, et utiliser la propriété IsNewSession pour déterminer quelle requête autoriser pour exécuter la vérification de cookie et la mise à jour de profil suivante. Selon Microsoft, l'utilisation de la session pour savoir si les cookies sont activés va à l'encontre du but (car la session dépend des cookies). Ils suggèrent de conserver votre valeur AreCookiesEnabled dans une base de données, mais ils ne mentionnent pas comment vous devez garder la trace de la valeur - si les cookies ne sont pas activés, comment pouvez-vous lier une requête à un enregistrement de base de données?

Répondre

1

J'ai implémenté la solution de contournement que j'ai trouvée dans mon article original, mais il s'est avéré être un peu différent de ce que j'ai décrit à l'origine. Le correctif peut être divisé en deux parties: l'une pour résoudre le problème de mise à jour de la base de données lorsque les cookies sont désactivés et l'autre pour détecter la désactivation des cookies sans redirection. J'ai déjà posté le solution to the anonymous profiles creating records when cookies are disabled. Maintenant, je vais me concentrer sur la deuxième partie - obtenir des informations dans le profil de la première page demandée. Cela ne doit être fait que si vous effectuez un suivi analytique ou quelque chose de similaire - la première partie prendra soin de protéger la base de données de remplir des données totalement inutiles lorsque 1) les cookies sont désactivés et 2) les propriétés de profil anonymes sont utilisées et fonctionnent la deuxième demande (ou première publication) à partir de. Lorsque j'ai recherché la question de vérifier si les cookies sont activés, la plupart des solutions utilisaient une redirection soit vers la même page ou vers une page différente et vice versa. Fait intéressant, MSDN était celui qui est venu avec la solution 2-redirection.

Alors que dans certaines circonstances, une redirection est acceptable, je ne voulais pas que l'impact sur les performances supplémentaires affecte la majorité de nos utilisateurs. Au lieu de cela, j'ai opté pour une autre approche - utilisez AJAX pour exécuter du code sur le serveur une fois la première requête terminée. Bien que cela ait l'avantage de ne pas provoquer de redirection, il présente l'inconvénient de ne pas fonctionner lorsque JavaScript est désactivé. Cependant, j'ai opté pour cette approche car le pourcentage de données perdues lors de la demande initiale est insignifiant et l'application elle-même ne dépend pas de ces données. Donc, en passant du début à la fin ...

Protected Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load 

    If Not Me.IsPostBack Then 

     If Session.IsNewSession Then 
      Me.InjectProfileJavaScript() 
     ElseIf AnonymousProfile.IsAnonymousCookieStored Then 
      'If cookies are supported, and this isn't the first request, update the 
      'profile using the current page data. 
      UpdateProfile(Request.RawUrl, Request.UrlReferrer.OriginalString, CurrentProductID.ToString) 
     End If 

    End If 

End Sub 

Ceci est la méthode Page_Load placé dans ma classe PageBase personnalisée, toutes les pages du projet héritent de. La première chose que nous vérifions est de savoir s'il s'agit d'une nouvelle session en vérifiant la propriété Session.IsNewSession. Cette propriété est toujours vraie si les cookies sont désactivés ou s'il s'agit de la première requête. Dans les deux cas, nous ne voulons pas écrire dans la base de données.

La section "else if" s'exécute si le client a accepté le cookie de session et ce n'est pas la première requête au serveur. La chose à noter à propos de cet extrait de code est que les deux sections ne peuvent pas s'exécuter dans la même requête, ce qui signifie que le profil ne peut être mis à jour que 1 (ou 0) fois par requête.

La classe AnonymousProfile est incluse dans mon other post.

Private Sub InjectProfileJavaScript() 

    Dim sb As New StringBuilder 

    sb.AppendLine("$(document).ready(function() {") 
    sb.AppendLine(" if (areCookiesSupported() == true) {") 
    sb.AppendLine(" $.ajax({") 
    sb.AppendLine("  type: 'POST',") 
    sb.AppendLine("  url: 'HttpHandlers/UpdateProfile.ashx',") 
    sb.AppendLine("  contentType: 'application/json; charset=utf-8',") 
    sb.AppendFormat("  data: ""{3}'RawUrl':'{0}', 'ReferralUrl':'{1}', 'ProductID':{2}{4}"",", Request.RawUrl, Request.UrlReferrer, CurrentProductID.ToString, "{", "}") 
    sb.AppendLine() 
    sb.AppendLine("  dataType: 'json'") 
    sb.AppendLine(" });") 
    sb.AppendLine(" }") 
    sb.AppendLine("});") 

    Page.ClientScript.RegisterClientScriptBlock(GetType(Page), "UpdateProfile", sb.ToString, True) 

End Sub 

Public Shared Sub UpdateProfile(ByVal RawUrl As String, ByVal ReferralUrl As String, ByVal ProductID As Integer) 
    Dim context As HttpContext = HttpContext.Current 
    Dim profile As ProfileCommon = CType(context.Profile, ProfileCommon) 

    Dim CurrentUrl As New System.Uri("http://www.test.com" & RawUrl) 
    Dim query As NameValueCollection = HttpUtility.ParseQueryString(CurrentUrl.Query) 
    Dim source As String = query.Item("source") 
    Dim search As String = query.Item("search") 
    Dim OVKEY As String = query.Item("OVKEY") 

    'Update the profile 
    profile.TestValue1 = source 
    profile.TestValue2 = search 

End Sub 

Ensuite, nous avons notre méthode pour injecter un appel AJAX dans la page. Gardez à l'esprit qu'il s'agit toujours de la classe de base. Ainsi, quelle que soit la page sur laquelle l'utilisateur atterrit sur ce code, elle s'exécutera sur la première requête de page. A l'intérieur du JavaScript, nous testons d'abord si les cookies sont activés et, si c'est le cas, appelons un gestionnaire personnalisé sur le serveur en utilisant AJAX et JQuery. Nous passons les paramètres du serveur dans ce code (bien que 2 d'entre eux aient pu être fournis par le client, les octets supplémentaires ne sont pas si importants).

La deuxième méthode met à jour le profil et contiendra ma logique personnalisée pour le faire. J'ai inclus un extrait sur la façon d'analyser les valeurs de chaîne de requête à partir d'une URL partielle. Mais la seule chose qui doit vraiment être connue ici est que c'est la méthode partagée qui met à jour le profil.

Important: Pour l'appel AJAX à la fonction, le gestionnaire suivant doit être ajouté à la section system.web du fichier web.config:

<httpModules> 
    <add name="ScriptModule" type="System.Web.Handlers.ScriptModule, System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35"/> 
</httpModules> 

j'ai décidé qu'il serait préférable de tester cookies sur le client et ne pas faire l'appel AJAX supplémentaire si les cookies sont désactivés. Pour tester les biscuits, utilisez ce code:

function areCookiesSupported() { 
    var c='c';var ret = false; 
    document.cookie = 'c=2;'; 
    if (document.cookie.indexOf(c,0) > -1) { 
     ret = true; 
    } else { 
     ret = false; 
    } 
    deleteCookie(c); 
    return ret 
} 
function deleteCookie(name) { 
    var d = new Date(); 
    document.cookie = name + '=1;expires=' + d.toGMTString() + ';' + ';'; 
} 

Ce sont 2 fonctions JavaScript (dans le fichier de coutume) qui écrivent simplement un cookie et relire pour déterminer si les cookies peuvent être lus. Il nettoie ensuite le cookie en définissant une date d'expiration dans le passé.

<%@ WebHandler Language="VB" Class="Handlers.UpdateProfile" %> 

Imports System 
Imports System.Web 
Imports System.Web.SessionState 
Imports Newtonsoft.Json 
Imports System.Collections.Generic 
Imports System.IO 

Namespace Handlers 

    Public Class UpdateProfile : Implements IHttpHandler : Implements IRequiresSessionState 

     Public Sub ProcessRequest(ByVal context As HttpContext) Implements IHttpHandler.ProcessRequest 

      If AnonymousProfile.IsAnonymousCookieStored Then 

       If context.Session.IsNewSession Then 
        'Writing to session state will reset the IsNewSession flag on the 
        'next request. This will fix a problem if there is no Session_Start 
        'defined in global.asax and no other session variables are written. 
        context.Session("ActivateSession") = "" 
       End If 

       Dim reader As New StreamReader(context.Request.InputStream) 
       Dim params As Dictionary(Of String, String) = JsonConvert.DeserializeObject(Of Dictionary(Of String, String))(reader.ReadToEnd()) 

       Dim RawUrl As String = params("RawUrl") 
       Dim ReferralUrl As String = params("ReferralUrl") 
       Dim ProductID As Integer = params("ProductID") 

       PageBase.UpdateProfile(RawUrl, ReferralUrl, ProductID) 
      End If 
     End Sub 

     Public ReadOnly Property IsReusable() As Boolean Implements IHttpHandler.IsReusable 
      Get 
       Return False 
      End Get 
     End Property 

    End Class 

End Namespace 

Ceci est notre classe HttpHandler personnalisée qui reçoit la requête AJAX. La requête est traitée uniquement si le cookie .ASPXANONYMOUS est passé (vérifié une fois de plus en utilisant la classe AnonymousProfile de mon autre message), ce qui empêchera les robots et autres scripts de l'exécuter.

Ensuite, nous exécutons du code pour mettre à jour l'objet de session s'il est requis. Pour une raison étrange, la valeur IsNewSession restera vraie jusqu'à ce que la session soit réellement mise à jour, mais seulement si un gestionnaire pour Session_Start n'existe pas dans Global.asax. Donc, pour que ce code fonctionne à la fois avec et sans fichier Global.asax et sans aucun autre code qui met à jour l'objet session, nous exécutons une mise à jour ici.

Le code suivant que j'ai saisi de this post et contient une dépendance au sérialiseur JSON.NET. J'ai été déchiré à propos de l'utilisation de cette approche en raison de la dépendance supplémentaire, mais finalement décidé que le sérialiseur JSON sera probablement utile à l'avenir que je continue à ajouter AJAX et JQuery sur le site.

Puis nous obtenons simplement les paramètres et les passons à notre méthode UpdateProfile partagée dans la classe PageBase qui a été définie précédemment.

<!-- Required for anonymous profiles --> 
<anonymousIdentification enabled="true"/> 
<profile defaultProvider="SqlProvider" inherits="AnonymousProfile"> 
    <providers> 
     <clear/> 
     <add name="SqlProvider" type="System.Web.Profile.SqlProfileProvider" connectionStringName="SqlServices" applicationName="MyApp" description="SqlProfileProvider for profile test web site"/> 
    </providers> 
    <properties> 
     <add name="TestValue1" allowAnonymous="true"/> 
     <add name="TestValue2" allowAnonymous="true"/> 
    </properties> 
</profile> 

Enfin, nous avons notre section de configuration pour les propriétés de profil, mis en place à utiliser de façon anonyme (j'omises délibérément la section de chaîne de connexion, mais sont également nécessaires chaîne de connexion correspondante et base de données). La principale chose à noter ici est l'inclusion de l'attribut inherits sur le profil. Ceci est encore une fois pour la classe AnonymousProfile définie dans other post.

Questions connexes