Đầu tiên, hệ thống khởi tạo thông tin dịch vụ từ assembly đang chạy:
var entryAssembly = Assembly.GetExecutingAssembly();
var baseName = entryAssembly.GetName().Name ?? string.Empty;
var suffix = baseName.Split('.').Last();
var migrationTarget = baseName.Replace($".{suffix}", ".Repository");
var serviceMetadata = ServiceMetadataFactory.Create(entryAssembly, migrationTarget);
Lớp ServiceMetadata sử dụng singleton pattern để quản lý thông tin dịch vụ toàn cục:
namespace Adnc.Shared.WebApi;
public class ServiceMetadata : IServiceMetadata
{
private static readonly Lazy<ServiceMetadata> _lazyInstance = new(() => new ServiceMetadata());
public string UniqueId { get; private set; } = string.Empty;
public string DisplayName { get; private set; } = string.Empty;
public string EnvironmentTag { get; private set; } = string.Empty;
public string ApiPrefix { get; private set; } = string.Empty;
public Assembly EntryPoint { get; private set; } = default!;
public string MigrationTarget { get; private set; } = string.Empty;
private ServiceMetadata() { }
public static ServiceMetadata Create(Assembly entryPoint, string? migrationTarget = null)
{
var instance = _lazyInstance.Value;
if (entryPoint == null)
throw new ArgumentNullException(nameof(entryPoint));
var versionInfo = entryPoint.GetName().Version ?? Version.Parse("0.0.0.0");
var assemblyName = entryPoint.GetName().Name ?? "unknown";
var displayName = assemblyName.Replace(".", "-").ToLowerInvariant();
var env = Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT")?.ToLowerInvariant() ?? "development";
var timestamp = DateTime.UtcNow.Ticks.ToString("x");
instance.UniqueId = $"{displayName}-{env}-{timestamp}";
instance.DisplayName = displayName;
instance.EnvironmentTag = env;
instance.ApiPrefix = string.Join("/", assemblyName.Split('.').TakeLast(2)).ToLowerInvariant();
instance.EntryPoint = entryPoint;
instance.MigrationTarget = migrationTarget ?? assemblyName.Replace($".{assemblyName.Split('.').Last()}", ".Migrations");
return instance;
}
}
Sau khi có metadata, ứng dụng gọi phương thức mở rộng để cấu hình mặc định:
var builder = WebApplication.CreateBuilder(args)
.ConfigureAdncDefaults(serviceMetadata)
.Build();
Phương thức ConfigureAdncDefaults sẽ kích hoạt quá trình đăng ký dịch vụ:
public static class WebApplicationBuilderExtensions
{
public static WebApplicationBuilder ConfigureAdncDefaults(this WebApplicationBuilder builder, IServiceMetadata metadata)
{
builder.Services.RegisterAdncCore(metadata);
return builder;
}
}
Trong đó, RegisterAdncCore sử dụng reflection để tìm và khởi tạo registrar cụ thể cho từng module:
public static class ServiceCollectionExtensions
{
public static IServiceCollection RegisterAdncCore(this IServiceCollection services, IServiceMetadata metadata)
{
var registrarType = metadata.EntryPoint.ExportedTypes
.FirstOrDefault(t => t.IsAssignableTo(typeof(IModuleRegistrar))
&& t.IsAssignableTo(typeof(WebApiModuleRegistrarBase))
&& !t.IsAbstract);
if (registrarType == null)
throw new InvalidOperationException("Không tìm thấy WebApi registrar");
var registrar = Activator.CreateInstance(registrarType, services) as IModuleRegistrar;
registrar?.RegisterServices();
return services;
}
}
Ví dụ với module User, lớp UserApiRegistrar sẽ kế thừa và ghi đè phương thức đăng ký:
public sealed class UserApiRegistrar : WebApiModuleRegistrarBase
{
public UserApiRegistrar(IServiceCollection services) : base(services) { }
public override void RegisterServices()
{
RegisterWebApiDefaults<JwtAuthHandler, RolePermissionValidator>();
EnableHealthChecks(enableRedis: true, enableDb: true);
Services.AddGrpc();
}
public override void ConfigurePipeline()
{
ConfigureWebApiDefaults(endpoints =>
{
endpoints.MapGrpcService<UserGrpcService>();
endpoints.MapGrpcService<AuthService>();
});
}
}
Phương thức RegisterWebApiDefaults sẽ tiếp tục gọi đến việc đăng ký các dịch vụ ứng dụng:
protected virtual void RegisterWebApiDefaults<TAuth, TPerm>()
where TAuth : AuthenticationHandlerBase
where TPerm : PermissionValidatorBase
{
Services.AddControllers();
Services.AddHttpContextAccessor();
RegisterApplicationLayer();
RegisterAuthentication<TAuth>();
RegisterAuthorization<TPerm>();
RegisterCorsPolicy();
if (Configuration.GetValue<bool>("Swagger:Enabled", true))
{
RegisterSwagger();
RegisterProfiler();
}
}
Quá trình đăng ký tầng Application được thực hiện qua reflection tương tự:
protected virtual void RegisterApplicationLayer()
{
var appAssembly = ServiceMetadata.Current.GetApplicationAssembly();
if (appAssembly != null)
{
var appRegistrarType = appAssembly.ExportedTypes
.FirstOrDefault(t => t.IsAssignableTo(typeof(IModuleRegistrar))
&& !t.IsAssignableTo(typeof(WebApiModuleRegistrarBase))
&& !t.IsAbstract);
if (appRegistrarType != null)
{
var appRegistrar = Activator.CreateInstance(appRegistrarType, Services) as IModuleRegistrar;
appRegistrar?.RegisterServices();
}
}
}
Phương thức GetApplicationAssembly xác định assembly ứng dụng bằng cách thay đổi hậu tố tên:
public static Assembly GetApplicationAssembly(this IServiceMetadata metadata)
{
var entryName = metadata.EntryPoint.GetName().Name ?? string.Empty;
var appName = entryName.Replace(".WebApi", ".Application");
return AppDomain.CurrentDomain.GetAssemblies()
.FirstOrDefault(a => a.GetName().Name == appName)
?? metadata.EntryPoint;
}
Lớp ApplicationRegistrar trong module User sẽ đăng ký các thành phần cốt lõi:
public sealed class ApplicationRegistrar : ApplicationModuleRegistrarBase
{
public override Assembly ApplicationAssembly => Assembly.GetExecutingAssembly();
public override Assembly ContractAssembly => typeof(IUserService).Assembly;
public override Assembly DomainAssembly => typeof(DomainEntity).Assembly;
public ApplicationRegistrar(IServiceCollection services) : base(services) { }
public override void RegisterServices() => RegisterApplicationDefaults();
}
Phương thức RegisterApplicationDefaults thiết lập toàn bộ infrastructure cần thiết:
protected virtual void RegisterApplicationDefaults()
{
Services.AddValidatorsFromAssembly(ContractAssembly)
.AddAutoMapperProfiles(ApplicationAssembly)
.AddDistributedIdGenerator(RedisConfig)
.AddServiceDiscovery(ConsulConfig)
.AddDatabaseAccess();
RegisterSharedServices();
RegisterInterceptedServices();
RegisterBackgroundServices();
RegisterDbContextAndRepositories();
RegisterDocumentStore();
RegisterCacheLayer();
}
Cuối cùng, việc đăng ký EF Core và repository được thực hiện như sau:
protected virtual void RegisterDbContextAndRepositories()
{
var entityInfoType = DomainAssembly.ExportedTypes
.FirstOrDefault(t => t.IsAssignableTo(typeof(IEntityMetadata)) && !t.IsAbstract);
if (entityInfoType != null)
Services.AddScoped(typeof(IEntityMetadata), entityInfoType);
RegisterDbContext();
}
protected virtual void RegisterDbContext()
{
var dbConfig = MySqlConfig.Get<DatabaseOptions>();
var serverVersion = new MariaDbServerVersion(new Version(10, 5, 4));
Services.AddDbContext<ApplicationDbContext>(options =>
{
options.UseMySql(dbConfig.ConnectionString, serverVersion, mysqlOptions =>
{
mysqlOptions.MigrationsAssembly(ServiceMetadata.Current.MigrationTarget)
.UseQuerySplittingBehavior(QuerySplittingBehavior.SplitQuery);
});
});
Services.AddScoped<IUnitOfWork, DatabaseUnitOfWork<ApplicationDbContext>>();
Services.AddScoped(typeof(IRepository<>), typeof(EfCoreRepository<>));
}
Toàn bộ luồng khởi tạo diễn ra theo chuỗi: Web API → Application Layer → Infrastructure Layer, với mỗi tầng đều sử dụng reflection để tự động phát hiện và đăng ký các thành phần cần thiết.