Веб-разработка на .NET // dotnet web dev
94 subscribers
1 photo
34 links
Разрабатываю на .NET. В этом канале пишу заметки о программировании в целом и разработке на дотнете в частности

Электропочта: [email protected]
Электросайт: boyarincev.net
Download Telegram
С термином инкапсуляция всё не очень просто - само слово нам мало что о себе сообщает, а его интерпретация в головах разных программистов варьируется в гигантских пределах, хотя сама инкапсуляция считается фундаментом на котором стоит ООП. Эту статью я написал по следам одного спора, как раз по поводу интерпретации термина Инкапсуляция. В ней я привожу разные точки зрения на него и пытаюсь докопаться до смысла заложенного в него при рождении.

https://boyarincev.net/articles/dotnet/what-is-encapsulation/

#misconceptions #oop
Если написав очередной switch/case, вы останавливались и задумывались - а нужно ли вообще обрабатывать default-ветку, а если обрабатывать, то бросать ли исключение, а если бросать то какое, а нормально ли вообще, что у меня эти switch/case по всей кодовой базе дублируются ? - то на эти вопросы я попробовал сформулировать своё мнение в этом тексте:
Какое исключение бросать в swith, если case не нашлось?
На работе у нас есть некое подобие самописной ORM, работающей напрямую с ADO.NET и одна из проблем с которой мы сталкивались при её разработке - это то, что у количества параметров используемых в DbCommand есть лимит, этот лимит накладывается базой данных и у разных баз данных он разный.

Например, у PostgreSQL в каждом SQL statement (под SQL statement имеется в виду то, что в разговорной речи называют SQL запросом) может использоваться не больше 65535 параметров (в одну DbCommand можно отправить множество SQL statement и таким образом в общем DbCommand может содержать больше 65535 параметров).

В большинстве запросов довольно сложно преодолеть разрешённую планку, мы столкнулись с этим ограничением в двух случаях - в Insert, когда за один запрос вставляется множество строк и в запросах с использованием оператора IN.

Первым делом я конечно же полез смотреть как эту проблему решает и решает ли вообще Entity Framework...

Написал небольшую заметку о том, можно ли при использовании Entity Framework столкнуться с ограничениями, которые накладывает использование DbCommand под его капотом: Entity Framework и ограничения DbCommand
Не используйте JObject.Parse() из JSON.NET

Для начала тест:

var json = "{ \"Date\":\"2024-09-14T00:00:00+04:00\"}";
var jsonObject = JObject.Parse(json);

Console.WriteLine(jsonObject.ToString());


Вопрос: Какая дата выведется в консоль ?

Ответ: Дата будет зависеть от вашей локальной таймзоны, от даты которую передают в json и от вашей локальной таблицы переводов времени в ОС, но если ваша локальная таймзона UTC, то скорее всего выведется

{
"Date": "2024-09-13T20:00:00+00:00"
}


Проблема в том, что JObject.Parse() умеет работать только с DateTime, поэтому во время его работы теряется часовой пояс переданного времени.

Вот несколько обсуждений этой проблемы:
https://stackoverflow.com/questions/46829888/how-parse-string-to-jobject-ignoring-time-zone
https://github.com/JamesNK/Newtonsoft.Json/issues/1110

В качестве решения предлагается использовать JsonConvert.DeserializeObject() - что далеко не всегда подходит, так как результат вызова Parse() позволяет манипулировать объектом как json'ом, например, исследовать его и изменять.

Интересно более глубокое раскрытие этой темы ?
Веб-разработка на .NET // dotnet web dev
Не используйте JObject.Parse() из JSON.NET Для начала тест: var json = "{ \"Date\":\"2024-09-14T00:00:00+04:00\"}"; var jsonObject = JObject.Parse(json); Console.WriteLine(jsonObject.ToString()); Вопрос: Какая дата выведется в консоль ? Ответ: Дата будет…
Вместо JObject.Parse(), кстати, можно использовать JOjbect.Load() - который Parse внутреннее и использует, но только в Load уже можно передать настроенный на парсинг DateTimeOffset JsonReader.
Вот текущая реализация Parse:

        public new static JObject Parse(string json, JsonLoadSettings? settings)
{
using (JsonReader reader = new JsonTextReader(new StringReader(json)))
{
JObject o = Load(reader, settings);

while (reader.Read())
{
// Any content encountered here other than a comment will throw in the reader.
}

return o;
}
}


А вот такой будет исправленная версия, поддерживающая парсинг в DateTimeOffset:

static JObject ParseWithDateTimeOffset(string json, JsonLoadSettings? settings)
{
using (JsonReader reader = new JsonTextReader(new StringReader(json)))
{
reader.DateParseHandling = DateParseHandling.DateTimeOffset;
JObject o = JObject.Load(reader, settings);

while (reader.Read())
{
// Any content encountered here other than a comment will throw in the reader.
}

return o;
}
}


И тогда результат выполнения кода:
var json = "{ \"Date\":\"2024-09-14T00:00:00+04:00\"}";
var jsonObject = ParseWithDateTimeOffset(json);

Console.WriteLine(jsonObject.ToString());


Будет:
{
"Date": "2024-09-14T00:00:00+04:00"
}
Библиотека, которая генерирует запрос для curl по HttpRequest - HttpClientToCurlGenerator

string curlScript = httpClient.GenerateCurlInString(httpRequestMessage);


Очень часто при проблемах с http-запросами нужно попробовать воспроизвести этот запрос вне приложения или передать его другой команде для исследования - что всегда геморрой, и в этом случае получить сразу готовый запрос для курла - бесценно.

Можно, например, прикрутить её вызов к обработке всех неуспешных статус кодов ответов и писать curl-запрос сразу в логи, единственный момент который в этом случае нужно учесть - это появление в логах логинов, паролей и прочих токенов, но это всё решаемо.
Сегодня начнём про приём дат во входящих запросах ASP.NET - в частности, про приём дат в json-теле запроса.

За привязку моделей из тела запроса у нас отвечают Input formatters.
Из коробки за привязку json отвечает SystemTextJsonInputFormatter

Он в свою очередь использует System.Text.Json, поэтому все особенности обработки дат, будут специфичны для этого сериализатора.

Есть статья описывающая как System.Text.Json работает с датами - DateTime and DateTimeOffset support in System.Text.Json, но она рассказывает далеко не о всех особенностях десериализации, поэтому будем подсматривать в исходном коде.

Начнём с десериализации дат из тела запроса в поля с типом DateTime в моделях.

Дата поддерживается в формате ISO 8601-1:2019

Нас интересует как десериализатор обработает вот такие даты:

"2024-02-08T00:00:00+00:00"
"2024-02-08T00:00:00+01:00"
"2024-02-08T00:00:00:10Z"
"2024-02-08T00:00:00"
"2024-02-08"

В исходном коде сначала выходим на TryGetDateTime в Utf8JsonReader а затем на JsonHelper.TryParseAsISO()

Правила следующие:

- Если есть offset, то дата создаётся c Kind Local и приводится к локальной дате согласно текущему часовому поясу системы.
- Если Z - то дата создаётся с Kind UTC
- В остальных случаях Kind Unspecified и время из присланной даты или 00:00:00, если времени не было

В первом случае с offset возможна потеря дня из-за часовых поясов.

    var jsonWithOffset0 = "{ \"Date\":\"2024-02-08T00:00:00+00:00\"}";
var jsonWithOffset1 = "{ \"Date\":\"2024-02-08T00:00:00+01:00\"}";
var jsonZ = "{ \"Date\":\"2024-02-08T00:00:00Z\"}";
var jsonDateTime = "{ \"Date\":\"2024-02-08T00:00:00\"}";
var jsonOnlyDate = "{ \"Date\":\"2024-02-08\"}";

Console.WriteLine($"Local TimeZone: {TimeZoneInfo.Local}");

var resultJsonWithOffset0 = System.Text.Json.JsonSerializer.Deserialize<DateTimeModel>(jsonWithOffset0);
Console.WriteLine(GetDateTimeString(resultJsonWithOffset0.Date));

var resultJsonWithOffset1 = System.Text.Json.JsonSerializer.Deserialize<DateTimeModel>(jsonWithOffset1);
Console.WriteLine(GetDateTimeString(resultJsonWithOffset1.Date));

var resultJsonZ = System.Text.Json.JsonSerializer.Deserialize<DateTimeModel>(jsonZ);
Console.WriteLine(GetDateTimeString(resultJsonZ.Date));

var resultJsonDateTime = System.Text.Json.JsonSerializer.Deserialize<DateTimeModel>(jsonDateTime);
Console.WriteLine(GetDateTimeString(resultJsonDateTime.Date));

var resultJsonOnlyDate = System.Text.Json.JsonSerializer.Deserialize<DateTimeModel>(jsonOnlyDate);
Console.WriteLine(GetDateTimeString(resultJsonOnlyDate.Date));

static string GetDateTimeString(DateTime dateTime, string json) => $"{dateTime} {dateTime.Kind} ({json})";

class DateTimeModel
{
public DateTime Date { get; set; }
}


Вывод:

Local TimeZone: (UTC) Время в формате UTC
08.02.2024 0:00:00 Local ({ "Date":"2024-02-08T00:00:00+00:00"})
07.02.2024 23:00:00 Local ({ "Date":"2024-02-08T00:00:00+01:00"})
08.02.2024 0:00:00 Utc ({ "Date":"2024-02-08T00:00:00Z"})
08.02.2024 0:00:00 Unspecified ({ "Date":"2024-02-08T00:00:00"})
08.02.2024 0:00:00 Unspecified ({ "Date":"2024-02-08"})


Во втором примере - 2024-02-08T00:00:00+01:00 при локальном часовом поясе +0 (время в UTC) мы во входящем запросе будем получать дату 07.02.2024 23:00:00

@boyarincevdotnet
Please open Telegram to view this post
VIEW IN TELEGRAM
Продолжим про десериализацию во входящих запросах ASP.NET - в частности, про приём дат в json-теле запроса.

Первая часть (https://teleg.eu/boyarincevdotnet/41) где мы посмотрели как десериализуется DateTime.

Сегодня посмотрим как десериализатор отработает по нашему тестовому набору дат:

"2024-02-08T00:00:00+00:00"
"2024-02-08T00:00:00+01:00"
"2024-02-08T00:00:00:10Z"
"2024-02-08"


Если целевой тип будет DateTimeOffse:

using System.Text.Json;
using System.Runtime.InteropServices;

var jsonWithOffset0 = "{ \"Date\":\"2024-02-08T00:00:00+00:00\"}";
var jsonWithOffset3 = "{ \"Date\":\"2024-02-08T00:00:00+03:00\"}";
var jsonZ = "{ \"Date\":\"2024-02-08T00:00:00Z\"}";
var jsonDateTime = "{ \"Date\":\"2024-02-08T00:00:00\"}";
var jsonOnlyDate = "{ \"Date\":\"2024-02-08\"}";

Console.WriteLine($"{RuntimeInformation.OSDescription}");

Console.WriteLine($"Local TimeZone: {TimeZoneInfo.Local}");

var resultJsonWithOffset0 = JsonSerializer.Deserialize<DateTimeOffsetModel>(jsonWithOffset0);
Console.WriteLine(GetDateTimeOffsetString(resultJsonWithOffset0.Date, jsonWithOffset0));

var resultJsonWithOffset3 = JsonSerializer.Deserialize<DateTimeOffsetModel>(jsonWithOffset3);
Console.WriteLine(GetDateTimeOffsetString(resultJsonWithOffset3.Date, jsonWithOffset3));

var resultJsonZ = JsonSerializer.Deserialize<DateTimeOffsetModel>(jsonZ);
Console.WriteLine(GetDateTimeOffsetString(resultJsonZ.Date, jsonZ));

var resultJsonDateTime = JsonSerializer.Deserialize<DateTimeOffsetModel>(jsonDateTime);
Console.WriteLine(GetDateTimeOffsetString(resultJsonDateTime.Date, jsonDateTime));

var resultJsonOnlyDate = JsonSerializer.Deserialize<DateTimeOffsetModel>(jsonOnlyDate);
Console.WriteLine(GetDateTimeOffsetString(resultJsonOnlyDate.Date, jsonOnlyDate));

string GetDateTimeOffsetString(DateTimeOffset dateTime, string json) =>
$"{dateTime} {dateTime.DateTime.Kind} ({json})";

class DateTimeOffsetModel
{
public DateTimeOffset Date { get; set; }
}


Результат:

Microsoft Windows 10.0.22621
Local TimeZone: (UTC) Время в формате UTC
08.02.2024 0:00:00 +00:00 Unspecified ({ "Date":"2024-02-08T00:00:00+00:00"})
08.02.2024 0:00:00 +01:00 Unspecified ({ "Date":"2024-02-08T00:00:00+01:00"})
08.02.2024 0:00:00 +00:00 Unspecified ({ "Date":"2024-02-08T00:00:00Z"})
08.02.2024 0:00:00 +00:00 Unspecified ({ "Date":"2024-02-08T00:00:00"})
08.02.2024 0:00:00 +00:00 Unspecified ({ "Date":"2024-02-08"})


Тут без сюрпризов - как мне кажется, мы получаем всё ровно так как и ожидаем.

Реализация в коде System.Text.Json
Please open Telegram to view this post
VIEW IN TELEGRAM
Десериализация DateOnly в json-теле запроса.

Поддержку DateOnly и TimeOnly добавили в System.Text.Json в .NET 7 (https://github.com/dotnet/runtime/issues/53539)

Реализацию можно посмотреть в DateOnlyConverter

Так как поддерживается только дата в формате yyyy-MM-dd, то проверять мы только её и будем:
    var jsonOnlyDate = "{ \"Date\":\"2024-02-08\"}";

Console.WriteLine($"{RuntimeInformation.OSDescription}");
Console.WriteLine($"Local TimeZone: {TimeZoneInfo.Local}");

var resultJsonOnlyDate = JsonSerializer.Deserialize<DateOnlyModel>(jsonOnlyDate);
Console.WriteLine(GetDateOnlyString(resultJsonOnlyDate.Date, jsonOnlyDate));

string GetDateOnlyString(DateOnly date, string json) => $"{date} ({json})";

public class DateOnlyModel
{
public DateOnly Date { get; set; }
}

И результат выполнения:
Microsoft Windows 10.0.22621
Local TimeZone: (UTC+03:00) Москва, Санкт-Петербург
08.02.2024 ({ "Date":"2024-02-08"})


Тут особо ничего не прокомментируешь - максимально прозрачное и ожидаемое поведение, единственной проблемой может быть необходимость использования .NET 7.
Best Bluetooth Speaker Sound Quality