Developer Kits düzenlemesi

This commit is contained in:
Sedat ÖZTÜRK 2025-11-05 16:17:10 +03:00
parent ce84b3baac
commit 2ec7f085e4
8 changed files with 397 additions and 137 deletions

View file

@ -68,6 +68,12 @@ public class CompileResultDto
public bool HasWarnings { get; set; } public bool HasWarnings { get; set; }
[JsonPropertyName("warnings")] [JsonPropertyName("warnings")]
public List<string> Warnings { get; set; } public List<string> Warnings { get; set; }
/// <summary>
/// Yüklenen assembly (sadece server-side kullanım için, JSON'a serialize edilmez)
/// </summary>
[System.Text.Json.Serialization.JsonIgnore]
public System.Reflection.Assembly LoadedAssembly { get; set; }
} }
/// <summary> /// <summary>

View file

@ -30,16 +30,9 @@ public class DynamicAppServiceAppService : PlatformAppService, IDynamicServiceAp
[Authorize(AppCodes.DeveloperKits.DynamicServices.TestCompile)] [Authorize(AppCodes.DeveloperKits.DynamicServices.TestCompile)]
public virtual async Task<CompileResultDto> TestCompileAsync(TestCompileRequestDto request) public virtual async Task<CompileResultDto> TestCompileAsync(TestCompileRequestDto request)
{ {
Logger.LogInformation("Test compile başlatıldı. Tenant: {TenantId}", CurrentTenant.Id);
try try
{ {
var result = await _compiler.CompileAndValidateAsync(request.Code, CurrentTenant.Id); return await _compiler.CompileAndValidateAsync(request.Code, CurrentTenant.Id);
Logger.LogInformation("Test compile tamamlandı. Başarılı: {Success}, Tenant: {TenantId}",
result.Success, CurrentTenant.Id);
return result;
} }
catch (Exception ex) catch (Exception ex)
{ {
@ -51,9 +44,6 @@ public class DynamicAppServiceAppService : PlatformAppService, IDynamicServiceAp
[Authorize(AppCodes.DeveloperKits.DynamicServices.Publish)] [Authorize(AppCodes.DeveloperKits.DynamicServices.Publish)]
public virtual async Task<PublishResultDto> PublishAsync(PublishAppServiceRequestDto request) public virtual async Task<PublishResultDto> PublishAsync(PublishAppServiceRequestDto request)
{ {
Logger.LogInformation("AppService yayınlama başlatıldı. Ad: {Name}, Tenant: {TenantId}",
request.Name, CurrentTenant.Id);
try try
{ {
// Önce kodu test compile et // Önce kodu test compile et
@ -82,11 +72,9 @@ public class DynamicAppServiceAppService : PlatformAppService, IDynamicServiceAp
existingService.PrimaryEntityType = request.PrimaryEntityType; existingService.PrimaryEntityType = request.PrimaryEntityType;
appService = await _dynamicAppServiceRepository.UpdateAsync(existingService); appService = await _dynamicAppServiceRepository.UpdateAsync(existingService);
Logger.LogInformation("Mevcut AppService güncellendi. ID: {Id}", appService.Id);
} }
else else
{ {
// Yeni servis oluştur
appService = new DynamicService( appService = new DynamicService(
GuidGenerator.Create(), GuidGenerator.Create(),
request.Name, request.Name,
@ -100,11 +88,9 @@ public class DynamicAppServiceAppService : PlatformAppService, IDynamicServiceAp
}; };
appService = await _dynamicAppServiceRepository.InsertAsync(appService); appService = await _dynamicAppServiceRepository.InsertAsync(appService);
Logger.LogInformation("Yeni AppService oluşturuldu. ID: {Id}", appService.Id);
} }
// Runtime'da derle ve yükle var assemblyName = $"{appService.Name}_{appService.Version}";
var assemblyName = $"DynamicAppService_{appService.Id}_{appService.Version}";
var loadResult = await _compiler.CompileAndRegisterForTenantAsync( var loadResult = await _compiler.CompileAndRegisterForTenantAsync(
CurrentTenant.Id ?? Guid.Empty, CurrentTenant.Id ?? Guid.Empty,
request.Code, request.Code,
@ -112,23 +98,21 @@ public class DynamicAppServiceAppService : PlatformAppService, IDynamicServiceAp
if (loadResult.Success) if (loadResult.Success)
{ {
// Başarılı derleme durumunu kaydet
appService.MarkCompilationSuccess(); appService.MarkCompilationSuccess();
await _dynamicAppServiceRepository.UpdateAsync(appService); await _dynamicAppServiceRepository.UpdateAsync(appService);
var result = new PublishResultDto var endpoints = loadResult.LoadedAssembly != null
? _compiler.ExtractEndpointsFromAssembly(loadResult.LoadedAssembly, request.Name)
: new List<string>();
return new PublishResultDto
{ {
Success = true, Success = true,
AppServiceId = appService.Id, AppServiceId = appService.Id,
ControllerName = appService.ControllerName, ControllerName = appService.ControllerName,
SwaggerUrl = "/swagger/index.html", SwaggerUrl = "/swagger/index.html",
GeneratedEndpoints = GenerateEndpointList(request.Name) GeneratedEndpoints = endpoints
}; };
Logger.LogInformation("AppService başarıyla yayınlandı. ID: {Id}, Controller: {Controller}",
appService.Id, appService.ControllerName);
return result;
} }
else else
{ {
@ -178,59 +162,38 @@ public class DynamicAppServiceAppService : PlatformAppService, IDynamicServiceAp
var dtos = ObjectMapper.Map<List<DynamicService>, List<DynamicServiceDto>>(items); var dtos = ObjectMapper.Map<List<DynamicService>, List<DynamicServiceDto>>(items);
Logger.LogDebug("AppService listesi döndürüldü. Toplam: {Total}, Dönen: {Count}",
totalCount, dtos.Count);
return new PagedResultDto<DynamicServiceDto>(totalCount, dtos); return new PagedResultDto<DynamicServiceDto>(totalCount, dtos);
} }
[Authorize(AppCodes.DeveloperKits.DynamicServices.ViewCode)] [Authorize(AppCodes.DeveloperKits.DynamicServices.ViewCode)]
public virtual async Task<DynamicServiceDto> GetAsync(Guid id) public virtual async Task<DynamicServiceDto> GetAsync(Guid id)
{ {
Logger.LogDebug("AppService detayı istendi. ID: {Id}, Tenant: {TenantId}", id, CurrentTenant.Id);
var appService = await _dynamicAppServiceRepository.GetAsync(id); var appService = await _dynamicAppServiceRepository.GetAsync(id);
var dto = ObjectMapper.Map<DynamicService, DynamicServiceDto>(appService); return ObjectMapper.Map<DynamicService, DynamicServiceDto>(appService);
return dto;
} }
[Authorize(AppCodes.DeveloperKits.DynamicServices.Delete)] [Authorize(AppCodes.DeveloperKits.DynamicServices.Delete)]
public virtual async Task DeleteAsync(Guid id) public virtual async Task DeleteAsync(Guid id)
{ {
Logger.LogInformation("AppService silme işlemi başlatıldı. ID: {Id}, Tenant: {TenantId}",
id, CurrentTenant.Id);
var appService = await _dynamicAppServiceRepository.GetAsync(id); var appService = await _dynamicAppServiceRepository.GetAsync(id);
// TODO: Runtime'dan assembly'yi kaldırma işlemi // TODO: Runtime'dan assembly'yi kaldırma işlemi
// (AssemblyLoadContext.Unload() çağrısı) // (AssemblyLoadContext.Unload() çağrısı)
await _dynamicAppServiceRepository.DeleteAsync(id); await _dynamicAppServiceRepository.DeleteAsync(id);
Logger.LogInformation("AppService silindi. ID: {Id}", id);
} }
[Authorize(AppCodes.DeveloperKits.DynamicServices.Manage)] [Authorize(AppCodes.DeveloperKits.DynamicServices.Manage)]
public virtual async Task SetActiveAsync(Guid id, bool isActive) public virtual async Task SetActiveAsync(Guid id, bool isActive)
{ {
Logger.LogInformation("AppService aktiflik durumu değiştiriliyor. ID: {Id}, Aktif: {Active}",
id, isActive);
var appService = await _dynamicAppServiceRepository.GetAsync(id); var appService = await _dynamicAppServiceRepository.GetAsync(id);
appService.IsActive = isActive; appService.IsActive = isActive;
await _dynamicAppServiceRepository.UpdateAsync(appService); await _dynamicAppServiceRepository.UpdateAsync(appService);
Logger.LogInformation("AppService aktiflik durumu güncellendi. ID: {Id}, Aktif: {Active}",
id, isActive);
} }
[Authorize(AppCodes.DeveloperKits.DynamicServices.Manage)] [Authorize(AppCodes.DeveloperKits.DynamicServices.Manage)]
public virtual async Task ReloadAllActiveServicesAsync() public virtual async Task ReloadAllActiveServicesAsync()
{ {
Logger.LogInformation("Tüm aktif AppService'ler yeniden yükleniyor. Tenant: {TenantId}",
CurrentTenant.Id);
var activeServices = await _dynamicAppServiceRepository var activeServices = await _dynamicAppServiceRepository
.GetListAsync(x => x.IsActive && x.CompilationStatus == CompilationStatus.Success); .GetListAsync(x => x.IsActive && x.CompilationStatus == CompilationStatus.Success);
@ -242,7 +205,8 @@ public class DynamicAppServiceAppService : PlatformAppService, IDynamicServiceAp
{ {
try try
{ {
var assemblyName = $"DynamicAppService_{service.Id}_{service.Version}"; // Service.Name üzerinden assembly adı oluştur
var assemblyName = $"{service.Name}_{service.Version}";
var result = await _compiler.CompileAndRegisterForTenantAsync( var result = await _compiler.CompileAndRegisterForTenantAsync(
CurrentTenant.Id ?? Guid.Empty, CurrentTenant.Id ?? Guid.Empty,
service.Code, service.Code,
@ -251,17 +215,12 @@ public class DynamicAppServiceAppService : PlatformAppService, IDynamicServiceAp
if (result.Success) if (result.Success)
{ {
successCount++; successCount++;
Logger.LogDebug("AppService yeniden yüklendi. ID: {Id}, Ad: {Name}",
service.Id, service.Name);
} }
else else
{ {
errorCount++; errorCount++;
service.MarkCompilationError(result.ErrorMessage); service.MarkCompilationError(result.ErrorMessage);
await _dynamicAppServiceRepository.UpdateAsync(service); await _dynamicAppServiceRepository.UpdateAsync(service);
Logger.LogWarning("AppService yeniden yüklenemedi. ID: {Id}, Hata: {Error}",
service.Id, result.ErrorMessage);
} }
} }
catch (Exception ex) catch (Exception ex)
@ -273,19 +232,14 @@ public class DynamicAppServiceAppService : PlatformAppService, IDynamicServiceAp
await _dynamicAppServiceRepository.UpdateAsync(service); await _dynamicAppServiceRepository.UpdateAsync(service);
} }
} }
Logger.LogInformation("AppService yeniden yükleme tamamlandı. Başarılı: {Success}, Hatalı: {Error}, Toplam: {Total}",
successCount, errorCount, activeServices.Count);
} }
[Authorize(AppCodes.DeveloperKits.DynamicServices.Manage)] [Authorize(AppCodes.DeveloperKits.DynamicServices.Manage)]
public virtual async Task<CompileResultDto> RecompileAsync(Guid id) public virtual async Task<CompileResultDto> RecompileAsync(Guid id)
{ {
Logger.LogInformation("AppService yeniden derleniyor. ID: {Id}", id);
var appService = await _dynamicAppServiceRepository.GetAsync(id); var appService = await _dynamicAppServiceRepository.GetAsync(id);
var assemblyName = $"DynamicAppService_{appService.Id}_{appService.Version + 1}"; var assemblyName = $"{appService.Name}_{appService.Version + 1}";
var result = await _compiler.CompileAndRegisterForTenantAsync( var result = await _compiler.CompileAndRegisterForTenantAsync(
CurrentTenant.Id ?? Guid.Empty, CurrentTenant.Id ?? Guid.Empty,
appService.Code, appService.Code,
@ -303,9 +257,6 @@ public class DynamicAppServiceAppService : PlatformAppService, IDynamicServiceAp
await _dynamicAppServiceRepository.UpdateAsync(appService); await _dynamicAppServiceRepository.UpdateAsync(appService);
Logger.LogInformation("AppService yeniden derleme tamamlandı. ID: {Id}, Başarılı: {Success}",
id, result.Success);
return result; return result;
} }
@ -324,16 +275,4 @@ public class DynamicAppServiceAppService : PlatformAppService, IDynamicServiceAp
return controllerName; return controllerName;
} }
private List<string> GenerateEndpointList(string serviceName)
{
var controllerName = GenerateControllerName(serviceName);
return
[
$"/api/app/{controllerName.ToLowerInvariant()}",
$"/api/app/{controllerName.ToLowerInvariant()}/{{id}}",
$"/api/app/{controllerName.ToLowerInvariant()}/list"
];
}
} }

View file

@ -178,30 +178,21 @@ public class DynamicServiceCompiler : ITransientDependency
var assembly = loadContext.LoadFromStream(ms); var assembly = loadContext.LoadFromStream(ms);
// ApplicationService türevlerini bul
var appServiceTypes = assembly.GetTypes() var appServiceTypes = assembly.GetTypes()
.Where(t => IsApplicationServiceType(t)) .Where(t => IsApplicationServiceType(t))
.ToList(); .ToList();
_logger.LogInformation("Tenant {TenantId} için {Count} ApplicationService bulundu",
tenantId, appServiceTypes.Count);
// Tenant assembly'leri listesine ekle
_tenantAssemblies.AddOrUpdate(tenantId, _tenantAssemblies.AddOrUpdate(tenantId,
new List<Assembly> { assembly }, new List<Assembly> { assembly },
(key, existing) => { existing.Add(assembly); return existing; }); (key, existing) => { existing.Add(assembly); return existing; });
// Background service'e assembly kaydı isteği gönder
// Bu, DI container ve MVC conventional controller sistemine kaydetmek için
NotifyAssemblyRegistration?.Invoke(tenantId, assembly, assemblyName); NotifyAssemblyRegistration?.Invoke(tenantId, assembly, assemblyName);
_logger.LogInformation("Tenant {TenantId} için assembly başarıyla yüklendi: {AssemblyName}",
tenantId, assemblyName);
return new CompileResultDto return new CompileResultDto
{ {
Success = true, Success = true,
CompilationTimeMs = validateResult.CompilationTimeMs CompilationTimeMs = validateResult.CompilationTimeMs,
LoadedAssembly = assembly
}; };
} }
catch (Exception ex) catch (Exception ex)
@ -331,12 +322,9 @@ public class DynamicServiceCompiler : ITransientDependency
{ {
references.Add(MetadataReference.CreateFromFile(assembly.Location)); references.Add(MetadataReference.CreateFromFile(assembly.Location));
} }
_logger.LogDebug("Toplam {Count} referans eklendi", references.Count);
} }
catch (Exception ex) catch
{ {
_logger.LogWarning(ex, "ABP referansları eklenirken hata");
} }
return references; return references;
@ -355,9 +343,8 @@ public class DynamicServiceCompiler : ITransientDependency
(type.Name.EndsWith("AppService") || type.Name.EndsWith("ApplicationService")) && (type.Name.EndsWith("AppService") || type.Name.EndsWith("ApplicationService")) &&
HasApplicationServiceBase(type); HasApplicationServiceBase(type);
} }
catch (Exception ex) catch
{ {
_logger.LogWarning(ex, "Tip kontrolü sırasında hata: {Type}", type?.FullName);
return false; return false;
} }
} }
@ -394,4 +381,109 @@ public class DynamicServiceCompiler : ITransientDependency
} }
return 1; return 1;
} }
/// <summary>
/// Assembly içindeki ApplicationService'lerden endpoint listesini çıkarır
/// </summary>
public List<string> ExtractEndpointsFromAssembly(Assembly assembly, string serviceName)
{
var endpoints = new List<string>();
try
{
// Assembly içindeki ApplicationService türlerini bul
var appServiceTypes = assembly.GetTypes()
.Where(t => IsApplicationServiceType(t) && t.Name == serviceName)
.ToList();
foreach (var serviceType in appServiceTypes)
{
// Controller adını oluştur (ABP convention: CustomerAppService -> Customer)
var controllerName = serviceType.Name;
if (controllerName.EndsWith("AppService"))
{
controllerName = controllerName.Substring(0, controllerName.Length - "AppService".Length);
}
else if (controllerName.EndsWith("ApplicationService"))
{
controllerName = controllerName.Substring(0, controllerName.Length - "ApplicationService".Length);
}
// ABP kebab-case convention (DynamicCustomer -> dynamic-customer)
var routePrefix = ToKebabCase(controllerName);
// Public method'ları bul (async method'lar genelde Async suffix'i ile biter)
var methods = serviceType.GetMethods(BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly)
.Where(m => !m.IsSpecialName) // Property getter/setter'ları hariç tut
.ToList();
foreach (var method in methods)
{
// Method adından Async suffix'ini kaldır ve kebab-case'e çevir
var methodName = method.Name;
if (methodName.EndsWith("Async"))
{
methodName = methodName.Substring(0, methodName.Length - "Async".Length);
}
var methodRoute = ToKebabCase(methodName);
// HTTP verb'ü belirle (basit heuristic)
var httpVerb = DetermineHttpVerb(method);
// Endpoint'i oluştur
var endpoint = $"{httpVerb} /api/app/{routePrefix}/{methodRoute}";
endpoints.Add(endpoint);
}
}
}
catch (Exception ex)
{
_logger.LogError(ex, "Endpoint çıkarma sırasında hata");
}
return endpoints;
}
/// <summary>
/// PascalCase'i kebab-case'e çevirir (DynamicCustomer -> dynamic-customer)
/// </summary>
private string ToKebabCase(string value)
{
if (string.IsNullOrEmpty(value))
return value;
return string.Concat(
value.Select((x, i) => i > 0 && char.IsUpper(x)
? "-" + x.ToString()
: x.ToString())
).ToLower();
}
/// <summary>
/// Method'un HTTP verb'ünü belirler (ABP convention'a göre)
/// </summary>
private string DetermineHttpVerb(MethodInfo method)
{
var methodName = method.Name.ToLowerInvariant();
// ABP naming convention
if (methodName.StartsWith("get") || methodName.StartsWith("find") || methodName.StartsWith("list"))
return "GET";
if (methodName.StartsWith("create") || methodName.StartsWith("insert") || methodName.StartsWith("add"))
return "POST";
if (methodName.StartsWith("update") || methodName.StartsWith("edit"))
return "PUT";
if (methodName.StartsWith("delete") || methodName.StartsWith("remove"))
return "DELETE";
// Default olarak GET (ABP'de parametre yoksa GET, varsa POST)
var parameters = method.GetParameters();
if (parameters.Length == 0 || parameters.All(p => p.ParameterType.IsValueType || p.ParameterType == typeof(string)))
{
return "GET";
}
return "POST";
}
} }

View file

@ -0,0 +1,47 @@
using System;
using System.Threading;
using Microsoft.AspNetCore.Mvc.Infrastructure;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Primitives;
namespace Kurs.Platform.DynamicServices;
/// <summary>
/// Provides a change token to notify MVC infrastructure (and Swagger/ApiExplorer)
/// that action descriptors have changed at runtime.
/// </summary>
public class ActionDescriptorChangeProvider : IActionDescriptorChangeProvider
{
private readonly ILogger<ActionDescriptorChangeProvider> _logger;
private CancellationTokenSource _cts = new CancellationTokenSource();
public ActionDescriptorChangeProvider(ILogger<ActionDescriptorChangeProvider> logger)
{
_logger = logger;
}
public IChangeToken GetChangeToken()
{
_logger.LogDebug("GetChangeToken çağrıldı");
return new CancellationChangeToken(_cts.Token);
}
/// <summary>
/// Call this method after adding/removing controllers at runtime to notify
/// MVC/Swagger that action descriptors must be refreshed.
/// </summary>
public void NotifyChanges()
{
try
{
_logger.LogInformation("NotifyChanges çağrıldı - MVC/Swagger yenilenecek");
var prev = Interlocked.Exchange(ref _cts, new CancellationTokenSource());
prev.Cancel();
_logger.LogInformation("Change token başarıyla iptal edildi - yenileme tetiklendi");
}
catch (Exception ex)
{
_logger.LogError(ex, "NotifyChanges sırasında hata");
}
}
}

View file

@ -23,6 +23,7 @@ namespace Kurs.Platform.DynamicServices
private readonly ILogger<DynamicAssemblyRegistrationService> _logger; private readonly ILogger<DynamicAssemblyRegistrationService> _logger;
private readonly ApplicationPartManager _partManager; private readonly ApplicationPartManager _partManager;
private readonly IOptions<AbpAspNetCoreMvcOptions> _mvcOptions; private readonly IOptions<AbpAspNetCoreMvcOptions> _mvcOptions;
private readonly ActionDescriptorChangeProvider _changeProvider;
// Bekleyen assembly kayıt istekleri // Bekleyen assembly kayıt istekleri
private static readonly Queue<AssemblyRegistrationRequest> _pendingRegistrations = new(); private static readonly Queue<AssemblyRegistrationRequest> _pendingRegistrations = new();
@ -32,12 +33,14 @@ namespace Kurs.Platform.DynamicServices
IServiceProvider serviceProvider, IServiceProvider serviceProvider,
ILogger<DynamicAssemblyRegistrationService> logger, ILogger<DynamicAssemblyRegistrationService> logger,
ApplicationPartManager partManager, ApplicationPartManager partManager,
IOptions<AbpAspNetCoreMvcOptions> mvcOptions) IOptions<AbpAspNetCoreMvcOptions> mvcOptions,
ActionDescriptorChangeProvider changeProvider)
{ {
_serviceProvider = serviceProvider; _serviceProvider = serviceProvider;
_logger = logger; _logger = logger;
_partManager = partManager; _partManager = partManager;
_mvcOptions = mvcOptions; _mvcOptions = mvcOptions;
_changeProvider = changeProvider;
} }
/// <summary> /// <summary>
@ -59,15 +62,15 @@ namespace Kurs.Platform.DynamicServices
protected override async Task ExecuteAsync(CancellationToken stoppingToken) protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{ {
_logger.LogInformation("Dynamic Assembly Registration Service başlatıldı"); await Task.Delay(3000, stoppingToken);
await LoadExistingServicesOnStartup();
while (!stoppingToken.IsCancellationRequested) while (!stoppingToken.IsCancellationRequested)
{ {
try try
{ {
await ProcessPendingRegistrations(); await ProcessPendingRegistrations();
// 5 saniye bekle
await Task.Delay(5000, stoppingToken); await Task.Delay(5000, stoppingToken);
} }
catch (Exception ex) catch (Exception ex)
@ -75,8 +78,44 @@ namespace Kurs.Platform.DynamicServices
_logger.LogError(ex, "Assembly registration service hatası"); _logger.LogError(ex, "Assembly registration service hatası");
} }
} }
}
_logger.LogInformation("Dynamic Assembly Registration Service durduruluyor"); private async Task LoadExistingServicesOnStartup()
{
try
{
using var scope = _serviceProvider.CreateScope();
var repository = scope.ServiceProvider.GetService<Volo.Abp.Domain.Repositories.IRepository<Kurs.Platform.Entities.DynamicService, Guid>>();
var compiler = scope.ServiceProvider.GetService<Kurs.Platform.DynamicServices.DynamicServiceCompiler>();
if (repository == null || compiler == null)
{
return;
}
var activeServices = await repository.GetListAsync(x => x.IsActive && x.CompilationStatus == Kurs.Platform.Entities.CompilationStatus.Success);
foreach (var service in activeServices)
{
try
{
var assemblyName = $"{service.Name}_{service.Version}";
await compiler.CompileAndRegisterForTenantAsync(
service.TenantId ?? Guid.Empty,
service.Code,
assemblyName);
}
catch (Exception ex)
{
_logger.LogError(ex, "Servis yükleme hatası: {Name}", service.Name);
}
}
}
catch (Exception ex)
{
_logger.LogError(ex, "Startup servis yükleme hatası");
}
} }
private async Task ProcessPendingRegistrations() private async Task ProcessPendingRegistrations()
@ -94,8 +133,6 @@ namespace Kurs.Platform.DynamicServices
if (requests.Count == 0) if (requests.Count == 0)
return; return;
_logger.LogDebug("İşlenecek {Count} assembly kaydı var", requests.Count);
foreach (var request in requests) foreach (var request in requests)
{ {
try try
@ -112,43 +149,86 @@ namespace Kurs.Platform.DynamicServices
private async Task RegisterAssembly(AssemblyRegistrationRequest request) private async Task RegisterAssembly(AssemblyRegistrationRequest request)
{ {
_logger.LogInformation("Assembly kaydediliyor. Tenant: {TenantId}, Assembly: {Assembly}", var lastUnderscoreIndex = request.AssemblyName.LastIndexOf('_');
request.TenantId, request.AssemblyName); if (lastUnderscoreIndex > 0)
{
var servicePrefix = request.AssemblyName.Substring(0, lastUnderscoreIndex + 1);
var oldParts = _partManager.ApplicationParts
.OfType<AssemblyPart>()
.Where(p => p.Name.StartsWith(servicePrefix) && p.Name != request.AssemblyName)
.ToList();
foreach (var oldPart in oldParts)
{
_partManager.ApplicationParts.Remove(oldPart);
}
var oldSettings = _mvcOptions.Value.ConventionalControllers.ConventionalControllerSettings
.Where(s => s.Assembly.GetName().Name?.StartsWith(servicePrefix) == true &&
s.Assembly.GetName().Name != request.AssemblyName)
.ToList();
foreach (var oldSetting in oldSettings)
{
_mvcOptions.Value.ConventionalControllers.ConventionalControllerSettings.Remove(oldSetting);
}
}
// ApplicationService türlerini bul
var appServiceTypes = request.Assembly.GetTypes() var appServiceTypes = request.Assembly.GetTypes()
.Where(t => IsApplicationServiceType(t)) .Where(t => IsApplicationServiceType(t))
.ToList(); .ToList();
if (appServiceTypes.Count == 0) if (appServiceTypes.Count == 0)
{ {
_logger.LogWarning("Assembly'de ApplicationService bulunamadı: {Assembly}", request.AssemblyName);
return; return;
} }
// Dependency Injection Container'a ekle foreach (var serviceType in appServiceTypes)
using (var scope = _serviceProvider.CreateScope())
{ {
var services = scope.ServiceProvider.GetRequiredService<IServiceCollection>(); DynamicServiceTypeRegistry.RegisterType(serviceType);
foreach (var serviceType in appServiceTypes)
{
// Transient olarak kaydet
services.AddTransient(serviceType);
_logger.LogDebug("DI'ya eklendi: {ServiceType}", serviceType.FullName);
}
} }
// MVC Application Parts'a ekle (Controllers için) var existingPart = _partManager.ApplicationParts
var assemblyPart = new AssemblyPart(request.Assembly); .OfType<AssemblyPart>()
_partManager.ApplicationParts.Add(assemblyPart); .FirstOrDefault(p => p.Assembly == request.Assembly);
if (existingPart == null)
{
var assemblyPart = new AssemblyPart(request.Assembly);
_partManager.ApplicationParts.Add(assemblyPart);
}
// ABP Conventional Controllers'a ekle try
_mvcOptions.Value.ConventionalControllers.Create(request.Assembly); {
var isConfigured = _mvcOptions.Value.ConventionalControllers.ConventionalControllerSettings
_logger.LogInformation("Assembly başarıyla kaydedildi. Tenant: {TenantId}, Assembly: {Assembly}, Service Count: {Count}", .Any(s => s.Assembly == request.Assembly);
request.TenantId, request.AssemblyName, appServiceTypes.Count);
if (!isConfigured)
{
_mvcOptions.Value.ConventionalControllers.Create(request.Assembly, options =>
{
options.RootPath = "app";
options.RemoteServiceName = "Default";
options.TypePredicate = t => appServiceTypes.Contains(t);
});
}
}
catch (Exception ex)
{
_logger.LogError(ex, "Conventional Controllers eklenirken hata");
}
try
{
_changeProvider.NotifyChanges();
}
catch (Exception ex)
{
_logger.LogError(ex, "ActionDescriptor notify hatası");
}
await Task.Delay(100);
await Task.CompletedTask; await Task.CompletedTask;
} }
@ -162,9 +242,8 @@ namespace Kurs.Platform.DynamicServices
(type.Name.EndsWith("AppService") || type.Name.EndsWith("ApplicationService")) && (type.Name.EndsWith("AppService") || type.Name.EndsWith("ApplicationService")) &&
HasApplicationServiceBase(type); HasApplicationServiceBase(type);
} }
catch (Exception ex) catch
{ {
_logger.LogWarning(ex, "Tip kontrolü sırasında hata: {Type}", type?.FullName);
return false; return false;
} }
} }

View file

@ -0,0 +1,96 @@
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Controllers;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
namespace Kurs.Platform.DynamicServices;
/// <summary>
/// Registry for dynamically loaded service types
/// </summary>
public static class DynamicServiceTypeRegistry
{
private static readonly ConcurrentBag<Type> _registeredTypes = new();
public static void RegisterType(Type type)
{
if (!_registeredTypes.Contains(type))
{
_registeredTypes.Add(type);
}
}
public static bool IsRegistered(Type type)
{
return _registeredTypes.Contains(type);
}
public static IEnumerable<Type> GetAllTypes()
{
return _registeredTypes.ToList();
}
}
/// <summary>
/// Custom controller activator that can create instances of dynamically loaded controllers
/// </summary>
public class DynamicControllerActivator : IControllerActivator
{
private readonly IServiceProvider _serviceProvider;
private readonly ILogger<DynamicControllerActivator> _logger;
public DynamicControllerActivator(
IServiceProvider serviceProvider,
ILogger<DynamicControllerActivator> logger)
{
_serviceProvider = serviceProvider;
_logger = logger;
}
public object Create(ControllerContext context)
{
var controllerType = context.ActionDescriptor.ControllerTypeInfo.AsType();
try
{
// First try to get from DI
var instance = _serviceProvider.GetService(controllerType);
if (instance != null)
{
return instance;
}
// If not in DI (dynamic controllers), use ActivatorUtilities
if (DynamicServiceTypeRegistry.IsRegistered(controllerType))
{
instance = ActivatorUtilities.CreateInstance(_serviceProvider, controllerType);
if (instance != null)
{
return instance;
}
}
// Last resort - use default activation
return ActivatorUtilities.CreateInstance(_serviceProvider, controllerType);
}
catch (Exception ex)
{
_logger.LogError(ex, "Controller oluşturma hatası: {Type}", controllerType.Name);
throw;
}
}
public void Release(ControllerContext context, object controller)
{
if (controller is IDisposable disposable)
{
disposable.Dispose();
}
}
}

View file

@ -14,6 +14,7 @@ using Kurs.Platform.FileManagement;
using Kurs.Platform.Identity; using Kurs.Platform.Identity;
using Kurs.Platform.Localization; using Kurs.Platform.Localization;
using Kurs.Settings; using Kurs.Settings;
using Microsoft.AspNetCore.Mvc.Infrastructure;
using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Cors; using Microsoft.AspNetCore.Cors;
using Microsoft.AspNetCore.Extensions.DependencyInjection; using Microsoft.AspNetCore.Extensions.DependencyInjection;
@ -50,6 +51,7 @@ using Volo.Abp.Swashbuckle;
using Volo.Abp.UI.Navigation.Urls; using Volo.Abp.UI.Navigation.Urls;
using Volo.Abp.VirtualFileSystem; using Volo.Abp.VirtualFileSystem;
using Kurs.Platform.DynamicServices; using Kurs.Platform.DynamicServices;
using Kurs.Platform.DeveloperKit;
using static Kurs.Platform.PlatformConsts; using static Kurs.Platform.PlatformConsts;
using static Kurs.Settings.SettingsConsts; using static Kurs.Settings.SettingsConsts;
@ -360,8 +362,15 @@ public class PlatformHttpApiHostModule : AbpModule
// Dynamic AppService Background Service // Dynamic AppService Background Service
context.Services.AddHostedService<DynamicAssemblyRegistrationService>(); context.Services.AddHostedService<DynamicAssemblyRegistrationService>();
// Roslyn Compiler Servisleri // Roslyn Compiler
context.Services.AddSingleton<DynamicServiceCompiler>(); context.Services.AddSingleton<DynamicServiceCompiler>();
// Action descriptor change provider for runtime controller registration
context.Services.AddSingleton<ActionDescriptorChangeProvider>();
context.Services.AddSingleton<IActionDescriptorChangeProvider>(sp => sp.GetRequiredService<ActionDescriptorChangeProvider>());
// Custom controller activator for dynamic dependency injection
context.Services.AddSingleton<Microsoft.AspNetCore.Mvc.Controllers.IControllerActivator, DynamicControllerActivator>();
} }
public override void OnApplicationInitialization(ApplicationInitializationContext context) public override void OnApplicationInitialization(ApplicationInitializationContext context)
@ -369,6 +378,12 @@ public class PlatformHttpApiHostModule : AbpModule
var app = context.GetApplicationBuilder(); var app = context.GetApplicationBuilder();
var env = context.GetEnvironment(); var env = context.GetEnvironment();
// Setup delegate for dynamic service registration
DynamicServiceCompiler.NotifyAssemblyRegistration = (tenantId, assembly, assemblyName) =>
{
DynamicAssemblyRegistrationService.RequestAssemblyRegistration(tenantId, assembly, assemblyName);
};
if (env.IsDevelopment()) if (env.IsDevelopment())
{ {
app.UseDeveloperExceptionPage(); app.UseDeveloperExceptionPage();

View file

@ -167,10 +167,8 @@ namespace DynamicServices
description: description, description: description,
primaryEntityType: primaryEntityType, primaryEntityType: primaryEntityType,
} }
console.log('Publish request data:', requestData)
const result = await dynamicServiceService.publish(requestData) const result = await dynamicServiceService.publish(requestData)
setPublishResult(result) setPublishResult(result)
if (result.success) { if (result.success) {
@ -548,9 +546,6 @@ namespace DynamicServices
{publishResult.success && ( {publishResult.success && (
<div className="space-y-2"> <div className="space-y-2">
<p className="text-green-700 text-sm">
AppService başarıyla yayınlandı ve Swagger'a eklendi.
</p>
{publishResult.controllerName && ( {publishResult.controllerName && (
<p className="text-sm text-gray-600"> <p className="text-sm text-gray-600">
Controller:{' '} Controller:{' '}
@ -574,15 +569,6 @@ namespace DynamicServices
</ul> </ul>
</div> </div>
)} )}
<div className="flex gap-2 mt-3">
<button
onClick={openSwagger}
className="flex items-center gap-1 bg-blue-600 text-white px-3 py-1 rounded text-sm hover:bg-blue-700"
>
<FaExternalLinkAlt className="w-3 h-3" />
Swagger'da Gör
</button>
</div>
</div> </div>
)} )}