Jag implementerade lösningen jag kom på i mitt ursprungliga inlägg, men det visade sig vara lite annorlunda än vad jag ursprungligen beskrev. Korrigeringen kan faktiskt delas upp i två delar - en för att åtgärda problemet med att databasen uppdateras när cookies är inaktiverade, och den andra för att upptäcka när cookies är inaktiverade utan att göra en omdirigering.
Jag har redan lagt upp lösning för de anonyma profilerna som skapar poster när cookies är inaktiverade .
Så nu kommer jag att fokusera på den andra delen - att få in information i profilen från den första sidan som efterfrågas. Detta behöver bara göras om du håller på med analysspårning eller liknande - den första delen kommer att ta hand om att skydda databasen från att fyllas med totalt värdelös data när 1) cookies är inaktiverade och 2) anonyma profilegenskaper används och fungerar från den andra begäran (eller första postback) och framåt.
När jag undersökte frågan om att kontrollera om cookies är aktiverade använde de flesta lösningarna en omdirigering antingen till samma sida eller en annan sida och tillbaka igen. Intressant nog är MSDN var den som kom med 2-omdirigeringslösningen.
Även om en omdirigering under vissa omständigheter är acceptabel, ville jag inte att den extra prestandapåverkan skulle påverka majoriteten av våra användare. Istället valde jag ett annat tillvägagångssätt - använd AJAX för att köra kod på servern efter att den första begäran har slutförts. Även om detta har fördelen att det inte orsakar en omdirigering, har det nackdelen att det inte fungerar när JavaScript är inaktiverat. Jag valde dock detta tillvägagångssätt eftersom andelen data som går förlorad vid den första begäran är obetydlig och själva applikationen är inte beroende av dessa data.
Så, gå igenom från början av processen till slutet...
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
Detta är metoden Page_Load placerad i min anpassade PageBase-klass, som alla sidor i projektet ärver från. Det första vi kontrollerar är om detta är en ny session genom att kontrollera egenskapen Session.IsNewSession. Den här egenskapen är alltid sann om cookies är inaktiverade eller om detta är den första begäran. I båda fallen vill vi inte skriva till databasen.
Avsnittet "annat om" körs om klienten accepterade sessionskakan och detta inte är den första begäran till servern. Saken att notera med detta kodavsnitt är att båda avsnitten inte kan köras i samma begäran, vilket innebär att profilen bara kan uppdateras 1 (eller 0) gånger per begäran.
Klassen AnonymousProfile ingår i mina annat inlägg .
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
Därefter har vi vår metod för att injicera ett AJAX-anrop på sidan. Tänk på att detta fortfarande är basklassen så oavsett vilken sida som användaren landar på den här koden kommer den att köras på den första sidans begäran.
Inuti JavaScript testar vi först för att se om cookies är aktiverade och i så fall anropar vi en anpassad hanterare på servern med AJAX och JQuery. Vi skickar parametrarna från servern till den här koden (även om två av dem bara kunde ha levererats av klienten, är de extra byten inte så betydande).
Den andra metoden uppdaterar profilen och kommer att innehålla min anpassade logik för att göra det. Jag inkluderade ett utdrag om hur man analyserar frågesträngsvärdena från en delvis URL. Men det enda som verkligen behöver vara känt här är att detta är den delade metoden som uppdaterar profilen.
Viktigt: För att AJAX-anropet ska fungera måste följande hanterare läggas till i system.web-delen av web.config-filen:
<httpModules>
<add name="ScriptModule" type="System.Web.Handlers.ScriptModule, System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35"/>
</httpModules>
Jag bestämde mig för att det skulle vara bäst att testa för cookies på klienten och inte göra det extra AJAX-anropet om cookies är inaktiverade. För att testa cookies, använd denna kod:
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() + ';' + ';';
}
Det här är 2 JavaScript-funktioner (i en anpassad .js-fil) som helt enkelt skriver en cookie och läser tillbaka den för att avgöra om cookies kan läsas. Den rensar sedan upp kakan genom att ange ett utgångsdatum i det förflutna.
<%@ 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
Detta är vår Custom HttpHandler-klass som tar emot AJAX-förfrågan. Begäran behandlas endast om .ASPXANONYMOUS-cookien skickas in (kontrolleras igen genom att använda klassen AnonymousProfile från mitt andra inlägg), vilket kommer att förhindra robotar och andra skript från att köra den.
Därefter kör vi lite kod för att uppdatera sessionsobjektet om det krävs. Av någon konstig anledning kommer IsNewSession-värdet att förbli sant tills sessionen faktiskt uppdateras, men bara om en hanterare för Session_Start inte finns i Global.asax. Så för att få den här koden att fungera både med och utan en Global.asax-fil och utan någon annan kod som uppdaterar sessionsobjektet, kör vi en uppdatering här.
Nästa bit kod hämtade jag från det här inlägget och innehåller ett beroende till JSON.NET serializer. Jag var sliten över att använda det här tillvägagångssättet på grund av det extra beroendet, men beslutade till slut att JSON-serializern sannolikt kommer att vara värdefull i framtiden när jag fortsätter att lägga till AJAX och JQuery på webbplatsen.
Sedan hämtar vi helt enkelt parametrarna och skickar dem till vår delade UpdateProfile-metod i klassen PageBase som definierades tidigare.
<!-- 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>
Slutligen har vi vår konfigurationssektion för profilegenskaperna, inställd för att användas anonymt (jag har avsiktligt utelämnat anslutningssträngsektionen, men en motsvarande anslutningssträng och databas krävs också). Det viktigaste att notera här är inkluderingen av arvsattributet i profilen. Detta är återigen för klassen AnonymousProfile som definieras i min annat inlägg .