Существуют ли более эффективные альтернативы анонимным профилям ASP.NET 2.0? - PullRequest
2 голосов
/ 07 октября 2010

Несколько лет назад я обнаружил профили ASP.NET и быстро и легко внедрил некоторые мощные функции на своем веб-сайте.В итоге я использовал исключительно «анонимные» свойства профиля, потому что я НЕ использую членство в ASP.NET и хотел отслеживать информацию и обеспечивать персонализацию для пользователей моего сайта, даже если они не вошли в систему.

Перемотка вперед 3 годаи теперь я борюсь за то, чтобы размер моей базы данных aspnetdb не превышал 3 ГБ для моего хостинг-провайдера.Год назад я решил, что я буду хранить только 6 месяцев данных профиля, и теперь мне каждый месяц приходится запускать процедуру aspnet_Profile_DeleteInactiveProfiles с последующим запросом поддержки на мой хост для усечения файла базы данных.

Реальный недостаток заключается в том, чтоТеперь, когда у меня реализована эта функциональность, веб-сайт использует эту базу данных, и каждый раз, когда мне нужно усечь, происходит простои.Сегодня все получилось - сайт не работал более 12 часов, пока выполнял обслуживание aspnetdb, и я закончил тем, что создал резервную копию базы данных aspnetdb, чтобы я мог снова подключить веб-сайт (исходная база данных aspnetdb все еще не работает).когда я пишу это).

В прошлом году я переписал некоторые функции, потому что они хранили небольшую коллекцию двоичных объектов, и я преобразовал ее для хранения строки идентификаторов, разделенных запятыми, уменьшив ее для хранения.

Я стремился получить больше дискового пространства, но реальность такова, что просто не стоит иметь базу данных, которая ежегодно увеличивается на 4 ГБ, просто для того, чтобы делать простые вещи, например, вести список каждого пользователя за последнее время.просмотрите товары, проследите, в какой категории они находились, для кнопки «продолжить покупки», чтобы забрать их обратно, и сохраните общую сумму (только валюта), которая находится в их корзине.Реально, менее 5% данных повторно используются повторными посетителями сайта, однако невозможно заранее определить, кто из этих пользователей вернется для доступа к информации своего профиля.

Так что мой вопросСуществует ли более эффективный способ хранения данных профиля, чтобы он не занимал так много места, или есть ли альтернативы созданию записей базы данных профиля для каждого пользователя для поддержки этой функции?

Я взглянул на провайдера табличного профиля , но может ли кто-нибудь подтвердить, что при его использовании будут храниться данные с использованием меньшего дискового пространства, чем у провайдера по умолчанию?

Обновление:

Я провел некоторое исследование, и кажется, что поле nvarchar (max) является прямой заменой полей ntext, которые используются реализацией aspnetdb по умолчанию, но для этого потребуется только половинадискового пространства согласно этой статье и этой .Это означает, что я должен иметь возможность создать новую копию aspnetdb, изменить тип данных во всей его схеме и коде и перенести данные в новую базу данных.Это должно исправить как способ хранения данных, так и любую фрагментацию, которая произошла при выполнении операции сжатия.

Я завершил тестирование и перенес это решение в производство.Сначала я подумал, что возникнет проблема, потому что Microsoft жестко закодировала тип данных NText в вызов хранимой процедуры внутри SqlProfileProvider.Однако, как оказалось, SQL Server неявно преобразует параметр NText в nvarchar (MAX).Короче говоря, мне не нужно было ничего менять, кроме типов в столбцах PropertyNames и PropertyValuesString таблицы aspnet_Profiles.

Данные должны были быть перезаписаны на диск, чтобы освободить пространство, но я в итогеЭкономия около 30% в целом только путем изменения типа данных.

Другое обновление:

Я также обнаружил, что, хотя я получаю около 700 уникальных посетителей каждый день, среднее число ежедневных «пользователей» в таблице aspnet_Users составляет в среднем около 4000-5000. Я предположил, что это связано с тем, что некоторые пользователи просматривают без файлов cookie. Я провел некоторое тестирование и обнаружил, что пользователь не будет создан, если профиль не обновлен, но если вы напишете в профиль, пользователь (и профиль) будут создаваться при каждом запросе, если куки не включены.

Я работаю над обходным путем для этого. Я пытаюсь написать javascript для вызова AJAX веб-методу. Теоретически, во втором запросе (вызов AJAX) должен присутствовать файл cookie .ASPXANONYMOUS, если файлы cookie включены, и поэтому я могу смело писать в профиль. Javascript будет добавлен на страницу только в том случае, если это начало сеанса (определяется свойством Session.IsNewSession). Пользователь никогда не должен знать о вызове AJAX - он только сообщает странице о том, включены ли файлы cookie, чтобы он мог обновить профиль.

Конечно, каждый последующий запрос может просто проверять коллекцию файлов cookie, чтобы убедиться, что файл cookie .ASPXANONYMOUS присутствует. Скорее всего, я создам разделяемую функцию, которая может вызываться как AJAX с именем webmethod, так и непосредственно событием Page_Load, и использую свойство IsNewSession, чтобы определить, какой запрос разрешить запуск проверки cookie и последующее обновление профиля.

По мнению Microsoft, использование сеанса для отслеживания того, включены ли файлы cookie, противоречит цели (поскольку сеанс зависит от файлов cookie). Они предлагают сохранить ваше значение AreCookiesEnabled в базе данных, но они не упоминают, как вы должны отслеживать, где вы сохранили значение - если куки не включены, как вы можете связать запрос с записью в базе данных?

1 Ответ

1 голос
/ 11 октября 2010

Я реализовал обходной путь, который придумал в своем первоначальном посте, однако он оказался немного другим, чем я описал изначально. Это исправление может быть фактически разбито на 2 части: одна для устранения проблемы с обновлением базы данных, когда куки отключены, а вторая для определения, когда куки отключены без перенаправления.

Я уже разместил решение для анонимных профилей, создающих записи, когда куки отключены .

Так что теперь я сосредоточусь на второй части - получении информации в профиле с первой запрашиваемой страницы. Это необходимо сделать только в том случае, если вы выполняете аналитическое отслеживание или что-то подобное - первая часть будет заботиться о защите базы данных от заполнения совершенно бесполезными данными, когда 1) cookie отключены и 2) анонимные свойства профиля используются и работают с второй запрос (или первый постбэк) и далее.

Когда я исследовал вопрос проверки наличия файлов cookie, большинство решений использовали перенаправление на ту же или другую страницу и обратно. Интересно, что MSDN был тем, кто придумал решение для 2-перенаправления.

Хотя при определенных обстоятельствах перенаправление приемлемо, я не хотел, чтобы дополнительное влияние на производительность оказывало влияние на большинство наших пользователей. Вместо этого я выбрал другой подход - использовать 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

Это метод Page_Load, помещенный в мой пользовательский класс PageBase, от которого наследуются все страницы в проекте. Первое, что мы проверяем, является ли это новым сеансом, проверяя свойство Session.IsNewSession. Это свойство всегда имеет значение true, если файлы cookie отключены или это первый запрос. В обоих случаях мы не хотим писать в базу данных.

Раздел «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 из них могли быть предоставлены клиентом, дополнительные байты не так уж значительны).

Второй метод обновляет профиль и будет содержать мою собственную логику для этого. Я включил фрагмент о том, как анализировать значения строки запроса из частичного URL. Но единственное, что действительно нужно знать, это то, что это общий метод, который обновляет профиль.

Важно: Для вызова функции AJAX необходимо добавить следующий обработчик в раздел system.web файла web.config:

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

Я решил, что было бы лучше проверить файлы 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, устанавливая дату истечения срока действия в прошлом.

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

Это наш пользовательский класс HttpHandler, который получает запрос AJAX. Запрос обрабатывается только в том случае, если передается файл cookie .ASPXANONYMOUS (проверено еще раз с использованием класса AnonymousProfile из моего другого поста), что не позволит роботам и другим сценариям выполнить его.

Затем мы запускаем некоторый код для обновления объекта сеанса, если это требуется. По какой-то странной причине значение IsNewSession будет оставаться истинным до тех пор, пока сессия не будет фактически обновлена, но только если в Global.asax не существует обработчика для Session_Start. Поэтому, чтобы этот код работал как с файлом Global.asax, так и без него, а также без какого-либо другого кода, который обновляет объект сеанса, мы запускаем обновление здесь.

Следующий фрагмент кода, который я взял из этого поста и содержит зависимость от сериализатора JSON.NET. Я порвал с этим подходом из-за дополнительной зависимости, но в конечном итоге решил, что сериализатор JSON, вероятно, будет полезен в будущем, поскольку я продолжаю добавлять AJAX и JQuery на сайт.

Затем мы просто получаем параметры и передаем их нашему общему методу UpdateProfile в классе PageBase, который был определен ранее.

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

Наконец, у нас есть раздел конфигурации для свойств профиля, настроенный для использования анонимно (я намеренно пропустил раздел строки подключения, но также требуется соответствующая строка подключения и база данных). Главное, на что следует обратить внимание, - это включение атрибута наследования в профиль. Это еще раз для класса AnonymousProfile, который определен в моем другом сообщении .

...