解决WebApi入参时多对象的问题

我们的项目是用WebApi提供数据服务,且WebPage跟APP中都有调用到。

WebApi提供的接口一多,就发现一个问题,我们项目中有很多接口是接收POST(安全原因,我们采用的是https)请求的,而且入参基本也就是一两个参数。在开发的后期发现WebApi中有很多对象,多到已经快到了难以管理的地步了。

比如我们的WebApi如下:

对应的入参对象是:

很难想象如果再有别的Controller的话,Models中会有多少对象,而这些对象往往使用一次,或者在一个方法中使用了一下就不再用了。

这显然令我非常不爽!

经过反复推敲,发现如果采用这种写法:

显然能减少不少对象,而且接口也相对更加清晰了,不用再按F12进入到对象里面看看里面到底有几个参数,每个参数又表示什么。

但是!WebApi不支持这种请求,这种把参数写在方法前的只能接受GET方式的请求,就是参数在url中的比如:http://localhost:8080/api/People/GetPeoplesByID?ID=1

这显然把我们的参数暴露了出来,https就没有卵用了!

怎么让WebApi接收这种请求那?

机智的我给出下面两种解决方法:

  • 方法一

我们注意到POST请求的过来的参数在Form Data中是这么写的:name=tlzzu&age=18,

相对的,而以GET方式请求过来的参数是这样的:http://localhost:端口号/api/People/ GetPeoplesByID? name=tlzzu&age=18

有没有注意到参数都是name=tlzzu&age=18 这样的,所以我们是不是可以把POST方式过来的Form Data 拼接到url中去,这样WebApi在后面解析的时候就能把他当成GET方式过来的参数了。

那么问题又来了,在哪里介入WebApi的请求处理过程呐?

我们知道ASP.NET WebApi是一个消息处理管道,这个管道是一组HttpMessageHandler的有序组-----引用自【ASP.NET Web API标准的"管道式"设计】

那也就是说我们可以写一个Handler来把POST过来的参数设置到GET里面。说干就干

新建一个SetPostToGetHandler.cs类

public
class
SetPostToGetHandler:System.Net.Http.DelegatingHandler

{

protected
override System.Threading.Tasks.Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, System.Threading.CancellationToken cancellationToken)

{

//如果不是POST请求,就不处理

if (request.Method !=
HttpMethod.Post)

return
base.SendAsync(request, cancellationToken);

//必须显示的指定 ContentType是application/x-www-form-urlencoded,如果是application/json则不处理

if (request.Content.Headers.ContentType ==null||string.IsNullOrWhiteSpace(request.Content.Headers.ContentType .MediaType)||!request.Content.Headers.ContentType.MediaType.Contains("application/x-www-form-urlencoded"))

return
base.SendAsync(request, cancellationToken);

//获取POST请求过来的参数

var formStr =request.Content.ReadAsFormDataAsync().Result.ToString();

if (!string.IsNullOrWhiteSpace(formStr))

{

var url =
string.IsNullOrWhiteSpace(request.RequestUri.Query) ?
string.Format("{0}?{1}", request.RequestUri.AbsoluteUri, formStr) : string.Format("{0}&{1}", request.RequestUri.AbsoluteUri, formStr);

//给request设置新的RequestUri对象

request.RequestUri =
new
Uri(url);

}

return
base.SendAsync(request, cancellationToken);

}

}

然后添加到WebApi MessageHandlers中

用Fidder测试,完美解决问题

  • 方法二

新建SimplePostVariableParameterBinding类

public
class
SimplePostVariableParameterBinding : HttpParameterBinding

{

private
const
string MultipleBodyParameters =
"MultipleBodyParameters";

?

public SimplePostVariableParameterBinding(HttpParameterDescriptor descriptor) : base(descriptor) { }

?

/// <summary>

/// Check for simple binding parameters in POST data. Bind POST

/// data as well as query string data

/// </summary>

/// <param name="metadataProvider"></param>

/// <param name="actionContext"></param>

/// <param name="cancellationToken"></param>

/// <returns></returns>

public
override
Task ExecuteBindingAsync(ModelMetadataProvider metadataProvider, HttpActionContext actionContext, CancellationToken cancellationToken)

{

string stringValue =
null;

try

{

NameValueCollection col = TryReadBody(actionContext.Request);

if (col !=
null)

stringValue = col[Descriptor.ParameterName];

// try reading query string if we have no POST/PUT match

if (stringValue ==
null)

{

var query = actionContext.Request.GetQueryNameValuePairs();

if (query !=
null)

{

var matches = query.Where(kv => kv.Key.ToLower() == Descriptor.ParameterName.ToLower());

if (matches.Count() >
0)

stringValue = matches.First().Value;

}

}

object value = StringToType(stringValue);

?

// Set the binding result here 给字段挨个赋值

SetValue(actionContext, value);

?

// now, we can return a completed task with no result

TaskCompletionSource<AsyncVoid> tcs =
new
TaskCompletionSource<AsyncVoid>();

tcs.SetResult(default(AsyncVoid));

return tcs.Task;

}

catch (Exception ex)

{

throw ex;

return
null;

}

}

?

?

/// <summary>

/// Method that implements parameter binding hookup to the global configuration object‘s

/// ParameterBindingRules collection delegate.

///

/// This routine filters based on POST/PUT method status and simple parameter

/// types.

/// </summary>

/// <example>

/// GlobalConfiguration.Configuration.

/// .ParameterBindingRules

/// .Insert(0,SimplePostVariableParameterBinding.HookupParameterBinding);

/// </example>

/// <param name="descriptor"></param>

/// <returns></returns>

public
static
HttpParameterBinding HookupParameterBinding(HttpParameterDescriptor descriptor)

{

try

{

var supportedMethods = descriptor.ActionDescriptor.SupportedHttpMethods;

// Only apply this binder on POST operations

if (supportedMethods.Contains(HttpMethod.Post))

{

var supportedTypes =
new
Type[] { typeof(string),

typeof(int),

typeof(long),

typeof(long?),

typeof(decimal),

typeof(double),

typeof(bool),

typeof(DateTime),

typeof(byte[])

};

if (supportedTypes.Where(typ => typ == descriptor.ParameterType).Count() >
0)

return
new
SimplePostVariableParameterBinding(descriptor);

}

}

catch (Exception ex)

{

throw ex;

}

return
null;

}

?

?

private
object StringToType(string stringValue)

{

object value =
null;

try

{

if (stringValue ==
null)

value =
null;

else
if (Descriptor.ParameterType ==
typeof(string))

value = stringValue;

else
if (Descriptor.ParameterType ==
typeof(int))

value =
int.Parse(stringValue, CultureInfo.CurrentCulture);

else
if (Descriptor.ParameterType ==
typeof(Int32))

value =
Int32.Parse(stringValue, CultureInfo.CurrentCulture);

else
if (Descriptor.ParameterType ==
typeof(Int64))

value =
Int64.Parse(stringValue, CultureInfo.CurrentCulture);

else
if (Descriptor.ParameterType ==
typeof(decimal))

value =
decimal.Parse(stringValue, CultureInfo.CurrentCulture);

else
if (Descriptor.ParameterType ==
typeof(double))

value =
double.Parse(stringValue, CultureInfo.CurrentCulture);

else
if (Descriptor.ParameterType ==
typeof(DateTime))

value =
DateTime.Parse(stringValue, CultureInfo.CurrentCulture);

else
if (Descriptor.ParameterType ==
typeof(bool))

{

value =
false;

if (stringValue ==
"true"
|| stringValue ==
"on"
|| stringValue ==
"1")

value =
true;

}

else

value = stringValue;

}

catch (Exception ex)

{

throw ex;

}

return value;

}

?

/// <summary>

/// Read and cache the request body

/// </summary>

/// <param name="request"></param>

/// <returns></returns>

private
NameValueCollection TryReadBody(HttpRequestMessage request)

{

object result =
null;

try

{

if (!request.Properties.TryGetValue(MultipleBodyParameters, out result))

{

var contentType = request.Content.Headers.ContentType.MediaType.ToLower();

if (contentType ==
null)

{

result =
null;

}

else
if (contentType.Contains("application/x-www-form-urlencoded"))

{

result = request.Content.ReadAsFormDataAsync().Result;

}

else
if (contentType.Contains("application/json"))//解决json问题

{

var jsonStr = request.Content.ReadAsStringAsync().Result;//{"Name":"tongl","Age":22}

var json =
JsonConvert.DeserializeObject<IDictionary<string, string>>(jsonStr);

if (json !=
null
|| json.Count >
0)

{

var nvc =
new
NameValueCollection();

foreach (var item in json)

{

nvc.Add(item.Key, item.Value);

}

result = nvc;

}

}

else

{

result =
null;

}

request.Properties.Add(MultipleBodyParameters, result);

}

}

catch (Exception ex)

{

throw ex;

}

return result as
NameValueCollection;

}

?

private
struct
AsyncVoid

{

}

}

这是我用bing(技术渣google实在翻不过去)搜了很久才找到的国外的这个大神写的办法,引用自这里

完整代码如下 源码下载

时间: 2024-10-13 08:17:00

解决WebApi入参时多对象的问题的相关文章

解决SpringMVC入参出现The request sent by the client was syntactically incorrect请求语法错误方法

使用SpringMVC出现The request sent by the client was syntactically incorrect.请求错误如下: 可以确定为提交的表单数据和目标方法的入参不一致所导致,表单数据可以多于目标入参个数,但目标参数没有被赋值,则会出现该异常,如下情况: 表单数据: 目标方法: Employee字段: 比对表单数据和Employee字段,可以发现,表单数据比Employee字段少,再加上目标方法的入参是一个 Employee对象,所以将会出现提交的数据不足,

特定元素个数的数组作为函数入参的两个方法

本文参考了如下链接:http://www.cnblogs.com/graphics/archive/2010/07/15/1777760.html 数组作为入参时,自动被转换为指针.因此数组定义的元素个数的参数无法带入函数.这不符合coder的预期,也是不严格和有风险的coding. 如何解决呢,一种办法是再加一个参数,指定元素个数 void Test(int* a, int n) { for (int i = 0; i < n; ++i) { cout << a[i] <<

sizeof对函数入参使用不准

被调用函数内部,无法知道入参的字符数组的可用内存长度 所以函数字符指针作为入参时,一定要加一个 此指针指向的内存空间长度入参int nSize 错误的写法: 1 #include "stdafx.h" 2 #include <string.h> 3 #include <stdarg.h> 4 5 // 返回非0表达式验证不通过,不用调用子系统功能号 6 int getFilters(char * pszInput, char *pszFuncid) 7 { 8

命令行通过入参调用jar包

命令行通过入参调用jar包 最近因为项目需要,需要实现一个功能,即定时执行服务器上的一个脚本去对数据库的数据进行业务处理,要操作的数据库有很多种,mysql.db2.oracle.sqlserver等等,但是shell目前我还不熟悉,所以就先用java去实现,打成一个jar包,通过入参即可调用不同的数据库,满足了要求.网上搜了一下,大家多使用了Apache Commons-CLI包进行命令行参数解析,但是说的都不太清楚,我在这里结合自己的实际操作进行总结,希望能够帮助到有需要的朋友. 我这里因为

RobotFramework:发现一个大坑,当post接口入参为json时,千万不能用sojson转化后的json串,直接用浏览器粘过来的就行

问题背景: 和以往一样愉快的进行着自动化测试,突然就不停的提示我,“程序异常”,查看log发现data中的json变为了数组?????? 那算了,我不先组装入参数据直接data=json入参吧,what`s up, Fu.......................,查看半天post requests关键字的入参,发现没有问题啊. 重新打开ride吧! 这什么工具啊,这么坑我. 解决: 回想之前接口测试用例写的多了,这什么鬼啊,会不会是我json串粘贴的有问题,算了直接把F12抓包过来的入参直接

spring mvc绑定对象String转Date解决入参不能是Date的问题

使用spring的mvc,直接将页面参数绑定到对象中,对象中有属性为Date时会报错,此时需要处理下. 同样的,其他的需要处理的类型也可以用这种方法. 在controller中加入代码 @InitBinder protected void initBinder(HttpServletRequest request, ServletRequestDataBinder binder) throws Exception { //对于需要转换为Date类型的属性,使用DateEditor进行处理 bin

webapi 在POST时的坑以及OracleCommand的参数绑定的坑

刚用webapi, 用$.get时,前后台的请求响应成功了,但在用$.post方法时遇到不少问题. 后搜到一英文资料,基本反映了我的问题,现摘译如下: web api 很好,但post参数问题上,需要用些手段,有三个事你要知道 1.传基本参数进去,必须用[frombody]标注一下 public string  Post(string value) post函数即使执行,但永远返回null .(在我这请求时,还必须把Post的action的名称也加上,才执行) $.post('/api/ttt/

C# WebApi传参之Post请求-AJAX

最近悟出来一个道理,在这儿分享给大家:学历代表你的过去,能力代表你的现在,学习代表你的将来. 十年河东十年河西,莫欺少年穷.  学无止境,精益求精    上一节讲述了C# WebApi传参之Get请求-AJAX 本节讲述C# WebApi传参之Post请求-AJAX,说起Ajax针对webApi的Post请求,真的不敢恭维,确实比较怪异,如果你不幸要写一个Ajax Post请求webApi接口,那么您还是有必要花点时间看看本篇博客,如果你也遇到了同样的问题,就不妨在最后给本篇博客点个赞.谢谢 说

springMVC中 request请求数据绑定到Controller入参 过程剖析

前言:Controller方法的参数类型可以是基本类型,也可以是封装后的普通Java类型.若这个普通Java类型没有声明任何注解,则意味着它的每一个属性都需要到Request中去查找对应的请求参数.众所周知,无论客户端传入的是什么类型的请求参数,最终都要以字节的形式传给服务端.而服务端通过Request的getParameter方法取到的参数也都是字符串形式的结果.所以,需要有一个把字符串形式的参数转换成服务端真正需要的类型的转换工具(基类:PropertyEditorSupport) 注:只有