2

数年前に ASP.NET プロファイルを発見し、自分の Web サイトにいくつかの強力な機能をすばやく簡単に実装できることに気付きました。私は ASP.NET メンバーシップを使用しておらず、サイトのユーザーがログインしていなくても、情報を追跡してパーソナライズを提供したかったため、「匿名」プロファイル プロパティのみを使用することになりました。

それから 3 年が経ちましたが、現在、aspnetdb データベースのサイズをホスティング プロバイダーの 3 GB の制限内に収めるのに苦労しています。私は 1 年前に、プロファイル データを 6 か月分だけ保持することに決めました。毎月 aspnet_Profile_DeleteInactiveProfiles プロシージャを実行し、その後、データベース ファイルを切り捨てるためにホストにサポート リクエストを送信する必要があります。

本当の欠点は、この機能を実装したことです。Web サイトはこのデータベースに依存しており、切り捨てが必要になるたびにダウンタイムが発生します。今日は本当に幸運でした。aspnetdb のメンテナンス中にサイトが 12 時間以上ダウンしていたため、Web サイトを再びオンラインにできるように aspnetdb データベースのフェールオーバー コピーを作成することになりました (元の aspnetdb データベースはまだダウンしています)。私がこれを書いているように)。

昨年、バイナリ オブジェクトの小さなコレクションを格納していたため、一部の機能を書き直しました。これを変換して、ID のコンマ区切り文字列を格納し、格納用に小さくしました。

ディスク容量を増やすことを検討しましたが、実際には、各ユーザーが最近閲覧した製品のリストを保持し、追跡するなどの単純なことを行うためだけに、毎年 4 GB ずつ増加するデータベースを使用する価値はありません。 「買い物を続ける」ボタンで元に戻し、ショッピング カートにある合計金額 (通貨のみ) を保存します。現実的には、サイトへの繰り返しの訪問者によって再利用されるデータは 5% 未満ですが、これらのユーザーのどれがプロファイル情報にアクセスするために戻ってくるかを事前に知る方法はありません。

だから私の質問は、プロファイル データを格納するためのより効率的な方法があるかどうか、つまり、この機能をサポートするためにすべてのユーザーのプロファイル データベース レコードを作成する代わりに、それほど多くのスペースを占有しないかどうかです。

table profile providerを見てみましたが、それを使用すると、デフォルトのプロバイダーよりも少ないディスク容量を使用してデータが保存されるかどうかを誰かが保証できますか?

アップデート:

いくつかの調査を行ったところ、nvarchar(max) フィールドは、aspnetdb の既定の実装で使用される ntext フィールドの直接の代替であると思われますが、この記事この記事によると、ディスク容量の半分しか必要としません。これは、aspnetdb の新しいコピーを作成し、そのスキーマとコード全体でデータ型を変更し、データを新しいデータベースに転送できることを意味します。これにより、データの保存方法と、縮小操作の実行時に発生した断片化の両方が修正されます。

テストを完了し、このソリューションを本番環境に移行しました。最初は、Microsoft が NText データ型を SqlProfileProvider 内のストアド プロシージャ コールにハードコーディングしたため、問題があると思いました。ただし、SQL Server は暗黙的に NText パラメーターを nvarchar(MAX) に変換することが判明しています。つまり、aspnet_Profiles テーブルの PropertyNames 列と PropertyValuesString 列の型以外は何も変更する必要はありませんでした。

データをディスクに書き直してスペースを空ける必要がありましたが、データ型を変更するだけで全体で約 30% 節約できました。

別の更新:

また、私は毎日約 700 人のユニーク ビジターを獲得していますが、aspnet_Users テーブルの 1 日の平均 "ユーザー" 数は平均して約 4000 ~ 5000 であることも発見しました。これは、一部のユーザーが Cookie なしでブラウジングしていることが原因であると理論付けました。いくつかのテストを行ったところ、プロファイルが更新されていないとユーザーが作成されないことがわかりましたが、プロファイルに書き込むと、Cookie が有効になっていない場合、要求ごとにユーザー (およびプロファイル) が作成されます。

私はこれの回避策に取り組んでいます。Web メソッドへの AJAX 呼び出し用の JavaScript を記述しようとしています。理論的には、Cookie が有効になっている場合、2 番目の要求 (AJAX 呼び出し) には .ASPXANONYMOUS Cookie が存在する必要があるため、プロファイルに安全に書き込むことができます。これがセッションの開始時 (Session.IsNewSession プロパティによって決定される) である場合にのみ、javascript がページに挿入されます。ユーザーは AJAX 呼び出しを意識する必要はありません。これは、プロファイルを更新できるように Cookie が有効になっているかどうかをページに通知するためだけのものです。

もちろん、後続のすべての要求では、Cookie コレクションを簡単にチェックして、.ASPXANONYMOUS Cookie が存在することを確認できます。ほとんどの場合、webmethod と呼ばれる AJAX と Page_Load イベントの両方から直接呼び出すことができる共有関数を作成し、IsNewSession プロパティを使用して、Cookie チェックとその後のプロファイル更新の実行を許可する要求を決定します。

Microsoft によると、Cookie が有効かどうかを追跡するためにセッションを使用すると、目的が無効になります (セッションは Cookie に依存するため)。彼らは、AreCookiesEnabled 値をデータベースに保持することを提案していますが、値を保存した場所を追跡する方法については言及していません。Cookie が有効になっていない場合、リクエストをデータベース レコードにリンクするにはどうすればよいでしょうか。

4

1 に答える 1

1

元の投稿で思いついた回避策を実装しましたが、最初に説明したものとは少し異なることが判明しました。この修正は、実際には 2 つの部分に分けることができます。1 つは、Cookie が無効になっているときにデータベースが更新される問題を修正するためのもので、もう 1 つは、リダイレクトを行わずに Cookie が無効になったことを検出するためのものです。

Cookie が無効になっている場合に記録を作成する匿名プロファイルへの解決策を既に投稿しました。

ここでは、要求された最初のページからプロファイルに情報を取得するという 2 番目の部分に焦点を当てます。これは、分析追跡などを行っている場合にのみ実行する必要があります。最初の部分は、1) Cookie が無効で、2) 匿名プロファイル プロパティが使用されており、 2 番目のリクエスト (または最初のポストバック) 以降。

Cookie が有効になっているかどうかを確認する問題を調査したところ、ほとんどのソリューションでは、同じページまたは別のページにリダイレクトしてから再び戻るという方法が使用されていました。興味深いことに、2 リダイレクト ソリューションを思いついたのはMSDNでした。

特定の状況ではリダイレクトは許容されますが、パフォーマンスへの余分な影響が大多数のユーザーに影響することは望んでいませんでした。代わりに、別のアプローチを選択しました。最初のリクエストが完了した後、AJAX を使用してサーバー上でコードを実行します。これにはリダイレクトが発生しないという利点がありますが、JavaScript が無効になっていると機能しないという欠点があります。ただし、最初の要求で失われるデータの割合は重要ではなく、アプリケーション自体はこのデータに依存しないため、このアプローチを選択しました。

それで、プロセスの最初から最後までを歩いて...

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

これは、プロジェクト内のすべてのページが継承するカスタム PageBase クラスに配置された Page_Load メソッドです。最初に、Session.IsNewSession プロパティをチェックして、これが新しいセッションかどうかを確認します。Cookie が無効になっている場合、またはこれが最初の要求である場合、このプロパティは常に true です。どちらの場合も、データベースに書き込みたくありません。

「else if」セクションは、クライアントがセッション Cookie を受け入れ、これがサーバーへの最初の要求ではない場合に実行されます。このコード スニペットについて注意すべき点は、両方のセクションを同じリクエストで実行できないことです。つまり、プロファイルはリクエストごとに 1 回 (または 0 回) しか更新できません。

AnonymousProfile クラスは私の別の投稿に含まれています。

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

次に、ページに AJAX 呼び出しを挿入するメソッドがあります。これは基本クラスであるため、ユーザーがこのコードに到達したページに関係なく、最初のページ要求で実行されることに注意してください。

JavaScript の内部では、まず Cookie が有効になっているかどうかをテストし、有効になっている場合は、AJAX と JQuery を使用してサーバー上でカスタム ハンドラーを呼び出します。サーバーからこのコードにパラメーターを渡します (そのうちの 2 つはクライアントによって提供された可能性がありますが、余分なバイトはそれほど重要ではありません)。

2 番目の方法は、プロファイルを更新し、それを行うためのカスタム ロジックを含めます。部分的な URL からクエリ文字列の値を解析する方法のスニペットを含めました。ただし、ここで知っておく必要がある唯一のことは、これがプロファイルを更新する共有メソッドであることです。

重要: AJAX 呼び出しを機能させるには、次のハンドラーを web.config ファイルの system.web セクションに追加する必要があります。

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

クライアントで Cookie をテストし、Cookie が無効になっている場合は余分な AJAX 呼び出しを行わないことが最善であると判断しました。Cookie をテストするには、次のコードを使用します。

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() + ';' + ';';
}

これらは 2 つの JavaScript 関数 (カスタム .js ファイル内) であり、単に Cookie を書き込み、それを読み戻して、Cookie を読み取ることができるかどうかを判断します。次に、過去の有効期限を設定して Cookie をクリーンアップします。

<%@ 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

これは、AJAX リクエストを受け取るカスタム HttpHandler クラスです。リクエストは、.ASPXANONYMOUS Cookie が渡された場合にのみ処理され (別の投稿の AnonymousProfile クラスを利用してもう一度チェックされます)、ロボットや他のスクリプトがそれを実行するのを防ぎます。

次に、必要に応じてセッション オブジェクトを更新するコードを実行します。奇妙な理由により、IsNewSession 値は、セッションが実際に更新されるまで true のままになりますが、それは、Session_Start のハンドラーが Global.asax に存在しない場合のみです。したがって、Global.asax ファイルの有無にかかわらず、またセッション オブジェクトを更新する他のコードがなくてもこのコードが機能するように、ここで更新を実行します。

この投稿から取得した次のコードには、JSON.NET シリアライザーへの依存関係が含まれています。余分な依存関係があるため、このアプローチを使用するかどうか迷っていましたが、サイトに AJAX と JQuery を追加し続けるにつれて、JSON シリアライザーが将来的に役立つ可能性が高いと最終的に判断しました。

次に、パラメータを取得して、前に定義した PageBase クラスの共有 UpdateProfile メソッドに渡します。

<!-- 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>

最後に、匿名で使用するように設定されたプロファイル プロパティの構成セクションがあります (意図的に接続文字列セクションを省略しましたが、対応する接続​​文字列とデータベースも必要です)。ここで注意すべき主なことは、プロファイルに inherits 属性が含まれていることです。これも、別の投稿で定義されている AnonymousProfile クラス用です。

于 2010-10-10T23:09:16.267 に答える