咱們的項目是用WebApi提供數據服務,且WebPage跟APP中都有調用到。html
WebApi提供的接口一多,就發現一個問題,咱們項目中有不少接口是接收POST(安全緣由,咱們採用的是https)請求的,並且入參基本也就是一兩個參數。在開發的後期發現WebApi中有不少對象,多到已經快到了難以管理的地步了。web
好比咱們的WebApi以下:json
對應的入參對象是:api
很難想象若是再有別的Controller的話,Models中會有多少對象,而這些對象每每使用一次,或者在一個方法中使用了一下就再也不用了。安全
這顯然令我很是不爽!app
通過反覆推敲,發現若是採用這種寫法:ide
顯然能減小很多對象,並且接口也相對更加清晰了,不用再按F12進入到對象裏面看看裏面到底有幾個參數,每一個參數又表示什麼。post
可是!WebApi不支持這種請求,這種把參數寫在方法前的只能接受GET方式的請求,就是參數在url中的好比:http://localhost:8080/api/People/GetPeoplesByID?ID=1測試
這顯然把咱們的參數暴露了出來,https就沒有卵用了!this
經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實在翻不過去)搜了好久才找到的國外的這個大神寫的辦法,引用自這裏
完整代碼以下 源碼下載