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); + } + } +}