解决WebApi入参时多对象的问题
我们的项目是用WebApi提供数据服务,且WebPage跟APP中都有调用到。
WebApi提供的接口一多,就发现一个问题,我们项目中有很多接口是接收POST(安全原因,我们采用的是https)请求的,而且入参基本也就是一两个参数。在开发的后期发现WebApi中有很多对象,多到已经快到了难以管理的地步了。
比如我们的WebApi如下:
对应的入参对象是:
很难想象如果再有别的Controller的话,Models中会有多少对象,而这些对象往往使用一次,或者在一个方法中使用了一下就不再用了。
这显然令我非常不爽!
经过反复推敲,发现如果采用这种写法:
显然能减少不少对象,而且接口也相对更加清晰了,不用再按F12进入到对象里面看看里面到底有几个参数,每个参数又表示什么。
但是!WebApi不支持这种请求,这种把参数写在方法前的只能接受GET方式的请求,就是参数在url中的比如:http://localhost:8080/api/People/GetPeoplesByID?ID=1
这显然把我们的参数暴露了出来,https就没有卵用了!
经Tech.Moonlight 指出,在https协议下会加密信息中包含了url,故https://xxx.com/index.html?a=123也是安装的!
怎么让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实在翻不过去)搜了很久才找到的国外的这个大神写的办法,引用自这里
完整代码如下 源码下载