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