Skip to content

Commit 6d23363

Browse files
Merge pull request #278 from TransactionProcessing/task/#268_add_querystring_builder
Add QueryStringBuilder and tests
2 parents 891cd5a + a7fd26e commit 6d23363

2 files changed

Lines changed: 184 additions & 0 deletions

File tree

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Linq;
4+
using System.Text;
5+
using System.Threading.Tasks;
6+
using Microsoft.VisualStudio.TestPlatform.TestHost;
7+
using Shared.Web;
8+
using Shouldly;
9+
using Xunit;
10+
11+
namespace Shared.Tests
12+
{
13+
public class QueryStringBuilderTests
14+
{
15+
[Fact]
16+
public void QueryStringBuilder_NoParameters_EmptyQueryStringReturned()
17+
{
18+
QueryStringBuilder builder = new QueryStringBuilder();
19+
20+
var queryString = builder.BuildQueryString();
21+
22+
queryString.ShouldBe(String.Empty);
23+
}
24+
25+
[Fact]
26+
public void QueryStringBuilder_SingleParameters_CorrectQueryStringReturned()
27+
{
28+
QueryStringBuilder builder = new QueryStringBuilder();
29+
30+
builder.AddParameter("param1", "testparam1");
31+
32+
var queryString = builder.BuildQueryString();
33+
34+
queryString.ShouldBe("param1=testparam1");
35+
}
36+
37+
[Fact]
38+
public void QueryStringBuilder_MultipleParameters_CorrectQueryStringReturned()
39+
{
40+
QueryStringBuilder builder = new QueryStringBuilder();
41+
42+
builder.AddParameter("param1", "testparam1");
43+
builder.AddParameter("param2", "testparam2");
44+
45+
var queryString = builder.BuildQueryString();
46+
47+
queryString.ShouldBe("param1=testparam1&param2=testparam2");
48+
}
49+
50+
static T GetDefaultGeneric<T>()
51+
{
52+
return default(T);
53+
}
54+
55+
static object GetDefault(Type type)
56+
{
57+
// Create a generic method with reflection to get the default value
58+
var method = typeof(QueryStringBuilderTests).GetMethod(nameof(GetDefaultGeneric), System.Reflection.BindingFlags.Static | System.Reflection.BindingFlags.NonPublic);
59+
var genericMethod = method.MakeGenericMethod(type);
60+
return genericMethod.Invoke(null, null);
61+
}
62+
63+
[Theory]
64+
[InlineData(typeof(Decimal))]
65+
[InlineData(typeof(DateTime))]
66+
[InlineData(typeof(Int32))]
67+
[InlineData(typeof(Int16))]
68+
public void QueryStringBuilder_AddParametersWithDefaultValue_NotIncludedInQueryStringReturned(Type t)
69+
{
70+
QueryStringBuilder builder = new QueryStringBuilder();
71+
72+
object defaultValue = GetDefault(t);
73+
74+
builder.AddParameter("param1", "testparam1");
75+
builder.AddParameter("param2", defaultValue);
76+
77+
var queryString = builder.BuildQueryString();
78+
79+
queryString.Contains("param2").ShouldBeFalse();
80+
}
81+
82+
[Theory]
83+
[InlineData(typeof(Decimal))]
84+
[InlineData(typeof(DateTime))]
85+
[InlineData(typeof(Int32))]
86+
[InlineData(typeof(Int16))]
87+
public void QueryStringBuilder_AddParametersWithDefaultValue_AlwaysInclude_ParameterIncludedInQueryStringReturned(Type t)
88+
{
89+
QueryStringBuilder builder = new QueryStringBuilder();
90+
91+
object defaultValue = GetDefault(t);
92+
93+
builder.AddParameter("param1", "testparam1");
94+
builder.AddParameter("param2", defaultValue, true);
95+
96+
var queryString = builder.BuildQueryString();
97+
98+
queryString.Contains("param2").ShouldBeTrue();
99+
}
100+
}
101+
}

Shared/Web/QueryStringBuilder.cs

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Linq;
4+
using System.Text;
5+
using System.Threading.Tasks;
6+
7+
namespace Shared.Web
8+
{
9+
public class QueryStringBuilder
10+
{
11+
private Dictionary<string, (object value, Boolean alwaysInclude)> parameters = new Dictionary<String, (Object value, Boolean alwaysInclude)>();
12+
13+
public QueryStringBuilder AddParameter(string key, object value, Boolean alwaysInclude = false)
14+
{
15+
this.parameters.Add(key, (value, alwaysInclude));
16+
return this;
17+
}
18+
19+
static Dictionary<string, object> FilterDictionary(Dictionary<string, (object value, Boolean alwaysInclude)> inputDictionary)
20+
{
21+
Dictionary<string, object> result = new Dictionary<string, object>();
22+
23+
foreach (KeyValuePair<String, (object value, Boolean alwaysInclude)> entry in inputDictionary)
24+
{
25+
if (entry.Value.value != null && !IsDefaultValue(entry.Value.value, entry.Value.alwaysInclude))
26+
{
27+
result.Add(entry.Key, entry.Value.value);
28+
}
29+
}
30+
31+
return result;
32+
}
33+
34+
static bool IsDefaultValue<T>(T value, Boolean alwaysInclude)
35+
{
36+
if (alwaysInclude)
37+
return false;
38+
39+
Object? defaultValue = GetDefault(value.GetType());
40+
41+
if (defaultValue == null && value.GetType() == typeof(String))
42+
{
43+
defaultValue = String.Empty;
44+
}
45+
return defaultValue.Equals(value);
46+
}
47+
48+
public static object GetDefault(Type t)
49+
{
50+
Func<object> f = GetDefault<object>;
51+
return f.Method.GetGenericMethodDefinition().MakeGenericMethod(t).Invoke(null, null);
52+
}
53+
54+
private static T GetDefault<T>()
55+
{
56+
return default(T);
57+
}
58+
59+
public string BuildQueryString()
60+
{
61+
Dictionary<String, Object> filtered = FilterDictionary(this.parameters);
62+
63+
if (filtered.Count == 0)
64+
{
65+
return string.Empty;
66+
}
67+
68+
StringBuilder queryString = new StringBuilder();
69+
70+
foreach (KeyValuePair<String, Object> kvp in filtered)
71+
{
72+
if (queryString.Length > 0)
73+
{
74+
queryString.Append("&");
75+
}
76+
77+
queryString.Append($"{Uri.EscapeDataString(kvp.Key)}={Uri.EscapeDataString(kvp.Value.ToString())}");
78+
}
79+
80+
return queryString.ToString();
81+
}
82+
}
83+
}

0 commit comments

Comments
 (0)