日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 编程语言 > asp.net >内容正文

asp.net

ASP.NET Core和json请求这样用真简单,axios、微信小程序得救了

發(fā)布時(shí)間:2023/12/4 asp.net 61 豆豆
生活随笔 收集整理的這篇文章主要介紹了 ASP.NET Core和json请求这样用真简单,axios、微信小程序得救了 小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

本文介紹了一種在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)在越來越多的前端請(qǐng)求體是json格式的,比如非常流行的AJAX前端庫axios的post請(qǐng)求默認(rèn)就是json格式的,微信小程序的請(qǐng)求也默認(rèn)是json格式的。在ASP.NET Core中可以通過[FromBody]來把Action的參數(shù)和請(qǐng)求數(shù)據(jù)綁定在一起。假如Http請(qǐng)求的內(nèi)容為:

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

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

public IActionResultLogin([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è)開源庫,讓我們可以用這樣的方式來進(jìn)行簡(jiǎn)單類型參數(shù)的綁定:

Test([FromJsonBody] int i2, [FromJsonBody("author.age")]intaAge, [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è)庫使用.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)目中通過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":"zackyang"}

?

客戶端的請(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('Sendfailed'); });

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

public IActionResultTest([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]stringname) {if(ModelState.IsValid==false){varerrors = ModelState.SelectMany(e =>e.Value.Errors).Select(e=>e.ErrorMessage);returnJson("Invalid input!"+string.Join("\r\n",errors));}returnJson($"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('Sendfailed'); });

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

public async Task<int>Post([FromJsonBody("i1")] int i3, [FromJsonBody] int i2,[FromJsonBody("author.age")]intaAge,[FromJsonBody("author.father.name")] string dadName) {Debug.WriteLine(aAge);Debug.WriteLine(dadName);returni3 + i2+aAge; }

??

三、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í)候,FromJsonBodyBinder類就會(huì)被調(diào)用來進(jìn)行具體的計(jì)算。FromJsonBody這個(gè)庫的核心代碼都在FromJsonBodyBinder類中。

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

?

public static IApplicationBuilderUseFromJsonBody(this IApplicationBuilder appBuilder) {return appBuilder.UseMiddleware<FromJsonBodyMiddleware>(); }

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

public sealed class FromJsonBodyMiddleware {public const string RequestJsonObject_Key = "RequestJsonObject";private readonly RequestDelegate _next;public FromJsonBodyMiddleware(RequestDelegate next){_next= next;}publicasync 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;}Streambody = 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;JsonElementjsonRoot = document.RootElement;context.Items[RequestJsonObject_Key]= jsonRoot;await _next(context);}}catch(JsonExceptionex){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的也不處理,這樣可以避免無關(guān)請(qǐng)求被處理的性能影響。

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

接下來,使用.NET 新的Json處理庫System.Text.Json來進(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<string, FromJsonBodyAttribute> fromJsonBodyAttrCache = new ConcurrentDictionary<string,FromJsonBodyAttribute>();public Task BindModelAsync(ModelBindingContext bindingContext){var key = FromJsonBodyMiddleware.RequestJsonObject_Key;objectitemValue =bindingContext.ActionContext.HttpContext.Items[key];JsonElement jsonObj =(JsonElement)itemValue;string fieldName = bindingContext.FieldName;FromJsonBodyAttribute fromJsonBodyAttr =GetFromJsonBodyAttr(bindingContext, fieldName);if(!string.IsNullOrWhiteSpace(fromJsonBodyAttr.PropertyName)){fieldName =fromJsonBodyAttr.PropertyName;}object jsonValue;if(ParseJsonValue(jsonObj, fieldName, out jsonValue)){objecttargetValue =jsonValue.ChangeType(bindingContext.ModelType);bindingContext.Result=ModelBindingResult.Success(targetValue);}else{bindingContext.Result= ModelBindingResult.Failed();}return Task.CompletedTask;}private static bool ParseJsonValue(JsonElement jsonObj, string fieldName, out objectjsonValue){int firstDotIndex = fieldName.IndexOf('.');if(firstDotIndex>=0){string firstPropName = fieldName.Substring(0, firstDotIndex);string leftPart = fieldName.Substring(firstDotIndex + 1);if(jsonObj.TryGetProperty(firstPropName,out JsonElement firstElement)){return ParseJsonValue(firstElement, leftPart, out jsonValue);}else{jsonValue= null;return false;}}else{bool b = jsonObj.TryGetProperty(fieldName, out JsonElement jsonProperty);if(b){jsonValue= jsonProperty.GetValue();}else{jsonValue= null;}return b;} }private static FromJsonBodyAttribute GetFromJsonBodyAttr(ModelBindingContext bindingContext, string fieldName){var actionDesc = bindingContext.ActionContext.ActionDescriptor;string actionId = actionDesc.Id;string cacheKey = $"{actionId}:{fieldName}";FromJsonBodyAttribute fromJsonBodyAttr;if(!fromJsonBodyAttrCache.TryGetValue(cacheKey, out fromJsonBodyAttr)){var ctrlActionDesc =bindingContext.ActionContext.ActionDescriptor as ControllerActionDescriptor;var fieldParameter =ctrlActionDesc.MethodInfo.GetParameters().Single(p =>p.Name == fieldName);fromJsonBodyAttr=fieldParameter.GetCustomAttributes(typeof(FromJsonBodyAttribute),false) .Single() as FromJsonBodyAttribute;fromJsonBodyAttrCache[cacheKey]= fromJsonBodyAttr;} return fromJsonBodyAttr;} }

下面對(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取出來。如果Action有5個(gè)參數(shù),那么BindModelAsync就會(huì)被調(diào)用5次,如果每次BindModelAsync都去做“Json請(qǐng)求體的解析”將會(huì)效率比較低,這樣在FromJsonBodyMiddleware中提前解析好就可以提升數(shù)據(jù)綁定的性能。

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

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

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

自定義的GetFromJsonBodyAttr方法使用反射獲得參數(shù)上標(biāo)注的FromJsonBodyAttribute對(duì)象。為了提升性能,這里把獲取的結(jié)果緩存起來。非常幸運(yùn)的是,ASP.NET Core中的ActionDescriptor對(duì)象有Id屬性,用來獲得一個(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è)英語學(xué)習(xí)網(wǎng)站一年的穩(wěn)定運(yùn)行驗(yàn)證,各位可以放心使用。希望這個(gè)開源項(xiàng)目能夠幫助大家,歡迎使用過程中反饋問題,如果感覺好用,歡迎推薦給其他朋友。

總結(jié)

以上是生活随笔為你收集整理的ASP.NET Core和json请求这样用真简单,axios、微信小程序得救了的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。