Анализировать нулевые параметры запроса в AspNet Core

Как обрабатывать нулевые параметры запроса в AspNet Core?

Допустим, у нас есть запрос ?key1=foo1&key1=foo2&key2=&key3=null

При его синтаксическом анализе я ожидал бы получить какой-то Dictionary> в результате при синтаксическом анализе этого URL-адреса, например:

  • key1: ["foo1", "foo2"] это должно быть несколько значений под одним и тем же ключом
  • key2: [""] это должна быть пустая строка
  • key3: ["null"] это должна быть строка, насколько я знаю, null в URL - это просто литерал

Мой вопрос: как мне обрабатывать нулевые параметры запроса?

Примечание: я не мог просто определить параметр запроса и предположить, что существующие параметры запроса равны нулю. Но я думаю, что null следует рассматривать как допустимое значение в явных параметрах запроса, если это необходимо.

Согласно этой теме: Как отправить NULL в строке запроса HTTP? стандарт - передать закодированное нулевое значение: см. 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;
}

Но %00 преобразуется в строку "\0", а не в null.

Сделать var decodedQuery= HttpUtility.UrlDecode(query); раньше тоже, похоже, не имеет никакого значения.

ОБНОВЛЕНИЕ1: После комментариев Кацпера и Криса Пратта (спасибо, ребята) я пошел со вторым предложением Кацпера, потому что я думаю, что интересно иметь сценарии, в которых запрашивающая сторона хочет различать нулевые параметры запроса, пустые параметры запроса и несуществующие параметры запроса.

Итак, это моя текущая реализация:

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

И если кому-то интересно, ниже приведены все модульные тесты, о которых я могу думать:

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

Наполовину несвязанный вопрос, какой фреймворк для тестирования вы используете? Мне нравится такой синтаксис!

Mattkwish 23.07.2018 15:13

Xunit с moq и fluentAssertions, чтобы сделать утверждения более удобочитаемыми.

diegosasw 23.07.2018 18:10

Кроме того, один файл для каждого метода для тестирования, один класс в этом файле для каждого сценария и столько методов тестирования, сколько утверждений для проверки.

diegosasw 23.07.2018 18:16
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
5
3
2 508
2

Ответы 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;
}

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

Спасибо. Знаете ли вы, правильное ли предположение, что key3=%00 должен быть преобразован в null, а key3=null должен быть преобразован в строковый литерал "null"?

diegosasw 23.07.2018 14:43

Извините, не могли бы вы пояснить? означать преобразование из HTTP-запроса в C# или из cz3 в Http?

Kacper 23.07.2018 14:48

Я имею в виду ... правильно ли предполагать, что параметр нулевого запроса в URL-адресе http get должен быть представлен с помощью% 00 или лучше представить его с помощью ключевого слова NULL в URL-адресе?

diegosasw 23.07.2018 18:14

Способ передать значение null - вообще не передавать значение или полностью исключить ключ, то есть:

?key1=foo1&key1=foo2&key2=&key3=

Или просто:

?key1=foo1&key1=foo2&key2=

Что касается вашего параметра key2, вы должны знать, что нет возможности передать пустую строку. ASP.NET Core будет интерпретировать это как нулевое значение. Если у вас есть строковое свойство, которое на самом деле не должно быть нулевым (т.е. вы хотите, чтобы оно всегда было пустой строкой в ​​таких случаях), вы можете обработать это с помощью специального получателя.

private string key2;
public string Key2
{
    get => key2 ?? string.Empty;
    set => key2 = value;
}

Затем, в тех случаях, когда для него установлено нулевое значение, вместо этого он будет материализоваться как пустая строка.

Спасибо. Проблема в том, что если я не передаю такое значение, как ?key2=, как отличить пустую строку от нулевого значения? Пустая строка может быть допустимым параметром запроса, отличным от нуля. Кроме того, если я полностью исключаю параметр запроса и нет ключа 3, как определить, хочет ли клиент, например, фильтровать по ключу 3 = null, если значение null является допустимым? Вот почему я хотел иметь что-то явное для трех сценариев, где клиент указывает, что параметр равен нулю, или параметр пуст, или параметр отсутствует вообще.

diegosasw 23.07.2018 15:02

Это невозможно. Недостаточно контекста, чтобы определить, действительно ли пользователь намеревался является пустым строковым значением или оставил его пустым (что фактически одно и то же). В результате каждый фреймворк индивидуально определяет, как они будут обрабатывать пустые значения запроса. В случае ASP.NET Core он интерпретируется как null. Однако, несмотря ни на что, у вас не может быть и того, и другого. Например, вы всегда можете создать настраиваемый связыватель моделей, чтобы заставить его интерпретировать вместо этого как пустую строку, но тогда вы больше не сможете получать нулевые значения. Это либо / либо, но не то и другое одновременно.

Chris Pratt 23.07.2018 15:07

Другие вопросы по теме