Опытный разработчик не так давно зашёл в .Net и поставил цель получить сертификат Microsoft. Свой ежедневный прогресс он описывает на канале .Net Разработчик. Заметки об изученном материале, советы по повышению производительности и поддержке мотивации, ин
builder.Services.AddAuthorization(opts =>
{
opts.AddPolicy("authenticated", p =>
p.RequireAuthenticatedUser());
});
Затем вызвать UseAuthentication и UseAuthorization. Важно это сделать до вызова MapReverseProxy.
app.UseAuthentication(); app.UseAuthorization(); app.MapReverseProxy();Теперь только остаётся добавить раздел AuthorizationPolicy в конфигурацию прокси:
"users-route": {
"ClusterId": "users-cluster",
"AuthorizationPolicy": "authenticated",
"Match": {
…
}
}
YARP может пересылать большинство типов данных авторизации (cookie, bearer-токены) в защищённые сервисы, поскольку может быть важно по-разному идентифицировать пользователя в отдельных микросервисах.
2. Добавление ограничений
API-шлюз можно использовать для ограничений в системе. Это метод ограничения количества запросов к вашему API для повышения безопасности и снижения нагрузки на серверы. YARP поддерживает механизм ограничений, добавленный в .NET 7.
Для этого надо определить политику ограничения скорости:
builder.Services.AddRateLimiter(opts =>
{
opts.AddFixedWindowLimiter("fixed", o =>
{
o.Window = TimeSpan.FromSeconds(10);
o.PermitLimit = 5;
});
});
Затем нужно вызвать UseRateLimiter перед вызовом MapReverseProxy.
app.UseRateLimiter(); app.MapReverseProxy();И добавить политику ограничений в раздел RateLimiterPolicy конфигурации:
"products-route": {
"ClusterId": "products-cluster",
"RateLimiterPolicy": "fixed",
"Match": {
…
}
}
Итого
API-шлюз — критически важный компонент надёжной реализации системы микросервисов. И YARP — отличный вариант, если вы хотите создать его в .NET. Полный код примера реализации API-шлюза с помощью YARP вы можете найти здесь.
Подробнее об использовании YARP можно почитать в документации.
Источник: https://www.milanjovanovic.tech/blog/implementing-an-api-gateway-for-microservices-with-yarpInstall-Package Yarp.ReverseProxyнеобходимо вызвать: - AddReverseProxy для добавления необходимых сервисов YARP, - LoadFromConfig для загрузки конфигурации из настроек приложения, - MapReverseProxy для внедрения промежуточного ПО обратного прокси.
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddReverseProxy()
.LoadFromConfig(
builder.Configuration
.GetSection("ReverseProxy"));
var app = builder.Build();
app.MapReverseProxy();
app.Run();
Нужно сообщить YARP, как направлять входящие запросы к отдельным микросервисам. YARP использует концепцию маршрутов для описания шаблонов запросов для прокси-сервера и кластеров для описания сервисов, к которым надо перенаправить эти запросы.
Вот пример конфигурации YARP. В системе есть две службы, Users.Api и Products.Api, которые являются приложениями .NET 7. Если запрос соответствует /users-service/{**catch-all}, например, /users-service/users, он будет перенаправлен в кластер пользователей. Та же логика применима к кластеру продуктов. Мы можем применить более сложные преобразования в разделе Transforms.
{
"ReverseProxy": {
"Routes": {
"users-route": {
"ClusterId": "users-cluster",
"Match": {
"Path": "/users-service/{**catch-all}"
},
"Transforms": [
{ "PathPattern": "{**catch-all}" }
]
},
"products-route": {
"ClusterId": "products-cluster",
"Match": {
"Path": "/products-service/{**catch-all}"
},
"Transforms": [
{ "PathPattern": "{**catch-all}" }
]
}
},
"Clusters": {
"users-cluster": {
"Destinations": {
"destination1": {
"Address": "https://localhost:5201/"
}
}
},
"products-cluster": {
"Destinations": {
"destination1": {
"Address": "https://localhost:5101/"
}
}
}
}
}
}
Окончание следует…
Источник: https://www.milanjovanovic.tech/blog/implementing-an-api-gateway-for-microservices-with-yarp<Counter @rendermode="@RenderMode.Server" />5. Интерактивный рендеринг в Blazor WebAssembly Чтобы включить поддержку режима рендеринга WebAssembly в проекте Blazor, добавьте связанные службы, вызвав
app.Services.AddRazorComponents().AddWebAssemblyComponents(), и режим рендеринга WebAssembly, вызвав app.MapRazorComponents<App>().AddWebAssemblyRenderMode(). Любые компоненты, которые вы хотите визуализировать в WebAssembly, должны быть загружены вместе со всеми их зависимостями в браузер. Вам потребуется настроить отдельный проект Blazor WebAssembly для создания любого кода, специфичного для WebAssembly, и ссылаться на него из приложения Blazor.
Вы можете указать режим интерактивной отрисовки WebAssembly для компонента, добавив атрибут [RenderModeWebAssembly] в определение компонента или указав @rendermode="@RenderMode.WebAssembly" в экземпляре компонента. Компоненты, которые вы настроили для рендеринга в WebAssembly, также будут предварительно визуализированы с сервера по умолчанию, поэтому обязательно либо создайте свои компоненты, чтобы они правильно отображались в любой среде, либо отключите предварительный рендеринг при указании режима рендеринга: [RenderModeWebAssembly(prerender: false)] или @rendermode="@(new WebAssemblyRenderMode(prerender: false)).
Вот пример, демонстрирующий, как настроить интерактивность на основе WebAssembly для компонента Counter, отображаемого на странице Index.
В настоящее время существует ограничение, при котором компоненты с маршрутами должны быть определены в той же сборке, что и компонент приложения, переданный в MapRazorComponents<App>(), поэтому в настоящее время их нельзя определить в клиентской сборке. Это будет исправлено в будущем обновлении.
Источник: https://devblogs.microsoft.com/dotnet/asp-net-core-updates-in-dotnet-8-preview-6/name: sample
on:
push:
branches:
- '*'
pull_request:
branches:
- '*'
concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true
Здесь:
- github.workflow – имя рабочего процесса,
- github.event.pull_request.number || github.ref – номер пул-реквеста или имя ветки, если это не пул-реквест,
- cancel-in-progress отменяет рабочий процесс, если новый рабочий процесс с тем же именем запущен в той же группе.
Подробнее о выражениях в GitHub Actions
Подробнее о переменных в GitHub Actions
Если вы хотите отменять незавершённые запуски только для веток, отличных от main, вы можете использовать следующую конфигурацию:
concurrency:
group: ${{ github.workflow }}-${{ github.ref == 'refs/heads/main' && github.run_id || github.event.pull_request.number || github.ref }}
cancel-in-progress: true
Здесь:
- используем github.run_id ветки main,
- используем github.event.pull_request.number пул-реквестов, т.к. номер уникален для каждого пул-реквеста,
- используем github.ref на других ветках, т.к. он уникален для каждой ветки.
Вместо жесткого кодирования ветки main вы можете использовать github.ref_protected, чтобы избежать отмены заданий в защищённых ветках.
Источник: https://www.meziantou.net/how-to-cancel-github-workflows-when-pushing-new-commits-on-a-branch.htminternal class NameOf
{
public string S { get; } = "";
public static int StaticField;
public string NameOfLength { get; }
= nameof(S.Length);
public static void NameOfExamples()
{
Console.WriteLine(nameof(S.Length));
Console.WriteLine(nameof(StaticField.MinValue));
}
[Description($"String {nameof(S.Length)}")]
public int StringLength(string s)
{ return s.Length; }
}
2. Встроенные массивы
Атрибут InlineArrayAttribute идентифицирует тип, который можно рассматривать как непрерывную последовательность примитивов для эффективных, типобезопасных, защищённых от переполнения индексируемых/разрезаемых встроенных данных. Библиотеки .NET повысят производительность приложений и инструментов, используя встроенные массивы.
Компилятор создаёт другой IL для доступа к встроенным массивам. Это приводит к некоторым ограничениям, таким как отсутствие поддержки шаблонов списков. Но в большинстве случаев вы сможете получить доступ к встроенным массивам так же, как и к обычным. Также другой IL обеспечивает прирост производительности без изменения вашего кода:
private static void InlineArrayAccess(
Buffer10<int> arr)
{
for (int i = 0; i < 10; i++)
{
arr[i] = i * i;
}
foreach (int i in arr)
{
Console.WriteLine(i);
}
}
В большинстве случаев вы будете потреблять встроенные массивы, а не создавать их. Встроенные массивы работают быстро, потому что они основаны на точном расположении данных заданной длины. Встроенный массив — это тип с одним полем, помеченный атрибутом InlineArrayAttribute, указывающим длину массива. В типе, использованном в предыдущем примере, среда выполнения создает хранилище ровно для десяти элементов в Buffer10<T> благодаря параметру атрибута:
[System.Runtime.CompilerServices.InlineArray(10)]
public struct Buffer10<T>
{
private T _element0;
}
3. Перехватчики
Перехватчики позволяют перенаправлять вызовы определённых методов в другой код. При этом атрибут перехватчика указывает фактическое расположение исходного кода, который надо перехватить. Во время выполнения код с указанного в атрибуте места переходит в код перехватчика и исполняет его вместо исходного метода. По задумке перехватчики предназначаются для генераторов кода. Это экспериментальная функция, и она может быть изменена или удалена в будущей версии, поэтому не может использоваться в производственном коде. У Ника Чапсаса недавно вышло видео на тему перехватчиков.
Источник: https://devblogs.microsoft.com/dotnet/new-csharp-12-preview-features/public interface ISystemClock
{
DateTimeOffset UtcNow { get; }
}
Аналогичный подход использовался внутри Microsoft, где один и тот же код был добавлен как минимум в четыре различных области .NET.
В .NET 8 были добавлены долгожданные абстракции времени: абстрактный класс TimeProvider и интерфейс ITimer. Они не безупречны, но это значительный прогресс.
Недостатки TimeProvider
1. Абстрактный класс получился громоздким. Не зная внутренних деталей класса, зависящего от TimeProvider, невозможно решить, какие его члены надо имитировать: GetUtcNow(), GetLocalNow(), CreateTimer(...) или все сразу. Разработчики предложили разбить новый тип на небольшие интерфейсы, но идея была отвергнута.
2. Экземпляр TimeProvider можно создать с помощью статического члена TimeProvider.System. Его легко использовать, хотя он не сильно отличается от DateTime.Now. В дальнейшем это приведёт к проблемам с написанием юнит-тестов, поэтому ожидается, что разработчики будут внедрять абстрактный TimeProvider в свои классы.
Преимущества TimeProvider и ITimer
1. Юнит-тесты сервисов, зависящих от времени, стали более универсальными. TimeProvider был добавлен в BCL и поддерживается в широком спектре сред выполнения .NET.
2. Команда Microsoft исправила в новой реализации старую ошибку, DateTime.Now было ошибочно введено как свойство вместо функции DateTime.Now(), поскольку по рекомендации Microsoft свойства не должны иметь побочных эффектов. TimeProvider правильно использует функции и методы: GetUtcNow(), GetLocalNow(), GetTimestamp() и т. д.
3. Можно тестировать события временных рядов с помощью функций TimeProvider.CreateTimer(...) и Timer.Change(...). Это особенно важно для вызовов функций Task.Delay(...) и Task.WaitAsync(...), которые теперь также принимают аргумент TimeProvider.
4. Планируется создать FakeTimeProvider как часть .NET для дальнейшего упрощения юнит-тестирования. Возможно, тогда пункт 2 из недостатков потеряет актуальность.
Стивен Тауб, инженер-программист Microsoft, надеется, что «в будущем почти никто не будет использовать ничего, кроме TimeProvider.System, в производственной среде. В отличие от многих других абстракций, эта особенная: она существует исключительно для возможности тестирования.»
Итого
Добавление класса TimeProvider в 4м превью .NET 8 определяет стандартизированную и унифицированную абстракцию для управления временем. Хотя он имеет ряд незначительных недостатков, команды Microsoft уже пометили свои интерфейсы времени ISystemClock как устаревшие и теперь выступают за внедрение TimeProvider.
Источник: https://www.infoq.com/articles/dotnet-unit-tests-time-timezone/var nowInNY = DateTime.Now;
var nyTZ = TimeZoneInfo
.FindSystemTimeZoneById("Eastern Standard Time");
nowInNY = DateTime
.SpecifyKind(nowInNY, DateTimeKind.Unspecified);
var utc = TimeZoneInfo
.ConvertTimeToUtc(nowInNY, nyTZ);
Альтернативным решением будет создавать и хранить дату только в UTC:
var utc = DateTime.UtcNow;и преобразовывать её в локальную:
var localTime = utc.ToLocalTime();или в нужный часовой пояс:
var nyTime = TimeZoneInfo .ConvertTimeFromUtc(utc, nyTZ);Это требует дополнительных проверок на предмет случайного использования DateTime с локальной инициализацией через DateTime.Now, а также не исключает таких нюансов, как изменение правил перехода на летнее время. В .NET 2 была введена структура DateTimeOffset. Она состоит из: - структуры DateTime - свойства Offset, хранящего смещение относительно UTC. Однако проблема с серверами в разных часовых поясах никак не решается через DateTimeOffset.Now. Всё дело в переходе на летнее время. Для разных дат смещение в одном и том же часовом поясе будет разным. Кроме того, правила перехода на летнее время могут меняться (нам ли в РФ не знать). А значит невозможно определить происхождение даты и времени, просто взглянув на значение смещения. Поэтому для международного ПО лучше хранить часовой пояс для возможного пересчёта смещения по новым правилам. Для этого подходит класс TimeZoneInfo. Окончание следует… Источник: https://www.infoq.com/articles/dotnet-unit-tests-time-timezone/
var users = GetAllUsers();
// Вариант 1
users.Where(u => u.IsStudent)
.Select(u =>
new { u.FirstName, u.LastName, u.IsStudent });
// Вариант 2
users.Select(u =>
new { u.FirstName, u.LastName, u.IsStudent })
.Where(u => u.IsStudent);
Результат обоих запросов будет одинаковым. Но это не означает, что они делают одно и то же. Первый вариант сначала отфильтрует всех пользователей, которые не являются студентами, а затем создаст анонимный тип. Второй вариант сначала создаст анонимный тип для всех пользователей, а затем отфильтрует тех, кто не является студентами. Какой запрос быстрее?
Скорее всего, первый, т.к. второй вариант создаст анонимный тип для всех пользователей, даже для тех, кто не является студентом. Т.е. второму варианту придётся проделать больше работы. Для больших списков разница может быть значительна. Как правило, всегда нужно пытаться отфильтровать как можно больше, прежде чем начинать создавать новые объекты. Аналогично с OrderBy. Если список предварительно не фильтруется, OrderBy должен будет проверить гораздо больше записей.
А теперь рассмотрим такой пример:
// вариант 1
users.Where(u => u.IsStudent && u.Age > 30);
// вариант 2
users.Where(u => u.IsStudent)
.Where(u => u.Age > 30);
Опять же семантически они делают одно и то же. Результат будет одинаковым, но способ его достижения - разным. Первый вариант сразу проверит оба условия. Второй вариант (хотя это и не очевидно) также пройдёт по списку только один раз, но здесь возникнут дополнительные накладные расходы на вызов нескольких функций. Кстати, автор оригинальной статьи (см. источник ниже) считает, что этими расходами можно пренебречь. Однако, судя по моим бенчмаркам (см. картинку ниже) разница в некоторых средах может быть довольно существенной (хотя, в .NET 8 выполнение заметно оптимизировали).
Если всё же не обойтись без нескольких предложений Where, имеет смысл поставить лучший фильтр в начале, либо попробовать реорганизовать все фильтры в один метод:
private bool IsStudentAndOlderThan30(User user)
{
return user.IsStudent && user.Age > 30;
}
users.Where(IsStudentAndOlderThan30);
Источник: https://steven-giesel.com/blogPost/57ed9867-4afd-4d02-9f35-e0941bc6f715<a href="/demo" ping="/ping">Demo</a>Вы можете обработать этот ping-запрос в вашем приложении ASP.NET Core application. Например:
app.MapPost("/ping",
([FromHeader(Name = "ping-from")]string from,
[FromHeader(Name = "ping-to")] string to) =>
{
// обрабатываем ping-запрос
return Results.Ok();
});
В примере выше заголовки ping-from и ping-to выдают исходный URL и запрашиваемый URL (указанный в атрибуте href) соответственно.
Если вам нужно различить несколько ссылок на одной странице, можно использовать параметр строки запроса. Например, если у вас есть две ссылки на одной странице, вы можете использовать <a ping="/ping?id=Link1"> и <a ping="/ping?id=Link2">, чтобы различать их.
Источник: https://www.meziantou.net/tracking-click-on-anchors-in-an-html-page.htmSELECT customers.name, COUNT(order_id) as Total_orders, SUM(order_amount) as total_spent FROM customers JOIN orders ON customers.id = orders.customer_id WHERE order_date >= '2023-01-01' GROUP BY customers.name HAVING total_spent >= 1000 ORDER BY customers.name LIMIT 100;Порядок выполнения этого запроса следующий: 1. FROM: определение таблиц, задействованных в запросе (customers и orders). 2. JOIN: выполнение операции соединения на основе условия соединения (customers.id = orders.customer_id). 3. WHERE: применение условия фильтрации к объединённой таблице (order_date >= '2023-01-01'), которое отбирает только заказы, сделанные 1 января 2023 года или позже. 4. GROUP BY: группировка строк по указанным столбцам (customers.name). 5. HAVING: фильтрация групп по условию (total_spent >= 1000), при котором выбираются только клиенты с общей потраченной суммой 1000 и более. 6. SELECT: выбор столбцов и агрегатных функций из каждой группы (customers.name, COUNT(order_id) и SUM(order_amount)). 7. ORDER BY: сортировка строк по указанным столбцам (customers.name). 8. LIMIT: ограничение результата максимум 100 строками. Запросы SARGABLE SARGABLE (Searched ARGUment ABLE) - это запрос, который может использовать индексы для более быстрого выполнения. Запрос SARGABLE использует операторы и функции, которые могут использовать преимущества индексов. Например, использование операторов равенства (=), неравенства (<>, !=), диапазона (BETWEEN) или членства (IN) в индексированных столбцах может сделать запрос пригодным для поиска. Запрос не является SARGABLE, если он использует операторы или функции, которые препятствуют использованию индекса или требуют полного сканирования таблицы. Например, использование операторов отрицания (NOT), подстановочных знаков (LIKE) или арифметических операций (+, -, *, /) в индексированных столбцах может сделать запрос недоступным для поиска. Чтобы написать SARGABLE-запрос, обычно нужно ИЗБЕГАТЬ использования следующих конструкций в предложении WHERE: 1. Функций для индексированных столбцов: UPPER(), LOWER(), SUBSTRING() и т. д. 2. Арифметических операций над индексированными столбцами: столбец + 1 > 10, столбец * 2 < 20 и т. д. 3. Операторов отрицания для индексированных столбцов: NOT IN, NOT LIKE, NOT EXISTS и т. д. 4. Ведущих подстановочных знаков в индексированных столбцах: LIKE '%abc', LIKE '%xyz%' и т. д. 5. Неявных преобразований типов, которые могут повлиять на использование индекса. Замечание: некоторые СУБД могут эффективно обрабатывать такие запросы и использовать индексы, а также некоторые виды индексов специально предназначены для таких запросов, но в общем случае полагаться на это не стоит. Окончание следует… Источник: https://dev.to/kanani_nirav/secret-to-optimizing-sql-queries-understand-the-sql-execution-order-28m1