我实施了我在原始帖子中提出的解决方法,但结果与我最初描述的有所不同。该修复实际上可以分为两部分 - 一个修复禁用 cookie 时更新数据库的问题,第二个用于检测何时禁用 cookie 而无需执行重定向。
我已经将解决方案发布到匿名配置文件在禁用 cookie 时创建记录。
所以现在我将专注于第二部分 - 从请求的第一页获取信息到配置文件中。仅当您正在执行分析跟踪或类似操作时才需要这样做 - 第一部分将负责保护数据库在 1) 禁用 cookie 和 2) 使用匿名配置文件属性并从第二个请求(或第一个回发)开始。
当我研究检查是否启用 cookie 的问题时,大多数解决方案都使用重定向到同一页面或不同页面并再次返回。有趣的是,MSDN是提出 2-redirect 解决方案的人。
虽然在某些情况下重定向是可以接受的,但我不希望额外的性能影响影响我们的大多数用户。相反,我选择了另一种方法——在第一个请求完成后,使用 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。在这两种情况下,我们都不想写入数据库。
如果客户端接受了会话 cookie 并且这不是对服务器的第一个请求,则“else if”部分运行。关于此代码片段需要注意的是,这两个部分不能在同一个请求中运行,这意味着每个请求只能更新配置文件 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 调用,必须将以下处理程序添加到 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 值将保持为真,直到会话实际更新,但前提是 Global.asax 中不存在 Session_Start 的处理程序。因此,为了使此代码在有和没有 Global.asax 文件以及没有任何其他更新会话对象的代码的情况下都能正常工作,我们在此处运行更新。
我从这篇文章中获取的下一段代码包含对 JSON.NET 序列化程序的依赖。由于额外的依赖性,我对使用这种方法感到很痛苦,但最终决定 JSON 序列化程序在未来可能会很有价值,因为我会继续向网站添加 AJAX 和 JQuery。
然后我们只需获取参数并将它们传递给我们之前定义的 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>
最后,我们有配置文件属性的配置部分,设置为匿名使用(我故意省略了连接字符串部分,但还需要相应的连接字符串和数据库)。这里要注意的主要事情是在配置文件中包含继承属性。这又是针对在我的另一篇文章中定义的 AnonymousProfile 类。