Convert from Object to QueryString [duplicate] - c#

How do I serialize an object into query-string format? I can't seem to find an answer on google. Thanks.
Here is the object I will serialize as an example.
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; }
}

I'm 99% sure there's no built-in utility method for this. It's not a very common task, since a web server doesn't typically respond with a URLEncoded key/value string.
How do you feel about mixing reflection and LINQ? This works:
var foo = new EditListItemActionModel() {
Id = 1,
State = 26,
Prefix = "f",
Index = "oo",
ParentID = null
};
var properties = from p in foo.GetType().GetProperties()
where p.GetValue(foo, null) != null
select p.Name + "=" + HttpUtility.UrlEncode(p.GetValue(foo, null).ToString());
// queryString will be set to "Id=1&State=26&Prefix=f&Index=oo"
string queryString = String.Join("&", properties.ToArray());
Update:
To write a method that returns the QueryString representation of any 1-deep object, you could do this:
public string GetQueryString(object obj) {
var properties = from p in obj.GetType().GetProperties()
where p.GetValue(obj, null) != null
select p.Name + "=" + HttpUtility.UrlEncode(p.GetValue(obj, null).ToString());
return String.Join("&", properties.ToArray());
}
// Usage:
string queryString = GetQueryString(foo);
You could also make it an extension method without much additional work
public static class ExtensionMethods {
public static string GetQueryString(this object obj) {
var properties = from p in obj.GetType().GetProperties()
where p.GetValue(obj, null) != null
select p.Name + "=" + HttpUtility.UrlEncode(p.GetValue(obj, null).ToString());
return String.Join("&", properties.ToArray());
}
}
// Usage:
string queryString = foo.GetQueryString();

Using Json.Net it would be much easier, by serializing and then deserializing to key value pairs.
Here is a code example:
using Newtonsoft.Json;
using System.Web;
string ObjToQueryString(object obj)
{
var step1 = JsonConvert.SerializeObject(obj);
var step2 = JsonConvert.DeserializeObject<IDictionary<string, string>>(step1);
var step3 = step2.Select(x => HttpUtility.UrlEncode(x.Key) + "=" + HttpUtility.UrlEncode(x.Value));
return string.Join("&", step3);
}

Building on the good ideas from other comments, I have made a generic extension method .ToQueryString(), which can be used on any object.
public static class UrlHelpers
{
public static string ToQueryString(this object request, string separator = ",")
{
if (request == null)
throw new ArgumentNullException("request");
// Get all properties on the object
var properties = request.GetType().GetProperties()
.Where(x => x.CanRead)
.Where(x => x.GetValue(request, null) != null)
.ToDictionary(x => x.Name, x => x.GetValue(request, null));
// Get names for all IEnumerable properties (excl. string)
var propertyNames = properties
.Where(x => !(x.Value is string) && x.Value is IEnumerable)
.Select(x => x.Key)
.ToList();
// Concat all IEnumerable properties into a comma separated string
foreach (var key in propertyNames)
{
var valueType = properties[key].GetType();
var valueElemType = valueType.IsGenericType
? valueType.GetGenericArguments()[0]
: valueType.GetElementType();
if (valueElemType.IsPrimitive || valueElemType == typeof (string))
{
var enumerable = properties[key] as IEnumerable;
properties[key] = string.Join(separator, enumerable.Cast<object>());
}
}
// Concat all key/value pairs into a string separated by ampersand
return string.Join("&", properties
.Select(x => string.Concat(
Uri.EscapeDataString(x.Key), "=",
Uri.EscapeDataString(x.Value.ToString()))));
}
}
It will also work for objects that have properties of the type Array and generic Lists if they only contain primitives or strings.
Try it out, comments are welcome: Serialize object into a query string with Reflection

Based on the the popular answers, I needed to update the code to support arrays as well. Sharing the implementation:
public string GetQueryString(object obj)
{
var result = new List<string>();
var props = obj.GetType().GetProperties().Where(p => p.GetValue(obj, null) != null);
foreach (var p in props)
{
var value = p.GetValue(obj, null);
var enumerable = value as ICollection;
if (enumerable != null)
{
result.AddRange(from object v in enumerable select string.Format("{0}={1}", p.Name, HttpUtility.UrlEncode(v.ToString())));
}
else
{
result.Add(string.Format("{0}={1}", p.Name, HttpUtility.UrlEncode(value.ToString())));
}
}
return string.Join("&", result.ToArray());
}

It will also be useful for nested objects
public static class HttpQueryStrings
{
private static readonly StringBuilder _query = new();
public static string ToQueryString<T>(this T #this) where T : class
{
_query.Clear();
BuildQueryString(#this, "");
if (_query.Length > 0) _query[0] = '?';
return _query.ToString();
}
private static void BuildQueryString<T>(T? obj, string prefix = "") where T : class
{
if (obj == null) return;
foreach (var p in obj.GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance))
{
if (p.GetValue(obj, Array.Empty<object>()) != null)
{
var value = p.GetValue(obj, Array.Empty<object>());
if (p.PropertyType.IsArray && value?.GetType() == typeof(DateTime[]))
foreach (var item in (DateTime[])value)
_query.Append($"&{prefix}{p.Name}={item.ToString("yyyy-MM-dd")}");
else if (p.PropertyType.IsArray)
foreach (var item in (Array)value!)
_query.Append($"&{prefix}{p.Name}={item}");
else if (p.PropertyType == typeof(string))
_query.Append($"&{prefix}{p.Name}={value}");
else if (p.PropertyType == typeof(DateTime) && !value!.Equals(Activator.CreateInstance(p.PropertyType))) // is not default
_query.Append($"&{prefix}{p.Name}={((DateTime)value).ToString("yyyy-MM-dd")}");
else if (p.PropertyType.IsValueType && !value!.Equals(Activator.CreateInstance(p.PropertyType))) // is not default
_query.Append($"&{prefix}{p.Name}={value}");
else if (p.PropertyType.IsClass)
BuildQueryString(value, $"{prefix}{p.Name}.");
}
}
}
}
An example of using the solution:
string queryString = new
{
date = new DateTime(2020, 1, 1),
myClass = new MyClass
{
FirstName = "john",
LastName = "doe"
},
myArray = new int[] { 1, 2, 3, 4 },
}.ToQueryString();

Perhaps this Generic approach will be useful:
public static string ConvertToQueryString<T>(T entity) where T: class
{
var props = typeof(T).GetProperties();
return $"?{string.Join('&', props.Where(r=> r.GetValue(entity) != null).Select(r => $"{HttpUtility.UrlEncode(r.Name)}={HttpUtility.UrlEncode(r.GetValue(entity).ToString())}"))}";
}

public static class UrlHelper
{
public static string ToUrl(this Object instance)
{
var urlBuilder = new StringBuilder();
var properties = instance.GetType().GetProperties(BindingFlags.Instance | BindingFlags.Public);
for (int i = 0; i < properties.Length; i++)
{
urlBuilder.AppendFormat("{0}={1}&", properties[i].Name, properties[i].GetValue(instance, null));
}
if (urlBuilder.Length > 1)
{
urlBuilder.Remove(urlBuilder.Length - 1, 1);
}
return urlBuilder.ToString();
}
}

This my solution:
public static class ObjectExtensions
{
public static string ToQueryString(this object obj)
{
if (!obj.GetType().IsComplex())
{
return obj.ToString();
}
var values = obj
.GetType()
.GetProperties()
.Where(o => o.GetValue(obj, null) != null);
var result = new QueryString();
foreach (var value in values)
{
if (!typeof(string).IsAssignableFrom(value.PropertyType)
&& typeof(IEnumerable).IsAssignableFrom(value.PropertyType))
{
var items = value.GetValue(obj) as IList;
if (items.Count > 0)
{
for (int i = 0; i < items.Count; i++)
{
result = result.Add(value.Name, ToQueryString(items[i]));
}
}
}
else if (value.PropertyType.IsComplex())
{
result = result.Add(value.Name, ToQueryString(value));
}
else
{
result = result.Add(value.Name, value.GetValue(obj).ToString());
}
}
return result.Value;
}
private static bool IsComplex(this Type type)
{
var typeInfo = type.GetTypeInfo();
if (typeInfo.IsGenericType && typeInfo.GetGenericTypeDefinition() == typeof(Nullable<>))
{
// nullable type, check if the nested type is simple.
return IsComplex(typeInfo.GetGenericArguments()[0]);
}
return !(typeInfo.IsPrimitive
|| typeInfo.IsEnum
|| type.Equals(typeof(Guid))
|| type.Equals(typeof(string))
|| type.Equals(typeof(decimal)));
}
}
I use this extension for my integration test, it works perfectly :)

Just another variation of the above, but I wanted to utilize the existing DataMember attributes in my model class, so only the properties I want to serialize are sent to the server in the url in the GET request.
public string ToQueryString(object obj)
{
if (obj == null) return "";
return "?" + string.Join("&", obj.GetType()
.GetProperties()
.Where(p => Attribute.IsDefined(p, typeof(DataMemberAttribute)) && p.GetValue(obj, null) != null)
.Select(p => $"{p.Name}={Uri.EscapeDataString(p.GetValue(obj).ToString())}"));
}

Here is something I wrote that does what you need.
public string CreateAsQueryString(PageVariables pv) //Pass in your EditListItemActionModel instead
{
int i = 0;
StringBuilder sb = new StringBuilder();
foreach (var prop in typeof(PageVariables).GetProperties())
{
if (i != 0)
{
sb.Append("&");
}
var x = prop.GetValue(pv, null).ToString();
if (x != null)
{
sb.Append(prop.Name);
sb.Append("=");
sb.Append(x.ToString());
}
i++;
}
Formating encoding = new Formating();
// I am encoding my query string - but you don''t have to
return "?" + HttpUtility.UrlEncode(encoding.RC2Encrypt(sb.ToString()));
}

I was looking for a solution to this for a Windows 10 (UWP) App. Taking the Relection approach suggested by Dave, and after adding the Microsoft.AspNet.WebApi.Client Nuget package, I used the following code,
which handles Url Encoding of the property values:
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;
}
}

A simple approach that supports list properties:
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;
}
}
A faster solution which is as fast as spelling out the code to serialize each type:
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; }
}
}
}
An example of using either solution:
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;
Output:
http://test.com/Days=Tuesday&Days=Wednesday&Time=14:30:00&Link=conferences.com%2Fapple%2Fstream%2F15
Neither of the solutions handle exotic types, indexed parameters, or nested parameters.
When manual serialization is simpler, this c#7/.net4.7 approach can help:
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();
}
}
}
Example usage:
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

In addition to existing answers
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;
}
}
Usage: var queryString = object.ToQueryString()

Faced with a similar situation what I did, is to XML serialize the object and pass it around as query string parameter.
The difficulty with this approach was that despite encoding, the receiving form throws exception saying "potentially dangerous request...". The way I got around was to encrypt the serialized object and then encode to pass it around as query string parameter. Which in turn made the query string tamper proof (bonus wandering into the HMAC territory)!
FormA XML serializes an object > encrypts the serialized string > encode > pass as query string to FormB
FormB decrypts the query parameter value (as request.querystring decodes also) > deserialize the resulting XML string to object using XmlSerializer.
I can share my VB.NET code upon request to howIdidit-at-applecart-dot-net

Related

How Do I Store Nullable values and Iterate Through Them?

Thanks for taking a moment. I have a url . I'm trying to build the querystring parameters from these nullable input parameters you see below. I'm thinking what I need to do is store the nullable parameters into some type of generic list and then if the given value of the parameter is not null, append it to the url. So for example, If they supply the string? state a value of 'OH' my url would look like (http://www.thiscoolsiteJas.com/api/v1/moratorium?state=OH). How do I do this ? Any advice or direction would be greatly appreciated. - Jason
public GetMoratoriums(string? state, int? system, DateTime? inEffectOn, bool? expired, int? reason) {
//How do I build this with parameters using the input parameters??
string url = http://www.thiscoolsiteJas.com/api/v1/moratorium?";
}
Something like this?
var queryStrings = new List<string>();
if (state.HasValue) { queryStrings.Add($"state={state.Value}"); }
if (system.HasValue) { queryStrings.Add($"system={system.Value}"); }
if (inEffectOn.HasValue) { queryStrings.Add($"inEffectOn={inEffectOn.Value}"); }
if (expired.HasValue) { queryStrings.Add($"expired={expired.Value}"); }
if (reason.HasValue) { queryStrings.Add($"reason={reason.Value}"); }
var result = #"http://www.thiscoolsiteJas.com/api/v1/moratorium";
if (queryStrings.Any()) { result += $"?{String.Join("&", queryStrings)}"; }
return result;
I'm sure there is a better way to do it but this is how I would handle it.
string url = http://www.thiscoolsiteJas.com/api/v1/moratorium?";
var queryStringParams = new List<KeyValuePair<string, string>>();
if (!string.IsNullOrEmpty(state))
{
queryStringParams.Add("state",state);
}
etc...
var newUrl = new Uri(QueryHelpers.AddQueryString(url, queryStringParams));
Object oriented approach ;).
public class Parameter
{
private readonly string _name;
private readonly object _value;
public Parameter(string name, object value)
{
_name = name;
_value = value;
}
public string ForQuery()
{
return _value == null ? null : $"{_name}={_value}";
}
}
Usage
string? state = "new_state";
int? system = 1;
DateTime? inEffectOn = default;
bool? expired = default;
var parameters = new[]
{
new QueryParameter("state", state),
new QueryParameter("system", system),
new QueryParameter("inEffectOn", inEffectOn),
new QueryParameter("expired", expired),
};
var queryParameters = parameters.Select(p => p.ForQuery()).Where(p => p != null);
var query = string.Join("&", queryParameters);
For example instead of passing long list of parameters you can pass a collection of value of type QueryParameter and build query.
With extension method
public static string AppendTo(this IEnumerable<QueryParameter> parameters, string baseUrl)
{
return parameters
.Select(p => p.ForQuery())
.Where(p => p != null)
.Aggregate(
new StringBuilder(baseUrl),
(builder, p) => builder.Append(p).Append("&"),
(builder) =>
{
if (builder.Length > 0)
{
builder.Insert(0, "?");
builder.Length -= 1; // remove last "&"
}
})
.ToString();
}
Usage
var parameters = new[]
{
new QueryParameter("state", "new"),
new QueryParameter("system", null),
new QueryParameter("inEffectOn", null),
new QueryParameter("expired", false),
};
var url = parameters.AppendTo("http://www.thiscoolsiteJas.com/api/v1/moratorium");

Creating GraphQL Structure from Dot Notation String in c#

The following javascript question is the same problem i'm attempting to solve but in c#
How can I merge 2 dot notation strings to a GraphQL query string
Expected structure is
{
"Case": {
"Owner": {
"Name": null,
"ProfilePic": null
},
"CaseNo": null,
"FieldOfLaw":{
"Name": null
},
"CaseType": {
"Name": null
},
"CaseSubType": {
"Name": null
},
},
"Client":{
"Policy":{
"PolicyNo": null
}
}
}
and my current output is
{
"Case": {
"Owner": {
"Name": null,
"ProfilePic": null
},
"CaseNo": null,
"FieldOfLaw": null,
"CaseType": null,
"CaseSubType": null
}
}
Below is my attempt using ExpandoObjects to try dynamically generate the objects needed. Any advice or pointers in the right direction would be appreciated.
public static void Main(string[] args)
{
var FieldList = new List<string>
{
"Case.Owner.Name",
"Case.Owner.ProfilePic",
"Case.CaseNo",
"Case.FieldOfLaw.Name",
"Case.CaseType.Name",
"Case.CaseSubType.Name",
"Client.Policy.PolicyNo",
};
Parser graphQL = new Parser();
var result = graphQL.Parse(FieldList);
Console.Write(result);
}
Below is the actual parse method, so i'm running an aggregation function over each element to create and return the expando objects into the initial holder. I traverse each split string recursively and exit the recursion once there are no more items left in the split list.
public class Parser
{
public string Parse(List<string> fieldList)
{
// List<ExpandoObject> queryHolder = new List<ExpandoObject>();
ExpandoObject intialSeed = new ExpandoObject();
fieldList.Aggregate(intialSeed, (holder, field) =>
{
holder = ParseToObject(holder, field.Split('.').ToList());
return holder;
});
return JsonConvert.SerializeObject(intialSeed);
}
public ExpandoObject ParseToObject(ExpandoObject holder, List<string> fieldSplit, string previousKey = null)
{
if (fieldSplit.Any())
{
var item = fieldSplit.Shift();
if (item == null)
return holder;
// If the current item doesn't exists in the dictionary
if (!((IDictionary<string, object>)holder).ContainsKey(item))
{
if (((IDictionary<string, object>)holder).Keys.Count() == 0)
holder.TryAdd(item, null);
else
_ = ((IDictionary<string, object>)holder).GetItemByKeyRecursively(previousKey, item);
}
previousKey = item;
ParseToObject(holder, fieldSplit, previousKey);
}
return holder;
}
}
Here are my two extensions methods, i'm having an issue with the GetItemByKeyRecursively when it goes into the 3rd level in it's recursion so example.
I'm adding FieldOfLaw it adds the property to the Case expandoObject but doesn't know how to get back to the leaf containing Owner, CaseNo etc.
public static class CollectionExtensions
{
public static T Shift<T>(this IList<T> list)
{
var shiftedElement = list.FirstOrDefault();
list.RemoveAt(0);
return shiftedElement;
}
public static IDictionary<string, object> GetItemByKeyRecursively(this IDictionary<string, object> dictionary, string parentKey, string keyToCreate)
{
foreach (string key in dictionary.Keys)
{
var leaf = dictionary[key];
if (key == parentKey)
{
var #value = dictionary[key];
if (#value is ExpandoObject)
{
(#value as ExpandoObject).TryAdd(keyToCreate, null);
}
else if (#value == null)
{
var item = new ExpandoObject();
item.TryAdd(keyToCreate, null);
dictionary[key] = item;
}
return dictionary;
}
if (leaf == null)
continue;
return GetItemByKeyRecursively((IDictionary<string, object>)leaf, parentKey, keyToCreate);
}
return null;
}
}
Nothing you can't accomplish mostly declaratively.
public string Parse(List<string> fieldList)
{
var fieldPaths = fieldList.Select(x => x.Split('.').ToList());
var groups = fieldPaths.GroupBy(x => x.First(), x => x.Skip(1));
return ParseGroups(groups, 1);
}
private string ParseGroups(IEnumerable<IGrouping<string, IEnumerable<string>>> groups, int level)
{
string indent = new string('\t', level - 1);
var groupResults = groups.Select(g =>
!g.First().Any() ?
$"\t{indent}{g.Key}: null" :
$"\t{indent}{g.Key}: " + string.Join(", \n",
ParseGroups(g.GroupBy(x => x.First(), x => x.Skip(1)), level + 1))
);
return indent + "{\n" + string.Join(", \n", groupResults) + "\n" + indent + "}";
}
See the complete sample code here: https://dotnetfiddle.net/RLygjt

Get all properties and subproperties from a class

I am using reflection to get a class name, and need to get all sub properties of the class, and all the sub properties' properties.
I am running into a recursion issue where the items get added to the incorrect list.
My code is as follows:
private List<Member> GetMembers(object instance)
{
var memberList = new List<Member>();
var childMembers = new List<Member>();
foreach (var propertyInfo in instance.GetType().GetProperties())
{
var member = new Member
{
Name = propertyInfo.PropertyType.IsList() ? propertyInfo.Name + "[]" : propertyInfo.Name,
Type = SetPropertyType(propertyInfo.PropertyType),
};
if (propertyInfo.PropertyType.IsEnum)
{
member.Members = GetEnumValues(propertyInfo).ToArray();
}
if (propertyInfo.PropertyType.BaseType == typeof(ModelBase))
{
var childInstance = propertyInfo.GetValue(instance) ?? Activator.CreateInstance(propertyInfo.PropertyType);
childMembers.AddRange(GetMembers(childInstance));
member.Members = childMembers.ToArray();
}
if (propertyInfo.PropertyType.IsGenericType && (propertyInfo.PropertyType.GetGenericTypeDefinition() == typeof(List<>) ||
propertyInfo.PropertyType.GetGenericTypeDefinition() == typeof(IList<>)))
{
var itemType = propertyInfo.PropertyType.GetGenericArguments()[0];
var childInstance = Activator.CreateInstance(itemType);
childMembers.AddRange(GetMembers(childInstance));
member.Members = childMembers.Distinct().ToArray();
}
memberList.Add(member);
}
return memberList;
}
I can't know for certain since I don't have the knowledge of your code to debug and test it; however, I believe your problem may be stemming from the fact that you're re-using the childMembers list. Let me know if this is not the case.
private List<Member> GetMembers(object instance)
{
var memberList = new List<Member>();
foreach (var propertyInfo in instance.GetType().GetProperties())
{
var childMembers = new List<Member>(); // Moved to here, so it's not shared among all propertyInfo iterations.
var member = new Member
{
Name = propertyInfo.PropertyType.IsList() ? propertyInfo.Name + "[]" : propertyInfo.Name,
Type = SetPropertyType(propertyInfo.PropertyType),
};
if (propertyInfo.PropertyType.IsEnum)
{
member.Members = GetEnumValues(propertyInfo).ToArray();
}
if (propertyInfo.PropertyType.BaseType == typeof(ModelBase))
{
var childInstance = propertyInfo.GetValue(instance) ?? Activator.CreateInstance(propertyInfo.PropertyType);
childMembers.AddRange(GetMembers(childInstance));
member.Members = childMembers.ToArray();
}
if (propertyInfo.PropertyType.IsGenericType && (propertyInfo.PropertyType.GetGenericTypeDefinition() == typeof(List<>) ||
propertyInfo.PropertyType.GetGenericTypeDefinition() == typeof(IList<>)))
{
var itemType = propertyInfo.PropertyType.GetGenericArguments()[0];
var childInstance = Activator.CreateInstance(itemType);
childMembers.AddRange(GetMembers(childInstance));
member.Members = childMembers.Distinct().ToArray();
}
memberList.Add(member);
}
return memberList;
}
Wouldn't the following do?
public static IEnumerable<PropertyInfo> GetProperties(this Type type, int depth = 1)
{
IEnumerable<PropertyInfo> getProperties(Type currentType, int currentDepth)
{
if (currentDepth >= depth)
yield break;
foreach (var property in currentType.GetProperties())
{
yield return property;
foreach (var subProperty in getProperties(property.PropertyType,
currentDepth + 1))
{
yield return subProperty;
}
}
}
if (depth < 1)
throw new ArgumentOutOfRangeException(nameof(depth));
return getProperties(type, 0);
}
Given the following type:
class Foo
{
public string S { get; }
public int I { get; }
}
The output of
Console.WriteLine(string.Join(Environment.NewLine,
typeof(Foo).GetProperties(2)
.Select(p => $"{p.DeclaringType.Name}: {p.Name}")));
would be:
Foo: S
String: Chars
String: Length
Foo: I

Reflection to Filter List<T>

I am new to Reflection so please excuse my noob question. How can I create a Method that takes two Parameters, a Generic List and a String and then finds all items in that List where any property value matches the string.
So for example we have an object with 3 properties, I pass a list of this object to the method and a search string and it returns back a list of objects where any of the properties may contain the search string.
I can do like this :
var temp = list.AsQueryable().Where("SomeField == 1").Select("it");
But how can I make this method Generic so I can pass any List of Objects to it ?
Thanks in advance...
If you are using Dynamic Linq, try this
public static IEnumerable<T> Filter<T>(IEnumerable<T> source, string searchStr)
{
var propsToCheck = typeof (T).GetProperties().Where(a => a.PropertyType == typeof(string));
var filter = propsToCheck.Aggregate(string.Empty, (s, p) => (s == string.Empty ? string.Empty : string.Format("{0} OR ", s)) + string.Format("{0} == #0", p.Name));
var filtered = source.AsQueryable().Where(filter, searchStr);
return filtered;
}
Use Type.GetProperties() to get all the properties of an object. Use PropertyInfo.GetValue() to get the value of a given property in a given object. You need to figure out how you want a match your string to a DateTime, to numbers, or to other complex objects. Put it all into a function like bool IsMatch(this object obj, string val). Then you can filter your list like list.Where(x => x.IsMatch("something")).
Here you go mate:
private static void Main(string[] args)
{
var list = new List<object> {new {prop1 = "A", prop2 = "B"},new {prop3 = "B", prop4 = "C"}};
var subList = SearchForStringInProperties(list, "C");
}
private static IEnumerable<object> SearchForStringInProperties(IEnumerable<object> list, string searchString)
{
return from obj in list where FindStringInObjProperties(obj, searchString) select obj;
}
private static bool FindStringInObjProperties(object obj, string searchString)
{
return obj.GetType().GetProperties().Any(property => obj.GetType().GetProperty(property.Name).GetValue(obj).ToString().Equals(searchString));
}
If you just want to match the properties with same type as your argument, this extension method can help,
public static class ListExtensions
{
public static IEnumerable<T> MatchWithAnyProperty<T, TK>(this IEnumerable<T> list, TK value)
{
var argType = typeof (TK);
var properties = typeof(T).GetProperties().Where(x => x.PropertyType.IsAssignableFrom(argType));
return list.Where(item => properties.Any(prop =>
{
var propertyValue = prop.GetValue(item, null);
if (value == null)
return propertyValue == null;
return propertyValue.Equals(value);
}));
}
}
This can be used like,
var items = new[]
{
new
{
Name = "Test",
Age = 20,
Test=25
},
new
{
Name = "Hello",
Age = 10,
Test=15
},
new
{
Name = "T2gdhest",
Age = 14,
Test=20
},
new
{
Name = "hai",
Age = 33,
Test=10
},
new
{
Name = "why not",
Age = 10,
Test=33
},
};
var match= items.MatchWithAnyProperty(10);
foreach (var item in match)
{
Console.WriteLine(item.Name);
}
Console.ReadKey();
And there is the old way ...
public static IList<T> MyMethod<T>(IList<T> myList, string filter)
{
if (myList == null) return null;
if (filter == null) return myList;
var tfilter = filter.GetType();
var properties = typeof(T).GetProperties().Where(x => x.PropertyType.FullName == typeof(string).FullName);
if (!properties.Any()) return null;
var res = new List<T>();
foreach(var el in myList)
{
foreach(var p in properties)
{
if ((string)p.GetValue(el) == filter)
{
res.Add(el);
break;
}
}
}
return res;
}

How-to generate querystring from model with asp.net mvc framework

I've a model, with some nested properties, lists ... and i want to get a querystring parameters from that model.
Is there any class/helper in asp.net mvc framework to do this ?
I know that with model binder we can bind a model from a querystring, but i want to do the inverse.
Thanks.
I'm fairly certain there is no "serialize to query string" functionality in the framework, mostly because I don't think there's a standard way to represent nested values and nested collections in a query string.
I thought this would be pretty easy to do using the ModelMetadata infrastructure, but it turns out that there are some complications around getting the items from a collection-valued property using ModelMetadata. I've hacked together an extension method that works around that and built a ToQueryString extension you can call from any ModelMetadata object you have.
public static string ToQueryString(this ModelMetadata modelMetadata)
{
if(modelMetadata.Model == null)
return string.Empty;
var parameters = modelMetadata.Properties.SelectMany (mm => mm.SelectPropertiesAsQueryStringParameters(null));
var qs = string.Join("&",parameters);
return "?" + qs;
}
private static IEnumerable<string> SelectPropertiesAsQueryStringParameters(this ModelMetadata modelMetadata, string prefix)
{
if(modelMetadata.Model == null)
yield break;
if(modelMetadata.IsComplexType)
{
IEnumerable<string> parameters;
if(typeof(IEnumerable).IsAssignableFrom(modelMetadata.ModelType))
{
parameters = modelMetadata.GetItemMetadata()
.Select ((mm,i) => new {
mm,
prefix = string.Format("{0}{1}[{2}]", prefix, modelMetadata.PropertyName, i)
})
.SelectMany (prefixed =>
prefixed.mm.SelectPropertiesAsQueryStringParameters(prefixed.prefix)
);
}
else
{
parameters = modelMetadata.Properties
.SelectMany (mm => mm.SelectPropertiesAsQueryStringParameters(string.Format("{0}{1}", prefix, modelMetadata.PropertyName)));
}
foreach (var parameter in parameters)
{
yield return parameter;
}
}
else
{
yield return string.Format("{0}{1}{2}={3}",
prefix,
prefix != null && modelMetadata.PropertyName != null ? "." : string.Empty,
modelMetadata.PropertyName,
modelMetadata.Model);
}
}
// Returns the metadata for each item from a ModelMetadata.Model which is IEnumerable
private static IEnumerable<ModelMetadata> GetItemMetadata(this ModelMetadata modelMetadata)
{
if(modelMetadata.Model == null)
yield break;
var genericType = modelMetadata.ModelType
.GetInterfaces()
.FirstOrDefault (x => x.IsGenericType && x.GetGenericTypeDefinition() == typeof(IEnumerable<>));
if(genericType == null)
yield return modelMetadata;
var itemType = genericType.GetGenericArguments()[0];
foreach (object item in ((IEnumerable)modelMetadata.Model))
{
yield return ModelMetadataProviders.Current.GetMetadataForType(() => item, itemType);
}
}
Example usage:
var vd = new ViewDataDictionary<Model>(model); // in a Controller, ViewData.ModelMetadata
var queryString = vd.ModelMetadata.ToQueryString();
I haven't tested it very thoroughly, so there may be some null ref errors lurking in it, but it spits out the correct query string for the complex objects I've tried.
#Steve's code had some minor bug when extra nesting and enumerables were the case.
Sample Model
public class BarClass {
public String prop { get; set; }
}
public class FooClass {
public List<BarClass> bar { get; set; }
}
public class Model {
public FooClass foo { get; set; }
}
Test Code
var model = new Model {
foo = new FooClass {
bar = new List<BarClass> {
new BarClass { prop = "value1" },
new BarClass { prop = "value2" }
}
}
};
var queryString = new ViewDataDictionary<Model>(model).ModelMetadata.ToQueryString();
The value of queryString should be:
"?foo.bar[0].prop=value1&foo.bar[1].prop=value2"
But #Steve's code produces the following output:
"?foobar[0].prop=value1&foobar[1].prop=value2"
Updated Code
Here is a slightly modified version of the #Steve's solution:
public static class QueryStringExtensions {
#region inner types
private struct PrefixedModelMetadata {
public readonly String Prefix;
public readonly ModelMetadata ModelMetadata;
public PrefixedModelMetadata (String prefix, ModelMetadata modelMetadata) {
Prefix = prefix;
ModelMetadata = modelMetadata;
}
}
#endregion
#region fields
private static readonly Type IEnumerableType = typeof(IEnumerable),
IEnumerableGenericType = typeof(IEnumerable<>);
#endregion
#region methods
public static String ToQueryString<ModelType> (this ModelType model) {
return new ViewDataDictionary<ModelType>(model).ModelMetadata.ToQueryString();
}
public static String ToQueryString (this ModelMetadata modelMetadata) {
if (modelMetadata.Model == null) {
return String.Empty;
}
var keyValuePairs = modelMetadata.Properties.SelectMany(mm =>
mm.SelectPropertiesAsQueryStringParameters(new List<String>())
);
return String.Join("&", keyValuePairs.Select(kvp => String.Format("{0}={1}", kvp.Key, kvp.Value)));
}
private static IEnumerable<KeyValuePair<String, String>> SelectPropertiesAsQueryStringParameters (this ModelMetadata modelMetadata, List<String> prefixChain) {
if (modelMetadata.Model == null) {
yield break;
}
if (modelMetadata.IsComplexType) {
IEnumerable<KeyValuePair<String, String>> keyValuePairs;
if (IEnumerableType.IsAssignableFrom(modelMetadata.ModelType)) {
keyValuePairs = modelMetadata.GetItemMetadata().Select((mm, i) =>
new PrefixedModelMetadata(
modelMetadata: mm,
prefix: String.Format("{0}[{1}]", modelMetadata.PropertyName, i)
)
).SelectMany(prefixed => prefixed.ModelMetadata.SelectPropertiesAsQueryStringParameters(
prefixChain.ToList().AddChainable(prefixed.Prefix, addOnlyIf: IsNeitherNullNorWhitespace)
));
}
else {
keyValuePairs = modelMetadata.Properties.SelectMany(mm =>
mm.SelectPropertiesAsQueryStringParameters(
prefixChain.ToList().AddChainable(
modelMetadata.PropertyName,
addOnlyIf: IsNeitherNullNorWhitespace
)
)
);
}
foreach (var keyValuePair in keyValuePairs) {
yield return keyValuePair;
}
}
else {
yield return new KeyValuePair<String, String>(
key: AntiXssEncoder.HtmlFormUrlEncode(
String.Join(".",
prefixChain.AddChainable(
modelMetadata.PropertyName,
addOnlyIf: IsNeitherNullNorWhitespace
)
)
),
value: AntiXssEncoder.HtmlFormUrlEncode(modelMetadata.Model.ToString()));
}
}
// Returns the metadata for each item from a ModelMetadata.Model which is IEnumerable
private static IEnumerable<ModelMetadata> GetItemMetadata (this ModelMetadata modelMetadata) {
if (modelMetadata.Model == null) {
yield break;
}
var genericType = modelMetadata.ModelType.GetInterfaces().FirstOrDefault(x =>
x.IsGenericType && x.GetGenericTypeDefinition() == IEnumerableGenericType
);
if (genericType == null) {
yield return modelMetadata;
}
var itemType = genericType.GetGenericArguments()[0];
foreach (Object item in ((IEnumerable) modelMetadata.Model)) {
yield return ModelMetadataProviders.Current.GetMetadataForType(() => item, itemType);
}
}
private static List<T> AddChainable<T> (this List<T> list, T item, Func<T, Boolean> addOnlyIf = null) {
if (addOnlyIf == null || addOnlyIf(item)) {
list.Add(item);
}
return list;
}
private static Boolean IsNeitherNullNorWhitespace (String value) {
return !String.IsNullOrWhiteSpace(value);
}
#endregion
}

Categories

Resources