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");
Related
I'm currently trying to refactor a method that receives a property name and set it's value:
private async Task < Enrichment > ParseEnrichmentNewDataAsync(int leadId, string property, string newValue) {
var enrichment = await _context.EnrichmentRepository.GetByLeadIdAsync(leadId);
if (enrichment == null) {
enrichment = new Enrichment() {
LeadId = leadId
};
}
Enum.TryParse < PropertyType > (property, out
var enumProperty);
switch (enumProperty) {
case PropertyType.Color:
enrichment.Color = newValue;
break;
case PropertyType.UsedVehicleModel:
enrichment.UsedModel = newValue;
break;
case PropertyType.UsedVehicleYear:
enrichment.UsedYear = newValue;
break;
case PropertyType.UsedVehicleKm:
enrichment.UsedKm = newValue;
break;
case PropertyType.Payment:
enrichment.Payment = Convert.ToInt32(EnumExtension.GetEnumValueFromDescription < PaymentType > (newValue));
break;
case PropertyType.ScheduleDate:
enrichment.ScheduleDate = DateTime.ParseExact(newValue, "dd/MM/yyyy", CultureInfo.InvariantCulture);
break;
case PropertyType.SchedulePeriod:
enrichment.SchedulePeriod = newValue;
break;
case PropertyType.SchedulePhone:
enrichment.SchedulePhone = newValue;
break;
case PropertyType.PurchaseType:
enrichment.PurchaseType = newValue;
break;
case PropertyType.HasOptInNextJeep:
enrichment.HasOptInNextJeep = Convert.ToBoolean(newValue);
break;
}
return enrichment;
}
Using reflection would work for most of the fields, but some values need to be converted before being assigned.
Is there a design pattern or a better way to improve this code?
Assuming PropertyType is a numeric 0-based enum, you could do this:
private static Action<Enrichment, string>[] _propertySetters = new Action<Enrichment, string>[]
{
// Assuming Color = 0
(enrichment, value) => enrichment.Color = value,
// Assuming UsedVehicleYear = 1
(enrichment, value) => enrichment.UsedVehicleYear = value,
// Assuming UsedVehicleModel = 2
(enrichment, value) => enrichment.UsedVehicleModel = value,
// ...
};
private async Task<Enrichment> ParseEnrichmentNewDataAsync(int leadId, string property, string newValue)
{
// ...
Enum.TryParse<PropertyType>(property, out var enumProperty);
var setter = _propertySetters[(int)enumProperty];
setter(enrichment, newValue);
// ...
}
You could also do this with a Dictionary<PropertyType, Action<Enrichment, string>> instead of a flat array, but if performance is what you're going for, this is unlikely to be better than using switch ... case until you get to very large numbers of properties.
Method should do only one thing. Function ParseEnrichmentNewDataAsync does three things:
Gets or creates Enrichment object
Converts the specified value if needed
Sets property of Enrichment object
It would be better to somehow separate converting from property setting.
I would suggest the EnrichmentPropertySetter class, which encapsulates property setting logic:
public abstract class EnrichmentPropertyActionBase
{
public abstract void SetProperty(Enrichment enrichment, string value);
}
public class EnrichmentPropertyAction<T> : EnrichmentPropertyActionBase
{
public Action<Enrichment, T> Set { get; set; }
public Func<string, T> Convert { get; set; }
public override void SetProperty(Enrichment enrichment, string value)
{
Set(enrichment, Convert(value));
}
}
public class EnrichmentPropertySetter
{
private readonly Dictionary<string, EnrichmentPropertyActionBase> _dictionary =
new Dictionary<string, EnrichmentPropertyActionBase>();
public EnrichmentPropertySetter()
{
_dictionary["Color"] = new EnrichmentPropertyAction<string>
{
Convert = s => s,
Set = (e, s) => e.Color = s
};
...
_dictionary["Payment"] = new EnrichmentPropertyAction<int>
{
Convert = s => Convert.ToInt32(EnumExtension.GetEnumValueFromDescription<PaymentType>(s)),
Set = (e, i) => e.Payment = i
};
_dictionary["ScheduleDate"] = new EnrichmentPropertyAction<DateTime>
{
Convert = s => DateTime.ParseExact(s, "dd/MM/yyyy", CultureInfo.InvariantCulture),
Set = (e, d) => e.ScheduleDate = d
};
...
_dictionary["HasOptInNextJeep"] = new EnrichmentPropertyAction<bool>
{
Convert = s => Convert.ToBoolean(s),
Set = (e, s) => e.HasOptInNextJeep = s
};
}
public void SetProperty(Enrichment enrichment, string property, string value)
{
if (!_dictionary.TryGetValue(property, out EnrichmentPropertyActionBase action))
throw new InvalidOperationException("Something goes wrong...");
action.SetProperty(enrichment, value);
}
}
Assuming that number of properties is limited, I don’t think using Dictionary may lead to significant performance issues.
But this solution is still not ideal, because we have to hardcode property names anyway. Maybe you should think about why you need to set properties by the specified name, and revisit the task.
I have this method:
public static SiteSettingEntity
GetSettings<SiteSettingEntity>(string siteId,
Expression<Func<SiteSettingEntity, object>> properties)
{
// This method returns a SiteSettingEntity
// filled with the values selected in my expression
}
If want to invoke:
var _siteSetting = SiteSettingService.GetSettings<SiteSettingEntity>(SiteID,
s => new { s.BillCycleType, s.InvoiceComment,
s.AllowInvoiceDetailApproval, s.JobMinimumHours });
So this returns an object with the properties filled that was selected by my lambda expression.
My question is: how I do to return a custom object dynamically same using my expression?
Taking an example: when if you want to use .Select(s => new{ filed1 = data.FieldX}), you can just use the property .filed1 in return object.
Thanks!
If you are trying to return a dynamic object you can do something like this simple example of using ExpandoObject:
public class DuckFactory
{
public ExpandoObject GetDuck()
{
dynamic duck = new ExpandoObject();
duck.Name = "Fauntleroy";
return duck;
}
}
And then call it like:
dynamic duck = new DuckFactory().GetDuck();
// Check the property exists before using it
if (((IDictionary<string, Object>)duck).ContainsKey("Name"))
{
Console.WriteLine(duck.Name); // Prints Fauntleroy
}
else
{
Console.WriteLine("Poor duck doesn't have a name.");
}
Just remember you won't get the benefits of the object being strongly typed.
This is my original method:
public static T GetSettings<T>(string siteId, Expression<Func<SiteSettingEntity, object>> properties)
{
string query = $"SELECT TOP 1 {DbTool.GetSqlFields(properties)} FROM {SiteSettingEntity.TABLE_NAME} (NOLOCK) WHERE Sites.SiteID = #SiteID";
var parameters = new Dictionary<string, object>
{
{"SiteID", siteId},
};
var _data = DbTool.SqlExec<T>(PowerDetailContext.GetConnectionString(siteId), CommandType.Text, query, parameters);
return _data != null ? _data.FirstOrDefault() : default(T);
}
Ang the when I made the call I have this:
var _siteSetting = SiteSettingService.GetSettings<SiteSettingEntity>(SiteID, s => new { s.BillCycleType, s.InvoiceComment, s.AllowInvoiceDetailApproval, s.JobMinimumHours });
the var _siteSetting I want just have the properties SELECTED between new{} when I called. If I try use _siteSetting.OtherProperty, the "OtherProperty" won't available in the code.
After reading it again I suppose you are just trying to return SiteSettingEntity from GetSettings method.
public static SiteSettingEntity GetSettings<T>(string siteId, Expression<Func<SiteSettingEntity, object>> properties)
{
string query = $"SELECT TOP 1 {DbTool.GetSqlFields(properties)} FROM {SiteSettingEntity.TABLE_NAME} (NOLOCK) WHERE Sites.SiteID = #SiteID";
var parameters = new Dictionary<string, object>
{
{"SiteID", siteId},
};
var _data = DbTool.SqlExec<SiteSettingEntity>(PowerDetailContext.GetConnectionString(siteId), CommandType.Text, query, parameters);
return _data != null ? _data.FirstOrDefault() : default(SiteSettingEntity);
}
If I have the following class
public class Customer
{
public string Name;
}
and then have the following log command in Serilog
Log.Logger = new LoggerConfiguration()
.WriteTo.Console()
.WriteTo.Seq("http://localhost:5341")
.CreateLogger();
var item = new Customer();
item.Name = "John";
Serilog.Log.Information("Customer {#item}", item);
The log just displays in Seq as
Customer {}
If I change the Name field to a property it works but I would prefer not to do that at this stage. Is there any way around it?
To do this just for the one type (recommended), you can use:
.Destructure.ByTransforming<Customer>(c => new { c.Name })
If you want to include public fields for all types, or those matching some kind of condition, you can plug in a policy to do it:
class IncludePublicFieldsPolicy : IDestructuringPolicy
{
public bool TryDestructure(
object value,
ILogEventPropertyValueFactory propertyValueFactory,
out LogEventPropertyValue result)
{
if (!(value is SomeBaseType))
{
result = null;
return false;
}
var fieldsWithValues = value.GetType().GetTypeInfo().DeclaredFields
.Where(f => f.IsPublic)
.Select(f => new LogEventProperty(f.Name,
propertyValueFactory.CreatePropertyValue(f.GetValue(value))));
result = new StructureValue(fieldsWithValues);
return true;
}
}
The example scopes this down to look at objects derived from SomeBaseType only.
You can plug it in with:
.Destructure.With<IncludePublicFieldsPolicy>()
(I think it's likely to require some tweaking, but should be a good starting point.)
Thanks to Nicholas Blumhardt for a good starting point. I just have a small tweak.
my class:
public class Dummy
{
public string Field = "the field";
public string Property { get; set; } = "the property";
}
log call:
Log.Information("Dummy = {#Dummy}", new Dummy());
IDestructuringPolicy implementation includes both fields and properties:
internal class IncludePublicFieldsPolicy : IDestructuringPolicy
{
public bool TryDestructure(object value, ILogEventPropertyValueFactory propertyValueFactory, out LogEventPropertyValue result)
{
var typeInfo = value.GetType().GetTypeInfo();
var fieldsWithValues = typeInfo
.DeclaredFields
.Where(f => f.IsPublic)
.Select(f =>
{
var val = f.GetValue(value);
var propval = propertyValueFactory.CreatePropertyValue(val);
var ret = new LogEventProperty(f.Name, propval);
return ret;
})
;
var propertiesWithValues = typeInfo
.DeclaredProperties
.Where(f => f.CanRead)
.Select(f =>
{
var val = f.GetValue(value);
var propval = propertyValueFactory.CreatePropertyValue(val);
var ret = new LogEventProperty(f.Name, propval);
return ret;
})
;
result = new StructureValue(fieldsWithValues.Union(propertiesWithValues));
return true;
}
}
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 have a class what takes care of reading and holding the XML file.
Right now a simple version of it looks like this:
public class EstEIDPersoConfig
{
public bool LaunchDebugger { get ; set; }
public string Password { get; set; }
public int Slot { get; set; }
public string Reader { get; set; }
public string TestInput { get; set; }
public bool Logging { get; set; }
public EstEIDPersoConfig()
{
XElement xml = XElement.Load(myxml.xml);
XElement Configuration = xml.Element("Configuration");
LaunchDebugger = Convert.ToBoolean(Configuration.Element("LaunchDebugger").Value);
Password = Configuration.Element("Password").Value;
Slot = Convert.ToInt32(Configuration.Element("Slot").Value);
Reader = Configuration.Element("Reader").Value;
TestInput = Configuration.Element("TestInput").Value;
Logging = Convert.ToBoolean(Configuration.Element("Logging").Value);
}
}
And there will be more later. so the problem is that if some element does not exist in xml i get System.NullReferenceException. So i need to check if the element is null or not. Heres one way to do this:
var value = Configuration.Element("LaunchDebugger").Value;
if (value != null)
LaunchDebugger = Convert.ToBoolean(value);
else
throw new Exception("LaunchDebugger element missing from xml!");
But doing that for every element would be just too much. So i need some good ideas how to simplify this system so it wouldn't end up in 1000 lines of code.
EDIT: Edited the last code snippet, the idea was not to set a default value, idea was to notify the user that this element whats null is missing from the xml.
The idea here comes directly from abatischev's answer so he deserves the credit.
As perscribed by Microsoft here you can just cast the XElement to the type you desire.
LaunchDebugger = (bool?)Configuration.Element("LaunchDebugger");
if you want to handle the null case I guess you could do
LaunchDebugger = (bool)(Configuration.Element("LaunchDebugger") ?? true);
or perhaps
LaunchDebugger = (bool)(Configuration.Element("LaunchDebugger") ?? false);
depending on your business logic. If you do the same coalescene for a specific type it may be appropriate to wrap this one liner in a method, extension or otherwise but I'm uncertain it would add much.
(bool)Configuration.Element("LaunchDebugger")
or
(bool?)Configuration.Element("LaunchDebugger")
should not throw exception.
See MSDN:
XElement Explicit Conversion (XElement to Boolean)
XElement Explicit Conversion (XElement to Nullable<Boolean>)
I have an extension method that I use for just this kind of thing:
public static T GetValue<T>(
this XElement #this,
XName name,
Func<XElement, T> cast,
Func<T> #default)
{
var e = #this.Element(name);
return (e != null) ? cast(e) : #default();
}
It gives you the casting required and also a default value factory.
Here's how you'd use it:
LaunchDebugger = Configuration.GetValue("LaunchDebugger",
x => Convert.ToBoolean(x), () => false);
Password = Configuration.GetValue("CMKPassword", x => (string)x, () => "");
Slot = Configuration.GetValue("CMKSlot", x => (int)x, () => -1);
Reader = Configuration.GetValue("Reader", x => (string)x, () => "");
TestInput = Configuration.GetValue("TestInput", x => (string)x, () => "");
Logging = Configuration.GetValue("Logging",
x => Convert.ToBoolean(x), () => false);
Extract the logic to a method and have overloaded methods for Int32,boolean and other data types conversion.
public static void GetElementValue(XElement xElement, string parameter, out bool value)
{
var stringValue = xElement.Element(parameter).Value;
value = false;
if (value != null)
value = Convert.ToBoolean(stringValue);
}
You can define a method to extract the value for you and do some checking on null there. So wrap the value retrieval in your own method like so:
public string GetXMLValue(XElement config, string elementName){
var element = Configuration.Element(elementName);
if(element == null)
return String.Empty;
return element.Value;
}
Of course you can extend this to work correctly with parsing to boolean etc.
How about an external method:
public static class XElementExtensions
{
public static bool AsBoolean(this XElement self, bool defaultValue)
{
if (self == null)
{
return defaultValue;
}
if (!string.IsNullOrEmpty(self.Value))
{
try
{
return XmlConvert.ToBoolean(self.Value);
}
catch
{
return defaultValue;
}
}
return defaultValue;
}
}
I've tested this with SnippetCompiler:
XElement test = new XElement("test",
new XElement("child1"),
new XElement("child2", new XText("true")),
new XElement("child3", new XText("false")),
new XElement("child4", new XText("rubbish")));
WL(test.Element("child1").AsBoolean(false)); // note, "child1" has no value (or is `""`)
WL(test.Element("child2").AsBoolean(false));
WL(test.Element("child3").AsBoolean(false));
WL(test.Element("child4").AsBoolean(false));
WL(test.Element("child5").AsBoolean(false)); // note, "child5" doesn't exist
To produce this result:
False
True
False
False
False
Add more such methods for other types and also add AsBoolean(defaultValue), as that can come in handy, when you want to default to true!
As others have stated, you can use the ?? operator to provide a value for null. This doesn't nest, though, so:
LaunchDebugger = XmlConvert.ToBoolean(Configuration.Element("LaunchDebugger").Value) ?? false;
will through a NullReferenceException if there is no such element in the XML file.