ASP.NET?Core?Web?API之Token驗(yàn)證的實(shí)現(xiàn)
在實(shí)際開(kāi)發(fā)中經(jīng)常需要對(duì)外提供接口以便客戶獲取數(shù)據(jù),由于數(shù)據(jù)屬于私密信息,并不能隨意供其他人訪問(wèn),所以就需要驗(yàn)證客戶身份。那么如何才能驗(yàn)證客戶的身份呢?一個(gè)簡(jiǎn)單的小例子,簡(jiǎn)述ASP.NET Core Web API開(kāi)發(fā)過(guò)程中,常用的一種JWT身份驗(yàn)證方式。
1.什么是JWT?
JSON WEB Token(JWT,讀作 [/d??t/]),是一種基于JSON的、用于在網(wǎng)絡(luò)上聲明某種主張的令牌(token)。主要用于認(rèn)證和保護(hù)API之間信息交換。
JWT通常由三部分組成: 頭信息(header), 消息體(payload)和簽名(signature)。
2.JWT 組成
JWT 由三部分組成:Header,Payload,Signature 三個(gè)部分組成,并且最后由.拼接而成 xxxxx.yyyyy.zzzzz。
2.1 頭部(Header)
Header 一般由alg 和 typ 兩個(gè)部分組成:
{ "alg": "HS256", (使用的hash算法,如:HMAC SHA256或RSA) "typ": "JWT" (Token的類型,在這里就是:JWT) }
然后使用Base64Url編碼成第一部分:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9
2.2. 載荷(Payload)
這部分是JWT主要的信息存儲(chǔ)部分,其中包含了許多種的聲明(claims)。
Claims的實(shí)體一般包含用戶和一些元數(shù)據(jù),這些claims分成三種類型:
- reserved claims:預(yù)定義的 一些聲明,并不是強(qiáng)制的但是推薦,它們包括 iss (issuer), exp (expiration time), sub (subject),aud(audience) 等(這里都使用三個(gè)字母的原因是保證 JWT 的緊湊)。
- public claims: 公有聲明,這個(gè)部分可以隨便定義,但是要注意和 IANA JSON Web Token 沖突。
- private claims: 私有聲明,這個(gè)部分是共享被認(rèn)定信息中自定義部分。
Pyload可以是這樣子的:
{ -- 官方的字段 "iss" (issuer):簽發(fā)人, "exp" (expiration time):過(guò)期時(shí)間, "sub" (subject):主題, "aud" (audience):訂閱者, "nbf" (Not Before):生效時(shí)間, "iat" (Issued At):簽發(fā)時(shí)間, "jti" (JWT ID):編號(hào), -- 自定義的字段 "user_id": 123456, "name": "John Doe", "admin": true }
這部分同樣使用Base64Url編碼成第二部分:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.
2.3 簽名(Signature)
Signature是對(duì)前兩部分的簽名,是用來(lái)驗(yàn)證發(fā)送者的JWT的同時(shí)也能確保在期間不被篡改。
首先需要指定一個(gè)密鑰(secret)。這個(gè)密鑰只有服務(wù)器才知道,不能泄露給用戶。
這個(gè)密鑰(secret)是大于16位的隨機(jī)字符串(數(shù)字),
生成jwt:
// 由 HMACSHA256 算法進(jìn)行簽名,secret 不能外泄 const sign = HMACSHA256(base64.encode(header) + '.' + base64.encode(payload), secret) 算出簽名以后,把 Header、Payload、Signature 三個(gè)部分拼成一個(gè)字符串,每個(gè)部分之間用"點(diǎn)"(.)分隔. // jwt 由三部分拼接而成 const jwt = base64.encode(header) + '.' + base64.encode(payload) + '.' + sign
為啥要用base64編碼,由于 ASCII 碼稱為了國(guó)際標(biāo)準(zhǔn),所以我們要把其它字符轉(zhuǎn)成 ASCII 就要用到 base64。
utf-8 -> base64(編碼) -> ASCII ASCII -> base64(解碼) -> utf-8
這樣就可以讓只支持 ASCII 的計(jì)算機(jī)支持 utf-8 了。
2.4 JWT的結(jié)構(gòu)
eyJhbGci0iJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJodHRwOi8vc2NoZW1hcy54bWxzb2FwLm9yZy93cy8yMDA1LZA1L21kZW50aXR5L2NsYW1tcy9zaWQi0iIxIiwiaHRBcDovL3NjaGVtYXMueG1sc29hcC5vcmcvd3MVMjAWNS8wNS9pZGVudG1Bes9jbGFpbXMvbmFtZSI6IuWFrOWtkOWwjWFrSIsImhBdHA6Ly9zY2hlbWFzLm1pY3Jvc29mdC5jb20vd3MvMjAw0C8wNi9pZGVudG1Bes9jbGFpbXMvcm9sZSI6IkFkbWluIiwiZXhwIjoxNjg3NZEyMDE1LCJpc3Mi0iLlhazlrzDlsI_lhagiLcJhdWQi0iLlhazlrZDlsI_1hagifQ.QeZ1Cy5JPV0s8fPfFR59g-rVI3SNKPNP2ZcODzr308Y
打開(kāi):JSON Web Tokens
3.應(yīng)用JWT步驟
安裝JWT授權(quán)庫(kù)
采用JWT進(jìn)行身份驗(yàn)證,需要安裝【Microsoft.AspNetCore.Authentication.JwtBearer】,可通過(guò)Nuget包管理器進(jìn)行安裝,如下所示:
添加JWT身份驗(yàn)證服務(wù)
在啟動(dòng)類Program.cs中,添加JWT身份驗(yàn)證服務(wù),如下所示:
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme) .AddJwtBearer(options => { options.TokenValidationParameters = new TokenValidationParameters { ValidateIssuer = true, ValidateAudience = true, ValidateLifetime = true, ValidateIssuerSigningKey = true, ValidIssuer = TokenParameter.Issuer, ValidAudience = TokenParameter.Audience, IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(TokenParameter.Secret)) }; });
應(yīng)用鑒權(quán)授權(quán)中間件
在啟動(dòng)類Program.cs中,添加鑒權(quán)授權(quán)中間件,如下所示:
app.UseAuthentication(); app.UseAuthorization();
配置Swagger身份驗(yàn)證輸入(可選)
在啟動(dòng)類Program.cs中,添加Swagger服務(wù)時(shí),配置Swagger可以輸入身份驗(yàn)證方式,如下所示:
builder.Services.AddSwaggerGen(options => { options.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme { Description = "請(qǐng)輸入token,格式為 Bearer xxxxxxxx(注意中間必須有空格)", Name = "Authorization", In = ParameterLocation.Header, Type = SecuritySchemeType.ApiKey, BearerFormat = "JWT", Scheme = "Bearer" }); //添加安全要求 options.AddSecurityRequirement(new OpenApiSecurityRequirement { { new OpenApiSecurityScheme{ Reference =new OpenApiReference{ Type = ReferenceType.SecurityScheme, Id ="Bearer" } },new string[]{ } } }); });
注意:此處配置主要是方便測(cè)試,如果采用Postman或者其他測(cè)試工具,此步驟可以省略。
創(chuàng)建JWT幫助類
創(chuàng)建JWT幫助類,主要用于生成Token,如下所示:
using DemoJWT.Models; using Microsoft.AspNetCore.Authentication.Cookies; using Microsoft.IdentityModel.Tokens; using System.IdentityModel.Tokens.Jwt; using System.Security.Claims; using System.Text; namespace DemoJWT.Authorization { public class JwtHelper { public static string GenerateJsonWebToken(User userInfo) { var securityKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(TokenParameter.Secret)); var credentials = new SigningCredentials(securityKey, SecurityAlgorithms.HmacSha256); var claimsIdentity = new ClaimsIdentity(CookieAuthenticationDefaults.AuthenticationScheme); claimsIdentity.AddClaim(new Claim(ClaimTypes.Sid, userInfo.Id)); claimsIdentity.AddClaim(new Claim(ClaimTypes.Name, userInfo.Name)); claimsIdentity.AddClaim(new Claim(ClaimTypes.Role, userInfo.Role)); var token = new JwtSecurityToken(TokenParameter.Issuer, TokenParameter.Audience, claimsIdentity.Claims, expires: DateTime.Now.AddMinutes(120), signingCredentials: credentials); return new JwtSecurityTokenHandler().WriteToken(token); } } }
其中用到的TokenParameter主要用于配置Token驗(yàn)證的頒發(fā)者,接收者,簽名秘鑰等信息,如下所示:
namespace DemoJWT.Authorization { public class TokenParameter { public const string Issuer = "公子小六";//頒發(fā)者 public const string Audience = "公子小六";//接收者 public const string Secret = "1234567812345678";//簽名秘鑰 public const int AccessExpiration = 30;//AccessToken過(guò)期時(shí)間(分鐘) } }
創(chuàng)建Token獲取接口
創(chuàng)建對(duì)應(yīng)的AuthController/GetToken方法,用于獲取Token信息,如下所示:
using DemoJWT.Authorization; using DemoJWT.Models; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using System.IdentityModel.Tokens.Jwt; namespace DemoJWT.Controllers { [Route("api/[controller]/[Action]")] [ApiController] public class AuthController : ControllerBase { [HttpPost] public ActionResult GetToken(User user) { string token = JwtHelper.GenerateJsonWebToken(user); return Ok(token); } } }
創(chuàng)建測(cè)試接口
創(chuàng)建測(cè)試接口,用于測(cè)試Token身份驗(yàn)證。如下所示:
using DemoJWT.Models; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using System.Security.Claims; namespace DemoJWT.Controllers { [Authorize] [Route("api/[controller]/[Action]")] [ApiController] public class TestController : ControllerBase { [HttpPost] public ActionResult GetTestInfo() { var claimsPrincipal = this.HttpContext.User; var name = claimsPrincipal.Claims.FirstOrDefault(r => r.Type == ClaimTypes.Name)?.Value; var role = claimsPrincipal.Claims.FirstOrDefault(r => r.Type == ClaimTypes.Role)?.Value; var test = new Test() { Id = 1, Name = name, Role = role, Author = "公子小六", Description = "this is a test.", }; return Ok(test); } } }
接口測(cè)試
運(yùn)行程序,看到公開(kāi)了兩個(gè)接口,如下所示:
校驗(yàn)邏輯
- 1)客戶端向授權(quán)服務(wù)系統(tǒng)發(fā)起請(qǐng)求,申請(qǐng)獲取“令牌”。
- 2)授權(quán)服務(wù)根據(jù)用戶身份,生成一張專屬“令牌”,并將該“令牌”以JWT規(guī)范返回給客戶端
- 3)客戶端將獲取到的“令牌”放到http請(qǐng)求的headers中后,向主服務(wù)系統(tǒng)發(fā)起請(qǐng)求。主服務(wù)系統(tǒng)收到請(qǐng)求后會(huì)從headers中獲取“令牌”,并從“令牌”中解析出該用戶的身份權(quán)限,然后做出相應(yīng)的處理(同意或拒絕返回資源)
1. 獲取Token運(yùn)行api/Auth/GetToken接口,輸入用戶信息,點(diǎn)擊Execute,在返回的ResponseBody中,就可以獲取接口返回的Token
2. 設(shè)置Token在Swagger上方,點(diǎn)擊Authorize,彈出身份驗(yàn)證配置窗口,如下所示:
3. 接口測(cè)試配置好身份認(rèn)證信息后,調(diào)用/api/Test/GetTestInfo接口,獲取信息如下:
如果清除掉Token配置,再進(jìn)行訪問(wèn)/api/Test/GetTestInfo接口,則會(huì)返回401未授權(quán)信息,如下所示:
JWT 與 Session
有無(wú)狀態(tài)對(duì)比
- Session 是一種記錄服務(wù)器和客戶端會(huì)話狀態(tài)的機(jī)制,需要在數(shù)據(jù)庫(kù)或者 Redis 中保存用戶信息和token信息,所以它是有狀態(tài)的。
- JWT 看完了前面的 JWT 結(jié)構(gòu)和 JWT 校驗(yàn)原理,在后端并不需要存儲(chǔ)數(shù)據(jù),直接通過(guò)私有密鑰驗(yàn)證就可以了。
當(dāng)有這樣的一個(gè)需求,一家公司下同時(shí)關(guān)聯(lián)了多個(gè)業(yè)務(wù),A業(yè)務(wù)網(wǎng)站,B業(yè)務(wù)網(wǎng)站,但是現(xiàn)在要求用戶在A網(wǎng)站登陸過(guò),再訪問(wèn)B網(wǎng)站的時(shí)候能夠自動(dòng)登陸,JWT 就可以很快的實(shí)現(xiàn)這個(gè)需求,把 JWT 直接存儲(chǔ)在前端,后端只要校驗(yàn) JWT 就可以了。
注:這個(gè)需求用 session 也是可以實(shí)現(xiàn)的,只是會(huì)存儲(chǔ)狀態(tài),查詢存儲(chǔ),沒(méi)有 JWT 方便而已。
適用場(chǎng)景對(duì)比
郵箱驗(yàn)證
很多網(wǎng)站在注冊(cè)成功后添加了郵箱驗(yàn)證功能,功能實(shí)現(xiàn):用戶注冊(cè)成功后,完善郵箱,服務(wù)端會(huì)給用戶郵箱發(fā)一個(gè)鏈接,用戶點(diǎn)開(kāi)鏈接校驗(yàn)成功,這個(gè)功能使用 JWT 是個(gè)不錯(cuò)的選擇。
// 把郵箱以及用戶id綁定在一起,設(shè)置生效時(shí)間 const code = jwt.sign({ email, userId }, secret, { expiresIn: 60 * 30 }) // 在此鏈接校驗(yàn)驗(yàn)證碼 const link = `https://www.inode.club/code=$[code]`
做那些短期的驗(yàn)證需求
比如在 BFF 層,用 JWT 去驗(yàn)證傳遞一些數(shù)據(jù)還是不錯(cuò)的選擇,可以把有效時(shí)間設(shè)置的短一些,過(guò)期了就需要重新去請(qǐng)求,我這么直接表述你可能還不太懂,舉個(gè)現(xiàn)實(shí)生活中的例子。
我們上學(xué)的時(shí)候,有班主任和學(xué)科老師這兩個(gè)概念,有一天你想請(qǐng)假,你需要先去找班主任開(kāi)一個(gè)請(qǐng)假條,然后請(qǐng)教條你的班主任簽完字之后,你會(huì)將請(qǐng)假條交給你的學(xué)科課教師,學(xué)科教師確認(rèn)簽字無(wú)誤后,把請(qǐng)假條收了,并在請(qǐng)假記錄表中作出了相應(yīng)記錄。
上訴的例子中,“請(qǐng)假申請(qǐng)單”就是JWT中的payload,領(lǐng)導(dǎo)簽字就是base64后的數(shù)字簽名,領(lǐng)導(dǎo)是issuer,“學(xué)科教師的老王”即為JWT的audience,audience需要驗(yàn)證班主任簽名是否合法,驗(yàn)證合法后根據(jù)payload中請(qǐng)求的資源給予相應(yīng)的權(quán)限,同時(shí)將JWT收回。
放到一些系統(tǒng)集成的應(yīng)用場(chǎng)景中,例如我前面說(shuō)的 BFF 中其實(shí) JWT 更適合一次性操作的認(rèn)證:
服務(wù) B 你好, 服務(wù) A 告訴我,我可以操作 <JWT內(nèi)容>, 這是我的憑證(即 JWT )
在這里,服務(wù) A 負(fù)責(zé)認(rèn)證用戶身份(類似于上例班主任批準(zhǔn)請(qǐng)假),并頒布一個(gè)很短過(guò)期時(shí)間的JWT給瀏覽器(相當(dāng)于上例的請(qǐng)假單),瀏覽器(相當(dāng)于上例請(qǐng)假的我們)在向服務(wù) B 的請(qǐng)求中帶上該 JWT,則服務(wù) B(相當(dāng)于上例的任課教師)可以通過(guò)驗(yàn)證該 JWT 來(lái)判斷用戶是否有權(quán)限執(zhí)行該操作。通過(guò)這樣,服務(wù) B 就成為一個(gè)安全的無(wú)狀態(tài)的服務(wù)。
個(gè)人還是認(rèn)為 JWT 更適合做一些一次性的安全認(rèn)證,好多其他場(chǎng)景考慮多了之后又做回了 session,傳統(tǒng)的 cookie-session 機(jī)制工作得更好,但是對(duì)于一次性的安全認(rèn)證,頒發(fā)一個(gè)有效期極短的JWT,即使暴露了危險(xiǎn)也很小。上面的郵箱驗(yàn)證其實(shí)也是一次性的安全認(rèn)證。
跨域認(rèn)證
因?yàn)?JWT 并不使用 Cookie ,所以你可以使用任何域名提供你的 API 服務(wù)而不需要擔(dān)心跨域資源共享問(wèn)題(CORS)。JWT 確實(shí)是跨域認(rèn)證的一個(gè)解決方案,但是對(duì)于跨域場(chǎng)景時(shí)要注意一點(diǎn)。 客戶端收到服務(wù)器返回的 JWT,可以儲(chǔ)存在 Cookie 里面,也可以儲(chǔ)存在 localStorage。
此后,客戶端每次與服務(wù)器通信,都要帶上這個(gè) JWT。你可以把它放在 Cookie 里面自動(dòng)發(fā)送,但是這樣不能跨域,所以更好的做法是放在 HTTP 請(qǐng)求的頭信息Authorization字段里面。
Authorization: Bearer另一種做法是,跨域的時(shí)候,JWT 就放在 POST 請(qǐng)求的數(shù)據(jù)體里面。
跨域知識(shí)擴(kuò)展
跨域這兩個(gè)字就像一塊狗皮膏藥一樣黏在每一個(gè)開(kāi)發(fā)者身上,無(wú)論你在工作上或者面試中無(wú)可避免會(huì)遇到這個(gè)問(wèn)題。為了應(yīng)付面試,我們每次都隨便背幾個(gè)方案。但是如果突然問(wèn)你為什么會(huì)有跨域這個(gè)問(wèn)題出現(xiàn)? ...停頓幾秒,這里只是普及一下,知道的可以忽略掉。
登陸驗(yàn)證
登陸驗(yàn)證:不需要控制登錄設(shè)備數(shù)量以及注銷登陸情況,無(wú)狀態(tài)的 jwt 是一個(gè)不錯(cuò)的選擇。具體實(shí)現(xiàn)流程,可以看上文中的校驗(yàn)原理,校驗(yàn)原理使用的登陸驗(yàn)證例子。
當(dāng)需求中出現(xiàn)控制登陸設(shè)備數(shù)量,或者可以注銷掉用戶時(shí),可以考慮使用原有的 session 模式,因?yàn)獒槍?duì)這種登陸需求,需要進(jìn)行的狀態(tài)存儲(chǔ)對(duì) jwt 添加額外的狀態(tài)支持,增加了認(rèn)證的復(fù)雜度,此時(shí)選用 session 是一個(gè)不錯(cuò)的選擇。 針對(duì)上面的特殊需求,可能也有小伙伴仍喜歡使用 jwt ,補(bǔ)充一下特殊案例
注銷登陸
用戶注銷時(shí)候要考慮 token 的過(guò)期時(shí)間。
session: 只需要把 user_id 對(duì)應(yīng)的 token 清掉即可 ;
jwt: 使用 redis,需要維護(hù)一張黑名單,用戶注銷時(shí)把該 token 加入黑名單,過(guò)期時(shí)間與 jwt 的過(guò)期時(shí)間保持一致。
用戶登陸設(shè)備控制
session: 使用 sql 類數(shù)據(jù)庫(kù),維護(hù)一個(gè)用戶驗(yàn)證token表,每次登陸重置表中 token 字段,每次請(qǐng)求需要權(quán)限接口時(shí),根據(jù) token 查找 user_id(也可以使用 redis 維護(hù) token 數(shù)據(jù)的存儲(chǔ))
jwt: 假使使用 sql 類數(shù)據(jù)庫(kù),維護(hù)一個(gè)用戶驗(yàn)證token表,表中添加 token 字段,每次登陸重置 token 字段,每次請(qǐng)求需要權(quán)限接口時(shí),根據(jù) jwt 獲取 user_id,根據(jù) user_id 查用戶表獲取 token 判斷 token 是否一致。(也可以使用 redis 維護(hù) token 數(shù)據(jù)的存儲(chǔ))
適合做那些事來(lái)講的,其實(shí)也就是針對(duì)JWT的優(yōu)勢(shì)來(lái)說(shuō)的,還有一些辯證性的理解。接下來(lái)說(shuō)說(shuō) JWT 的缺點(diǎn)。
JWT 缺點(diǎn)
- 更多的空間占用。如果將原存在服務(wù)端session中的信息都放在JWT中保存,會(huì)造成JWT占用的空間變大,需要考慮客戶端cookie的空間限制等因素,如果放在Local Storage,則可能會(huì)受到 XSS 攻擊。
- 無(wú)法作廢已頒布的令牌。JWT 使用時(shí)由于服務(wù)器不需要存儲(chǔ) Session 狀態(tài),因此使用過(guò)程中無(wú)法廢棄某個(gè) Token 或者更改 Token 的權(quán)限。也就是說(shuō)一旦 JWT 簽發(fā)了,到期之前就會(huì)始終有效,除非服務(wù)器部署額外的邏輯。
- 用戶信息安全。通過(guò)J WT 的組成結(jié)構(gòu)可以看出,Payload 存儲(chǔ)的一些用戶信息,它是通過(guò)Base64加密的,可以直接解密,不能將秘密數(shù)據(jù)寫入 JWT,如果使用需要對(duì) JWT 進(jìn)行二次加密。
到此這篇關(guān)于ASP.NET Core Web API之Token驗(yàn)證的實(shí)現(xiàn)的文章就介紹到這了,更多相關(guān)ASP.NET Core Token驗(yàn)證內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
ASP.NET2.0服務(wù)器控件之自定義狀態(tài)管理
ASP.NET2.0服務(wù)器控件之自定義狀態(tài)管理...2006-09-09C#實(shí)現(xiàn)支持?jǐn)帱c(diǎn)續(xù)傳多線程下載客戶端工具類
C#實(shí)現(xiàn)支持?jǐn)帱c(diǎn)續(xù)傳多線程下載的 Http Web 客戶端工具類 (C# DIY HttpWebClient),感興趣的朋友可以參考下本文,或許對(duì)你有所幫助2013-04-04ASP.NET?Core?MVC中的標(biāo)簽助手(TagHelper)用法
這篇文章介紹了ASP.NET?Core?MVC中標(biāo)簽助手(TagHelper)的用法,文中通過(guò)示例代碼介紹的非常詳細(xì)。對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2022-04-04讓Asp.NET的DataGrid可排序、可選擇、可分頁(yè)
讓Asp.NET的DataGrid可排序、可選擇、可分頁(yè)...2007-02-02Entity?Framework?Core關(guān)聯(lián)刪除
關(guān)聯(lián)刪除通常是一個(gè)數(shù)據(jù)庫(kù)術(shù)語(yǔ),用于描述在刪除行時(shí)允許自動(dòng)觸發(fā)刪除關(guān)聯(lián)行的特征;即當(dāng)主表的數(shù)據(jù)行被刪除時(shí),自動(dòng)將關(guān)聯(lián)表中依賴的數(shù)據(jù)行進(jìn)行刪除,或者將外鍵更新為NULL或默認(rèn)值。本文將為大家具體介紹一下Entity?Framework?Core關(guān)聯(lián)刪除,需要的可以參考一下2021-12-12Asp.net MVC 中利用jquery datatables 實(shí)現(xiàn)數(shù)據(jù)分頁(yè)顯示功能
這篇文章主要介紹了Asp.net MVC 中利用jquery datatables 實(shí)現(xiàn)數(shù)據(jù)分頁(yè)顯示功能,非常不錯(cuò),具有參考借鑒價(jià)值,需要的朋友可以參考下2017-06-06asp.net 文件路徑之獲得虛擬目錄的網(wǎng)站的根目錄
asp.net下獲取文件路徑常用代碼,獲得虛擬目錄的網(wǎng)站的根目錄2012-10-10