65.9K
CodeProject 正在变化。 阅读更多。
Home

ASP.NET Ajax 揭秘

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.94/5 (98投票s)

2006 年 11 月 30 日

CPOL

18分钟阅读

viewsIcon

467366

downloadIcon

1428

改变核心运行时的高性能技巧和硬核技巧,不适合胆小者

ASP.net Ajax under the hood secrets

引言

Microsoft ASP.NET Ajax 是一个功能强大的 Ajax 框架。然而,当您构建像 Web 2.0 世界中的那些真实 Ajax 站点时,您会遇到许多在任何地方都很难找到文档记录的问题。在本文中,我将展示我在构建 Pageflakes 时学到的一些高级想法。我们将探讨批量调用、Ajax 调用超时、浏览器调用阻塞问题、ASP.NET 2.0 中 Web 服务响应缓存的错误,等等。

为什么使用 ASP.NET Ajax

当别人看到 Pageflakes 时,他们问我的第一个问题是:“你为什么不使用 Protopage 或 Dojo 库?为什么选择 Atlas?”Microsoft Atlas(现已更名为 ASP.NET Ajax)是一个非常有前途的 Ajax 框架。他们为此付出了巨大的努力,构建了许多可重用组件,这些组件可以真正为您节省大量时间,并以相对较低的努力或改动为您的 Web 应用程序提供完整的改头换面。它与 ASP.NET 集成得很好,并且与 ASP.NET Membership 和 Profile 提供程序兼容。Ajax Control Toolkit 项目包含 28 个扩展器,您可以将它们拖放到页面上,调整一些属性,然后为页面添加非常酷的效果。查看示例,了解 ASP.NET Ajax 框架现在有多么强大。

当我们开始开发 Pageflakes 时,Atlas 尚处于起步阶段。我们只能使用 Atlas 的页面方法和 Web 服务方法调用功能。我们不得不自己构建拖放、组件架构、弹出窗口、折叠/展开功能等。然而,现在您可以从 Atlas 获取所有这些功能,从而节省大量开发时间。Atlas 的 Web 服务代理功能堪称奇迹。您可以将一个 `<script> 标签指向一个 *.asmx 文件,并直接从 Web 服务定义生成一个 JavaScript 类。

JavaScript 类包含 Web 服务类中的确切方法。这使得添加/删除新的 Web 服务以及添加/删除 Web 服务中的方法变得非常容易,而无需对客户端进行任何更改。它还提供了对 Ajax 调用的许多控制,并提供了丰富的 JavaScript 异常捕获功能。服务器端异常会很好地抛出到客户端 JavaScript 代码,您可以捕获它们并向用户显示格式良好的错误消息。Atlas 与 ASP.NET 2.0 配合得非常好,完全消除了集成问题。您无需担心页面方法和 Web 服务方法的身份验证和授权。这样,您可以节省大量的客户端代码——当然,Atlas Runtime 因此非常庞大——您可以将更多精力集中在自己的代码上,而不是构建所有这些与框架相关的代码。

Atlas 的最新版本与 ASP.NET Membership 和 Profile 服务配合得很好,可以实现无需页面回发的 JavaScript 登录/登出功能。您可以直接从 JavaScript 读取/写入 `Profile` 对象。当您的 Web 应用程序大量使用 ASP.NET Membership 和 Profile 提供程序时(我们在 Pageflakes 就是如此),这会非常方便。

在 Atlas 的早期版本中,没有办法进行 HTTP `GET` 调用。所有调用都是 HTTP `POST`,因此调用成本很高。现在您可以指定哪些调用应该是 HTTP `GET`。一旦有了 HTTP `GET`,您就可以利用 HTTP 响应缓存功能,我很快就会向您展示。

批量调用并不总是更快

ASP.NET Ajax 在 CTP 版本(及早期版本)中有一个功能,可以将多个请求批处理成一个请求。它会自动运行,您不会注意到任何异常,也不需要编写任何特殊代码。一旦启用 Batch 功能,在一定时间内发出的所有 Web 服务调用都会被批处理成一个调用。这样,就可以节省往返时间和总响应时间。

实际响应时间可能会缩短,但感知延迟会更高。如果三个 Web 服务调用被批处理,第一个调用不会先完成。如果您正在进行一些 UI 更新,所有三个调用会在每个 WS 调用完成后同时完成;它不会一个接一个地发生。所有调用一次性完成,然后 UI 会一次性更新。

因此,您看不到 UI 的增量更新。相反,您会看到 UI 更新的长时间延迟。如果其中任何一个调用——比如说第三个调用——下载了大量数据,用户将看不到任何进展,直到所有三个调用都完成。所以,第一个调用的持续时间几乎等于所有三个调用的总和。虽然实际总持续时间减少了,但感知持续时间却更长。批量调用在每个调用传输的数据量很少时非常方便。因此,三个小调用可以在一次往返中执行。

让我们处理一个一个调用三个电话的场景。调用实际执行的方式如下。

第二个调用需要一点时间才能到达服务器,因为第一个调用正在占用带宽。出于同样的原因,下载也需要更长的时间。浏览器会打开两个到服务器的并发连接,所以一次只能进行两个调用。一旦第二个/第一个调用完成,就进行第三个调用。当这三个调用被批处理成一个时

这里总下载时间减少了(如果启用了 IIS 压缩),并且只有一个网络延迟开销。所有三个调用都在服务器上一次性执行,合并的响应在一个调用中下载。然而,对用户来说,感知速度会变慢,因为所有 UI 更新都发生在 **整个批量** 调用完成 **之后**。批量调用完成的总持续时间将始终高于两个调用的持续时间。此外,如果您一次又一次地执行大量 UI 更新,Internet Explorer 会暂时冻结,给用户留下不良印象。有时,昂贵的 UI 更新会导致浏览器屏幕变为空白。Firefox 和 Opera 没有这个问题。

批量调用也有一些优点。总下载时间比下载单个调用响应的时间要短,因为如果您在 IIS 中使用 gzip 压缩,则整个结果会被压缩,而不是单独压缩每个结果。所以,总的来说,对于小型调用,批量调用更好。但是,如果一个调用将发送大量数据或返回,比如说,20 KB 的响应,那么最好不要使用批量。批量调用还会出现另一个问题,例如,两个调用非常小,但第三个调用却很大。如果这三个调用被批处理,那么较小的调用将由于第三个较大的调用而遭受长时间的延迟。

糟糕的调用导致好的调用超时

如果两个 HTTP 调用意外地卡住太长时间,这两个糟糕的调用也会导致一些好的调用超时,而这些调用在此期间已被排队。这里有一个很好的例子

function TestTimeout()
{
    debug.trace("--Start--");
    TestService.set_defaultFailedCallback( 
            function(result, userContext, methodName)
    {
        var timedOut = result.get_timedOut();
        if( timedOut )
            debug.trace( "Timedout: " + methodName );
        else
            debug.trace( "Error: " + methodName );
    });
    TestService.set_defaultSucceededCallback( function(result)
    {
        debug.trace( result );
    });
    
    TestService.set_timeout(5000);
    TestService.HelloWorld("Call 1");
    TestService.Timeout("Call 2");
    TestService.Timeout("Call 3");
    TestService.HelloWorld("Call 4");
    TestService.HelloWorld("Call 5");
    TestService.HelloWorld(null); // This one will produce Error

}

在服务器端,Web 服务非常简单

[WebService(Namespace = "http://tempuri.org/")]
[WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
[ScriptService]
public class TestService : System.Web.Services.WebService {

    public TestService () {

        //Uncomment the following line if using designed components 

        //InitializeComponent(); 

    }

    [WebMethod][ScriptMethod(UseHttpGet=true)]
    public string HelloWorld(string param) {
        Thread.Sleep(1000);
        return param;
    }
    
    [WebMethod][ScriptMethod(UseHttpGet=true)]
    public string Timeout(string param) {
        Thread.Sleep(10000);
        return param;
    }
}

我正在调用服务器上的一个名为 `Timeout` 的方法,它什么都不做,只是长时间等待,直到调用超时。之后,我调用了一个不会超时的方法。您猜输出是什么?

只有第一次调用成功了。所以,如果浏览器在任何时候的两个连接都卡住,那么您可以预期其他等待的调用也会超时。在 Pageflakes 中,我们曾收到近 400 到 600 条来自用户浏览器的超时错误报告。我们一直无法弄清楚这是如何发生的。起初,我们怀疑是互联网连接速度慢,但这么多用户不可能都慢。然后我们怀疑是托管提供商的网络有问题。我们进行了大量的网络分析,以确定网络是否存在任何问题,但我们未能检测到任何问题。

我们使用 SQL Profiler 查看是否有长时间运行的查询导致 ASP.NET 请求执行时间超时,但没有成功。我们最终发现,这主要是由于一些卡住的糟糕调用导致好的调用也过期了。因此,我们修改了 Atlas Runtime,并在其中引入了自动重试,问题就完全消失了。然而,这种自动重试需要对 ASP.NET Ajax 框架 JavaScript 进行复杂的 心脏搭桥手术。其思想是让每个调用在超时时重试一次。为了做到这一点,我们需要拦截所有 Web 方法调用,并在 `onFailed` 回调上实现一个钩子,如果失败原因是超时,它会再次调用同一个 Web 方法。

我们在旅行时还发现了另一个有趣的现象,那就是,当我们尝试从酒店或机场的无线互联网连接访问 Pageflakes 时,第一次访问总是失败,并且第一次尝试的所有 Web 服务调用总是失败。直到我们刷新,什么都无法正常工作。这也是我们实现 Web 服务调用即时自动重试的主要原因,这解决了问题。

以下是实现方法。`Sys$Net$WebServiceProxy$invoke` 函数负责进行所有 Web 服务调用。因此,我们用自定义实现替换该函数,该实现传递一个自定义的 `onFailure` 回调。每当发生错误或超时时,该自定义回调就会触发。因此,当发生超时时,它会再次调用此函数,从而发生重试。

Sys.Net.WebServiceProxy.retryOnFailure = 
    function(result, userContext, methodName, retryParams, onFailure)
{
    if( result.get_timedOut() )
    {
        if( typeof retryParams != "undefined" )
        {
            debug.trace("Retry: " + methodName);
            Sys.Net.WebServiceProxy.original_invoke.apply(this, retryParams );
        }
        else
        {
            if( onFailure ) onFailure(result, userContext, methodName);
        }
    }
    else
    {
        if( onFailure ) onFailure(result, userContext, methodName);
    }
}

Sys.Net.WebServiceProxy.original_invoke = Sys.Net.WebServiceProxy.invoke;
Sys.Net.WebServiceProxy.invoke = 
    function Sys$Net$WebServiceProxy$invoke(servicePath, methodName, useGet, 
        params, onSuccess, onFailure, userContext, timeout)
{   
    var retryParams = [ servicePath, methodName, useGet, params, 
        onSuccess, onFailure, userContext, timeout ];
    
    // Call original invoke but with a new onFailure

    // handler which does the auto retry

    var newOnFailure = Function.createDelegate( this, 
        function(result, userContext, methodName) 
        { 
            Sys.Net.WebServiceProxy.retryOnFailure(result, userContext, 
                methodName, retryParams, onFailure); 
        } );
        
    Sys.Net.WebServiceProxy.original_invoke(servicePath, methodName, useGet, 
        params, onSuccess, newOnFailure, userContext, timeout);
}

运行时,它将重试每个超时的调用一次。

这里您可以看到第一个方法成功了,而其他所有方法都超时并重试。然而,您会看到,在重试之后,它们都成功了。发生这种情况是因为服务器端方法在重试时不会超时。因此,这证明了我们的实现是正确的。

浏览器一次允许两个调用,并且不期望任何顺序

浏览器一次会向一个域发出两个并发的 Ajax 调用。如果您发出五个 Ajax 调用,浏览器将首先发出两个调用,然后等待其中任何一个完成。然后它发出另一个调用,直到所有剩余的四个调用都完成。此外,您不能期望调用以您发出调用的相同顺序执行。原因如下

这里您看到调用 3 的响应下载量很大,因此比调用 5 花费的时间更长。所以,调用 5 实际上在调用 3 之前执行。HTTP 的世界是不可预测的。

浏览器在队列中有超过两个调用时不会响应

试试这个:访问世界上任何一个首次访问时会加载大量 RSS 的起始页(例如 Pageflakes、Netvibes、Protopage),然后在加载时,尝试单击一个链接,该链接会带您到另一个网站,或尝试访问另一个网站。您会看到浏览器卡住了。直到浏览器中的所有排队 Ajax 调用完成,浏览器才接受任何其他活动。这在 Internet Explorer 中最糟糕;Firefox 和 Opera 没有那么多问题。

问题在于,当您发出大量 Ajax 调用时,浏览器会将所有调用保存在队列中,并一次执行两个。因此,如果您单击某个内容或尝试导航到另一个站点,浏览器必须等待正在运行的调用完成,然后才能接受另一个调用。解决此问题的方法是防止一次将超过两个调用排入浏览器队列。我们需要自己维护一个队列,并将调用一次一个地从我们的队列发送到浏览器的队列。解决方案非常令人震惊;准备好迎接冲击

var GlobalCallQueue = {
    _callQueue : [],    // Maintains the list of webmethods to call

    _callInProgress : 0,    // Number of calls currently in progress by browser

    _maxConcurrentCall : 2, // Max number of calls to execute at a time

    _delayBetweenCalls : 50, // Delay between execution of calls 

    call : function(servicePath, methodName, useGet, 
        params, onSuccess, onFailure, userContext, timeout)
    {
        var queuedCall = new QueuedCall(servicePath, methodName, useGet, 
            params, onSuccess, onFailure, userContext, timeout);

        Array.add(GlobalCallQueue._callQueue,queuedCall);
        GlobalCallQueue.run();
    },
    run : function()
    {
        /// Execute a call from the call queue

        
        if( 0 == GlobalCallQueue._callQueue.length ) return;
        if( GlobalCallQueue._callInProgress < 
            GlobalCallQueue._maxConcurrentCall )
        {
            GlobalCallQueue._callInProgress ++;
            // Get the first call queued

            var queuedCall = GlobalCallQueue._callQueue[0];
            Array.removeAt( GlobalCallQueue._callQueue, 0 );
            
            // Call the web method

            queuedCall.execute();
        }
        else
        {
            // cannot run another call. Maximum concurrent 

            // webservice method call in progress

        }            
    },
    callComplete : function()
    {
        GlobalCallQueue._callInProgress --;
        GlobalCallQueue.run();
    }
};

QueuedCall = function( servicePath, methodName, useGet, params, 
    onSuccess, onFailure, userContext, timeout )
{
    this._servicePath = servicePath;
    this._methodName = methodName;
    this._useGet = useGet;
    this._params = params;
    
    this._onSuccess = onSuccess;
    this._onFailure = onFailure;
    this._userContext = userContext;
    this._timeout = timeout;
}

QueuedCall.prototype = 
{
    execute : function()
    {
        Sys.Net.WebServiceProxy.original_invoke( 
            this._servicePath, this._methodName, this._useGet, this._params,  
            Function.createDelegate(this, this.onSuccess), // Handle call complete

            Function.createDelegate(this, this.onFailure), // Handle call complete

            this._userContext, this._timeout );
    },
    onSuccess : function(result, userContext, methodName)
    {
        this._onSuccess(result, userContext, methodName);
        GlobalCallQueue.callComplete();            
    },        
    onFailure : function(result, userContext, methodName)
    {
        this._onFailure(result, userContext, methodName);
        GlobalCallQueue.callComplete();            
    }        
};

`QueuedCall` 封装了一个 Web 方法调用。它接受实际 Web 服务调用的所有参数,并覆盖 `onSuccess` 和 `onFailure` 回调。我们需要知道何时调用完成或失败,以便我们可以从队列中发出另一个调用。`GlobalCallQueue` 维护所有 Web 服务调用的列表。每当调用 Web 方法时,我们首先将调用放入 `GlobalCallQueue` 中,然后自己一个一个地执行队列中的调用。这确保了浏览器一次不会获得超过 2 个 Web 服务调用,因此浏览器不会卡住。为了启用基于队列的调用,我们需要再次覆盖 ASP.NET Ajax Web 方法调用,就像我们之前做的那样。

Sys.Net.WebServiceProxy.original_invoke = Sys.Net.WebServiceProxy.invoke;
Sys.Net.WebServiceProxy.invoke = 
    function Sys$Net$WebServiceProxy$invoke(servicePath, methodName, 
        useGet, params, onSuccess, onFailure, userContext, timeout)
{   
    GlobalCallQueue.call(servicePath, methodName, useGet, params, 
        onSuccess, onFailure, userContext, timeout);
}

缓存 Web 服务响应到浏览器并显着节省带宽

浏览器可以在用户的硬盘上缓存图像、JavaScript 和 CSS 文件,如果调用是 HTTP `GET`,它们也可以缓存 XML HTTP 调用。缓存基于 URL。如果 URL 相同且已在计算机上缓存,那么当再次请求时,响应将从缓存中加载,而不是从服务器加载。基本上,浏览器可以缓存任何 HTTP `GET` 调用,并根据 URL 返回缓存的数据。如果您将 XML HTTP 调用作为 HTTP `GET` 进行,并且服务器返回一些特殊标头指示浏览器缓存响应,那么在将来的调用中,响应将立即从缓存中加载。这节省了网络往返延迟和下载时间。

在 Pageflakes,我们缓存用户的状态,以便当用户第二天再次访问时,用户会获得一个从浏览器缓存即时加载的页面,而不是从服务器加载。因此,第二次加载变得非常快。我们还缓存页面上根据用户操作出现的几个小部分。当用户再次执行相同的操作时,会立即从本地缓存加载缓存结果,从而节省网络往返时间。用户获得一个加载速度快且响应迅速的站点。感知速度急剧增加。

其思想是在进行 Atlas Web 服务调用时进行 HTTP `GET` 调用,并返回一些特定的 HTTP `Response` 标头,指示浏览器在特定持续时间内缓存响应。如果您在响应中返回 `Expires` 标头,浏览器将缓存 XML HTTP 响应。您需要在响应中返回两个标头,它们将指示浏览器缓存响应

HTTP/1.1 200 OK 
Expires: Fri, 1 Jan 2030 
Cache-Control: public

这将指示浏览器缓存响应直到 2030 年 1 月。只要您使用相同的参数进行相同的 XML HTTP 调用,您就会从计算机中获得缓存的响应,并且不会有调用发送到服务器。还有更高级的方法可以进一步控制响应缓存。例如,这里有一个标头,它将指示浏览器缓存 60 秒,但在 60 秒后不联系服务器获取新响应。当浏览器本地缓存过期 60 秒后,它还会阻止代理返回缓存的响应。

HTTP/1.1 200 OK 
Cache-Control: private, must-revalidate, proxy-revalidate, max-age=60

让我们尝试从 ASP.NET Web 服务调用生成这样的响应标头

[WebMethod][ScriptMethod(UseHttpGet=true)]
public string CachedGet()
{
    TimeSpan cacheDuration = TimeSpan.FromMinutes(1);
    Context.Response.Cache.SetCacheability(HttpCacheability.Public);
    Context.Response.Cache.SetExpires(DateTime.Now.Add(cacheDuration));
    Context.Response.Cache.SetMaxAge(cacheDuration);
    Context.Response.Cache.AppendCacheExtension(
           "must-revalidate, proxy-revalidate");

    return DateTime.Now.ToString();
}

这将产生以下响应标头

`Expires` 标头设置正确,但问题在于 `Cache` 控制。它显示 `max-age` 设置为零,这将阻止浏览器进行任何类型的缓存。如果您真的想阻止缓存,则应发出这样的缓存控制标头。这看起来恰恰相反。输出一如既往地不正确且未缓存

ASP.NET 2.0 中存在一个错误,您无法更改 `max-age` 标头。由于 `max-age` 设置为零,ASP.NET 2.0 将 `Cache` 控制设置为 `private`,因为 `max-age = 0` 意味着不需要缓存。您无法让 ASP.NET 2.0 返回正确的标头来缓存响应。是时候进行一次黑客攻击了。在反编译 `HttpCachePolicy` 类(`Context.Response.Cache` 对象的类)的代码后,我发现了以下代码

不知何故,`this._maxAge` 被设置为零,并且检查 "if (!this._isMaxAgeSet || (delta < this._maxAge))" 阻止其设置为更大的值。由于这个问题,我们需要绕过 `SetMaxAge` 函数,并使用 Reflection 直接设置 `_maxAge` 字段的值。

[WebMethod][ScriptMethod(UseHttpGet=true)]
public string CachedGet2()
{
    TimeSpan cacheDuration = TimeSpan.FromMinutes(1);

    FieldInfo maxAge = Context.Response.Cache.GetType().GetField("_maxAge", 
        BindingFlags.Instance|BindingFlags.NonPublic);
    maxAge.SetValue(Context.Response.Cache, cacheDuration);

    Context.Response.Cache.SetCacheability(HttpCacheability.Public);
    Context.Response.Cache.SetExpires(DateTime.Now.Add(cacheDuration));
    Context.Response.Cache.AppendCacheExtension(
            "must-revalidate, proxy-revalidate");

    return DateTime.Now.ToString();
}

这将返回以下标头

现在 `max-age` 设置为 `60`,因此浏览器将缓存响应 60 秒。如果您在 60 秒内再次进行相同的调用,它将返回相同的响应。这是一个测试输出,显示从服务器返回的日期时间

1 分钟后,缓存过期,浏览器再次向服务器发出调用。客户端代码如下

function testCache()
{
    TestService.CachedGet(function(result)
    {
        debug.trace(result);
    });
}

还有另一个问题需要解决。在 *web.config* 中,您会看到 ASP.NET Ajax 添加了

        <system.web>
        <trust level="Medium"/>

这阻止我们设置 `Response` 对象的 `_maxAge` 字段,因为它需要 Reflection。因此,您必须删除此信任级别或将其设置为 `Full`。

    <system.web> 
    <trust level="Full"/>

当“this”不是真正的“this”时

Atlas 回调不在调用它们的相同上下文中执行。例如,如果您像这样从 JavaScript 类进行 Web 方法调用

function SampleClass()
{
    this.id = 1;
    this.call = function()
    {
        TestService.DoSomething( "Hi", function(result)
        {
            debug.dump( this.id );
        } );
    }
}

当您调用 `call` 方法时会发生什么?您会在调试控制台中得到 `1` 吗?不,您会在调试控制台中得到 `null`,因为 `this` 不再是类的实例。这是每个人都会犯的常见错误。由于 Atlas 文档中尚未记录这一点,我看到许多开发人员花费时间来找出问题所在。原因如下:我们知道,每当 JavaScript 事件被触发时,`this` 指的是生成该事件的 HTML 元素。所以,如果您这样做

function SampleClass()
{
    this.id = 1;
    this.call = function()
    {
        TestService.DoSomething( "Hi", function(result)
        {
            debug.dump( this.id );
        } );
    }
}

<input type="button" id="ButtonID" onclick="o.onclick" />

如果您单击按钮,您会看到 `ButtonID` 而不是 `1`。原因在于按钮正在发出调用。因此,调用是在按钮对象的上下文中进行的,所以 `this` 映射到按钮对象。类似地,当 XML HTTP 触发 `onreadystatechanged` 事件(Atlas 在触发回调之前会捕获该事件)时,代码执行仍在 XML HTTP 的上下文中。是 XML HTTP 对象触发了事件。因此,`this` 指的是 XML HTTP 对象,而不是声明了回调的类。为了使回调在类的实例的上下文中触发,以便 `this` 指向类的实例,您需要进行以下更改

function SampleClass()
{
    this.id = 1;
    this.call = function()
    {
        TestService.DoSomething( "Hi", 
            Function.createDelegate( this, function(result)
        {
            debug.dump( this.id );
        } ) );
    }
}

在这里,`Function.createDelegate` 用于创建一个委托,该委托在 `this` 上下文下调用给定的函数。`Function.createDelegate` 在 `AtlasRuntime` 中定义

Function.createDelegate = function(instance, method) {
    return function() {
        return method.apply(instance, arguments);
    }
}

HTTP POST 比 HTTP GET 慢,但它是 ASP.NET Ajax 的默认设置

ASP.NET Ajax 默认对所有 Web 服务调用都使用 HTTP `POST`。HTTP `POST` 比 HTTP `GET` 更昂贵。它在网络上传输更多字节,从而占用宝贵的网络时间,并且还使 ASP.NET 在服务器端执行额外的处理。因此,您应该尽可能使用 HTTP `GET`。但是,HTTP `GET` 不允许您将对象作为参数传递。您只能传递数字、字符串和日期。当您进行 HTTP `GET` 调用时,Atlas 会构建一个编码后的 URL 并命中该 URL。因此,您不应传递过多的内容,以免 URL 超过 2048 个字符。据我所知,这是任何 URL 的最大长度。为了在 Web 服务方法上启用 HTTP `GET`,您需要使用 `[ScriptMethod(UseHttpGet=true)]` 属性装饰该方法

[WebMethod] [ScriptMethod(UseHttpGet=true)] 
public string HelloWorld()
{
}

`POST` 与 `GET` 的另一个问题是 `POST` 需要两次网络往返。当您首次发出 `POST` 请求时,Web 服务器会发送一个“HTTP 100 Continue”消息,这意味着 Web 服务器已准备好接收内容。之后,浏览器发送实际数据。因此,`POST` 请求的启动比 `GET` 耗时更长。网络延迟(您的计算机与服务器之间的往返时间)是 Ajax 应用程序中最大的问题,因为 Ajax 会进行许多需要在毫秒内完成的小调用。否则,应用程序就不会感觉流畅,并会引起用户的烦恼。Ethereal 是一个很好的工具,可以查看 `POST` 和 `GET` 底层发生了什么

从上图可以看出,`POST` 在发送实际数据之前需要 Web 服务器的确认,“HTTP/1.1 100 Continue”。之后,它传输数据。另一方面,`GET` 在不等待任何确认的情况下传输数据。因此,您应该使用 HTTP `GET` 从服务器下载数据,例如页面部分、网格中的内容、文本块等。但是,您不应该使用 HTTP `GET` 将数据发送到服务器,例如提交 Web 表单。

结论

上述极端技巧已在 Pageflakes 中实现,但不完全与此处所述相同,但原理相同。因此,您可以放心地依赖这些技术。这些技术将使您免于许多在开发环境中可能永远不会发现的问题,但当您进行大规模部署时,来自世界各地的人们将面临这些问题。从一开始就实现这些技巧将为您节省大量的开发和客户支持工作。请持续关注 我的博客,了解更多技巧。

历史

  • 2008 年 3 月 5 日 -- 文章内容已更新
  • 2006 年 12 月 22 日 -- 为 ASP.NET Ajax RC 版本更新了下载和文章内容
  • 2006 年 11 月 30 日 -- 发布了原始版本
© . All rights reserved.