解決WebApi入參時多對象的問題

咱們的項目是用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實在翻不過去)搜了好久才找到的國外的這個大神寫的辦法,引用自這裏

完整代碼以下 源碼下載

相關文章
相關標籤/搜索