在 AspNet Core 中解析空查询参数

ibe*_*dev 5 c# url http query-parameters asp.net-core

如何在 AspNet Core 中处理空查询参数?

假设我们有一个查询 ?key1=foo1&key1=foo2&key2=&key3=null

在解析它时,我希望在解析这个 URL 时有某种 Dictionary> 结果,例如:

  • key1 : ["foo1", "foo2"] 这应该是同一个键下的多个值
  • key2 : [""] 这应该是一个空字符串
  • key3 : ["null"] 这应该是一个字符串,据我所知,URL 中的 null 只是一个文字

我的问题是:我应该如何处理空查询参数?

注意:我不能简单地定义查询参数并假设不存在的查询参数为空。但我认为如果需要,应该将 null 视为显式查询参数中的有效值。

根据这个线程:How to send NULL in HTTP query string? 标准是传递编码的空值:见https://www.w3schools.com/tags/ref_urlencode.asp

所以如果我想传递一个空值,我应该这样做: ?key1=foo1&key1=foo2&key2=&key3=%00

问题是我不知道如何解码它以便将 %00 解析为空值。

我尝试了以下方法:

public Dictionary<string, List<string>> CreateFromQuery(string query)
{
    if (query == null)
    {
        return new Dictionary<string, List<string>>();
    }

    var queryDictionary = Microsoft.AspNetCore.WebUtilities.QueryHelpers.ParseQuery(query);

    var result = queryDictionary.ToDictionary(kv => kv.Key, kv => kv.Value.ToList());
    return result;
}
Run Code Online (Sandbox Code Playgroud)

但是%00被转换成一个"\0"字符串,而不是一个null.

做一个var decodedQuery= HttpUtility.UrlDecode(query);before 似乎也没有任何区别。

UPDATE1:在 Kacper 和 Chris Pratt 发表评论(谢谢你们)之后,我现在采用了 Kacper 的第二个建议,因为我认为让请求者想要区分空查询参数、空查询参数和不存在的查询参数的场景很有趣。

所以这是我目前的实现:

public class QueryParserFactory
    : IQueryParseable
{
    public Dictionary<string, List<string>> CreateFromQuery(string query)
    {
        if (query == null)
        {
            return new Dictionary<string, List<string>>();
        }

        var queryDecoded = HttpUtility.UrlDecode(query);

        var queryDictionary = QueryHelpers.ParseQuery(queryDecoded);

        var result = queryDictionary
            .ToDictionary(
                kv => kv.Key,
                kv => kv.Value.Select(s => s == "\0" ? null : s).ToList());
        return result;
    }
}
Run Code Online (Sandbox Code Playgroud)

如果有人感兴趣,下面是我能想到的所有单元测试

public static class CreateFromQueryTests
{
    public class Given_An_Empty_Query_String_When_Creating_From_A_Query
        : Given_When_Then_Test
    {
        private QueryParserFactory _sut;
        private Dictionary<string, List<string>> _result;

        protected override void Given()
        {
            _sut = new QueryParserFactory();
        }

        protected override void When()
        {
            _result = _sut.CreateFromQuery("");
        }

        [Fact]
        public void Then_It_Should_Return_A_Valid_Result()
        {
            _result.Should().NotBeNull();
        }

        [Fact]
        public void Then_It_Should_Not_Have_Any_Key()
        {
            _result.Keys.Count.Should().Be(0);
        }

        [Fact]
        public void Then_It_Should_Not_Have_Any_Items_In_Dictionary()
        {
            _result.Count.Should().Be(0);
        }
    }

    public class Given_A_Query_String_With_Empty_Values_When_Creating_From_A_Query
        : Given_When_Then_Test
    {
        private QueryParserFactory _sut;
        private Dictionary<string, List<string>> _result;
        private List<string> _expectedValueForKey1;
        private List<string> _expectedValueForKey2;

        protected override void Given()
        {
            _expectedValueForKey1 = new List<string>
            {
                string.Empty
            };

            _expectedValueForKey2 = new List<string>
            {
                string.Empty
            };

            _sut = new QueryParserFactory();
        }

        protected override void When()
        {
            _result = _sut.CreateFromQuery("?key1=&key2=");
        }

        [Fact]
        public void Then_It_Should_Return_A_Valid_Result()
        {
            _result.Should().NotBeNull();
        }

        [Fact]
        public void Then_It_Should_Have_Key_For_All_Fulfilled_Parameters()
        {
            _result.Keys.Count.Should().Be(2);
        }

        [Fact]
        public void Then_It_Should_Have_Empty_Value_For_The_First_Key_Parameter()
        {
            _result["key1"].Should().BeEquivalentTo(_expectedValueForKey1);
        }

        [Fact]
        public void Then_It_Should_Have_Empty_Value_For_The_Second_Key_Parameter()
        {
            _result["key2"].Should().BeEquivalentTo(_expectedValueForKey2);
        }
    }

    public class Given_A_Query_String_With_Single_Values_When_Creating_From_A_Query
        : Given_When_Then_Test
    {
        private QueryParserFactory _sut;
        private Dictionary<string, List<string>> _result;
        private List<string> _expectedValueForKey1;
        private List<string> _expectedValueForKey2;

        protected override void Given()
        {
            _expectedValueForKey1 = new List<string>()
            {
                "value1"
            };

            _expectedValueForKey2 = new List<string>()
            {
                "value2"
            };

            _sut = new QueryParserFactory();
        }

        protected override void When()
        {
            _result = _sut.CreateFromQuery("?key1=value1&key2=value2");
        }

        [Fact]
        public void Then_It_Should_Return_A_Valid_Result()
        {
            _result.Should().NotBeNull();
        }

        [Fact]
        public void Then_It_Should_Have_Key_For_All_Fulfilled_Parameters()
        {
            _result.Keys.Count.Should().Be(2);
        }

        [Fact]
        public void Then_It_Should_Have_The_Correct_Multiple_Values_For_Keys_With_Multiple_Parameters()
        {
            _result["key1"].Should().BeEquivalentTo(_expectedValueForKey1);
        }

        [Fact]
        public void Then_It_Should_Have_The_Correct_Single_Value_For_Keys_With_One_Parameter()
        {
            _result["key2"].Should().BeEquivalentTo(_expectedValueForKey2);
        }

        [Fact]
        public void Then_It_Should_Not_Have_Entries_For_Inexistent_Parameters()
        {
            _result.TryGetValue("key3", out List<string> _).Should().BeFalse();
        }
    }

    public class Given_A_Query_String_With_Multiple_Values_For_The_Same_Key_When_Creating_From_A_Query
        : Given_When_Then_Test
    {
        private QueryParserFactory _sut;
        private Dictionary<string, List<string>> _result;
        private List<string> _expectedValueForKey1;

        protected override void Given()
        {
            _expectedValueForKey1 = new List<string>()
            {
                "value1",
                "value2",
                "value3"
            };

            _sut = new QueryParserFactory();
        }

        protected override void When()
        {
            _result = _sut.CreateFromQuery("?key1=value1&key1=value2&key1=value3");
        }

        [Fact]
        public void Then_It_Should_Return_A_Valid_Result()
        {
            _result.Should().NotBeNull();
        }

        [Fact]
        public void Then_It_Should_Have_Only_One_Key()
        {
            _result.Keys.Count.Should().Be(1);
        }

        [Fact]
        public void Then_It_Should_Have_The_Correct_Multiple_Values_For_Keys_With_Multiple_Parameters()
        {
            _result["key1"].Should().BeEquivalentTo(_expectedValueForKey1);
        }

        [Fact]
        public void Then_It_Should_Not_Have_Entries_For_Inexistent_Parameters()
        {
            _result.TryGetValue("key2", out List<string> _).Should().BeFalse();
        }
    }

    public class Given_A_Query_String_With_Non_Url_Encoded_Null_Values_When_Creating_From_A_Query
        : Given_When_Then_Test
    {
        private QueryParserFactory _sut;
        private Dictionary<string, List<string>> _result;
        private List<string> _expectedValueForKey1;
        private List<string> _expectedValueForKey2;

        protected override void Given()
        {
            _expectedValueForKey1 = new List<string>()
            {
                "null"
            };

            _expectedValueForKey2 = new List<string>()
            {
                "null"
            };

            _sut = new QueryParserFactory();
        }

        protected override void When()
        {
            _result = _sut.CreateFromQuery("?key1=null&key2=null");
        }

        [Fact]
        public void Then_It_Should_Return_A_Valid_Result()
        {
            _result.Should().NotBeNull();
        }

        [Fact]
        public void Then_It_Should_Have_Key_For_All_Fulfilled_Parameters()
        {
            _result.Keys.Count.Should().Be(2);
        }

        [Fact]
        public void Then_It_Should_Have_A_Null_Literal_For_The_First_Parameter()
        {
            _result["key1"].Should().BeEquivalentTo(_expectedValueForKey1);
        }

        [Fact]
        public void Then_It_Should_Have_A_Null_Literal_For_The_Second_Parameter()
        {
            _result["key2"].Should().BeEquivalentTo(_expectedValueForKey2);
        }

        [Fact]
        public void Then_It_Should_Not_Have_Entries_For_Inexistent_Parameters()
        {
            _result.TryGetValue("key3", out List<string> _).Should().BeFalse();
        }
    }

    public class Given_A_Query_String_With_Url_Encoded_Null_Values_When_Creating_From_A_Query
        : Given_When_Then_Test
    {
        private QueryParserFactory _sut;
        private Dictionary<string, List<string>> _result;
        private List<string> _expectedValueForKey1;
        private List<string> _expectedValueForKey2;

        protected override void Given()
        {
            _expectedValueForKey1 = new List<string>()
            {
                null
            };

            _expectedValueForKey2 = new List<string>()
            {
                null
            };

            _sut = new QueryParserFactory();
        }

        protected override void When()
        {
            _result = _sut.CreateFromQuery("?key1=%00&key2=%00");
        }

        [Fact]
        public void Then_It_Should_Return_A_Valid_Result()
        {
            _result.Should().NotBeNull();
        }

        [Fact]
        public void Then_It_Should_Have_Key_For_All_Fulfilled_Parameters()
        {
            _result.Keys.Count.Should().Be(2);
        }

        [Fact]
        public void Then_It_Should_Have_A_Null_Literal_For_The_First_Parameter()
        {
            _result["key1"].Should().BeEquivalentTo(_expectedValueForKey1);
        }

        [Fact]
        public void Then_It_Should_Have_A_Null_Literal_For_The_Second_Parameter()
        {
            _result["key2"].Should().BeEquivalentTo(_expectedValueForKey2);
        }

        [Fact]
        public void Then_It_Should_Not_Have_Entries_For_Inexistent_Parameters()
        {
            _result.TryGetValue("key3", out List<string> _).Should().BeFalse();
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

Kac*_*per 2

我找不到任何内置的东西来制作它。所以我有两个选择,具体取决于什么适合你。

1.

public Dictionary<string, List<string>> CreateFromQuery(string query)
{
    if (query == null)
    {
        return new Dictionary<string, List<string>>();
    }

    var queryDictionary = Microsoft.AspNetCore.WebUtilities.QueryHelpers.ParseQuery(query);

    var result = queryDictionary
       .ToDictionary(
           kv => kv.Key, 
           kv => kv.Value.Select(s => s.Trim("\0")).ToList()); //There you will have String.Empty
    return result;
}
Run Code Online (Sandbox Code Playgroud)

2.

public Dictionary<string, List<string>> CreateFromQuery(string query)
{
    if (query == null)
    {
        return new Dictionary<string, List<string>>();
    }

    var queryDictionary = Microsoft.AspNetCore.WebUtilities.QueryHelpers.ParseQuery(query);

    var result = queryDictionary
       .ToDictionary(
           kv => kv.Key, 
           kv => kv.Value.Select(s => s == "\0" ? null : s).ToList()); //There you will have nulls
    return result;
}
Run Code Online (Sandbox Code Playgroud)