Как обрабатывать нулевые параметры запроса в AspNet Core?
Допустим, у нас есть запрос
?key1=foo1&key1=foo2&key2=&key3=null
При его синтаксическом анализе я ожидал бы получить какой-то Dictionary> в результате при синтаксическом анализе этого 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();
}
}
}
Xunit с moq и fluentAssertions, чтобы сделать утверждения более удобочитаемыми.
Кроме того, один файл для каждого метода для тестирования, один класс в этом файле для каждого сценария и столько методов тестирования, сколько утверждений для проверки.





Я не могу найти ничего встроенного, чтобы сделать это. Так что у меня есть два варианта в зависимости от того, что вам подойдет.
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"?
Извините, не могли бы вы пояснить? означать преобразование из HTTP-запроса в C# или из cz3 в Http?
Я имею в виду ... правильно ли предполагать, что параметр нулевого запроса в URL-адресе http get должен быть представлен с помощью% 00 или лучше представить его с помощью ключевого слова NULL в URL-адресе?
Способ передать значение 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 является допустимым? Вот почему я хотел иметь что-то явное для трех сценариев, где клиент указывает, что параметр равен нулю, или параметр пуст, или параметр отсутствует вообще.
Это невозможно. Недостаточно контекста, чтобы определить, действительно ли пользователь намеревался является пустым строковым значением или оставил его пустым (что фактически одно и то же). В результате каждый фреймворк индивидуально определяет, как они будут обрабатывать пустые значения запроса. В случае ASP.NET Core он интерпретируется как null. Однако, несмотря ни на что, у вас не может быть и того, и другого. Например, вы всегда можете создать настраиваемый связыватель моделей, чтобы заставить его интерпретировать вместо этого как пустую строку, но тогда вы больше не сможете получать нулевые значения. Это либо / либо, но не то и другое одновременно.
Наполовину несвязанный вопрос, какой фреймворк для тестирования вы используете? Мне нравится такой синтаксис!