如何将一个对象序列化成查询字符串格式?

101

我如何将一个对象序列化为查询字符串格式?在谷歌上好像找不到答案。谢谢。

这是我将要作为示例序列化的对象。

public class EditListItemActionModel
{
    public int? Id { get; set; }
    public int State { get; set; }
    public string Prefix { get; set; }
    public string Index { get; set; }
    public int? ParentID { get; set; }
}

为什么不创建自己的函数来进行序列化呢? - James Black
你想得到这样的结果吗:Id=1&State=CA&Prefix=Mr...之类的吗?如果是的话,我同意@James的观点。 - Bob Kaufman
4
哇,那是唯一的方法吗?我以为 .NET 中有某种内置的东西。我的想法有点像 MVC 模型绑定器的反向操作。一定有这样的方法吧? - Benjamin
如果没有内置函数,你能给我一些提示如何编写一个吗? - Benjamin
3
Flurl是一个URL构建器/HTTP客户端,广泛使用对象表示名称-值对类似的东西(查询字符串、标头、URL编码表单值等)。SetQueryParams可以完全满足您的需求。如果您只需要URL构建器而不是所有的HTTP内容,则可以在此处找到它(https://www.nuget.org/packages/Flurl/)。[免责声明:我是这个项目的作者] - Todd Menier
显示剩余8条评论
14个回答

0
我在寻找针对Windows 10 (UWP)应用的解决方案。按照Dave建议的反射方法,并添加Microsoft.AspNet.WebApi.Client Nuget包后,我使用了以下代码来处理属性值的URL编码:
 private void AddContentAsQueryString(ref Uri uri, object content)
    {            
        if ((uri != null) && (content != null))
        {
            UriBuilder builder = new UriBuilder(uri);

            HttpValueCollection query = uri.ParseQueryString();

            IEnumerable<PropertyInfo> propInfos = content.GetType().GetRuntimeProperties();

            foreach (var propInfo in propInfos)
            {
                object value = propInfo.GetValue(content, null);
                query.Add(propInfo.Name, String.Format("{0}", value));
            }

            builder.Query = query.ToString();
            uri = builder.Uri;                
        }
    }

0

除了现有的答案之外

public static string ToQueryString<T>(this T input)
        {
            if (input == null)
            {
                return string.Empty;
            }
                
            var queryStringBuilder = new StringBuilder("?");

            var properties = input.GetType().GetProperties(BindingFlags.Instance | BindingFlags.Public);

            foreach (var property in properties)
            {
                var value = property.GetValue(input);
                if (value is null || property.HasIgnoreDataMember())
                    continue;

                queryStringBuilder.AppendFormat("{0}={1}&", property.GetName(), HttpUtility.UrlEncode(value.ToString()));
            }
            queryStringBuilder.Length--;

            return queryStringBuilder.ToString();
        }

        private static bool HasIgnoreDataMember(this PropertyInfo propertyInfo)
        {
            return propertyInfo.GetCustomAttribute(typeof(IgnoreDataMemberAttribute), true) is not null;
        }

        private static DataMemberAttribute GetDataMemberAttribute(this PropertyInfo propertyInfo)
        {
            return propertyInfo.GetCustomAttribute<DataMemberAttribute>();
        }

        private static T GetCustomAttribute<T>(this PropertyInfo propertyInfo) where T : class
        {
            return propertyInfo.GetCustomAttribute(typeof(T), true) as T;
        }

        private static string GetName(this PropertyInfo propertyInfo)
        {
            return propertyInfo.GetDataMemberAttribute()?.Name ?? propertyInfo.Name;
        }
    }

使用方法: var queryString = object.ToQueryString()


0
一个支持列表属性的简单方法:
public static class UriBuilderExtensions
{
    public static UriBuilder SetQuery<T>(this UriBuilder builder, T parameters)
    {
        var fragments = typeof(T).GetProperties()
            .Where(property => property.CanRead)
            .Select(property => new
            {
                property.Name,
                Value = property.GetMethod.Invoke(parameters, null)
            })
            .Select(pair => new
            {
                pair.Name,
                List = (!(pair.Value is string) && pair.Value is IEnumerable list ? list.Cast<object>() : new[] { pair.Value })
                    .Select(element => element?.ToString())
                    .Where(element => !string.IsNullOrEmpty(element))
            })
            .Where(pair => pair.List.Any())
            .SelectMany(pair => pair.List.Select(value => Uri.EscapeDataString(pair.Name) + '=' + Uri.EscapeDataString(value)));

        builder.Query = string.Join("&", fragments);
        return builder;
    }
}

一种更快的解决方案,其速度与拼写序列化每种类型的代码一样快:
public static class UriBuilderExtensions
{
    public static UriBuilder SetQuery<TSource>(this UriBuilder builder, TSource parameters)
    {
        var fragments = Cache<TSource>.Properties
            .Select(property => new
            {
                property.Name,
                List = property.FetchValue(parameters)?.Where(item => !string.IsNullOrEmpty(item))
            })
            .Where(parameter => parameter.List?.Any() ?? false)
            .SelectMany(pair => pair.List.Select(item => Uri.EscapeDataString(pair.Name) + '=' + Uri.EscapeDataString(item)));

        builder.Query = string.Join("&", fragments);
        return builder;
    }

    /// <summary>
    /// Caches dynamically emitted code which converts a types getter property values to a list of strings.
    /// </summary>
    /// <typeparam name="TSource">The type of the object being serialized</typeparam>
    private static class Cache<TSource>
    {
        public static readonly IEnumerable<IProperty> Properties =
            typeof(TSource).GetProperties()
            .Where(propertyInfo => propertyInfo.CanRead)
            .Select(propertyInfo =>
            {
                var source = Expression.Parameter(typeof(TSource));
                var getter = Expression.Property(source, propertyInfo);
                var cast = Expression.Convert(getter, typeof(object));
                var expression = Expression.Lambda<Func<TSource, object>>(cast, source).Compile();
                return new Property
                {
                    Name = propertyInfo.Name,
                    FetchValue = typeof(IEnumerable).IsAssignableFrom(propertyInfo.PropertyType) && propertyInfo.PropertyType != typeof(string) ?
                        CreateListFetcher(expression) :
                        CreateValueFetcher(expression)
                };
            })
            .OrderBy(propery => propery.Name)
            .ToArray();

        /// <summary>
        /// Creates a function which serializes a <see cref="IEnumerable"/> property value to a list of strings.
        /// </summary>
        /// <param name="get">A lambda function which retrieves the property value from a given source object.</param>
        private static Func<TSource, IEnumerable<string>> CreateListFetcher(Func<TSource, object> get)
           => obj => ((IEnumerable)get(obj))?.Cast<object>().Select(item => item?.ToString());

        /// <summary>
        /// Creates a function which serializes a <see cref="object"/> property value to a list of strings.
        /// </summary>
        /// <param name="get">A lambda function which retrieves the property value from a given source object.</param>
        private static Func<TSource, IEnumerable<string>> CreateValueFetcher(Func<TSource, object> get)
            => obj => new[] { get(obj)?.ToString() };

        public interface IProperty
        {
            string Name { get; }
            Func<TSource, IEnumerable<string>> FetchValue { get; }
        }

        private class Property : IProperty
        {
            public string Name { get; set; }
            public Func<TSource, IEnumerable<string>> FetchValue { get; set; }
        }
    }
}

使用任一解决方案的示例:

var url = new UriBuilder("test.com").SetQuerySlow(new
{
    Days = new[] { WeekDay.Tuesday, WeekDay.Wednesday },
    Time = TimeSpan.FromHours(14.5),
    Link = "conferences.com/apple/stream/15",
    Pizzas = default(int?)
}).Uri;

输出:
http://test.com/Days=Tuesday&Days=Wednesday&Time=14:30:00&Link=conferences.com%2Fapple%2Fstream%2F15
这两种解决方案都无法处理异类类型、索引参数或嵌套参数。

当手动序列化更简单时,这种c#7/.net4.7方法可以帮助:

public static class QueryParameterExtensions
{
    public static UriBuilder SetQuery(this UriBuilder builder, params (string Name, object Obj)[] parameters)
    {
        var list = parameters
            .Select(parameter => new
            {
                parameter.Name,
                Values = SerializeToList(parameter.Obj).Where(value => !string.IsNullOrEmpty(value))
            })
            .Where(parameter => parameter.Values.Any())
            .SelectMany(parameter => parameter.Values.Select(item => Uri.EscapeDataString(parameter.Name) + '=' + Uri.EscapeDataString(item)));
        builder.Query = string.Join("&", list);
        return builder;
    }

    private static IEnumerable<string> SerializeToList(object obj)
    {
        switch (obj)
        {
            case string text:
                yield return text;
                break;
            case IEnumerable list:
                foreach (var item in list)
                {
                    yield return SerializeToValue(item);
                }
                break;
            default:
                yield return SerializeToValue(obj);
                break;
        }
    }

    private static string SerializeToValue(object obj)
    {
        switch (obj)
        {
            case bool flag:
                return flag ? "true" : null;
            case byte number:
                return number == default(byte) ? null : number.ToString();
            case short number:
                return number == default(short) ? null : number.ToString();
            case ushort number:
                return number == default(ushort) ? null : number.ToString();
            case int number:
                return number == default(int) ? null : number.ToString();
            case uint number:
                return number == default(uint) ? null : number.ToString();
            case long number:
                return number == default(long) ? null : number.ToString();
            case ulong number:
                return number == default(ulong) ? null : number.ToString();
            case float number:
                return number == default(float) ? null : number.ToString();
            case double number:
                return number == default(double) ? null : number.ToString();
            case DateTime date:
                return date == default(DateTime) ? null : date.ToString("s");
            case TimeSpan span:
                return span == default(TimeSpan) ? null : span.ToString();
            case Guid guid:
                return guid == default(Guid) ? null : guid.ToString();
            default:
                return obj?.ToString();
        }
    }
}

示例用法:

var uri = new UriBuilder("test.com")
    .SetQuery(("days", standup.Days), ("time", standup.Time), ("link", standup.Link), ("pizzas", standup.Pizzas))
    .Uri;

Output:
http://test.com/?days=Tuesday&days=Wednesday&time=14:30:00&link=conferences.com%2Fapple%2Fstream%2F15


-3
面对类似的情况,我所做的是将对象进行XML序列化,并将其作为查询字符串参数传递。这种方法的困难在于,尽管进行了编码,但接收表单仍会抛出异常,显示“潜在危险请求...”。我解决的方法是加密序列化的对象,然后进行编码以将其作为查询字符串参数传递。这反过来使查询字符串防篡改(奖励进入HMAC领域)!
FormA将对象进行XML序列化>加密序列化字符串>编码>作为查询字符串传递到FormB FormB解密查询参数值(因为request.querystring也进行了解码)>使用XmlSerializer将结果XML字符串反序列化为对象。
如需了解我的VB.NET代码,请发送电子邮件至howIdidit-at-applecart-dot-net。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接