* Architectural rework

This commit is contained in:
2026-03-06 23:19:24 +04:00
parent 74d778e6da
commit 866129f2ff
144 changed files with 413 additions and 368 deletions
@@ -0,0 +1,14 @@
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Routing;
namespace Telegrator.Hosting.Web
{
/// <summary>
/// Interface for Telegram bot hosts with Webhook update receiving.
/// Combines wbe application capabilities with reactive Telegram bot functionality.
/// </summary>
public interface ITelegramBotWebHost : ITelegramBotHost, IEndpointRouteBuilder, IApplicationBuilder, IAsyncDisposable
{
}
}
@@ -0,0 +1,175 @@
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Http.Features;
using Microsoft.AspNetCore.Routing;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using Telegrator.Core;
namespace Telegrator.Hosting.Web
{
/// <summary>
/// Represents a web hosted telegram bot
/// </summary>
public class TelegramBotWebHost : ITelegramBotWebHost
{
private readonly WebApplication _innerApp;
private readonly IUpdateRouter _updateRouter;
private readonly ILogger<TelegramBotWebHost> _logger;
private bool _disposed;
/// <inheritdoc/>
public IServiceProvider Services => _innerApp.Services;
/// <inheritdoc/>
public IUpdateRouter UpdateRouter => _updateRouter;
/// <inheritdoc/>
public ICollection<EndpointDataSource> DataSources => ((IEndpointRouteBuilder)_innerApp).DataSources;
/// <summary>
/// Allows consumers to be notified of application lifetime events.
/// </summary>
public IHostApplicationLifetime Lifetime => _innerApp.Lifetime;
/// <summary>
/// This application's logger
/// </summary>
public ILogger<TelegramBotWebHost> Logger => _logger;
// Private interface fields
IServiceProvider IEndpointRouteBuilder.ServiceProvider => Services;
IServiceProvider IApplicationBuilder.ApplicationServices { get => Services; set => throw new NotImplementedException(); }
IFeatureCollection IApplicationBuilder.ServerFeatures => ((IApplicationBuilder)_innerApp).ServerFeatures;
IDictionary<string, object?> IApplicationBuilder.Properties => ((IApplicationBuilder)_innerApp).Properties;
/// <summary>
/// Initializes a new instance of the <see cref="WebApplicationBuilder"/> class.
/// </summary>
/// <param name="webApplicationBuilder">The proxied instance of host builder.</param>
/// <param name="handlers"></param>
public TelegramBotWebHost(WebApplicationBuilder webApplicationBuilder, IHandlersCollection handlers)
{
// Registering this host in services for easy access
RegisterHostServices(webApplicationBuilder.Services, handlers);
// Building proxy application
_innerApp = webApplicationBuilder.Build();
_innerApp.UseTelegratorWeb();
// Reruesting services for this host
_updateRouter = Services.GetRequiredService<IUpdateRouter>();
_logger = Services.GetRequiredService<ILogger<TelegramBotWebHost>>();
}
/// <summary>
/// Creates new <see cref="TelegramBotHostBuilder"/> with default services and webhook update receiving scheme
/// </summary>
/// <returns></returns>
public static TelegramBotWebHostBuilder CreateBuilder(TelegramBotWebOptions settings)
{
ArgumentNullException.ThrowIfNull(settings, nameof(settings));
WebApplicationBuilder innerApp = WebApplication.CreateBuilder(settings.ToWebApplicationOptions());
TelegramBotWebHostBuilder builder = new TelegramBotWebHostBuilder(innerApp, settings);
builder.Services.AddTelegramBotHostDefaults();
builder.Services.AddTelegramWebhook();
return builder;
}
/// <summary>
/// Creates new SLIM <see cref="TelegramBotHostBuilder"/> with default services and webhook update receiving scheme
/// </summary>
/// <returns></returns>
public static TelegramBotWebHostBuilder CreateSlimBuilder(TelegramBotWebOptions settings)
{
ArgumentNullException.ThrowIfNull(settings, nameof(settings));
WebApplicationBuilder innerApp = WebApplication.CreateSlimBuilder(settings.ToWebApplicationOptions());
TelegramBotWebHostBuilder builder = new TelegramBotWebHostBuilder(innerApp, settings);
builder.Services.AddTelegramBotHostDefaults();
builder.Services.AddTelegramWebhook();
return builder;
}
/// <summary>
/// Creates new EMPTY <see cref="TelegramBotHostBuilder"/> WITHOUT any services or update receiving schemes
/// </summary>
/// <returns></returns>
public static TelegramBotWebHostBuilder CreateEmptyBuilder(TelegramBotWebOptions settings)
{
ArgumentNullException.ThrowIfNull(settings, nameof(settings));
WebApplicationBuilder innerApp = WebApplication.CreateEmptyBuilder(settings.ToWebApplicationOptions());
return new TelegramBotWebHostBuilder(innerApp, settings);
}
/// <inheritdoc/>
public async Task StartAsync(CancellationToken cancellationToken = default)
{
await _innerApp.StartAsync(cancellationToken);
}
/// <inheritdoc/>
public async Task StopAsync(CancellationToken cancellationToken = default)
{
await _innerApp.StopAsync(cancellationToken);
}
/// <inheritdoc/>
public IApplicationBuilder CreateApplicationBuilder()
=> ((IEndpointRouteBuilder)_innerApp).CreateApplicationBuilder();
/// <inheritdoc/>
public IApplicationBuilder Use(Func<RequestDelegate, RequestDelegate> middleware)
=> _innerApp.Use(middleware);
/// <inheritdoc/>
public IApplicationBuilder New()
=> ((IApplicationBuilder)_innerApp).New();
/// <inheritdoc/>
public RequestDelegate Build()
=> ((IApplicationBuilder)_innerApp).Build();
/// <summary>
/// Disposes the host.
/// </summary>
public async ValueTask DisposeAsync()
{
if (_disposed)
return;
await _innerApp.DisposeAsync();
GC.SuppressFinalize(this);
_disposed = true;
}
/// <summary>
/// Disposes the host.
/// </summary>
public void Dispose()
{
if (_disposed)
return;
// Sorry for this, i really dont know how to handle such cases
ValueTask disposeTask = _innerApp.DisposeAsync();
while (!disposeTask.IsCompleted)
Thread.Sleep(100);
GC.SuppressFinalize(this);
_disposed = true;
}
private void RegisterHostServices(IServiceCollection services, IHandlersCollection handlers)
{
//service.RemoveAll<IHost>();
//service.AddSingleton<IHost>(this);
services.AddSingleton<ITelegramBotHost>(this);
services.AddSingleton<ITelegramBotWebHost>(this);
services.AddSingleton<ITelegratorBot>(this);
}
}
}
@@ -0,0 +1,107 @@
using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Telegram.Bot;
using Telegrator;
using Telegrator.Core;
using Telegrator.Hosting.Configuration;
using Telegrator.Providers;
#pragma warning disable IDE0001
namespace Telegrator.Hosting.Web
{
/// <summary>
/// Represents a web hosted telegram bots and services builder that helps manage configuration, logging, lifetime, and more.
/// </summary>
public class TelegramBotWebHostBuilder : ITelegramBotHostBuilder
{
private readonly WebApplicationBuilder _innerBuilder;
private readonly TelegramBotWebOptions _settings;
private readonly IHandlersCollection _handlers;
/// <inheritdoc/>
public IHandlersCollection Handlers => _handlers;
/// <inheritdoc/>
public IConfigurationManager Configuration => _innerBuilder.Configuration;
/// <inheritdoc/>
public ILoggingBuilder Logging => _innerBuilder.Logging;
/// <inheritdoc/>
public IServiceCollection Services => _innerBuilder.Services;
/// <inheritdoc/>
public IHostEnvironment Environment => _innerBuilder.Environment;
/// <summary>
/// Initializes a new instance of the <see cref="TelegramBotWebHostBuilder"/> class.
/// </summary>
/// <param name="webApplicationBuilder"></param>
/// <param name="settings"></param>
public TelegramBotWebHostBuilder(WebApplicationBuilder webApplicationBuilder, TelegramBotWebOptions settings)
{
_innerBuilder = webApplicationBuilder ?? throw new ArgumentNullException(nameof(webApplicationBuilder));
_settings = settings ?? throw new ArgumentNullException(nameof(settings));
_handlers = new HostHandlersCollection(Services, _settings);
}
/// <summary>
/// Initializes a new instance of the <see cref="TelegramBotWebHostBuilder"/> class.
/// </summary>
/// <param name="webApplicationBuilder"></param>
/// <param name="handlers"></param>
/// <param name="settings"></param>
public TelegramBotWebHostBuilder(WebApplicationBuilder webApplicationBuilder, TelegramBotWebOptions settings, IHandlersCollection handlers)
{
_innerBuilder = webApplicationBuilder ?? throw new ArgumentNullException(nameof(webApplicationBuilder));
_settings = settings ?? throw new ArgumentNullException(nameof(settings));
_handlers = handlers ?? throw new ArgumentNullException(nameof(settings));
_innerBuilder.AddTelegratorWeb(settings);
}
/// <summary>
/// Builds the host.
/// </summary>
/// <returns></returns>
public TelegramBotWebHost Build()
{
if (_handlers is IHostHandlersCollection hostHandlers)
{
foreach (PreBuildingRoutine preBuildRoutine in hostHandlers.PreBuilderRoutines)
{
try
{
preBuildRoutine.Invoke(this);
}
catch (NotImplementedException)
{
_ = 0xBAD + 0xC0DE;
}
}
}
if (!_settings.DisableAutoConfigure)
{
Services.Configure<TelegratorWebOptions>(Configuration.GetSection(nameof(TelegratorWebOptions)));
Services.Configure<TelegramBotClientOptions>(Configuration.GetSection(nameof(TelegramBotClientOptions)), new TelegramBotClientOptionsProxy());
}
else
{
if (null == Services.SingleOrDefault(srvc => srvc.ImplementationType == typeof(IOptions<TelegratorWebOptions>)))
throw new MissingMemberException("Auto configuration disabled, yet no options of type 'TelegratorWebOptions' wasn't registered. This configuration is runtime required!");
if (null == Services.SingleOrDefault(srvc => srvc.ImplementationType == typeof(IOptions<TelegramBotClientOptions>)))
throw new MissingMemberException("Auto configuration disabled, yet no options of type 'TelegramBotClientOptions' wasn't registered. This configuration is runtime required!");
}
Services.AddSingleton<IConfigurationManager>(Configuration);
Services.AddSingleton<IOptions<TelegratorOptions>>(Options.Create(_settings));
return new TelegramBotWebHost(_innerBuilder, _handlers);
}
}
}
@@ -0,0 +1,40 @@
using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.Options;
namespace Telegrator.Hosting.Web
{
/// <summary>
/// Options for configuring the behavior for TelegramBotWebHost.
/// </summary>
public class TelegramBotWebOptions : TelegratorOptions
{
/// <summary>
/// Disables automatic configuration for all of required <see cref="IOptions{TOptions}"/> instances
/// </summary>
public bool DisableAutoConfigure { get; set; }
/// <inheritdoc cref="WebApplicationOptions.Args"/>
public string[]? Args { get; init; }
/// <inheritdoc cref="WebApplicationOptions.EnvironmentName"/>
public string? EnvironmentName { get; init; }
/// <inheritdoc cref="WebApplicationOptions.ApplicationName"/>
public string? ApplicationName { get; init; }
/// <inheritdoc cref="WebApplicationOptions.ContentRootPath"/>
public string? ContentRootPath { get; init; }
/// <inheritdoc cref="WebApplicationOptions.WebRootPath"/>
public string? WebRootPath { get; init; }
internal WebApplicationOptions ToWebApplicationOptions() => new WebApplicationOptions()
{
ApplicationName = ApplicationName,
Args = Args,
ContentRootPath = ContentRootPath,
EnvironmentName = EnvironmentName,
WebRootPath = WebRootPath
};
}
}
@@ -0,0 +1,35 @@
using System.Diagnostics.CodeAnalysis;
namespace Telegrator.Hosting.Web
{
/// <summary>
/// Configuration options for Telegram bot behavior and execution settings.
/// Controls various aspects of bot operation including concurrency, routing, webhook receiving, and execution policies.
/// </summary>
public class TelegratorWebOptions
{
/// <summary>
/// Gets or sets HTTPS URL to send updates to. Use an empty string to remove webhook integration
/// </summary>
[StringSyntax(StringSyntaxAttribute.Uri)]
public required string WebhookUri { get; set; }
/// <summary>
/// A secret token to be sent in a header “X-Telegram-Bot-Api-Secret-Token” in every webhook request, 1-256 characters.
/// Only characters A-Z, a-z, 0-9, _ and - are allowed.
/// The header is useful to ensure that the request comes from a webhook set by you.
/// </summary>
public string? SecretToken { get; set; }
/// <summary>
/// The maximum allowed number of simultaneous HTTPS connections to the webhook for update delivery, 1-100. Defaults to 40.
/// Use lower values to limit the load on your bot's server, and higher values to increase your bot's throughput.
/// </summary>
public int MaxConnections { get; set; } = 40;
/// <summary>
/// Pass true to drop all pending updates
/// </summary>
public bool DropPendingUpdates { get; set; }
}
}