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

时间:2015-08-31 13:32:14   收藏:0   阅读:10102

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

完整代码如下 源码下载

评论(0
© 2014 mamicode.com 版权所有 京ICP备13008772号-2  联系我们:gaon5@hotmail.com
迷上了代码!