diff --git a/src/Microsoft.AspNet.Http.Abstractions/QueryString.cs b/src/Microsoft.AspNet.Http.Abstractions/QueryString.cs
index 068782cdae..6d156bc7ee 100644
--- a/src/Microsoft.AspNet.Http.Abstractions/QueryString.cs
+++ b/src/Microsoft.AspNet.Http.Abstractions/QueryString.cs
@@ -2,6 +2,8 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
+using System.Collections.Generic;
+using System.Text;
using Microsoft.Framework.Internal;
using Microsoft.Framework.WebEncoders;
@@ -33,16 +35,6 @@ namespace Microsoft.AspNet.Http
_value = value;
}
- ///
- /// Initialize a query string with a single given parameter name and value.
- ///
- /// The un-encoded parameter name
- /// The un-encoded parameter value
- public QueryString(string name, string value)
- {
- _value = "?" + UrlEncoder.Default.UrlEncode(name) + '=' + UrlEncoder.Default.UrlEncode(value);
- }
-
///
/// The escaped query string with the leading '?' character
///
@@ -114,6 +106,67 @@ namespace Microsoft.AspNet.Http
return new QueryString(queryValue);
}
+ ///
+ /// Create a query string with a single given parameter name and value.
+ ///
+ /// The un-encoded parameter name
+ /// The un-encoded parameter value
+ public static QueryString Create(string name, string value)
+ {
+ return new QueryString("?" + UrlEncoder.Default.UrlEncode(name) + '=' + UrlEncoder.Default.UrlEncode(value));
+ }
+
+ ///
+ /// Creates a query string composed from the given name value pairs.
+ ///
+ ///
+ ///
+ public static QueryString Create(IEnumerable> parameters)
+ {
+ var builder = new StringBuilder();
+ bool first = true;
+ foreach (var pair in parameters)
+ {
+ builder.Append(first ? "?" : "&");
+ first = false;
+ builder.Append(UrlEncoder.Default.UrlEncode(pair.Key));
+ builder.Append("=");
+ builder.Append(UrlEncoder.Default.UrlEncode(pair.Value));
+ }
+
+ return new QueryString(builder.ToString());
+ }
+
+ public QueryString Add(QueryString other)
+ {
+ if (!HasValue || Value.Equals("?", StringComparison.Ordinal))
+ {
+ return other;
+ }
+ if (!other.HasValue || other.Value.Equals("?", StringComparison.Ordinal))
+ {
+ return this;
+ }
+
+ // ?name1=value1 Add ?name2=value2 returns ?name1=value1&name2=value2
+ return new QueryString(_value + "&" + other.Value.Substring(1));
+ }
+
+ public QueryString Add(string name, string value)
+ {
+ if (!HasValue || Value.Equals("?", StringComparison.Ordinal))
+ {
+ return Create(name, value);
+ }
+
+ var builder = new StringBuilder(Value);
+ builder.Append("&");
+ builder.Append(UrlEncoder.Default.UrlEncode(name));
+ builder.Append("=");
+ builder.Append(UrlEncoder.Default.UrlEncode(value));
+ return new QueryString(builder.ToString());
+ }
+
public bool Equals(QueryString other)
{
return string.Equals(_value, other._value);
@@ -142,5 +195,10 @@ namespace Microsoft.AspNet.Http
{
return !left.Equals(right);
}
+
+ public static QueryString operator +(QueryString left, QueryString right)
+ {
+ return left.Add(right);
+ }
}
}
diff --git a/test/Microsoft.AspNet.Http.Abstractions.Tests/QueryStringTests.cs b/test/Microsoft.AspNet.Http.Abstractions.Tests/QueryStringTests.cs
new file mode 100644
index 0000000000..f36b8eeefc
--- /dev/null
+++ b/test/Microsoft.AspNet.Http.Abstractions.Tests/QueryStringTests.cs
@@ -0,0 +1,111 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading.Tasks;
+using Microsoft.AspNet.Testing;
+using Xunit;
+
+namespace Microsoft.AspNet.Http.Abstractions
+{
+ public class QueryStringTests
+ {
+ [Fact]
+ public void CtorThrows_IfQueryDoesNotHaveLeadingQuestionMark()
+ {
+ // Act and Assert
+ ExceptionAssert.ThrowsArgument(() => new QueryString("hello"), "value", "The leading '?' must be included for a non-empty query.");
+ }
+
+ [Fact]
+ public void CtorNullOrEmpty_Success()
+ {
+ var query = new QueryString();
+ Assert.False(query.HasValue);
+ Assert.Null(query.Value);
+
+ query = new QueryString(null);
+ Assert.False(query.HasValue);
+ Assert.Null(query.Value);
+
+ query = new QueryString(string.Empty);
+ Assert.False(query.HasValue);
+ Assert.Equal(string.Empty, query.Value);
+ }
+
+ [Fact]
+ public void CtorJustAQuestionMark_Success()
+ {
+ var query = new QueryString("?");
+ Assert.True(query.HasValue);
+ Assert.Equal("?", query.Value);
+ }
+
+ [Fact]
+ public void ToString_EncodesHash()
+ {
+ var query = new QueryString("?Hello=Wor#ld");
+ Assert.Equal("?Hello=Wor%23ld", query.ToString());
+ }
+
+ [Theory]
+ [InlineData("name", "value", "?name=value")]
+ [InlineData("na me", "val ue", "?na%20me=val%20ue")]
+ [InlineData("name", "", "?name=")]
+ [InlineData("", "value", "?=value")]
+ [InlineData("", "", "?=")]
+ [InlineData(null, null, "?=")]
+ public void CreateNameValue_Success(string name, string value, string exepcted)
+ {
+ var query = QueryString.Create(name, value);
+ Assert.Equal(exepcted, query.Value);
+ }
+
+ [Fact]
+ public void CreateFromList_Success()
+ {
+ var query = QueryString.Create(new[]
+ {
+ new KeyValuePair("key1", "value1"),
+ new KeyValuePair("key2", "value2"),
+ new KeyValuePair("key3", "value3"),
+ });
+ Assert.Equal("?key1=value1&key2=value2&key3=value3", query.Value);
+ }
+
+ [Theory]
+ [InlineData(null, null, null)]
+ [InlineData("", "", "")]
+ [InlineData(null, "?name2=value2", "?name2=value2")]
+ [InlineData("", "?name2=value2", "?name2=value2")]
+ [InlineData("?", "?name2=value2", "?name2=value2")]
+ [InlineData("?name1=value1", null, "?name1=value1")]
+ [InlineData("?name1=value1", "", "?name1=value1")]
+ [InlineData("?name1=value1", "?", "?name1=value1")]
+ [InlineData("?name1=value1", "?name2=value2", "?name1=value1&name2=value2")]
+ public void AddQueryString_Success(string query1, string query2, string expected)
+ {
+ var q1 = new QueryString(query1);
+ var q2 = new QueryString(query2);
+ Assert.Equal(expected, q1.Add(q2).Value);
+ Assert.Equal(expected, (q1 + q2).Value);
+ }
+
+ [Theory]
+ [InlineData(null, null, null, "?=")]
+ [InlineData("", "", "", "?=")]
+ [InlineData("?", "", "", "?=")]
+ [InlineData("?", "name2", "value2", "?name2=value2")]
+ [InlineData("?name1=value1", "name2", "value2", "?name1=value1&name2=value2")]
+ [InlineData("?name1=value1", "na me2", "val ue2", "?name1=value1&na%20me2=val%20ue2")]
+ [InlineData("?name1=value1", "", "", "?name1=value1&=")]
+ public void AddNameValue_Success(string query1, string name2, string value2, string expected)
+ {
+ var q1 = new QueryString(query1);
+ var q2 = q1.Add(name2, value2);
+ Assert.Equal(expected, q2.Value);
+ }
+ }
+}