最美情侣中文字幕电影,在线麻豆精品传媒,在线网站高清黄,久久黄色视频

歡迎光臨散文網(wǎng) 會(huì)員登陸 & 注冊(cè)

ASP.NET Core和json請(qǐng)求這樣用真簡(jiǎn)單,axios、微信小程序得救了

2021-03-06 14:39 作者:楊中科  | 我要投稿

本文介紹了一種在ASP.NET Core MVC/ASP.NET Core WebAPI中,將axios等前端提交的json格式請(qǐng)求數(shù)據(jù),映射到Action方法的普通類型參數(shù)的方法,并且講解了其實(shí)現(xiàn)原理。

一、????? 為什么要簡(jiǎn)化json格式請(qǐng)求的參數(shù)綁定

?在ASP.NET Core MVC/ ASP.NET Core WebAPI(以下簡(jiǎn)稱ASP.NET Core)中,可以使用[FromQuery] 從QueryString中獲取參數(shù)值,也可以使用[FromForm]從表單格式(x-www-form-urlencoded)的請(qǐng)求中獲取參數(shù)值。

隨著前后端分離的流行,現(xiàn)在越來(lái)越多的前端請(qǐng)求體是json格式的,比如非常流行的AJAX前端庫(kù)axios的post請(qǐng)求默認(rèn)就是json格式的,微信小程序的請(qǐng)求也默認(rèn)是json格式的。在ASP.NET Core中可以通過(guò)[FromBody]來(lái)把Action的參數(shù)和請(qǐng)求數(shù)據(jù)綁定在一起。假如Http請(qǐng)求的內(nèi)容為:

{“UserName”:”test”,”Password”:”123”}

那么就要先聲明一個(gè)包含UserName、Password兩個(gè)屬性的User類,然后再把Action的參數(shù)如下聲明:

public IActionResult Login([FromBody]User u);

這樣幾乎每一個(gè)Action方法都要聲明一個(gè)和請(qǐng)求對(duì)應(yīng)的復(fù)雜類,如果項(xiàng)目中Action很多的話,也就會(huì)有非常多的“Action參數(shù)類”,不勝其煩。ASP.NET Core對(duì)于Json請(qǐng)求,并不能像[FromQuery]一樣把Json的某個(gè)屬性和簡(jiǎn)單類型的Action參數(shù)綁定到一起。

因此我開發(fā)了YouZack.FromJsonBody這個(gè)開源庫(kù),讓我們可以用這樣的方式來(lái)進(jìn)行簡(jiǎn)單類型參數(shù)的綁定:

Test([FromJsonBody] int i2,

[FromJsonBody("author.age")]int aAge,

[FromJsonBody("author.father.name")] string dadName)

這樣的Action參數(shù)可以直接從如下的Json請(qǐng)求中獲取數(shù)據(jù):

{"i1":1,"i2":5,"author":{"name":"yzk","age":18,"father":{"name":"laoyang","age":28}}}

二、????? FromJsonBody使用方法

這個(gè)庫(kù)使用.NET Standard開發(fā),因此可以支持.NET Framework及.NET Core,既支持ASP.NET Core MVC,也支持ASP.NET Core Web API。

GitHub地址:https://github.com/yangzhongke/YouZack.FromJsonBody

第一步:

在ASP.NET Core項(xiàng)目中通過(guò)NuGet安裝包:

Install-Package YouZack.FromJsonBody

第二步:

在項(xiàng)目的Startup.cs中添加using YouZack.FromJsonBody;

然后在Configure方法的UseEndpoints()之前添加如下代碼:

app.UseFromJsonBody();

第三步:

在Controller的Action參數(shù)中[FromJsonBody]這個(gè)Attribute,參數(shù)默認(rèn)從Json請(qǐng)求的同名的屬性中綁定獲取值。如果設(shè)定FromJsonBody的PropertyName參數(shù),則從Json請(qǐng)求的PropertyName這個(gè)名字的屬性中綁定獲取值,PropertyName的值也支持[FromJsonBody("author.father.name")]這樣的多級(jí)屬性綁定。

?

舉例1,對(duì)于如下的Json請(qǐng)求:

{"phoneNumber":"119110","age":3,"salary":333.3,"gender":true,"dir":"west","name":"zack yang"}

?客戶端的請(qǐng)求代碼:

axios.post('@Url.Action("Test","Home")',

 ? ? ? { phoneNumber: "119110", age: 3, salary: 333.3, gender: true,dir:"west",name:"zack yang" })

.then(function (response)

{

 ? ? ? alert(response.data);

})

.catch(function (error)

{

 ? ? ? alert('Send failed');

});

服務(wù)器端Controller的Action代碼:

public IActionResult Test([FromJsonBody]string phoneNumber, [FromJsonBody]string test1,

 ? ? ? [FromJsonBody][Range(0,100,ErrorMessage ="Age must be between 0 and 100")]int? age,

 ? ? ? [FromJsonBody] bool gender,

 ? ? ? [FromJsonBody] double salary,[FromJsonBody]DirectionTypes dir,

 ? ? ? [FromJsonBody][Required]string name)

{

 ? ? ? if(ModelState.IsValid==false)

 ? ? ? {

 ? ? ? ? ? ? ?var errors = ModelState.SelectMany(e => e.Value.Errors).Select(e=>e.ErrorMessage);

 ? ? ? ? ? ? ?return Json("Invalid input!"+string.Join("\r\n",errors));

 ? ? ? }

 ? ? ?return Json($"phoneNumber={phoneNumber},test1={test1},age={age},gender={gender},salary={salary},dir={dir}");

}

舉例2,對(duì)于如下的Json請(qǐng)求:

?

{"i1":1,"i2":5,"author":{"name":"yzk","age":18,"father":{"name":"laoyang","age":28}}}

客戶端的請(qǐng)求代碼:

axios.post('/api/API',

 ? ? ? { i1: 1, i2: 5, author: { name: 'yzk', age: 18, father: {name:'laoyang',age:28}} })

.then(function (response)

{

 ? ? ? alert(response.data);

})

.catch(function (error)

{

 ? ? ? alert('Send failed');

});

服務(wù)器端Controller的Action代碼:

public async Task

三、????? FromJsonBody原理講解

項(xiàng)目的全部代碼請(qǐng)參考GitHub地址:

https://github.com/yangzhongke/YouZack.FromJsonBody

FromJsonBodyAttribute是一個(gè)自定義的數(shù)據(jù)綁定的Attribute,主要源代碼如下:

public class FromJsonBodyAttribute : ModelBinderAttribute

{

 ? ? ? public string PropertyName { get; private set; }

 

 ? ? ? public FromJsonBodyAttribute(string propertyName=null) : base(typeof(FromJsonBodyBinder))

 ? ? ? {

 ? ? ? ? ? ? ?this.PropertyName = propertyName;

 ? ? ? }

}

所有數(shù)據(jù)綁定Attribute都要繼承自ModelBinderAttribute類,當(dāng)需要嘗試計(jì)算一個(gè)被FromJsonBodyAttribute修飾的參數(shù)的綁定值的時(shí)候,F(xiàn)romJsonBodyBinder類就會(huì)被調(diào)用來(lái)進(jìn)行具體的計(jì)算。FromJsonBody這個(gè)庫(kù)的核心代碼都在FromJsonBodyBinder類中。

因?yàn)镕romJsonBodyBinder需要從Json請(qǐng)求體中獲取數(shù)據(jù),為了提升性能,我們編寫了一個(gè)自定義的中間件FromJsonBodyMiddleware來(lái)進(jìn)行Json請(qǐng)求體字符串到解析完成的內(nèi)存對(duì)象JsonDocument,然后把解析完成的JsonDocument對(duì)象供后續(xù)的FromJsonBodyBinder使用。我們?cè)赟tartup中調(diào)用的UseFromJsonBody()方法就是在應(yīng)用FromJsonBodyMiddleware中間件,可以看一下UseFromJsonBody()方法的源代碼如下:

?

public static IApplicationBuilder UseFromJsonBody(this IApplicationBuilder appBuilder)

{

 ? ? ? return appBuilder.UseMiddleware

如下是FromJsonBodyMiddleware類的主要代碼(全部代碼見Github)

public sealed class FromJsonBodyMiddleware

{

 ? ? ? public const string RequestJsonObject_Key = "RequestJsonObject";

 

 ? ? ? private readonly RequestDelegate _next;

 

 ? ? ? public FromJsonBodyMiddleware(RequestDelegate next)

 ? ? ? {

 ? ? ? ? ? ? ?_next = next;

 ? ? ? }

 

 ? ? ? public async Task Invoke(HttpContext context)

 ? ? ? {

 ? ? ? ? ? ? ?string method = context.Request.Method;

 ? ? ? ? ? ? ?if (!Helper.ContentTypeIsJson(context, out string charSet)

 ? ? ? ? ? ? ? ? ? ? ||"GET".Equals(method, StringComparison.OrdinalIgnoreCase))

 ? ? ? ? ? ? ?{

 ? ? ? ? ? ? ? ? ? ? await _next(context);

 ? ? ? ? ? ? ? ? ? ? return;

 ? ? ? ? ? ? ?}

 ? ? ? ? ? ? ?Encoding encoding;

 ? ? ? ? ? ? ?if(string.IsNullOrWhiteSpace(charSet))

 ? ? ? ? ? ? ?{

 ? ? ? ? ? ? ? ? ? ? encoding = Encoding.UTF8;

 ? ? ? ? ? ? ?}

 ? ? ? ? ? ? ?else

 ? ? ? ? ? ? ?{

 ? ? ? ? ? ? ? ? ? ? encoding = Encoding.GetEncoding(charSet);

 ? ? ? ? ? ? ?} ? ?

 

 ? ? ? ? ? ? ?context.Request.EnableBuffering();

 ? ? ? ? ? ? ?int contentLen = 255;

 ? ? ? ? ? ? ?if (context.Request.ContentLength != null)

 ? ? ? ? ? ? ?{

 ? ? ? ? ? ? ? ? ? ? contentLen = (int)context.Request.ContentLength;

 ? ? ? ? ? ? ?}

 ? ? ? ? ? ? ?Stream body = context.Request.Body;

 ? ? ? ? ? ? ?string bodyText;

 ? ? ? ? ? ? ?if(contentLen<=0)

 ? ? ? ? ? ? ?{

 ? ? ? ? ? ? ? ? ? ? bodyText = "";

 ? ? ? ? ? ? ?}

 ? ? ? ? ? ? ?else

 ? ? ? ? ? ? ?{

 ? ? ? ? ? ? ? ? ? ? using (StreamReader reader = new StreamReader(body, encoding, true, contentLen, true))

 ? ? ? ? ? ? ? ? ? ? {

 ? ? ? ? ? ? ? ? ? ? ? ? ? ?bodyText = await reader.ReadToEndAsync();

 ? ? ? ? ? ? ? ? ? ? }

 ? ? ? ? ? ? ?}

 ? ? ? ? ? ? ?if(string.IsNullOrWhiteSpace(bodyText))

 ? ? ? ? ? ? ?{

 ? ? ? ? ? ? ? ? ? ? await _next(context);

 ? ? ? ? ? ? ? ? ? ? return;

 ? ? ? ? ? ? ?}

 ? ? ? ? ? ? ?if(!(bodyText.StartsWith("{")&& bodyText.EndsWith("}")))

 ? ? ? ? ? ? ?{

 ? ? ? ? ? ? ? ? ? ? await _next(context);

 ? ? ? ? ? ? ? ? ? ? return;

 ? ? ? ? ? ? ?}

 ? ? ? ? ? ? 

 ? ? ? ? ? ? ?try

 ? ? ? ? ? ? ?{

 ? ? ? ? ? ? ? ? ? ? using (JsonDocument document = JsonDocument.Parse(bodyText))

 ? ? ? ? ? ? ? ? ? ? {

 ? ? ? ? ? ? ? ? ? ? ? ? ? ?body.Position = 0;

 ? ? ? ? ? ? ? ? ? ? ? ? ? ?JsonElement jsonRoot = document.RootElement;

 ? ? ? ? ? ? ? ? ? ? ? ? ? ?context.Items[RequestJsonObject_Key] = jsonRoot;

 ? ? ? ? ? ? ? ? ? ? ? ? ? ?await _next(context);

 ? ? ? ? ? ? ? ? ? ? }

 ? ? ? ? ? ? ?}

 ? ? ? ? ? ? ?catch(JsonException ex)

 ? ? ? ? ? ? ?{

 ? ? ? ? ? ? ? ? ? ? await _next(context);

 ? ? ? ? ? ? ? ? ? ? return;

 ? ? ? ? ? ? ?}

 ? ? ? }

}

每個(gè)Http請(qǐng)求到達(dá)服務(wù)器的時(shí)候,Invoke都會(huì)被調(diào)用。因?yàn)镚et請(qǐng)求一般不帶請(qǐng)求體,所以這里對(duì)于Get請(qǐng)求不處理;同時(shí)對(duì)于請(qǐng)求的ContentType不是application/json的也不處理,這樣可以避免無(wú)關(guān)請(qǐng)求被處理的性能影響。

為了減少內(nèi)存占用,默認(rèn)情況下,ASP.NET Core中對(duì)于請(qǐng)求體的數(shù)據(jù)只能讀取一次,不能重復(fù)讀取。FromJsonBodyMiddleware需要讀取解析請(qǐng)求體的Json,但是后續(xù)的ASP.NET Core的其他組件也可能會(huì)還要再讀取請(qǐng)求體,因此我們通過(guò)Request.EnableBuffering()允許請(qǐng)求體的多次讀取,這樣會(huì)對(duì)內(nèi)存占用有輕微的提升。不過(guò)一般情況下Json請(qǐng)求的請(qǐng)求體都不會(huì)太大,所以這不會(huì)是一個(gè)嚴(yán)重的問(wèn)題。

接下來(lái),使用.NET 新的Json處理庫(kù)System.Text.Json來(lái)進(jìn)行Json請(qǐng)求的解析:

JsonDocument document = JsonDocument.Parse(bodyText)

解析完成的Json對(duì)象放到context.Items中,供FromJsonBodyBinder使用:

context.Items[RequestJsonObject_Key] = jsonRoot

?下面是FromJsonBodyBinder類的核心代碼:

public class FromJsonBodyBinder : IModelBinder

{

 ? ? ? public static readonly IDictionary

下面對(duì)FromJsonBodyBinder類的代碼做一下分析,當(dāng)對(duì)一個(gè)標(biāo)注了[FromJsonBody]的參數(shù)進(jìn)行綁定的時(shí)候,BindModelAsync方法會(huì)被調(diào)用,綁定的結(jié)果(也就是計(jì)算后參數(shù)的值)要設(shè)置到bindingContext.Result中,如果綁定成功就設(shè)置:ModelBindingResult.Success(綁定的值),如果因?yàn)閿?shù)據(jù)非法等導(dǎo)致綁定失敗就設(shè)置ModelBindingResult.Failed()

在FromJsonBodyBinder類的BindModelAsync方法中,首先從bindingContext.ActionContext.HttpContext.Items[key]中把FromJsonBodyMiddleware中解析完成的JsonElement取出來(lái)。如果Action有5個(gè)參數(shù),那么BindModelAsync就會(huì)被調(diào)用5次,如果每次BindModelAsync都去做“Json請(qǐng)求體的解析”將會(huì)效率比較低,這樣在FromJsonBodyMiddleware中提前解析好就可以提升數(shù)據(jù)綁定的性能。

接下來(lái)調(diào)用自定義方法GetFromJsonBodyAttr取到方法參數(shù)上標(biāo)注的FromJsonBodyAttribute對(duì)象,檢測(cè)一下FromJsonBodyAttribute上是否設(shè)置了PropertyName:如果設(shè)置了的話,就用PropertyName做為要綁定的Json的屬性名;如果沒有設(shè)置PropertyName,則用bindingContext.FieldName這個(gè)綁定的參數(shù)的變量名做為要綁定的Json的屬性名。

接下來(lái)調(diào)用自定義方法ParseJsonValue從Json對(duì)象中取出對(duì)應(yīng)屬性的值,由于從Json對(duì)象中取出來(lái)的數(shù)據(jù)類型可能和參數(shù)的類型不一致,所以需要調(diào)用自定義的擴(kuò)展方法ChangeType()進(jìn)行類型轉(zhuǎn)換。ChangeType方法就是對(duì)Convert.ChangeType的封裝,然后對(duì)于可空類型、枚舉、Guid等特殊類型做了處理,具體到github上看源碼即可。

自定義的ParseJsonValue方法中通過(guò)簡(jiǎn)單的遞歸完成了對(duì)于"author.father.name"這樣多級(jí)Json嵌套的支持。firstPropName變量就是取出來(lái)的” author”, leftPart變量就是剩下的"father.name",然后遞歸調(diào)用ParseJsonValue進(jìn)一步計(jì)算。

自定義的GetFromJsonBodyAttr方法使用反射獲得參數(shù)上標(biāo)注的FromJsonBodyAttribute對(duì)象。為了提升性能,這里把獲取的結(jié)果緩存起來(lái)。非常幸運(yùn)的是,ASP.NET Core中的ActionDescriptor對(duì)象有Id屬性,用來(lái)獲得一個(gè)Action方法唯一的標(biāo)識(shí)符,再加上參數(shù)的名字,就構(gòu)成了這個(gè)緩存項(xiàng)的Key。

?

四、????? 總結(jié)

Zack. FromJsonBody可以讓ASP.NET Core MVC和ASP.NET Core WebAPI程序的普通參數(shù)綁定到Http請(qǐng)求的Json報(bào)文體中。這個(gè)開源項(xiàng)目已經(jīng)被youzack.com這個(gè)英語(yǔ)學(xué)習(xí)網(wǎng)站一年的穩(wěn)定運(yùn)行驗(yàn)證,各位可以放心使用。希望這個(gè)開源項(xiàng)目能夠幫助大家,歡迎使用過(guò)程中反饋問(wèn)題,如果感覺好用,歡迎推薦給其他朋友。

ASP.NET Core和json請(qǐng)求這樣用真簡(jiǎn)單,axios、微信小程序得救了的評(píng)論 (共 條)

分享到微博請(qǐng)遵守國(guó)家法律
肃宁县| 苗栗县| 呼图壁县| 公安县| 保靖县| 慈利县| 玉山县| 安顺市| 金沙县| 合水县| 张家界市| 西盟| 从化市| 万荣县| 轮台县| 安仁县| 交城县| 平塘县| 平度市| 邯郸县| 黔西县| 日土县| 南安市| 鄂温| 方城县| 皮山县| 巨野县| 安康市| 长春市| 安宁市| 大庆市| 华蓥市| 涿鹿县| 萍乡市| 剑河县| 本溪| 宜城市| 紫云| 永川市| 曲麻莱县| 宝兴县|