
Converting Objects To and From QueryStrings
Hello fellow developers!
Query strings are a fundamental part of web development, serving as a bridge for data exchange between clients and servers. In C#, converting objects to query strings and back again can sometimes be a bit tricky, especially when you venture beyond the conveniences of Minimal APIs. This article provides a comprehensive guide to mastering query string conversions in C#, equipping you with the techniques needed to handle various scenarios effectively
While Minimal APIs provide the handy AsParameters
attribute for effortlessly converting query string parameters to objects, this functionality isn’t always available in other environments like MVC or Azure Functions. This is where alternative methods come into play.
ToQueryString<T>(T obj)
Let’s explore two powerful approaches to transform your C# objects into query strings:
1. Reflection:
Reflection allows us to inspect and manipulate object properties at runtime, making it a perfect tool for this task. Here’s how you can implement the ToQueryString<T>
method using reflection:
public static string ToQueryStringV1_1<T>(this T obj)
{
var properties = typeof(T).GetProperties();
var sb = new StringBuilder();
sb.Append('?');
foreach (var prop in properties)
{
var value = prop.GetValue(obj);
if (value != null)
{
sb.Append($"{prop.Name}={value}&");
}
}
return sb.ToString().TrimEnd('&');
}
public static string ToQueryStringV1_2<T>(T obj)
{
var properties = typeof(T).GetProperties()
.Where(p => p.GetValue(obj, null) != null);
var queryString = string.Join("&", properties
.Select(p => $"{Uri.EscapeDataString(p.Name)}={Uri.EscapeDataString(p.GetValue(obj, null).ToString())}"));
return queryString;
}
This code snippet retrieves the properties of the obj
object, filters out those with null
values, and concatenates them into a query string format. The Uri.EscapeDataString
method ensures a safe query string by escaping invalid URL characters.
2. JSON Serialization:
For a more concise approach, we can leverage JSON serialization to convert the entire object into a JSON string and then embed it within the query string. This is particularly useful when dealing with complex object structures. Here’s how you can do it using System.Text.Json
:
public static string ToQueryStringV2<T>(this T obj)
{
var json = JsonSerializer.Serialize(obj);
var dict = JsonSerializer.Deserialize<Dictionary<string, object>>(json, new JsonSerializerOptions()
{
PropertyNameCaseInsensitive = true,
});
//return "?" + string.Join("&", dict.Select(x => $"{x.Key}={x.Value}")); // in a single line
// Build the query string with StringBuilder
var sb = new StringBuilder();
sb.Append('?');
foreach (var item in dict)
{
sb.Append($"{item.Key}={item.Value}&");
}
return sb.ToString().TrimEnd('&');
}
Important Note: This method works seamlessly for simple objects. However, if your object contains nested complex types, you’ll need to implement additional logic to handle those nested structures correctly.
Creating Objects from Query Strings:
We can leverage a built-in method within the System.Net.Http.Formatting.Extension
class for this operation:
1 – System.Net.Http.Formatting.Extension (with NuGet Package)
The System.Net.Http.Formatting.Extension
library provides a convenient method called TryReadQueryAs<T>
that simplifies this task:
public static T ToObjectFromQueryStringV1<T>(this HttpRequest request) where T : class, new()
{
var displayUrl = request.GetDisplayUrl();
var uri = new Uri(displayUrl);
uri.TryReadQueryAs<T>(out var query); // Nuget -> System.Net.Http.Formatting.Extension
return query;
}
This method handles the parsing of the query string and the creation of the object for you. However, it requires adding the System.Net.Http.Formatting.Extension
NuGet package to your project.
2. Manual Implementation (without NuGet Package)
If you prefer to avoid external dependencies, you can implement the query string parsing and object creation logic yourself:
public static bool TryReadQueryAs<T>(this Uri uri, out T query) where T : class, new()
{
query = null;
if (string.IsNullOrWhiteSpace(uri.Query))
return false;
var queryString = uri.Query.TrimStart('?');
var queryParts = queryString.Split('&');
var queryDict = new Dictionary<string, string>(capacity: queryParts.Length);
foreach (var queryPart in queryParts)
{
var queryPair = queryPart.Split('=');
queryDict.Add(queryPair[0], queryPair[1]);
}
var queryType = typeof(T);
var queryProperties = queryType.GetProperties();
var queryInstance = Activator.CreateInstance(queryType);
foreach (var queryProperty in queryProperties)
{
if (queryDict.ContainsKey(queryProperty.Name))
{
var value = queryDict[queryProperty.Name];
queryProperty.SetValue(queryInstance, Convert.ChangeType(value, queryProperty.PropertyType));
}
}
query = queryInstance as T;
return true;
}
Important Note: Similar to the JSON serialization approach, this method might require additional handling for nested objects.
3. Direct Conversion with HttpRequest.Query
Another way to achieve this is by using the HttpRequest.Query
property along with JSON serialization:
public static T ToObjectFromQueryStringV2<T>(this HttpRequest request) where T: class
{
var query = request.Query.ToDictionary(x => x.Key, x => x.Value.ToString()); // ToString is not good for nested objects
var json = JsonSerializer.Serialize(query);
var obj = JsonSerializer.Deserialize<T>(json, new JsonSerializerOptions()
{
PropertyNameCaseInsensitive = true,
NumberHandling = JsonNumberHandling.AllowReadingFromString
});
return obj;
}
This method converts the query string parameters into a dictionary, serializes it to JSON, and then deserializes it into an object of the desired type. The NumberHandling
option ensures that numeric values in the query string are correctly deserialized even if they are represented as strings.
This article has explored various techniques for converting objects to and from query strings in C#. By understanding these methods and their nuances, you can confidently handle query string manipulations in your web development projects. Remember to consider the complexity of your objects and your project’s dependencies when choosing the most suitable approach.