Split Program.cs DI registrations into per-namespace DependencyRegistration classes
Each root namespace (Audit, Common, Data, Environments, Features/Usage, Identities, Organizations, Projects, Segments, Users, Webhooks) gets its own static DependencyRegistration class exposing an Add<Name>Services extension method, so Program.cs just wires them together instead of listing every registration inline.
This commit is contained in:
12
src/api/MicCheck.Api/Audit/DependencyRegistration.cs
Normal file
12
src/api/MicCheck.Api/Audit/DependencyRegistration.cs
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
namespace MicCheck.Api.Audit;
|
||||||
|
|
||||||
|
public static class DependencyRegistration
|
||||||
|
{
|
||||||
|
public static IServiceCollection AddAuditServices(this IServiceCollection services)
|
||||||
|
{
|
||||||
|
services.AddScoped<IAuditService, AuditService>();
|
||||||
|
services.AddScoped<AuditLogQueryService>();
|
||||||
|
|
||||||
|
return services;
|
||||||
|
}
|
||||||
|
}
|
||||||
65
src/api/MicCheck.Api/Common/DependencyRegistration.cs
Normal file
65
src/api/MicCheck.Api/Common/DependencyRegistration.cs
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
using System.Text;
|
||||||
|
using MicCheck.Api.Common.Security.ApiKeys;
|
||||||
|
using MicCheck.Api.Common.Security.Authentication;
|
||||||
|
using MicCheck.Api.Common.Security.Authorization;
|
||||||
|
using MicCheck.Api.Common.Validation;
|
||||||
|
using Microsoft.AspNetCore.Authentication;
|
||||||
|
using Microsoft.AspNetCore.Authentication.JwtBearer;
|
||||||
|
using Microsoft.AspNetCore.Authorization;
|
||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using Microsoft.IdentityModel.Tokens;
|
||||||
|
|
||||||
|
namespace MicCheck.Api.Common;
|
||||||
|
|
||||||
|
public static class DependencyRegistration
|
||||||
|
{
|
||||||
|
public static IServiceCollection AddCommonServices(this IServiceCollection services, IConfiguration configuration)
|
||||||
|
{
|
||||||
|
services.AddAuthentication()
|
||||||
|
.AddScheme<AuthenticationSchemeOptions, EnvironmentKeyAuthenticationHandler>(
|
||||||
|
EnvironmentKeyAuthenticationHandler.SchemeName, _ => { })
|
||||||
|
.AddScheme<AuthenticationSchemeOptions, ApiKeyAuthenticationHandler>(
|
||||||
|
ApiKeyAuthenticationHandler.SchemeName, _ => { })
|
||||||
|
.AddJwtBearer(JwtBearerDefaults.AuthenticationScheme, options =>
|
||||||
|
{
|
||||||
|
options.TokenValidationParameters = new TokenValidationParameters
|
||||||
|
{
|
||||||
|
ValidateIssuer = true,
|
||||||
|
ValidateAudience = true,
|
||||||
|
ValidateIssuerSigningKey = true,
|
||||||
|
ValidIssuer = configuration["Jwt:Issuer"],
|
||||||
|
ValidAudience = configuration["Jwt:Audience"],
|
||||||
|
IssuerSigningKey = new SymmetricSecurityKey(
|
||||||
|
Encoding.UTF8.GetBytes(configuration["Jwt:SecretKey"]!))
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
services.AddAuthorization(options =>
|
||||||
|
{
|
||||||
|
options.AddPolicy(AuthorizationPolicies.FlagsApiAccess, policy =>
|
||||||
|
policy.AddAuthenticationSchemes(EnvironmentKeyAuthenticationHandler.SchemeName)
|
||||||
|
.RequireClaim("EnvironmentId"));
|
||||||
|
|
||||||
|
options.AddPolicy(AuthorizationPolicies.AdminApiAccess, policy =>
|
||||||
|
policy.AddAuthenticationSchemes(ApiKeyAuthenticationHandler.SchemeName, JwtBearerDefaults.AuthenticationScheme)
|
||||||
|
.RequireAuthenticatedUser());
|
||||||
|
|
||||||
|
options.AddPolicy(AuthorizationPolicies.OrganizationAdmin, policy =>
|
||||||
|
policy.AddAuthenticationSchemes(ApiKeyAuthenticationHandler.SchemeName, JwtBearerDefaults.AuthenticationScheme)
|
||||||
|
.RequireClaim("OrganizationRole", "Admin"));
|
||||||
|
});
|
||||||
|
|
||||||
|
services.AddScoped<ITokenService, TokenService>();
|
||||||
|
services.AddScoped<AuthService>();
|
||||||
|
services.AddScoped<ApiKeyService>();
|
||||||
|
services.AddScoped<IAuthorizationHandler, ProjectPermissionRequirementHandler>();
|
||||||
|
|
||||||
|
services.AddModelValidatorsFromAssemblyContaining<Program>();
|
||||||
|
services.Configure<ApiBehaviorOptions>(options =>
|
||||||
|
{
|
||||||
|
options.InvalidModelStateResponseFactory = context => ValidationProblemResponseFactory.Create(context.ModelState);
|
||||||
|
});
|
||||||
|
|
||||||
|
return services;
|
||||||
|
}
|
||||||
|
}
|
||||||
21
src/api/MicCheck.Api/Data/DependencyRegistration.cs
Normal file
21
src/api/MicCheck.Api/Data/DependencyRegistration.cs
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
|
||||||
|
namespace MicCheck.Api.Data;
|
||||||
|
|
||||||
|
public static class DependencyRegistration
|
||||||
|
{
|
||||||
|
public static IServiceCollection AddDataServices(this IServiceCollection services, IConfiguration configuration)
|
||||||
|
{
|
||||||
|
services.AddScoped<DatabaseSeeder>();
|
||||||
|
|
||||||
|
var connectionString = configuration.GetConnectionString("miccheck")
|
||||||
|
?? (System.Environment.GetEnvironmentVariable("DATABASE_URL") is { } databaseUrl
|
||||||
|
? DatabaseUrlParser.ToNpgsqlConnectionString(databaseUrl)
|
||||||
|
: configuration.GetConnectionString("DefaultConnection")!);
|
||||||
|
|
||||||
|
services.AddDbContext<MicCheckDbContext>(options => options.UseNpgsql(connectionString));
|
||||||
|
services.AddScoped<IMicCheckDbContext>(sp => sp.GetRequiredService<MicCheckDbContext>());
|
||||||
|
|
||||||
|
return services;
|
||||||
|
}
|
||||||
|
}
|
||||||
12
src/api/MicCheck.Api/Environments/DependencyRegistration.cs
Normal file
12
src/api/MicCheck.Api/Environments/DependencyRegistration.cs
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
namespace MicCheck.Api.Environments;
|
||||||
|
|
||||||
|
public static class DependencyRegistration
|
||||||
|
{
|
||||||
|
public static IServiceCollection AddEnvironmentsServices(this IServiceCollection services)
|
||||||
|
{
|
||||||
|
services.AddScoped<EnvironmentService>();
|
||||||
|
services.AddScoped<EnvironmentDocumentService>();
|
||||||
|
|
||||||
|
return services;
|
||||||
|
}
|
||||||
|
}
|
||||||
20
src/api/MicCheck.Api/Features/DependencyRegistration.cs
Normal file
20
src/api/MicCheck.Api/Features/DependencyRegistration.cs
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
using MicCheck.Api.Features.Usage;
|
||||||
|
|
||||||
|
namespace MicCheck.Api.Features;
|
||||||
|
|
||||||
|
public static class DependencyRegistration
|
||||||
|
{
|
||||||
|
public static IServiceCollection AddFeaturesServices(this IServiceCollection services)
|
||||||
|
{
|
||||||
|
services.AddScoped<FeatureEvaluationService>();
|
||||||
|
services.AddSingleton<FlagCache>();
|
||||||
|
services.AddScoped<FeatureService>();
|
||||||
|
services.AddScoped<FeatureStateService>();
|
||||||
|
services.AddScoped<FeatureSegmentService>();
|
||||||
|
services.AddScoped<TagService>();
|
||||||
|
|
||||||
|
services.AddFeatureUsageServices();
|
||||||
|
|
||||||
|
return services;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -12,11 +12,9 @@ public class FlagCache(IMemoryCache cache)
|
|||||||
return flags;
|
return flags;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Set(int environmentId, IReadOnlyList<FeatureStateResult> flags)
|
public void Set(int environmentId, IReadOnlyList<FeatureStateResult> flags) => cache.Set(CacheKey(environmentId), flags, CacheDuration);
|
||||||
=> cache.Set(CacheKey(environmentId), flags, CacheDuration);
|
|
||||||
|
|
||||||
public void Invalidate(int environmentId)
|
public void Invalidate(int environmentId) => cache.Remove(CacheKey(environmentId));
|
||||||
=> cache.Remove(CacheKey(environmentId));
|
|
||||||
|
|
||||||
private static string CacheKey(int environmentId) => $"flags:{environmentId}";
|
private static string CacheKey(int environmentId) => $"flags:{environmentId}";
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,13 @@
|
|||||||
|
namespace MicCheck.Api.Features.Usage;
|
||||||
|
|
||||||
|
public static class DependencyRegistration
|
||||||
|
{
|
||||||
|
public static IServiceCollection AddFeatureUsageServices(this IServiceCollection services)
|
||||||
|
{
|
||||||
|
services.AddSingleton<FeatureUsageMetrics>();
|
||||||
|
services.AddScoped<FeatureUsageQueryService>();
|
||||||
|
services.AddHostedService<FeatureUsageFlushBackgroundService>();
|
||||||
|
|
||||||
|
return services;
|
||||||
|
}
|
||||||
|
}
|
||||||
12
src/api/MicCheck.Api/Identities/DependencyRegistration.cs
Normal file
12
src/api/MicCheck.Api/Identities/DependencyRegistration.cs
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
namespace MicCheck.Api.Identities;
|
||||||
|
|
||||||
|
public static class DependencyRegistration
|
||||||
|
{
|
||||||
|
public static IServiceCollection AddIdentitiesServices(this IServiceCollection services)
|
||||||
|
{
|
||||||
|
services.AddScoped<IdentityResolutionService>();
|
||||||
|
services.AddScoped<AdminIdentityService>();
|
||||||
|
|
||||||
|
return services;
|
||||||
|
}
|
||||||
|
}
|
||||||
11
src/api/MicCheck.Api/Organizations/DependencyRegistration.cs
Normal file
11
src/api/MicCheck.Api/Organizations/DependencyRegistration.cs
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
namespace MicCheck.Api.Organizations;
|
||||||
|
|
||||||
|
public static class DependencyRegistration
|
||||||
|
{
|
||||||
|
public static IServiceCollection AddOrganizationsServices(this IServiceCollection services)
|
||||||
|
{
|
||||||
|
services.AddScoped<OrganizationService>();
|
||||||
|
|
||||||
|
return services;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,28 +1,19 @@
|
|||||||
using System.Text;
|
|
||||||
using System.Threading.RateLimiting;
|
using System.Threading.RateLimiting;
|
||||||
using MicCheck.Api.Common.Security.ApiKeys;
|
|
||||||
using MicCheck.Api.Audit;
|
using MicCheck.Api.Audit;
|
||||||
using MicCheck.Api.Common.Security.Authentication;
|
using MicCheck.Api.Common;
|
||||||
|
using MicCheck.Api.Common.Security.ApiKeys;
|
||||||
using MicCheck.Api.Common.Security.Authorization;
|
using MicCheck.Api.Common.Security.Authorization;
|
||||||
using MicCheck.Api.Common.Validation;
|
using MicCheck.Api.Common.Validation;
|
||||||
using MicCheck.Api.Data;
|
using MicCheck.Api.Data;
|
||||||
using MicCheck.Api.Environments;
|
using MicCheck.Api.Environments;
|
||||||
using MicCheck.Api.Features;
|
using MicCheck.Api.Features;
|
||||||
using MicCheck.Api.Features.Usage;
|
|
||||||
using MicCheck.Api.Identities;
|
using MicCheck.Api.Identities;
|
||||||
using MicCheck.Api.Organizations;
|
using MicCheck.Api.Organizations;
|
||||||
using MicCheck.Api.Projects;
|
using MicCheck.Api.Projects;
|
||||||
using MicCheck.Api.Segments;
|
using MicCheck.Api.Segments;
|
||||||
using MicCheck.Api.Users;
|
using MicCheck.Api.Users;
|
||||||
using MicCheck.Api.Webhooks;
|
using MicCheck.Api.Webhooks;
|
||||||
using Microsoft.AspNetCore.Authentication;
|
|
||||||
using Microsoft.AspNetCore.Authentication.JwtBearer;
|
|
||||||
using Microsoft.AspNetCore.Authorization;
|
|
||||||
using Microsoft.AspNetCore.Identity;
|
|
||||||
using Microsoft.AspNetCore.Mvc;
|
|
||||||
using Microsoft.AspNetCore.RateLimiting;
|
using Microsoft.AspNetCore.RateLimiting;
|
||||||
using Microsoft.EntityFrameworkCore;
|
|
||||||
using Microsoft.IdentityModel.Tokens;
|
|
||||||
using Scalar.AspNetCore;
|
using Scalar.AspNetCore;
|
||||||
using Serilog;
|
using Serilog;
|
||||||
|
|
||||||
@@ -47,39 +38,7 @@ try
|
|||||||
options.JsonSerializerOptions.Converters.Add(
|
options.JsonSerializerOptions.Converters.Add(
|
||||||
new System.Text.Json.Serialization.JsonStringEnumConverter()));
|
new System.Text.Json.Serialization.JsonStringEnumConverter()));
|
||||||
|
|
||||||
builder.Services.AddAuthentication()
|
builder.Services.AddCommonServices(builder.Configuration);
|
||||||
.AddScheme<AuthenticationSchemeOptions, EnvironmentKeyAuthenticationHandler>(
|
|
||||||
EnvironmentKeyAuthenticationHandler.SchemeName, _ => { })
|
|
||||||
.AddScheme<AuthenticationSchemeOptions, ApiKeyAuthenticationHandler>(
|
|
||||||
ApiKeyAuthenticationHandler.SchemeName, _ => { })
|
|
||||||
.AddJwtBearer(JwtBearerDefaults.AuthenticationScheme, options =>
|
|
||||||
{
|
|
||||||
options.TokenValidationParameters = new TokenValidationParameters
|
|
||||||
{
|
|
||||||
ValidateIssuer = true,
|
|
||||||
ValidateAudience = true,
|
|
||||||
ValidateIssuerSigningKey = true,
|
|
||||||
ValidIssuer = builder.Configuration["Jwt:Issuer"],
|
|
||||||
ValidAudience = builder.Configuration["Jwt:Audience"],
|
|
||||||
IssuerSigningKey = new SymmetricSecurityKey(
|
|
||||||
Encoding.UTF8.GetBytes(builder.Configuration["Jwt:SecretKey"]!))
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
builder.Services.AddAuthorization(options =>
|
|
||||||
{
|
|
||||||
options.AddPolicy(AuthorizationPolicies.FlagsApiAccess, policy =>
|
|
||||||
policy.AddAuthenticationSchemes(EnvironmentKeyAuthenticationHandler.SchemeName)
|
|
||||||
.RequireClaim("EnvironmentId"));
|
|
||||||
|
|
||||||
options.AddPolicy(AuthorizationPolicies.AdminApiAccess, policy =>
|
|
||||||
policy.AddAuthenticationSchemes(ApiKeyAuthenticationHandler.SchemeName, JwtBearerDefaults.AuthenticationScheme)
|
|
||||||
.RequireAuthenticatedUser());
|
|
||||||
|
|
||||||
options.AddPolicy(AuthorizationPolicies.OrganizationAdmin, policy =>
|
|
||||||
policy.AddAuthenticationSchemes(ApiKeyAuthenticationHandler.SchemeName, JwtBearerDefaults.AuthenticationScheme)
|
|
||||||
.RequireClaim("OrganizationRole", "Admin"));
|
|
||||||
});
|
|
||||||
|
|
||||||
builder.Services.AddHttpContextAccessor();
|
builder.Services.AddHttpContextAccessor();
|
||||||
|
|
||||||
@@ -92,58 +51,20 @@ try
|
|||||||
limiter.QueueLimit = 0;
|
limiter.QueueLimit = 0;
|
||||||
}));
|
}));
|
||||||
|
|
||||||
builder.Services.AddModelValidatorsFromAssemblyContaining<Program>();
|
|
||||||
|
|
||||||
builder.Services.Configure<ApiBehaviorOptions>(options =>
|
|
||||||
{
|
|
||||||
options.InvalidModelStateResponseFactory = context => ValidationProblemResponseFactory.Create(context.ModelState);
|
|
||||||
});
|
|
||||||
|
|
||||||
builder.Services.AddMemoryCache();
|
builder.Services.AddMemoryCache();
|
||||||
builder.Services.AddMetrics();
|
builder.Services.AddMetrics();
|
||||||
|
|
||||||
builder.Services.AddScoped<ITokenService, TokenService>();
|
builder.Services.AddAuditServices();
|
||||||
builder.Services.AddScoped<AuthService>();
|
builder.Services.AddIdentitiesServices();
|
||||||
builder.Services.AddScoped<ApiKeyService>();
|
builder.Services.AddEnvironmentsServices();
|
||||||
builder.Services.AddScoped<DatabaseSeeder>();
|
builder.Services.AddSegmentsServices();
|
||||||
builder.Services.AddScoped<IPasswordHasher<User>, PasswordHasher<User>>();
|
builder.Services.AddOrganizationsServices();
|
||||||
builder.Services.AddScoped<IAuthorizationHandler, ProjectPermissionRequirementHandler>();
|
builder.Services.AddProjectsServices();
|
||||||
builder.Services.AddScoped<FeatureEvaluationService>();
|
builder.Services.AddFeaturesServices();
|
||||||
builder.Services.AddScoped<IdentityResolutionService>();
|
builder.Services.AddUsersServices();
|
||||||
builder.Services.AddScoped<EnvironmentDocumentService>();
|
builder.Services.AddWebhooksServices();
|
||||||
builder.Services.AddSingleton<SegmentEvaluator>();
|
|
||||||
builder.Services.AddSingleton<FlagCache>();
|
|
||||||
builder.Services.AddScoped<IAuditService, AuditService>();
|
|
||||||
builder.Services.AddScoped<AuditLogQueryService>();
|
|
||||||
builder.Services.AddScoped<OrganizationService>();
|
|
||||||
builder.Services.AddScoped<ProjectService>();
|
|
||||||
builder.Services.AddScoped<EnvironmentService>();
|
|
||||||
builder.Services.AddScoped<FeatureService>();
|
|
||||||
builder.Services.AddScoped<FeatureStateService>();
|
|
||||||
builder.Services.AddScoped<FeatureSegmentService>();
|
|
||||||
builder.Services.AddScoped<SegmentService>();
|
|
||||||
builder.Services.AddScoped<TagService>();
|
|
||||||
builder.Services.AddScoped<WebhookService>();
|
|
||||||
builder.Services.AddScoped<WebhookDispatcher>();
|
|
||||||
builder.Services.AddScoped<AdminIdentityService>();
|
|
||||||
builder.Services.AddScoped<UserService>();
|
|
||||||
builder.Services.AddSingleton<WebhookQueue>();
|
|
||||||
builder.Services.AddHostedService<WebhookBackgroundService>();
|
|
||||||
builder.Services.AddHostedService<WebhookRetryBackgroundService>();
|
|
||||||
builder.Services.AddSingleton<FeatureUsageMetrics>();
|
|
||||||
builder.Services.AddScoped<FeatureUsageQueryService>();
|
|
||||||
builder.Services.AddHostedService<FeatureUsageFlushBackgroundService>();
|
|
||||||
builder.Services.AddHttpClient("Webhooks", client =>
|
|
||||||
client.DefaultRequestHeaders.Add("User-Agent", "MicCheck-Webhook/1.0"));
|
|
||||||
|
|
||||||
var connectionString = builder.Configuration.GetConnectionString("miccheck")
|
builder.Services.AddDataServices(builder.Configuration);
|
||||||
?? (System.Environment.GetEnvironmentVariable("DATABASE_URL") is { } databaseUrl
|
|
||||||
? DatabaseUrlParser.ToNpgsqlConnectionString(databaseUrl)
|
|
||||||
: builder.Configuration.GetConnectionString("DefaultConnection")!);
|
|
||||||
|
|
||||||
builder.Services.AddDbContext<MicCheckDbContext>(options =>
|
|
||||||
options.UseNpgsql(connectionString));
|
|
||||||
builder.Services.AddScoped<IMicCheckDbContext>(sp => sp.GetRequiredService<MicCheckDbContext>());
|
|
||||||
|
|
||||||
var app = builder.Build();
|
var app = builder.Build();
|
||||||
|
|
||||||
|
|||||||
11
src/api/MicCheck.Api/Projects/DependencyRegistration.cs
Normal file
11
src/api/MicCheck.Api/Projects/DependencyRegistration.cs
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
namespace MicCheck.Api.Projects;
|
||||||
|
|
||||||
|
public static class DependencyRegistration
|
||||||
|
{
|
||||||
|
public static IServiceCollection AddProjectsServices(this IServiceCollection services)
|
||||||
|
{
|
||||||
|
services.AddScoped<ProjectService>();
|
||||||
|
|
||||||
|
return services;
|
||||||
|
}
|
||||||
|
}
|
||||||
12
src/api/MicCheck.Api/Segments/DependencyRegistration.cs
Normal file
12
src/api/MicCheck.Api/Segments/DependencyRegistration.cs
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
namespace MicCheck.Api.Segments;
|
||||||
|
|
||||||
|
public static class DependencyRegistration
|
||||||
|
{
|
||||||
|
public static IServiceCollection AddSegmentsServices(this IServiceCollection services)
|
||||||
|
{
|
||||||
|
services.AddSingleton<SegmentEvaluator>();
|
||||||
|
services.AddScoped<SegmentService>();
|
||||||
|
|
||||||
|
return services;
|
||||||
|
}
|
||||||
|
}
|
||||||
14
src/api/MicCheck.Api/Users/DependencyRegistration.cs
Normal file
14
src/api/MicCheck.Api/Users/DependencyRegistration.cs
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
using Microsoft.AspNetCore.Identity;
|
||||||
|
|
||||||
|
namespace MicCheck.Api.Users;
|
||||||
|
|
||||||
|
public static class DependencyRegistration
|
||||||
|
{
|
||||||
|
public static IServiceCollection AddUsersServices(this IServiceCollection services)
|
||||||
|
{
|
||||||
|
services.AddScoped<UserService>();
|
||||||
|
services.AddScoped<IPasswordHasher<User>, PasswordHasher<User>>();
|
||||||
|
|
||||||
|
return services;
|
||||||
|
}
|
||||||
|
}
|
||||||
17
src/api/MicCheck.Api/Webhooks/DependencyRegistration.cs
Normal file
17
src/api/MicCheck.Api/Webhooks/DependencyRegistration.cs
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
namespace MicCheck.Api.Webhooks;
|
||||||
|
|
||||||
|
public static class DependencyRegistration
|
||||||
|
{
|
||||||
|
public static IServiceCollection AddWebhooksServices(this IServiceCollection services)
|
||||||
|
{
|
||||||
|
services.AddScoped<WebhookService>();
|
||||||
|
services.AddScoped<WebhookDispatcher>();
|
||||||
|
services.AddSingleton<WebhookQueue>();
|
||||||
|
services.AddHostedService<WebhookBackgroundService>();
|
||||||
|
services.AddHostedService<WebhookRetryBackgroundService>();
|
||||||
|
services.AddHttpClient("Webhooks", client =>
|
||||||
|
client.DefaultRequestHeaders.Add("User-Agent", "MicCheck-Webhook/1.0"));
|
||||||
|
|
||||||
|
return services;
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user