Related
I have a .NET worker service which will run periodically.
When the service runs, I get an object from data source with identifier
I change the above retrieved object to required format, and store this object through some service with the same identifier
When my service run next time, I get the object from data source with same identifier (object A)
I will fetch the stored object from some service with same identifier (object B)
I want to know what changed between these two objects A & B, any property change, any list item got added/updated/deleted? These objects will contain nested complex data types.
We have a working solution, using reflection, but we are not sure whether this is correct approach
I have given my existing code to track changes, I am aware this not optimized code, but before proceeding to optimize and refactor it, I want to know any other proven approach is available?
public class TrackerService
{
public T TrackChanges<T>(T newObj, T oldObj) where T : class
{
var clonedObj= ((ResourceBase)(object)oldObj).Clone();
TrackChanges(newObj, oldObj, clonedObj);
return (T)clonedObj;
}
private T TrackChanges<T>(T newObj, T oldObj, T clonedObj) where T : class
{
var oldResourceBase = (ResourceBase)(object)oldObj;
var newResourceBase = (ResourceBase)(object)newObj;
if (oldResourceBase.ResourceId != newResourceBase.ResourceId)
{
throw new Exception($"ResourceId not matching, Type: {newObj.GetType()}, Old Id {oldResourceBase.ResourceId}, New Id {newResourceBase.ResourceId}");
}
var properties = newObj.GetType().GetProperties();
foreach (var property in properties)
{
if (!property.PropertyType.IsTrackableType())
{
throw new Exception($"Property is not trackable type, Type: {property.PropertyType}, Name: {property.Name}");
}
if (property.PropertyType.IsSimpleType())
{
TrackSimplePropertyChanges(property, newObj, oldObj, clonedObj);
}
else if (property.PropertyType.IsCollection())
{
TrackCollectionPropertyChanges(property, newObj, oldObj, clonedObj);
}
else if (property.PropertyType.BaseType == typeof(ResourceBase))
{
TrackComplexPropertyChanges(property, newObj, oldObj, clonedObj);
}
}
return clonedObj;
}
private void TrackSimplePropertyChanges<T>(PropertyInfo property, T newObj, T oldObj, T clonedObj) where T : class
{
var clonedResourceBase = (ResourceBase)(object)clonedObj;
var oldPropertyObj = property.GetValue(oldObj, null);
var newPropertyObj = property.GetValue(newObj, null);
if (oldPropertyObj != null && newPropertyObj != null)
{
if (!oldPropertyObj.Equals(newPropertyObj))
{
property.SetValue(clonedObj, newPropertyObj);
clonedResourceBase.State = ResourceState.Modified;
}
}
else if (oldPropertyObj == null && newPropertyObj != null)
{
property.SetValue(clonedObj, newPropertyObj);
clonedResourceBase.State = ResourceState.Modified;
}
else if (newPropertyObj == null && oldPropertyObj != null)
{
property.SetValue(clonedObj, null);
clonedResourceBase.State = ResourceState.Modified;
}
}
private void TrackComplexPropertyChanges<T>(PropertyInfo property, T newObj, T oldObj, T clonedObj) where T : class
{
var clonedResourceBase = (ResourceBase)(object)clonedObj;
var oldPropertyObj = property.GetValue(oldObj, null);
var newPropertyObj = property.GetValue(newObj, null);
var clonedPropertyObj = property.GetValue(clonedObj, null);
if (oldPropertyObj != null && newPropertyObj != null && clonedPropertyObj != null)
{
TrackChanges(oldPropertyObj, newPropertyObj, clonedPropertyObj);
var clonedPropertyResourceBase = (ResourceBase)(object)clonedPropertyObj;
if (clonedPropertyResourceBase.State != ResourceState.UnModified)
{
clonedResourceBase.State = ResourceState.Modified;
}
}
else if (oldPropertyObj != null && clonedPropertyObj != null && newPropertyObj == null)
{
var clonedPropertyResourceBase = (ResourceBase)(object)clonedPropertyObj;
clonedPropertyResourceBase.State = ResourceState.Deleted;
clonedResourceBase.State = ResourceState.Modified;
}
else if (oldPropertyObj == null && clonedPropertyObj == null && newPropertyObj != null)
{
var newPropertyResourceBase = (ResourceBase)(object)newPropertyObj;
var clonedPropertyResourceBase = (ResourceBase)newPropertyResourceBase.Clone();
clonedPropertyResourceBase.State = ResourceState.Added;
property.SetValue(clonedObj, clonedPropertyResourceBase);
clonedResourceBase.State = ResourceState.Modified;
}
}
private void TrackCollectionPropertyChanges<T>(PropertyInfo property, T newObj, T oldObj, T clonedObj) where T : class
{
var clonedResourceBase = (ResourceBase)(object)clonedObj;
if (oldObj != null && newObj != null)
{
if (property.PropertyType.GetGenericICollectionsArgument().IsSimpleType())
{
TrackSimpleCollectionChanges(property, newObj, oldObj, clonedObj, clonedResourceBase);
}
else
{
TrackComplexCollectionChanges(property, newObj, oldObj, clonedObj, clonedResourceBase);
}
}
}
private void TrackSimpleCollectionChanges<T>(PropertyInfo property, T newObj, T oldObj, T clonedObj, ResourceBase clonedResourceBase) where T : class
{
var oldPropertyObj = (IEnumerable<ResourceBase>)property.GetValue(oldObj, null)!;
var newPropertyObj = (IEnumerable<ResourceBase>)property.GetValue(newObj, null)!;
var clonedPropertyCollection = property.GetValue(clonedObj, null)!;
var castedOldPropertyObj = oldPropertyObj.ToList();
var castedNewPropertyObj = newPropertyObj.ToList();
var tobeAdded = castedNewPropertyObj.Except(castedOldPropertyObj);
var tobeRemoved = castedOldPropertyObj.Except(castedNewPropertyObj);
foreach (var item in tobeAdded)
{
clonedPropertyCollection.GetType().GetMethod("Add")!.Invoke(clonedPropertyCollection, new[] { item });
}
foreach (var item in tobeRemoved)
{
clonedPropertyCollection.GetType().GetMethod("Remove")!.Invoke(clonedPropertyCollection, new[] { item });
}
if (tobeAdded.Count() > 0 || tobeRemoved.Count() > 0)
{
clonedResourceBase.State = ResourceState.Modified;
}
}
private void TrackComplexCollectionChanges<T>(PropertyInfo property, T newObj, T oldObj, T clonedObj, ResourceBase clonedResourceBase) where T : class
{
var oldPropertyObj = (IEnumerable<ResourceBase>)property.GetValue(oldObj, null)!;
var newPropertyObj = (IEnumerable<ResourceBase>)property.GetValue(newObj, null)!;
var clonedPropertyObj = (IEnumerable<ResourceBase>)property.GetValue(clonedObj, null)!;
var clonedPropertyCollection = property.GetValue(clonedObj, null)!;
var castedOldPropertyObj = oldPropertyObj.ToList();
var castedNewPropertyObj = newPropertyObj.ToList();
var castedClonedPropertyObj = clonedPropertyObj.ToList();
var tobeAdded = castedNewPropertyObj.Except(castedOldPropertyObj, new ResourceComparer());
var tobeRemoved = castedOldPropertyObj.Except(castedNewPropertyObj, new ResourceComparer());
foreach (ResourceBase item in tobeAdded)
{
var clonedItem = (ResourceBase)item.Clone();
clonedItem.State = ResourceState.Added;
clonedPropertyCollection.GetType().GetMethod("Add")!.Invoke(clonedPropertyCollection, new[] { clonedItem });
}
foreach (ResourceBase item in tobeRemoved)
{
ResourceBase clonedItem = castedClonedPropertyObj.First(r => r.ResourceId == item.ResourceId);
clonedItem.State = ResourceState.Deleted;
}
var matchingItem = castedNewPropertyObj.Intersect(castedOldPropertyObj, new ResourceComparer());
foreach (ResourceBase item in matchingItem)
{
var newCollectionObj = castedNewPropertyObj.First(r => r.ResourceId == item.ResourceId);
var oldCollectionObj = castedOldPropertyObj.First(r => r.ResourceId == item.ResourceId);
var clonedCollectionObj = castedClonedPropertyObj.First(r => r.ResourceId == item.ResourceId);
clonedCollectionObj = TrackChanges(newCollectionObj, oldCollectionObj, clonedCollectionObj);
if (clonedCollectionObj.State != ResourceState.UnModified)
{
clonedResourceBase.State = ResourceState.Modified;
}
}
}
}
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
I get an exception when I try to set a nested member Property using FastMember. For example when having these classes
public class A
{
public B First { get; set; }
}
public class B
{
public string Second { get; set; }
}
and I want to set First.Second of an instance to "hello".
var b = new B{ Second = "some value here" };
var a = new A{ First = b };
var accessor = ObjectAccessor.Create(a);
accessor["First.Second"] = value; // this does not work and gives ArgumentOutOfRangeException
I can't split it up into ["First"]["Second"] because I don't know the depth at this point. Is there a magical access for nested properties or do I have to split the hierarchy myself?
I solved the problem recursively using an Extension Method this way:
public static class FastMemberExtensions
{
public static void AssignValueToProperty(this ObjectAccessor accessor, string propertyName, object value)
{
var index = propertyName.IndexOf('.');
if (index == -1)
{
accessor[propertyName] = value;
}
else
{
accessor = ObjectAccessor.Create(accessor[propertyName.Substring(0, index)]);
AssignValueToProperty(accessor, propertyName.Substring(index + 1), value);
}
}
}
... and this is started as follows:
ObjectAccessor.Create(a).AssignValueToProperty("First.Second", "hello")
You need to traverse the object graph using multiple ObjectAccessor instances.
public static void UseFastMember()
{
var b = new B { Second = "some value here" };
var a = new A { First = b };
var value = "hello";
var a_accessor = ObjectAccessor.Create(a);
var first = a_accessor["First"];
var b_accessor = ObjectAccessor.Create(first);
b_accessor["Second"] = value;
}
Hats off to #Beachwalker for the inspiration. But should you be using TypeAccessor as opposed to ObjectAccessor this is an extension method I've had much success with:
public static class TypeAccessorExtensions
{
public static void AssignValue<T>(this TypeAccessor accessor, T t, MemberSet members, string fieldName, object fieldValue)
{
var index = fieldName.IndexOf('.');
if (index == -1)
{
if (members.Any(m => string.Equals(m.Name, fieldName, StringComparison.OrdinalIgnoreCase)))
accessor[t, fieldName] = fieldValue;
}
else
{
string fieldNameNested = fieldName.Substring(0, index);
var member = members.FirstOrDefault(m => string.Equals(m.Name, fieldNameNested, StringComparison.OrdinalIgnoreCase));
if (member != null)
{
var nestedAccesor = TypeAccessor.Create(member.Type);
var tNested = accessor[t, fieldNameNested];
if (tNested == null)
{
tNested = Activator.CreateInstance(member.Type);
accessor[t, fieldNameNested] = tNested;
}
nestedAccesor.AssignValue(tNested, nestedAccesor.GetMembers(), fieldName.Substring(index + 1), fieldValue);
}
}
}
}
I need to list all properties for containing class, I have a lot of them, so I don't know the type in the code, but I can get it trough prop.BastType.FullName, but I can't cast it, and I don't want to change all my classes to crate "Cast" methods everywere.
To avoid printing Property Properties such as DeclaringType,ReflectedType, etc, I'm excluding them in the code, but I don't like this way...
if (("DeclaringType,ReflectedType,MetadataToken,Module,PropertyType,Attributes,CanRead" +
",CanWrite,GetMethod,SetMethod,IsSpecialName,CustomAttributes,MemberType")
.Contains(prop.Name))
continue;
full code of Method:
private static void GetAllPropertiesFor(object oo, int level, List<KeyValuePair<string,string>> result)
{
Type entType = oo.GetType();
IList<PropertyInfo> props = new List<PropertyInfo>(entType.GetProperties());
foreach (PropertyInfo prop in props)
{
object propValue = prop.GetValue(oo, null);
if (("DeclaringType,ReflectedType,MetadataToken,Module,PropertyType,Attributes,CanRead" +
",CanWrite,GetMethod,SetMethod,IsSpecialName,CustomAttributes,MemberType")
.Contains(prop.Name))
continue;
if (propValue == null)
{
result.Add(new KeyValuePair<string, string>((new string(' ', level * 2)) + prop.Name, "Object"));
//GetAllPropertiesFor(prop, level + (1*3), result);
}
else
{
if (result == null) result = new List<KeyValuePair<string, string>>();
result.Add(new KeyValuePair<string, string>((new string(' ', level)) + prop.Name, propValue.ToString()));
}
if (entType.Namespace == "System.Data.Entity.DynamicProxies" && entType.BaseType.FullName.StartsWith("RE2012.Data.Models."))
{
GetAllPropertiesFor(prop, level + 1, result);
}
// Do something with propValue
}
}
any suggestions?
this way I don't have in results, the values of:
PropertyType : System.Int32
Attributes : None
CanRead : True
CanWrite : True
just what I need.
Disclaim i can't really work out what you want so this answer is just a shot in the dark
Based on what i think i understood you want to get the Properties from a specific Class so here is my example:
First my same Inheritance
class BaseClass
{
public int Base { get; set; }
}
class ChildClass : BaseClass
{
public double Child { get; set; }
}
class ChildChildClass : ChildClass
{
public string ChildChild { get; set; }
}
now lets find the single Properties
class Program
{
static void Main(string[] args)
{
var obj = new ChildChildClass();
var ChildChildType = obj.GetType();
var p = new Program();
// here we get the ChildClass properties
var t = p.getBaseType(ChildChildType, 1);
Console.WriteLine(t.Name);
p.getproperties(t);
// here we get the BaseClass properties
t = p.getBaseType(ChildChildType, 2);
Console.WriteLine(t.Name);
p.getproperties(t);
// here we get the Object properties
t = p.getBaseType(ChildChildType, 3);
Console.WriteLine(t.Name);
p.getproperties(t);
Console.Read();
}
internal Type getBaseType(Type t, int level)
{
Type temp ;
for (int i = 0; i < level; i++)
{
temp = t.BaseType;
if(temp == null)
throw new ArgumentOutOfRangeException("you are digging to deep");
else
t = temp;
}
return t;
}
private void getproperties(Type t)
{
PropertyInfo[] properties = t.GetProperties(BindingFlags.DeclaredOnly |
BindingFlags.Public |
BindingFlags.Instance);
foreach (PropertyInfo property in properties)
{
Console.WriteLine(property.Name);
}
Console.WriteLine("");
}
}
if you want some information about BindingFlags here is a Link
I have a class, that has several elements of normal types, like int, String, etc.
It also has several elements that are various lists of other classes, that could be empty or have 1 to many items.
I have a function that I call with a generic type of the parent class, and I want to analyze data that could be in the sub elements, without knowing the types.
I am getting the parent members with the following code:
var getProperty = System.Runtime.CompilerServices.
CallSite<Func<System.Runtime.CompilerServices.CallSite,
object, object>>
.Create(Microsoft.CSharp.RuntimeBinder.
Binder.GetMember(0, property.Name, thisObject.GetType(), new[]
{
Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfo.Create(0, null)
}));
var thisValue = getProperty.Target(getProperty, thisObject);
I get the value into the var thisValue. At this point if I determine the underlying type of thisValue is a type of list, how can I grab the type of the list contents?
Here is the actual function....I can't seem to get it formatted nicely.
public static bool ObjectIsLike<T>(this T thisObject, T compareObject, params object[] argumentsToExclude)
{
for (int counter = 0; counter < argumentsToExclude.Length - 1; counter++)
{
argumentsToExclude[counter] = argumentsToExclude[counter].ToString().ToUpper();
}
bool objectIsLike = true;
foreach (var property in thisObject.GetType().GetProperties())
{
string fieldName = property.Name;
if (!argumentsToExclude.Contains(fieldName.ToUpper()))
{
try
{
var getProperty = System.Runtime.CompilerServices.CallSite<Func<System.Runtime.CompilerServices.CallSite, object, object>>.Create(Microsoft.CSharp.RuntimeBinder.Binder.GetMember(0, property.Name, thisObject.GetType(), new[] { Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfo.Create(0, null) }));
var thisValue = getProperty.Target(getProperty, thisObject);
getProperty = System.Runtime.CompilerServices.CallSite<Func<System.Runtime.CompilerServices.CallSite, object, object>>.Create(Microsoft.CSharp.RuntimeBinder.Binder.GetMember(0, property.Name, compareObject.GetType(), new[] { Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfo.Create(0, null) }));
var compareValue = getProperty.Target(getProperty, compareObject);
if (!(compareValue == null && thisValue == null))
{
if (compareValue == null || thisValue == null)
objectIsLike = false;
else
if (compareValue.GetType().FullName.Contains("List"))
{
//Ignore Lists
}
else
if (!compareValue.Equals(thisValue))
{
objectIsLike = false;
}
}
}
catch
{
objectIsLike = false;
}
}
}
return objectIsLike;
}
would GetType() work for you?
class Program
{
static void Main(string[] args)
{
MyClass1 c1 = new MyClass1();
foreach (var s in c1.pp)
{
Console.WriteLine(s.GetType());
}
Console.Read();
}
}
public class MyClass1
{
public MyClass2 p;
public List<object> pp;
public MyClass1()
{
p = new MyClass2();
pp = new List<object>();
pp.Add(new MyClass2());
pp.Add(new MyClass3());
pp.Add(new MyClass4());
}
}
public class MyClass2
{
public List<object> ppp;
public MyClass2()
{
ppp = new List<object>();
ppp.Add(new MyClass3());
ppp.Add(new MyClass4());
}
}
public class MyClass3
{
public int v;
}
public class MyClass4
{
public int v;
}