ASP.NET Core處理管道的深入理解
前言
在 ASP.NET Core 的管道處理部分,實(shí)現(xiàn)思想已經(jīng)不是傳統(tǒng)的面向?qū)ο竽J剑乔袚Q到了函數(shù)式編程模式。這導(dǎo)致代碼的邏輯大大簡(jiǎn)化,但是,對(duì)于熟悉面向?qū)ο缶幊?,而不是函?shù)式編程思路的開發(fā)者來說,是一個(gè)比較大的挑戰(zhàn)。
處理請(qǐng)求的函數(shù)
在 ASP.NET Core 中,一次請(qǐng)求的完整表示是通過一個(gè) HttpContext 對(duì)象來完成的,通過其 Request 屬性可以獲取當(dāng)前請(qǐng)求的全部信息,通過 Response 可以獲取對(duì)響應(yīng)內(nèi)容進(jìn)行設(shè)置。
對(duì)于一次請(qǐng)求的處理可以看成一個(gè)函數(shù),函數(shù)的處理參數(shù)就是這個(gè) HttpContext 對(duì)象,處理的結(jié)果并不是輸出結(jié)果,結(jié)果是通過 Response 來完成的,從程序調(diào)度的角度來看,函數(shù)的輸出結(jié)果是一個(gè)任務(wù) Task。
這樣的話,具體處理 Http 請(qǐng)求的函數(shù)可以使用如下的 RequestDelegate 委托進(jìn)行定義。
public delegate Task RequestDelegate(HttpContext context);
在函數(shù)參數(shù) HttpContext 中則提供了此次請(qǐng)求的所有信息,context 的 Request 屬性中提供了所有關(guān)于該次請(qǐng)求的信息,而處理的結(jié)果則在 context 的 Response 中表示。通常我們會(huì)修改 Response 的響應(yīng)頭,或者響應(yīng)內(nèi)容來表達(dá)處理的結(jié)果。
需要注意的是,該函數(shù)的返回結(jié)果是一個(gè) Task,表示異步處理,而不是真正處理的結(jié)果。
參見:在 Doc 中查看 RequestDelegate 定義
我們從 ASP.NET Core 的源代碼中選取一段作為參考,這就是在沒有我們自定義的處理時(shí),ASP.NET Core 最終的處理方式,返回 404。這里使用函數(shù)式定義。
RequestDelegate app = context =>
{
// ......
context.Response.StatusCode = StatusCodes.Status404NotFound;
return Task.CompletedTask;
};
來源:在 GitHub 中查看 ApplicationBuilder 源碼
把它翻譯成熟悉的方法形式,就是下面這個(gè)樣子:
public Task app(HttpContext context)
{
// ......
context.Response.StatusCode = StatusCodes.Status404NotFound;
return Task.CompletedTask;
};
這段代碼只是設(shè)置了 Http 的響應(yīng)狀態(tài)碼為 404,并直接返回了一個(gè)已經(jīng)完成的任務(wù)對(duì)象。
為了脫離 ASP.NET Core 復(fù)雜的環(huán)境,可以簡(jiǎn)單地進(jìn)行后繼的演示,我們自定義一個(gè)模擬 HttpContext 的類型 HttpContextSample 和相應(yīng)的 RequestDelegate 委托類型。
在模擬請(qǐng)求的 HttpContextSample 中,我們內(nèi)部定義了一個(gè) StringBuilder 來保存處理的結(jié)果,以便進(jìn)行檢查。其中的 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);
這樣,我們可以定義一個(gè)基礎(chǔ)的,使用 RequestDelegate 的示例代碼。
// 定義一個(gè)表示處理請(qǐng)求的委托對(duì)象
RequestDelegate app = context =>
{
context.Output.AppendLine("End of output.");
return Task.CompletedTask;
};
// 創(chuàng)建模擬當(dāng)前請(qǐng)求的對(duì)象
var context1 = new HttpContextSample();
// 處理請(qǐng)求
app(context1);
// 輸出請(qǐng)求的處理結(jié)果
Console.WriteLine(context1.Output.ToString());
執(zhí)行之后,可以得到如下的輸出
End of output.
處理管道中間件
所謂的處理管道是使用多個(gè)中間件串聯(lián)起來實(shí)現(xiàn)的。每個(gè)中間件當(dāng)然需要提供處理請(qǐng)求的 RequestDelegate 支持。在請(qǐng)求處理管道中,通常會(huì)有多個(gè)中間件串聯(lián)起來,構(gòu)成處理管道。

但是,如何將多個(gè)中間件串聯(lián)起來呢?
可以考慮兩種實(shí)現(xiàn)方式:函數(shù)式和方法式。
方法式就是再通過另外的方法將注冊(cè)的中間件組織起來,構(gòu)建一個(gè)處理管道,以后通過調(diào)用該方法來實(shí)現(xiàn)管道。而函數(shù)式是將整個(gè)處理管道看成一個(gè)高階函數(shù),以后通過調(diào)用該函數(shù)來實(shí)現(xiàn)管道。
方法式的問題是在后繼中間件處理之前需要一個(gè)方法,后繼中間件處理之后需要一個(gè)方法,這就是為什么 ASP.NET Web Form 有那么多事件的原因。
如果我們只是把后繼的中間件中的處理看成一個(gè)函數(shù),那么,每個(gè)中間件只需要分成 3 步即可:
- 前置處理
- 調(diào)用后繼的中間件
- 后置處理
在 ASP.NET Core 中是使用函數(shù)式來實(shí)現(xiàn)請(qǐng)求的處理管道的。
在函數(shù)式編程中,函數(shù)本身是可以作為一個(gè)參數(shù)來進(jìn)行傳遞的。這樣可以實(shí)現(xiàn)高階函數(shù)。也就是說函數(shù)的組合結(jié)果還是一個(gè)函數(shù)。
對(duì)于整個(gè)處理管道,我們最終希望得到的形式還是一個(gè) RequestDelegate,也就是一個(gè)對(duì)當(dāng)前請(qǐng)求的 HttpContext 進(jìn)行處理的函數(shù)。
本質(zhì)上來講,中間件就是一個(gè)用來生成 RequestDelegate 對(duì)象的生成函數(shù)。
為了將多個(gè)管道中間件串聯(lián)起來,每個(gè)中間件需要接收下一個(gè)中間件的處理請(qǐng)求的函數(shù)作為參數(shù),中間件本身返回一個(gè)處理請(qǐng)求的 RequestDelegate 委托對(duì)象。所以,中間件實(shí)際上是一個(gè)生成器函數(shù)。
使用 C# 的委托表示出來,就是下面的一個(gè)類型。所以,在 ASP.NET Core 中,中間件的類型就是這個(gè) Func<T, TResult>。
Func<RequestDelegate, RequestDelegate>
在 Doc 中查看 Func<T, TResult> 的文檔
這個(gè)概念比較抽象,與我們所熟悉的面向?qū)ο缶幊谭绞酵耆煌?,下面我們使用一個(gè)示例進(jìn)行說明。
我們通過一個(gè)中間件來演示它的模擬實(shí)現(xiàn)代碼。下面的代碼定義了一個(gè)中間件,該中間件接收一個(gè)表示后繼處理的函數(shù),中間件的返回結(jié)果是創(chuàng)建的另外一個(gè) RequestDelegate 對(duì)象。它的內(nèi)部通過調(diào)用下一個(gè)處理函數(shù)來完成中間件之間的級(jí)聯(lián)。
// 定義中間件
Func<RequestDelegate, RequestDelegate> middleware1 = next => {
// 中間件返回一個(gè) RequestDelegate 對(duì)象
return (HttpContextSample context) => {
// 中間件 1 的處理內(nèi)容
context.Output.AppendLine("Middleware 1 Processing.");
// 調(diào)用后繼的處理函數(shù)
return next(context);
};
};
把它和我們前面定義的 app 委托結(jié)合起來如下所示,注意調(diào)用中間件的結(jié)果是返回一個(gè)新的委托函數(shù)對(duì)象,它就是我們的處理管道。
// 最終的處理函數(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);
};
};
// 得到一個(gè)有一個(gè)處理步驟的管道
var pipeline1 = middleware1(app);
// 準(zhǔn)備一個(gè)表示當(dāng)前請(qǐng)求的對(duì)象
var context2 = new HttpContextSample();
// 通過管道處理當(dāng)前請(qǐng)求
pipeline1(context2);
// 輸出請(qǐng)求的處理結(jié)果
Console.WriteLine(context2.Output.ToString());
可以得到如下的輸出
Middleware 1 Processing.
End of output.
繼續(xù)增加第二個(gè)中間件來演示多個(gè)中間件的級(jí)聯(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);
// 準(zhǔn)備當(dāng)前的請(qǐng)求對(duì)象
var context3 = new HttpContextSample();
// 處理請(qǐng)求
pipeline2(context3);
// 輸出處理結(jié)果
Console.WriteLine(context3.Output.ToString());
當(dāng)前的輸出
Middleware 2 Processing.
Middleware 1 Processing.
End of output.
如果我們把這些中間件保存到幾個(gè)列表中,就可以通過循環(huán)來構(gòu)建處理管道。下面的示例重復(fù)使用了前面定義的 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)建請(qǐng)求上下文對(duì)象
var context4 = new HttpContextSample();
// 使用處理管道處理請(qǐng)求
app(context4);
// 輸出處理結(jié)果
Console.WriteLine(context4.Output.ToString());
輸出結(jié)果與上一示例完全相同
Middleware 2 Processing.
Middleware 1 Processing.
End of output.
但是,有一個(gè)問題,我們后加入到列表中的中間件 2 是先執(zhí)行的,而先加入到列表中的中間件 1 是后執(zhí)行的。如果希望實(shí)際的執(zhí)行順序與加入的順序一致,只需要將這個(gè)列表再反轉(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)在,我們可以回到實(shí)際的 ASP.NET Core 代碼中,把 ASP.NET Core 中 ApplicationBuilder 的核心代碼 Build() 方法抽象之后,可以得到如下的關(guān)鍵代碼。
注意 Build() 方法就是構(gòu)建我們的請(qǐng)求處理管道,它返回了一個(gè) RequestDelegate 對(duì)象,該對(duì)象實(shí)際上是一個(gè)委托對(duì)象,代表了一個(gè)處理當(dāng)前請(qǐng)求的處理管道函數(shù),它就是我們所謂的處理管道,以后我們將通過該委托來處理請(qǐng)求。
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 源碼
強(qiáng)類型的中間件
函數(shù)形式的中間件使用比較方便,可以直接在管道定義中使用。但是,如果我們希望能夠定義獨(dú)立的中間件,使用強(qiáng)類型的類來定義會(huì)更加方便一些。
public interface IMiddleware {
public System.Threading.Tasks.Task InvokeAsync (
Microsoft.AspNetCore.Http.HttpContext context,
Microsoft.AspNetCore.Http.RequestDelegate next);
}
我們定義的強(qiáng)類型中間件可以選擇實(shí)現(xiàn)裝個(gè)接口。
next 表示請(qǐng)求處理管道中的下一個(gè)中間件,處理管道會(huì)將它提供給你定義的中間件。這是將各個(gè)中間件連接起來的關(guān)鍵。
如果當(dāng)前中間件需要將請(qǐng)求繼續(xù)分發(fā)給后繼的中間件繼續(xù)處理,只需要調(diào)用這個(gè)委托對(duì)象即可。否則,應(yīng)用程序針對(duì)該請(qǐng)求的處理到此為止。
例如,增加一個(gè)可以添加自定義響應(yīng)頭的中間件,如下所示:
using System.Threading.Tasks;
public class CustomResponseHeader: IMiddleware
{
// 使用構(gòu)造函數(shù)完成服務(wù)依賴的定義
public CustomResponseHeader()
{
}
public Task InvodeAsync(HttpContextSample context, RequestDelegate next)
{
context.Output.AppendLine("From Custom Middleware.");
return next(context);
}
}
這更好看懂了,可是它怎么變成那個(gè) 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ā)者可以使用熟悉的對(duì)象方式開發(fā)中間件,而系統(tǒng)內(nèi)部自動(dòng)根據(jù)你的定義,生成出來一個(gè) Func<RequestDelegate, RequestDelegate> 形式的中間件。
ASP.NET Core 使用該類型中間件的形式如下所示,這是提供了一個(gè)方便的擴(kuò)展方法來完成這個(gè)工作。
.UseMiddleware<CustomResponseHeader>();
按照約定定義中間件
除了實(shí)現(xiàn) IMiddleware 這個(gè)接口,還可以使用約定方式來創(chuàng)建中間件。
按照約定定義中間件不需要實(shí)現(xiàn)某個(gè)預(yù)定義的接口或者繼承某個(gè)基類,而是需要遵循一些約定即可。約定主要體現(xiàn)在如下幾個(gè)方面:
- 中間件需要一個(gè)公共的有效構(gòu)造函數(shù),該構(gòu)造函數(shù)必須包含一個(gè)類型為 RequestDelegate 類型的參數(shù)。它代表后繼的中間件處理函數(shù)。構(gòu)造函數(shù)不僅可以包含任意其它參數(shù),對(duì) RequestDelegate 參數(shù)出現(xiàn)的位置也沒有任何限制。
- 針對(duì)請(qǐng)求的處理實(shí)現(xiàn)再返回類型為 Task 的 InvokeAsync() 方法或者同步的 Invoke() 方法中,方法的第一個(gè)參數(shù)表示當(dāng)前的請(qǐng)求上下文 HttpContext 對(duì)象,對(duì)于其他參數(shù),雖然約定并未進(jìn)行限制,但是由于這些參數(shù)最終由依賴注入框架提供,所以,相應(yīng)的服務(wù)注冊(cè)必須提供。
構(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 中使用按照約定定義的中間件語法與使用強(qiáng)類型方式相同:
.UseMiddleware<RequestCultureMiddleware >();
中間件的順序
中間件安裝一定順尋構(gòu)造成為請(qǐng)求處理管道,常見的處理管道如下所示:

實(shí)現(xiàn) BeginRequest 和 EndRequest
理解了請(qǐng)求處理管道的原理,下面看它的一個(gè)應(yīng)用。
在 ASP.NET 中我們可以使用預(yù)定義的 Begin_Request 和 EndRequest 處理步驟。
現(xiàn)在整個(gè)請(qǐng)求處理管道都是我們自己來進(jìn)行構(gòu)建了,那么怎么實(shí)現(xiàn) Begin_Request 和 EndRequest 呢?使用中間件可以很容易實(shí)現(xiàn)它。
首先,這兩個(gè)步驟是請(qǐng)求處理的第一個(gè)和最后一個(gè)步驟,顯然,該中間件必須是第一個(gè)注冊(cè)到管道中的。
所謂的 Begin_Request 就是在調(diào)用 next() 之間的處理了,而 End_Request 就是在調(diào)用 next() 之后的處理了。在 https://stackoverflow.com/questions/40604609/net-core-endrequest-middleware 中就有一個(gè)示例,我們將它修改一下,如下所示:
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)
{
// 第一個(gè)注冊(cè)
app.UseMiddleware<BeginEndRequestMiddleware>();
// Register other middelware here such as:
app.UseMvc();
}
總結(jié)
到此這篇關(guān)于ASP.NET Core處理管道深入理解的文章就介紹到這了,更多相關(guān)ASP.NET Core處理管道內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
relaxlife.net發(fā)布一個(gè)自己開發(fā)的中文分詞程序
relaxlife.net發(fā)布一個(gè)自己開發(fā)的中文分詞程序...2007-03-03
.NET?Core中簡(jiǎn)單的郵箱格式校驗(yàn)方式
這篇文章主要給大家介紹了關(guān)于.NET?Core中簡(jiǎn)單的郵箱格式校驗(yàn)方式的相關(guān)資料,文中通過實(shí)例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2022-03-03
運(yùn)用.net core中實(shí)例講解RabbitMQ
RabbitMQ是實(shí)現(xiàn)了高級(jí)消息隊(duì)列協(xié)議(AMQP)的開源消息代理軟件(亦稱面向消息的中間件),本文詳細(xì)講解了RabbitMQ以及運(yùn)用.net core中實(shí)例講解其6中模式,感興趣的小伙伴一起來學(xué)習(xí)吧2021-09-09
ASP.NET Core MVC解決控制器同名Action請(qǐng)求不明確的問題
這篇文章主要介紹了ASP.NET Core MVC解決控制器同名Action請(qǐng)求不明確的問題,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2021-03-03
C#實(shí)現(xiàn)HTTP協(xié)議迷你服務(wù)器(兩種方法)
用C#語言實(shí)現(xiàn)HTTP協(xié)議的服務(wù)器類本文將以兩種稍微有差別的方式用C#語言實(shí)現(xiàn);要完成高性能的Web服務(wù)功能,通常都是需要寫入到服務(wù),如IIS,Apache Tomcat感興趣的朋友可以了解下,或許對(duì)你學(xué)習(xí)c#有所幫助2013-02-02
EasyUI Tree+Asp.net實(shí)現(xiàn)權(quán)限樹或目錄樹導(dǎo)航的簡(jiǎn)單實(shí)例
本篇文章主要是對(duì)EasyUI Tree+Asp.net實(shí)現(xiàn)權(quán)限樹或目錄樹導(dǎo)航的簡(jiǎn)單實(shí)例進(jìn)行了介紹,需要的朋友可以過來參考下,希望對(duì)大家有所幫助2014-02-02
asp.net 簡(jiǎn)易生成注冊(cè)碼(數(shù)字+大小寫字母)
注釋寫的很詳細(xì),不做過多的描述了,希望能給初學(xué)者帶來一些幫助,同時(shí)也是自己知識(shí)的一個(gè)積累過程。2008-11-11
"PageMethods未定義"或"對(duì)象不支持此屬性或方法"解決方法分享
PageMethods未定義或?qū)ο蟛恢С执藢傩曰蚍椒ń鉀Q方法,需要的朋友可以參考下。2010-12-12

