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

完整代码如下 源码下载

posted @ 2015-08-31 12:19  tongling  阅读(2567)  评论(4编辑  收藏  举报