解决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实在翻不过去)搜了很久才找到的国外的这个大神写的办法,引用自这里
完整代码如下 源码下载