-
Notifications
You must be signed in to change notification settings - Fork 0
Dependency Injection
This page documents LogsParserServiceCollectionExtensions — the two AddLogsParser overloads, the services they
register, their lifetimes and the LogsParserRegistrationOptions object that configures them.
namespace LogsParser.DependencyInjection;
public static class LogsParserServiceCollectionExtensionsThe library depends on Microsoft.Extensions.DependencyInjection directly, so no extra package is needed to use
these extensions.
public static IServiceCollection AddLogsParser(this IServiceCollection services)Registers the whole pipeline with default settings and no credentials.
| Parameter | Type | Default | Description |
|---|---|---|---|
services |
IServiceCollection |
— | The collection the services are added to. Returned unchanged for chaining. |
Returns the same IServiceCollection instance.
Throws:
| Exception | When |
|---|---|
ArgumentNullException |
services is 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)Creates a fresh LogsParserRegistrationOptions, hands it to configure, then registers the pipeline according to
the values the callback left behind.
| Parameter | Type | Default | Description |
|---|---|---|---|
services |
IServiceCollection |
— | The collection the services are added to. Returned unchanged for chaining. |
configure |
Action<LogsParserRegistrationOptions> |
— | Callback invoked once, immediately, with a new options instance. |
Returns the same IServiceCollection instance.
Throws:
| Exception | When |
|---|---|
ArgumentNullException |
services is null
|
ArgumentNullException |
configure is 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");
});| Service | Implementation | Lifetime |
|---|---|---|
ICookieStorage |
MemoryCookieStorage |
Singleton |
LogsParserHttpOptions |
The instance held by LogsParserRegistrationOptions.HttpOptions; the parameterless overload registers new LogsParserHttpOptions()
|
Singleton |
ILogsParserDataSource |
LogsParserHttpDataSource, built by an internal factory |
Transient |
LogsParserClient |
LogsParserClient |
Transient |
LogsParserCredentials |
The instance held by LogsParserRegistrationOptions.Credentials — registered only when it is not null
|
Singleton |
LogsParserCredentials is never registered by the parameterless overload and is never registered when
options.Credentials stays null. The data source resolves it with GetService<LogsParserCredentials>(), so its
absence is legal and simply produces an unauthenticated transport — enough for pages the site serves anonymously,
and enough for parsing-only work.
The concrete LogsParserHttpDataSource type is not registered on its own; only ILogsParserDataSource is. To
read the rate-limit properties, which live on the concrete class, cast the resolved instance:
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}");
}Two different mechanisms are in play, and the difference decides who wins when a service is registered twice.
-
Defaults use
TryAdd*.ICookieStorage,LogsParserHttpOptions,ILogsParserDataSourceandLogsParserClientare added withTryAddSingleton/TryAddTransient. If the caller already registered that service type before callingAddLogsParser, the earlier registration stays and the library's default is skipped. -
Factories from the options use
Replace. WhenCookieStorageFactoryorDataSourceFactoryis notnull, the corresponding descriptor is applied withservices.Replace(...), which drops the first existing descriptor for that service type and appends the new one. A factory supplied through the options therefore overrides any earlier registration of that service.
var services = new ServiceCollection();
// Registered first — survives, because the default is a TryAdd.
services.AddSingleton<ICookieStorage>(new MemoryCookieStorage());
services.AddLogsParser(options =>
{
// Not supplied, so the line above wins.
// If it were supplied, Replace would remove the line above.
options.HttpOptions = new LogsParserHttpOptions { MaxRetryAttempts = 3 };
});Calling AddLogsParser twice is therefore idempotent for the defaults, but the second call's CookieStorageFactory
or DataSourceFactory would replace what the first call registered.
namespace LogsParser.Models;
public sealed class LogsParserRegistrationOptionsA mutable settings object, used only during registration. It is not itself registered in the container.
| Property | Type | Default | Description |
|---|---|---|---|
Credentials |
LogsParserCredentials? |
null |
Login, password and Base32 TOTP secret. Registered as a singleton when not null; when null, the transport runs unauthenticated. |
HttpOptions |
LogsParserHttpOptions |
new() |
Base URI, User-Agent, Accept, MaxRetryAttempts, WaitForRateLimitReset. Always registered as a singleton instance (with TryAddSingleton). |
CookieStorageFactory |
Func<IServiceProvider, ICookieStorage>? |
null |
When set, registered as the ICookieStorage singleton via Replace, instead of MemoryCookieStorage. |
DataSourceFactory |
Func<IServiceProvider, ILogsParserDataSource>? |
null |
When set, registered as the ILogsParserDataSource transient via Replace, instead of the built-in LogsParserHttpDataSource factory. |
LogsParserCredentials and LogsParserHttpOptions are documented in full on Models; the runtime meaning
of the HTTP options is on Transport and Authentication.
The built-in data source factory resolves ILoggerFactory from the container and falls back to
NullLoggerFactory.Instance when none is registered. The factory it settles on is passed to
LogsParserHttpDataSource and also installed process-wide through LogsParserLogging.UseLoggerFactory.
Two consequences follow:
- Call
services.AddLogging(...)before the first service resolution if you want library logs. The lookup happens whenILogsParserDataSourceis first constructed; a logging provider added to the collection afterwards is irrelevant, because the collection has already been built into a provider. - When you supply your own
DataSourceFactory, the built-in factory never runs, so nothing rewiresLogsParserLogging. CallLogsParserLogging.UseLoggerFactory(...)yourself, or pass anILoggerFactoryinto theLogsParserHttpDataSourceconstructor inside your factory.
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();See Logging for categories and the level taxonomy.
Enough for anonymous pages and for code that only needs the client to reach a parser.
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));The default MemoryCookieStorage loses the session when the process exits, which forces a fresh login plus TOTP
confirmation on every start. A storage that survives restarts is registered through 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");
});The factory receives the IServiceProvider, so the storage may take its own dependencies:
options.CookieStorageFactory = sp => new FileCookieStorage(
sp.GetRequiredService<IHostEnvironment>().ContentRootPath + "/cookies.json");Both the session cookies and the anti-DDoS token share this one flat store — see Cookie Storage.
DataSourceFactory swaps out the whole HTTP layer: useful for tests, for a proxy-aware HttpClient, or for a
cached/offline source.
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, // mandatory: the 302s are the auth protocol
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>());
};
});An injected HttpClient whose handler follows redirects breaks authentication silently: the library never sees the
302 and returns login-page HTML as content. An injected client also keeps its own BaseAddress and User-Agent in
preference to the LogsParserHttpOptions values — set them yourself if they matter.
A stub source is the same shape, and is exactly how the test suite overrides the transport:
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);
}
// The transient LogsParserHttpDataSource created inside the scope is disposed here.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 configures logging before Build(), so the ILoggerFactory lookup inside the data source
factory finds the host's factory and library logs flow into the usual providers. The client is transient and
resolved per request, which means the request scope owns and disposes the data source created for it.
LogsParserHttpDataSource implements IDisposable and is registered as transient. Microsoft's container tracks
disposable transients and disposes them together with the scope that created them.
-
Every resolution of
LogsParserClientbuilds a new data source, and the built-in factory passeshttpClient: null, so every data source builds its ownHttpClientandHttpClientHandlerand disposes them on its own disposal. Resolving the client in a tight loop churns connections; hold one client for the duration of a unit of work. - The cookie storage is a singleton, so the authenticated session survives across data source instances even though each instance is short-lived. That is what keeps the login/TOTP round trip from repeating.
-
Resolving from the root provider leaks by design. A transient disposable resolved straight from the root
IServiceProvideris tracked by the root and disposed only when the whole provider is disposed — i.e. at application shutdown. Preferprovider.CreateScope(), an ASP.NET Core request scope, or anIServiceScopeFactory. -
When you need tighter control, register your own factory:
DataSourceFactorymay hand back a long-lived instance you own and dispose yourself, or one built over a sharedHttpClientfromIHttpClientFactory.
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
- Внедрение зависимостей
- Логирование
- Исключения