欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

ASP.NET Core處理管道的深入理解

 更新時間:2020年11月22日 10:09:24   作者:冠軍  
這篇文章主要給大家介紹了關(guān)于ASP.NET Core處理管道的深入理解,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧

前言

在 ASP.NET Core 的管道處理部分,實現(xiàn)思想已經(jīng)不是傳統(tǒng)的面向?qū)ο竽J剑乔袚Q到了函數(shù)式編程模式。這導致代碼的邏輯大大簡化,但是,對于熟悉面向?qū)ο缶幊?,而不是函?shù)式編程思路的開發(fā)者來說,是一個比較大的挑戰(zhàn)。

處理請求的函數(shù)

在 ASP.NET Core 中,一次請求的完整表示是通過一個 HttpContext 對象來完成的,通過其 Request 屬性可以獲取當前請求的全部信息,通過 Response 可以獲取對響應內(nèi)容進行設置。

對于一次請求的處理可以看成一個函數(shù),函數(shù)的處理參數(shù)就是這個 HttpContext 對象,處理的結(jié)果并不是輸出結(jié)果,結(jié)果是通過 Response 來完成的,從程序調(diào)度的角度來看,函數(shù)的輸出結(jié)果是一個任務 Task。

這樣的話,具體處理 Http 請求的函數(shù)可以使用如下的 RequestDelegate 委托進行定義。

public delegate Task RequestDelegate(HttpContext context);

在函數(shù)參數(shù) HttpContext 中則提供了此次請求的所有信息,context 的 Request 屬性中提供了所有關(guān)于該次請求的信息,而處理的結(jié)果則在 context 的 Response 中表示。通常我們會修改 Response 的響應頭,或者響應內(nèi)容來表達處理的結(jié)果。

需要注意的是,該函數(shù)的返回結(jié)果是一個 Task,表示異步處理,而不是真正處理的結(jié)果。

參見:在 Doc 中查看 RequestDelegate 定義

我們從 ASP.NET Core 的源代碼中選取一段作為參考,這就是在沒有我們自定義的處理時,ASP.NET Core 最終的處理方式,返回 404。這里使用函數(shù)式定義。

RequestDelegate app = context =>
{
 // ......

 context.Response.StatusCode = StatusCodes.Status404NotFound;
 return Task.CompletedTask;
};

來源:在 GitHub 中查看 ApplicationBuilder 源碼

把它翻譯成熟悉的方法形式,就是下面這個樣子:

public Task app(HttpContext context)
{
 // ......

 context.Response.StatusCode = StatusCodes.Status404NotFound;
 return Task.CompletedTask;
};

這段代碼只是設置了 Http 的響應狀態(tài)碼為 404,并直接返回了一個已經(jīng)完成的任務對象。

為了脫離 ASP.NET Core 復雜的環(huán)境,可以簡單地進行后繼的演示,我們自定義一個模擬 HttpContext 的類型 HttpContextSample 和相應的 RequestDelegate 委托類型。

在模擬請求的 HttpContextSample 中,我們內(nèi)部定義了一個 StringBuilder 來保存處理的結(jié)果,以便進行檢查。其中的 Output 用來模擬 Response 來處理輸出。

而 RequestDelegate 則需要支持現(xiàn)在的 HttpContextSample。

using System.Threading.Tasks;
using System.Text;

public class HttpContextSample
{
 public StringBuilder Output { get; set; }
 public HttpContextSample() {
  Output = new StringBuilder();
 }
}
public delegate Task RequestDelegate(HttpContextSample context);

這樣,我們可以定義一個基礎(chǔ)的,使用 RequestDelegate 的示例代碼。

// 定義一個表示處理請求的委托對象
RequestDelegate app = context =>
{
 context.Output.AppendLine("End of output.");
 return Task.CompletedTask;
};

// 創(chuàng)建模擬當前請求的對象
var context1 = new HttpContextSample();
// 處理請求
app(context1);
// 輸出請求的處理結(jié)果
Console.WriteLine(context1.Output.ToString());

執(zhí)行之后,可以得到如下的輸出

End of output.

處理管道中間件

所謂的處理管道是使用多個中間件串聯(lián)起來實現(xiàn)的。每個中間件當然需要提供處理請求的 RequestDelegate 支持。在請求處理管道中,通常會有多個中間件串聯(lián)起來,構(gòu)成處理管道。

但是,如何將多個中間件串聯(lián)起來呢?

可以考慮兩種實現(xiàn)方式:函數(shù)式和方法式。

方法式就是再通過另外的方法將注冊的中間件組織起來,構(gòu)建一個處理管道,以后通過調(diào)用該方法來實現(xiàn)管道。而函數(shù)式是將整個處理管道看成一個高階函數(shù),以后通過調(diào)用該函數(shù)來實現(xiàn)管道。

方法式的問題是在后繼中間件處理之前需要一個方法,后繼中間件處理之后需要一個方法,這就是為什么 ASP.NET Web Form 有那么多事件的原因。

如果我們只是把后繼的中間件中的處理看成一個函數(shù),那么,每個中間件只需要分成 3 步即可:

  • 前置處理
  • 調(diào)用后繼的中間件
  • 后置處理

在 ASP.NET Core 中是使用函數(shù)式來實現(xiàn)請求的處理管道的。

在函數(shù)式編程中,函數(shù)本身是可以作為一個參數(shù)來進行傳遞的。這樣可以實現(xiàn)高階函數(shù)。也就是說函數(shù)的組合結(jié)果還是一個函數(shù)。

對于整個處理管道,我們最終希望得到的形式還是一個 RequestDelegate,也就是一個對當前請求的 HttpContext 進行處理的函數(shù)。

本質(zhì)上來講,中間件就是一個用來生成 RequestDelegate 對象的生成函數(shù)。

為了將多個管道中間件串聯(lián)起來,每個中間件需要接收下一個中間件的處理請求的函數(shù)作為參數(shù),中間件本身返回一個處理請求的 RequestDelegate 委托對象。所以,中間件實際上是一個生成器函數(shù)。

使用 C# 的委托表示出來,就是下面的一個類型。所以,在 ASP.NET Core 中,中間件的類型就是這個 Func<T, TResult>。

Func<RequestDelegate, RequestDelegate>

在 Doc 中查看 Func<T, TResult> 的文檔

這個概念比較抽象,與我們所熟悉的面向?qū)ο缶幊谭绞酵耆煌?,下面我們使用一個示例進行說明。

我們通過一個中間件來演示它的模擬實現(xiàn)代碼。下面的代碼定義了一個中間件,該中間件接收一個表示后繼處理的函數(shù),中間件的返回結(jié)果是創(chuàng)建的另外一個 RequestDelegate 對象。它的內(nèi)部通過調(diào)用下一個處理函數(shù)來完成中間件之間的級聯(lián)。

// 定義中間件
Func<RequestDelegate, RequestDelegate> middleware1 = next => {
  // 中間件返回一個 RequestDelegate 對象
 return (HttpContextSample context) => {
  // 中間件 1 的處理內(nèi)容
  context.Output.AppendLine("Middleware 1 Processing.");

  // 調(diào)用后繼的處理函數(shù)
  return next(context);
 };
};

把它和我們前面定義的 app 委托結(jié)合起來如下所示,注意調(diào)用中間件的結(jié)果是返回一個新的委托函數(shù)對象,它就是我們的處理管道。

// 最終的處理函數(shù)
RequestDelegate app = context =>
{
 context.Output.AppendLine("End of output.");
 return Task.CompletedTask;
};

// 定義中間件 1
Func<RequestDelegate, RequestDelegate> middleware1 = next =>
{
 return (HttpContextSample context) =>
 {
  // 中間件 1 的處理內(nèi)容
  context.Output.AppendLine("Middleware 1 Processing.");

  // 調(diào)用后繼的處理函數(shù)
  return next(context);
 };
};

// 得到一個有一個處理步驟的管道
var pipeline1 = middleware1(app);
// 準備一個表示當前請求的對象
var context2 = new HttpContextSample();
// 通過管道處理當前請求
pipeline1(context2);
// 輸出請求的處理結(jié)果
Console.WriteLine(context2.Output.ToString());

可以得到如下的輸出

Middleware 1 Processing.
End of output.

繼續(xù)增加第二個中間件來演示多個中間件的級聯(lián)處理。

RequestDelegate app = context =>
{
 context.Output.AppendLine("End of output.");
 return Task.CompletedTask;
};

// 定義中間件 1
Func<RequestDelegate, RequestDelegate> middleware1 = next =>
{
 return (HttpContextSample context) =>
 {
  // 中間件 1 的處理內(nèi)容
  context.Output.AppendLine("Middleware 1 Processing.");

  // 調(diào)用后繼的處理函數(shù)
  return next(context);
 };
};

// 定義中間件 2
Func<RequestDelegate, RequestDelegate> middleware2 = next =>
{

 return (HttpContextSample context) =>
 {
  // 中間件 2 的處理
  context.Output.AppendLine("Middleware 2 Processing.");

  // 調(diào)用后繼的處理函數(shù)
  return next(context);
 };
};

// 構(gòu)建處理管道
var step1 = middleware1(app);
var pipeline2 = middleware2(step1);
// 準備當前的請求對象
var context3 = new HttpContextSample();
// 處理請求
pipeline2(context3);
// 輸出處理結(jié)果
Console.WriteLine(context3.Output.ToString());

當前的輸出

Middleware 2 Processing.
Middleware 1 Processing.
End of output.

如果我們把這些中間件保存到幾個列表中,就可以通過循環(huán)來構(gòu)建處理管道。下面的示例重復使用了前面定義的 app 變量。

List<Func<RequestDelegate, RequestDelegate>> _components
 = new List<Func<RequestDelegate, RequestDelegate>>();
_components.Add(middleware1);
_components.Add(middleware2);

// 構(gòu)建處理管道
foreach (var component in _components)
{
 app = component(app);
}

// 構(gòu)建請求上下文對象
var context4 = new HttpContextSample();
// 使用處理管道處理請求
app(context4);
// 輸出處理結(jié)果
Console.WriteLine(context4.Output.ToString());

輸出結(jié)果與上一示例完全相同

Middleware 2 Processing.
Middleware 1 Processing.
End of output.

但是,有一個問題,我們后加入到列表中的中間件 2 是先執(zhí)行的,而先加入到列表中的中間件 1 是后執(zhí)行的。如果希望實際的執(zhí)行順序與加入的順序一致,只需要將這個列表再反轉(zhuǎn)一下即可。

// 反轉(zhuǎn)此列表
_components.Reverse();
foreach (var component in _components)
{
 app = component(app);
}

var context5 = new HttpContextSample();
app(context5);
Console.WriteLine(context5.Output.ToString());

輸出結(jié)果如下

Middleware 1 Processing.
Middleware 2 Processing.
End of output.

現(xiàn)在,我們可以回到實際的 ASP.NET Core 代碼中,把 ASP.NET Core 中 ApplicationBuilder 的核心代碼 Build() 方法抽象之后,可以得到如下的關(guān)鍵代碼。

注意 Build() 方法就是構(gòu)建我們的請求處理管道,它返回了一個 RequestDelegate 對象,該對象實際上是一個委托對象,代表了一個處理當前請求的處理管道函數(shù),它就是我們所謂的處理管道,以后我們將通過該委托來處理請求。

public RequestDelegate Build()
{
 RequestDelegate app = context =>
 {
  // ......

  context.Response.StatusCode = StatusCodes.Status404NotFound;
  return Task.CompletedTask;
 };

 foreach (var component in _components.Reverse())
 {
  app = component(app);
 }

 return app;
}

完整的 ApplicationBuilder 代碼如下所示:

// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Http.Features;
using Microsoft.Extensions.Internal;

namespace Microsoft.AspNetCore.Builder
{
 public class ApplicationBuilder : IApplicationBuilder
 {
  private const string ServerFeaturesKey = "server.Features";
  private const string ApplicationServicesKey = "application.Services";

  private readonly IList<Func<RequestDelegate, RequestDelegate>> _components = new List<Func<RequestDelegate, RequestDelegate>>();

  public ApplicationBuilder(IServiceProvider serviceProvider)
  {
   Properties = new Dictionary<string, object?>(StringComparer.Ordinal);
   ApplicationServices = serviceProvider;
  }

  public ApplicationBuilder(IServiceProvider serviceProvider, object server)
   : this(serviceProvider)
  {
   SetProperty(ServerFeaturesKey, server);
  }

  private ApplicationBuilder(ApplicationBuilder builder)
  {
   Properties = new CopyOnWriteDictionary<string, object?>(builder.Properties, StringComparer.Ordinal);
  }

  public IServiceProvider ApplicationServices
  {
   get
   {
    return GetProperty<IServiceProvider>(ApplicationServicesKey)!;
   }
   set
   {
    SetProperty<IServiceProvider>(ApplicationServicesKey, value);
   }
  }

  public IFeatureCollection ServerFeatures
  {
   get
   {
    return GetProperty<IFeatureCollection>(ServerFeaturesKey)!;
   }
  }

  public IDictionary<string, object?> Properties { get; }

  private T? GetProperty<T>(string key)
  {
   return Properties.TryGetValue(key, out var value) ? (T)value : default(T);
  }

  private void SetProperty<T>(string key, T value)
  {
   Properties[key] = value;
  }

  public IApplicationBuilder Use(Func<RequestDelegate, RequestDelegate> middleware)
  {
   _components.Add(middleware);
   return this;
  }

  public IApplicationBuilder New()
  {
   return new ApplicationBuilder(this);
  }

  public RequestDelegate Build()
  {
   RequestDelegate app = context =>
   {
    // If we reach the end of the pipeline, but we have an endpoint, then something unexpected has happened.
    // This could happen if user code sets an endpoint, but they forgot to add the UseEndpoint middleware.
    var endpoint = context.GetEndpoint();
    var endpointRequestDelegate = endpoint?.RequestDelegate;
    if (endpointRequestDelegate != null)
    {
     var message =
      $"The request reached the end of the pipeline without executing the endpoint: '{endpoint!.DisplayName}'. " +
      $"Please register the EndpointMiddleware using '{nameof(IApplicationBuilder)}.UseEndpoints(...)' if using " +
      $"routing.";
     throw new InvalidOperationException(message);
    }

    context.Response.StatusCode = StatusCodes.Status404NotFound;
    return Task.CompletedTask;
   };

   foreach (var component in _components.Reverse())
   {
    app = component(app);
   }

   return app;
  }
 }
}

見:在 GitHub 中查看 ApplicationBuilder 源碼

強類型的中間件

函數(shù)形式的中間件使用比較方便,可以直接在管道定義中使用。但是,如果我們希望能夠定義獨立的中間件,使用強類型的類來定義會更加方便一些。

public interface IMiddleware {
 public System.Threading.Tasks.Task InvokeAsync (
  Microsoft.AspNetCore.Http.HttpContext context, 
  Microsoft.AspNetCore.Http.RequestDelegate next);
}

在 Doc 中查看 IMiddleware 定義

我們定義的強類型中間件可以選擇實現(xiàn)裝個接口。

next 表示請求處理管道中的下一個中間件,處理管道會將它提供給你定義的中間件。這是將各個中間件連接起來的關(guān)鍵。

如果當前中間件需要將請求繼續(xù)分發(fā)給后繼的中間件繼續(xù)處理,只需要調(diào)用這個委托對象即可。否則,應用程序針對該請求的處理到此為止。

例如,增加一個可以添加自定義響應頭的中間件,如下所示:

using System.Threading.Tasks;

public class CustomResponseHeader: IMiddleware 
{
  // 使用構(gòu)造函數(shù)完成服務依賴的定義
  public CustomResponseHeader()
  {
  }

  public Task InvodeAsync(HttpContextSample context, RequestDelegate next)
  {
    context.Output.AppendLine("From Custom Middleware.");

    return next(context);
  }
}

這更好看懂了,可是它怎么變成那個 Func<RequestDelegate, RequestDelegate> 呢?

在演示程序中使用該中間件。

List<Func<RequestDelegate, RequestDelegate>> _components
  = new List<Func<RequestDelegate, RequestDelegate>>();
_components.Add(middleware1);
_components.Add(middleware2);

var middleware3 = new CustomResponseHeader();
Func<RequestDelegate, RequestDelegate> middleware3 = next =>
{
  return (HttpContextSample context) =>
  {
    // 中間件 3 的處理
    var result = middleware3.InvodeAsync(context, next);
    return result;
  };
};
_components.Add(middleware3);

這樣開發(fā)者可以使用熟悉的對象方式開發(fā)中間件,而系統(tǒng)內(nèi)部自動根據(jù)你的定義,生成出來一個 Func<RequestDelegate, RequestDelegate> 形式的中間件。

ASP.NET Core 使用該類型中間件的形式如下所示,這是提供了一個方便的擴展方法來完成這個工作。

 .UseMiddleware<CustomResponseHeader>();

按照約定定義中間件

除了實現(xiàn) IMiddleware 這個接口,還可以使用約定方式來創(chuàng)建中間件。

按照約定定義中間件不需要實現(xiàn)某個預定義的接口或者繼承某個基類,而是需要遵循一些約定即可。約定主要體現(xiàn)在如下幾個方面:

  • 中間件需要一個公共的有效構(gòu)造函數(shù),該構(gòu)造函數(shù)必須包含一個類型為 RequestDelegate 類型的參數(shù)。它代表后繼的中間件處理函數(shù)。構(gòu)造函數(shù)不僅可以包含任意其它參數(shù),對 RequestDelegate 參數(shù)出現(xiàn)的位置也沒有任何限制。
  • 針對請求的處理實現(xiàn)再返回類型為 Task 的 InvokeAsync() 方法或者同步的 Invoke() 方法中,方法的第一個參數(shù)表示當前的請求上下文 HttpContext 對象,對于其他參數(shù),雖然約定并未進行限制,但是由于這些參數(shù)最終由依賴注入框架提供,所以,相應的服務注冊必須提供。

構(gòu)造函數(shù)和 Invoke/InvokeAsync 的其他參數(shù)由依賴關(guān)系注入 (DI) 填充。

using System.Threading.Tasks;

public class RequestCultureMiddleware {
  private readonly RequestDelegate _next;

  public RequestCultureMiddleware (RequestDelegate next) {
    _next = next;
  }

  public async Task InvokeAsync (HttpContextSample context) {
    context.Output.AppendLine("Middleware 4 Processing.");

    // Call the next delegate/middleware in the pipeline
    await _next (context);
  }
}

在演示程序中使用按照約定定義的中間件。

Func<RequestDelegate, RequestDelegate> middleware4 = next => {
  return (HttpContextSample context) => {
    
    var step4 = new RequestCultureMiddleware(next);
    // 中間件 4 的處理
    var result = step4.InvokeAsync (context);
    return result;
  };
};
_components.Add (middleware4);

在 ASP.NET Core 中使用按照約定定義的中間件語法與使用強類型方式相同:

 .UseMiddleware<RequestCultureMiddleware >();

中間件的順序

中間件安裝一定順尋構(gòu)造成為請求處理管道,常見的處理管道如下所示:

實現(xiàn) BeginRequest 和 EndRequest

理解了請求處理管道的原理,下面看它的一個應用。

在 ASP.NET 中我們可以使用預定義的 Begin_Request 和 EndRequest 處理步驟。

現(xiàn)在整個請求處理管道都是我們自己來進行構(gòu)建了,那么怎么實現(xiàn) Begin_Request 和 EndRequest 呢?使用中間件可以很容易實現(xiàn)它。

首先,這兩個步驟是請求處理的第一個和最后一個步驟,顯然,該中間件必須是第一個注冊到管道中的。

所謂的 Begin_Request 就是在調(diào)用 next() 之間的處理了,而 End_Request 就是在調(diào)用 next() 之后的處理了。在 https://stackoverflow.com/questions/40604609/net-core-endrequest-middleware 中就有一個示例,我們將它修改一下,如下所示:

public class BeginEndRequestMiddleware
{
  private readonly RequestDelegate _next;

  public BeginEndRequestMiddleware(RequestDelegate next)
  {
    _next = next;
  }

  public void Begin_Request(HttpContext context) {
    // do begin request
  }

  public void End_Request(HttpContext context) {
    // do end request
  }

  public async Task Invoke(HttpContext context)
  {
    // Do tasks before other middleware here, aka 'BeginRequest'
    Begin_Request(context);

    // Let the middleware pipeline run
    await _next(context);

    // Do tasks after middleware here, aka 'EndRequest'
    End_Request();
  }
}

Register

public void Configure(IApplicationBuilder app)
{
  // 第一個注冊
  app.UseMiddleware<BeginEndRequestMiddleware>();

  // Register other middelware here such as:
  app.UseMvc();
}

總結(jié)

到此這篇關(guān)于ASP.NET Core處理管道深入理解的文章就介紹到這了,更多相關(guān)ASP.NET Core處理管道內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

您可能感興趣的文章:

相關(guān)文章

  • ASP.NET五步打包下載Zip文件實例

    ASP.NET五步打包下載Zip文件實例

    本篇文章主要介紹了ASP.NET五步打包下載Zip文件實例,現(xiàn)在分享給大家,也給大家做個參考。有需要的小伙伴可以參考下。
    2016-11-11
  • ASP.NET Core3.1 Ocelot路由的實現(xiàn)

    ASP.NET Core3.1 Ocelot路由的實現(xiàn)

    這篇文章主要介紹了ASP.NET Core3.1 Ocelot路由的實現(xiàn),文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧
    2020-11-11
  • ASP.NET對無序列表批量操作的三種方法小結(jié)

    ASP.NET對無序列表批量操作的三種方法小結(jié)

    在網(wǎng)頁開發(fā)中,經(jīng)常要用到無序列表。事實上在符合W3C標準的div+css布局中,無序列表被大量使用,ASP.NET雖然內(nèi)置了BulletedList控件,用于創(chuàng)建和操作無序列表,但感覺不太好用
    2012-01-01
  • 壓力測試中需要掌握的幾個基本概念

    壓力測試中需要掌握的幾個基本概念

    一個asp.net的頁面。對于壓力測試,必須時時刻刻做,如果不知道自己的應用能夠承載多少的并發(fā)用戶數(shù),那基本上就是在扔定時炸彈
    2011-09-09
  • ASP.NET 廣告控件AdRotator的使用方法與實例

    ASP.NET 廣告控件AdRotator的使用方法與實例

    廣告控件是asp.net中一個獨有的東西,他可以利用asp.net來生成廣告控件所需的xml文檔,然后再利用AdRotator來調(diào)用廣告xml文件,根據(jù)我們的參考進行顯示
    2013-08-08
  • GridView高效分頁和搜索功能的實現(xiàn)代碼

    GridView高效分頁和搜索功能的實現(xiàn)代碼

    GridView高效分頁和搜索功能的實現(xiàn)代碼,需要的朋友可以參考一下
    2013-03-03
  • moq 的常用使用方法(推薦)

    moq 的常用使用方法(推薦)

    這篇文章主要介紹了moq 的常用使用方法的相關(guān)資料,非常不錯,具有參考借鑒價值,需要的朋友可以參考下
    2016-11-11
  • Asp.Net?Core7?preview4限流中間件新特性詳解

    Asp.Net?Core7?preview4限流中間件新特性詳解

    這篇文章主要為大家介紹了Asp.Net?Core7?preview4限流中間件的新特性示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪
    2022-05-05
  • 在AspNetCore中使用極驗做行為認證的驗證流程

    在AspNetCore中使用極驗做行為認證的驗證流程

    這篇文章主要介紹了在AspNetCore中使用極驗做行為認證的驗證流程 ,本文有圖文介紹有實例代碼給大家介紹的非常詳細,具有一定的參考借鑒價值,需要的朋友可以參考下
    2018-09-09
  • asp.net使用AJAX實現(xiàn)無刷新分頁

    asp.net使用AJAX實現(xiàn)無刷新分頁

    AJAX(Asynchronous JavaScript and XML)是一種進行頁面局部異步刷新的技術(shù)。用AJAX向服務器發(fā)送請求和獲得服務器返回的數(shù)據(jù)并且更新到界面中,不是整個頁面刷新,而是在頁面中使用Js創(chuàng)建XMLHTTPRequest對象來向服務器發(fā)出請求以及獲得返回的數(shù)據(jù)。
    2014-11-11

最新評論