您的位置:首页 > 其它

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

2015-08-31 12:19 253 查看
我们的项目是用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实在翻不过去)搜了很久才找到的国外的这个大神写的办法,引用自这里

完整代码如下 源码下载
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: