-
Notifications
You must be signed in to change notification settings - Fork 0
Dependency Injection RU
Страница описывает LogsParserServiceCollectionExtensions — две перегрузки AddLogsParser, регистрируемые ими
сервисы, их времена жизни и объект настроек LogsParserRegistrationOptions.
namespace LogsParser.DependencyInjection;
public static class LogsParserServiceCollectionExtensionsБиблиотека напрямую зависит от Microsoft.Extensions.DependencyInjection, поэтому дополнительных пакетов для этих
расширений не требуется.
public static IServiceCollection AddLogsParser(this IServiceCollection services)Регистрирует весь конвейер с настройками по умолчанию и без учётных данных.
| Параметр | Тип | По умолчанию | Описание |
|---|---|---|---|
services |
IServiceCollection |
— | Коллекция, в которую добавляются сервисы. Возвращается без изменений для цепочки вызовов. |
Возвращает тот же экземпляр IServiceCollection.
Исключения:
| Исключение | Когда |
|---|---|
ArgumentNullException |
services равен null
|
using LogsParser;
using LogsParser.DependencyInjection;
using Microsoft.Extensions.DependencyInjection;
var services = new ServiceCollection();
services.AddLogsParser();
using var provider = services.BuildServiceProvider();
var client = provider.GetRequiredService<LogsParserClient>();public static IServiceCollection AddLogsParser(
this IServiceCollection services,
Action<LogsParserRegistrationOptions> configure)Создаёт новый LogsParserRegistrationOptions, передаёт его в configure, после чего регистрирует конвейер в
соответствии со значениями, которые оставил обратный вызов.
| Параметр | Тип | По умолчанию | Описание |
|---|---|---|---|
services |
IServiceCollection |
— | Коллекция, в которую добавляются сервисы. Возвращается без изменений для цепочки вызовов. |
configure |
Action<LogsParserRegistrationOptions> |
— | Обратный вызов, выполняемый один раз, немедленно, с новым экземпляром настроек. |
Возвращает тот же экземпляр IServiceCollection.
Исключения:
| Исключение | Когда |
|---|---|
ArgumentNullException |
services равен null
|
ArgumentNullException |
configure равен null
|
using LogsParser.DependencyInjection;
using LogsParser.Models;
using Microsoft.Extensions.DependencyInjection;
var services = new ServiceCollection();
services.AddLogsParser(options =>
{
options.Credentials = new LogsParserCredentials("my_login", "my_password", "BASE32SECRET");
});| Сервис | Реализация | Время жизни |
|---|---|---|
ICookieStorage |
MemoryCookieStorage |
Singleton |
LogsParserHttpOptions |
Экземпляр из LogsParserRegistrationOptions.HttpOptions; перегрузка без параметров регистрирует new LogsParserHttpOptions()
|
Singleton |
ILogsParserDataSource |
LogsParserHttpDataSource, создаваемый внутренней фабрикой |
Transient |
LogsParserClient |
LogsParserClient |
Transient |
LogsParserCredentials |
Экземпляр из LogsParserRegistrationOptions.Credentials — регистрируется только если он не null
|
Singleton |
Перегрузка без параметров не регистрирует LogsParserCredentials никогда; перегрузка с настройками — только если
options.Credentials отличается от null. Источник данных получает учётные данные через
GetService<LogsParserCredentials>(), поэтому их отсутствие допустимо и просто даёт неаутентифицированный
транспорт — этого достаточно для страниц, которые сайт отдаёт анонимно, и для задач, ограниченных разбором HTML.
Конкретный тип LogsParserHttpDataSource отдельно не регистрируется — регистрируется только
ILogsParserDataSource. Чтобы прочитать свойства лимита запросов, объявленные на конкретном классе, приведите
разрешённый экземпляр к типу:
using LogsParser.Abstractions;
using LogsParser.Net;
using Microsoft.Extensions.DependencyInjection;
var dataSource = provider.GetRequiredService<ILogsParserDataSource>();
if (dataSource is LogsParserHttpDataSource http)
{
Console.WriteLine($"{http.RateLimitRemaining}/{http.RateLimitMax}, reset at {http.RateLimitReset:u}");
}Здесь работают два разных механизма, и разница между ними определяет, кто побеждает при двойной регистрации сервиса.
-
Значения по умолчанию добавляются через
TryAdd*.ICookieStorage,LogsParserHttpOptions,ILogsParserDataSourceиLogsParserClientрегистрируются вызовамиTryAddSingleton/TryAddTransient. Если вызывающий код зарегистрировал этот тип сервиса доAddLogsParser, его регистрация сохраняется, а значение библиотеки по умолчанию пропускается. -
Фабрики из настроек применяются через
Replace. ЕслиCookieStorageFactoryилиDataSourceFactoryне равныnull, соответствующий дескриптор регистрируется черезservices.Replace(...), который удаляет первый существующий дескриптор для этого типа сервиса и добавляет новый. Поэтому фабрика, переданная через настройки, перекрывает любую более раннюю регистрацию этого сервиса.
var services = new ServiceCollection();
// Зарегистрировано первым — сохраняется, потому что значение по умолчанию добавляется через TryAdd.
services.AddSingleton<ICookieStorage>(new MemoryCookieStorage());
services.AddLogsParser(options =>
{
// Не задано, поэтому побеждает строка выше.
// Если бы фабрика была задана, Replace удалил бы строку выше.
options.HttpOptions = new LogsParserHttpOptions { MaxRetryAttempts = 3 };
});Двойной вызов AddLogsParser идемпотентен для значений по умолчанию, но CookieStorageFactory или
DataSourceFactory из второго вызова заменят то, что зарегистрировал первый.
namespace LogsParser.Models;
public sealed class LogsParserRegistrationOptionsИзменяемый объект настроек, используемый только на этапе регистрации. Сам он в контейнер не помещается.
| Свойство | Тип | По умолчанию | Описание |
|---|---|---|---|
Credentials |
LogsParserCredentials? |
null |
Логин, пароль и TOTP-секрет в Base32. Регистрируется как singleton, если не null; при null транспорт работает без аутентификации. |
HttpOptions |
LogsParserHttpOptions |
new() |
Базовый URI, User-Agent, Accept, MaxRetryAttempts, WaitForRateLimitReset. Всегда регистрируется как singleton-экземпляр (через TryAddSingleton). |
CookieStorageFactory |
Func<IServiceProvider, ICookieStorage>? |
null |
Если задана, регистрируется как singleton ICookieStorage через Replace, вместо MemoryCookieStorage. |
DataSourceFactory |
Func<IServiceProvider, ILogsParserDataSource>? |
null |
Если задана, регистрируется как transient ILogsParserDataSource через Replace, вместо встроенной фабрики LogsParserHttpDataSource. |
LogsParserCredentials и LogsParserHttpOptions полностью описаны на странице Модели; поведение
HTTP-настроек во время выполнения — на странице Транспорт и аутентификация.
Встроенная фабрика источника данных получает ILoggerFactory из контейнера и откатывается к
NullLoggerFactory.Instance, если ни одна не зарегистрирована. Выбранная фабрика передаётся в
LogsParserHttpDataSource и одновременно устанавливается глобально для процесса через
LogsParserLogging.UseLoggerFactory.
Отсюда следуют два вывода:
- Вызывайте
services.AddLogging(...)до первого разрешения сервисов, если нужны логи библиотеки. Поиск фабрики происходит в момент первого созданияILogsParserDataSource; провайдер логирования, добавленный в коллекцию позже, уже ни на что не влияет, поскольку коллекция к тому времени превращена в провайдер. - Если вы задаёте собственную
DataSourceFactory, встроенная фабрика не выполняется иLogsParserLoggingникем не перенастраивается. ВызовитеLogsParserLogging.UseLoggerFactory(...)самостоятельно либо передайтеILoggerFactoryв конструкторLogsParserHttpDataSourceвнутри своей фабрики.
using LogsParser.DependencyInjection;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
var services = new ServiceCollection();
services.AddLogging(builder => builder
.AddSimpleConsole()
.SetMinimumLevel(LogLevel.Debug));
services.AddLogsParser();Категории и уровни описаны на странице Логирование.
Достаточно для анонимных страниц и для кода, которому клиент нужен лишь как способ добраться до парсера.
using LogsParser;
using LogsParser.DependencyInjection;
using Microsoft.Extensions.DependencyInjection;
var services = new ServiceCollection();
services.AddLogsParser();
using var provider = services.BuildServiceProvider();
var client = provider.GetRequiredService<LogsParserClient>();
var catalog = await client.GetLogsFilterCatalogAsync();
Console.WriteLine($"{catalog.Filters.Count} filters");using LogsParser;
using LogsParser.DependencyInjection;
using LogsParser.Models;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
var services = new ServiceCollection();
services.AddLogging(builder => builder.AddSimpleConsole().SetMinimumLevel(LogLevel.Information));
services.AddLogsParser(options =>
{
options.Credentials = new LogsParserCredentials(
Login: "my_login",
Password: "my_password",
TotpSecret: "GEZDGNBVGY3TQOJQGEZDGNBVGY3TQOJQ");
options.HttpOptions = new LogsParserHttpOptions
{
MaxRetryAttempts = 3,
WaitForRateLimitReset = true
};
});
using var provider = services.BuildServiceProvider();
var client = provider.GetRequiredService<LogsParserClient>();
var page = await client.GetLogsAsync(new LogsQuery(ServerId: 201, Limit: 500));MemoryCookieStorage по умолчанию теряет сессию при завершении процесса, из-за чего каждый запуск начинается с
повторного входа и подтверждения TOTP. Хранилище, переживающее перезапуски, регистрируется через
CookieStorageFactory.
using System.Text.Json;
using LogsParser;
using LogsParser.Abstractions;
using LogsParser.DependencyInjection;
using Microsoft.Extensions.DependencyInjection;
public sealed class FileCookieStorage : ICookieStorage
{
private readonly string _path;
private readonly object _sync = new();
public FileCookieStorage(string path) => _path = path;
public IReadOnlyCollection<ParserCookie> GetCookies()
{
lock (_sync)
{
return File.Exists(_path)
? JsonSerializer.Deserialize<ParserCookie[]>(File.ReadAllText(_path)) ?? []
: [];
}
}
public void SetCookies(IReadOnlyCollection<ParserCookie> cookies)
{
lock (_sync)
{
File.WriteAllText(_path, JsonSerializer.Serialize(cookies));
}
}
}
var services = new ServiceCollection();
services.AddLogsParser(options =>
{
options.CookieStorageFactory = _ => new FileCookieStorage("cookies.json");
});Фабрика получает IServiceProvider, поэтому хранилище может иметь собственные зависимости:
options.CookieStorageFactory = sp => new FileCookieStorage(
sp.GetRequiredService<IHostEnvironment>().ContentRootPath + "/cookies.json");Cookie сессии и токен защиты от DDoS хранятся в одном плоском хранилище — см. Хранилище cookie.
DataSourceFactory заменяет весь HTTP-слой: это удобно для тестов, для HttpClient с прокси или для
кеширующего/офлайнового источника.
using System.Net;
using LogsParser.Abstractions;
using LogsParser.DependencyInjection;
using LogsParser.Models;
using LogsParser.Net;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
var services = new ServiceCollection();
services.AddLogsParser(options =>
{
options.Credentials = new LogsParserCredentials("my_login", "my_password", "BASE32SECRET");
options.DataSourceFactory = sp =>
{
var handler = new HttpClientHandler
{
AllowAutoRedirect = false, // обязательно: ответы 302 и есть протокол аутентификации
Proxy = new WebProxy("socks5://127.0.0.1:1488"),
UseProxy = true
};
return new LogsParserHttpDataSource(
credentials: sp.GetService<LogsParserCredentials>(),
cookieStorage: sp.GetRequiredService<ICookieStorage>(),
options: sp.GetRequiredService<LogsParserHttpOptions>(),
httpClient: new HttpClient(handler, disposeHandler: true),
loggerFactory: sp.GetService<ILoggerFactory>());
};
});Внедрённый HttpClient, обработчик которого следует за перенаправлениями, ломает аутентификацию незаметно:
библиотека не видит 302 и возвращает HTML страницы входа как содержимое. Внедрённый клиент также сохраняет
собственные BaseAddress и User-Agent вместо значений из LogsParserHttpOptions — задавайте их сами, если они
важны.
Заглушка источника устроена так же — именно так транспорт подменяется в тестах:
using LogsParser;
using LogsParser.Abstractions;
public sealed class StubDataSource(string html) : ILogsParserDataSource
{
public Task<string> GetContentAsync(ParserRequest request, CancellationToken cancellationToken = default)
=> Task.FromResult(html);
}
services.AddLogsParser(options => options.DataSourceFactory = _ => new StubDataSource("<html></html>"));using LogsParser;
using LogsParser.DependencyInjection;
using LogsParser.Models;
using Microsoft.Extensions.DependencyInjection;
var services = new ServiceCollection();
services.AddLogsParser();
using var provider = services.BuildServiceProvider();
using (var scope = provider.CreateScope())
{
var client = scope.ServiceProvider.GetRequiredService<LogsParserClient>();
var report = await client.GetTopOperationsAsync(new TopOperationsQuery());
Console.WriteLine(report.MetaInfo.TotalTransactions);
}
// Созданный внутри области transient-экземпляр LogsParserHttpDataSource освобождается здесь.using LogsParser;
using LogsParser.DependencyInjection;
using LogsParser.Models;
using Microsoft.AspNetCore.Mvc;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddLogsParser(options =>
{
options.Credentials = new LogsParserCredentials(
builder.Configuration["LogsParser:Login"]!,
builder.Configuration["LogsParser:Password"]!,
builder.Configuration["LogsParser:TotpSecret"]!);
});
var app = builder.Build();
app.MapGet("/logs/{serverId:int}", async (int serverId, [FromServices] LogsParserClient client) =>
{
var page = await client.GetLogsAsync(new LogsQuery(ServerId: serverId, Limit: 100));
return Results.Ok(page.Entries);
});
app.Run();WebApplicationBuilder настраивает логирование до вызова Build(), поэтому поиск ILoggerFactory внутри фабрики
источника данных находит фабрику хоста и логи библиотеки попадают в обычные провайдеры. Клиент — transient и
разрешается на каждый запрос, то есть область запроса владеет созданным для неё источником данных и освобождает
его.
LogsParserHttpDataSource реализует IDisposable и регистрируется как transient. Контейнер Microsoft
отслеживает освобождаемые transient-объекты и освобождает их вместе с областью, в которой они были созданы.
-
Каждое разрешение
LogsParserClientсоздаёт новый источник данных, а встроенная фабрика передаётhttpClient: null, поэтому каждый источник создаёт собственныеHttpClientиHttpClientHandlerи освобождает их при собственном освобождении. Разрешение клиента в тесном цикле расходует соединения — держите один клиент на всю единицу работы. - Хранилище cookie — singleton, поэтому аутентифицированная сессия переживает смену экземпляров источника данных, хотя каждый из них живёт недолго. Именно это избавляет от повторного входа и подтверждения TOTP.
-
Разрешение из корневого провайдера удерживает объекты по построению. Освобождаемый transient, полученный
напрямую из корневого
IServiceProvider, отслеживается корнем и освобождается только вместе со всем провайдером, то есть при завершении приложения. Предпочитайтеprovider.CreateScope(), область запроса ASP.NET Core илиIServiceScopeFactory. -
Если нужен более тонкий контроль, зарегистрируйте собственную фабрику:
DataSourceFactoryможет возвращать долгоживущий экземпляр, которым вы владеете и освобождаете сами, либо экземпляр поверх общегоHttpClientизIHttpClientFactory.
LogsParser · CC BY-NC 4.0 — non-commercial use only / только некоммерческое использование
English
- Home
- Getting Started
- LogsParserClient
- Requests and URI Builder
- Models
- Parsers
- Transport and Authentication
- Cookie Storage
- Dependency Injection
- Logging
- Exceptions
Русский
- Главная
- Начало работы
- LogsParserClient
- Запросы и построитель URI
- Модели
- Парсеры
- Транспорт и авторизация
- Хранение cookies
- Внедрение зависимостей
- Логирование
- Исключения