diff --git a/api/src/Kurs.Platform.Application.Contracts/DeveloperKit/DynamicServiceDtos.cs b/api/src/Kurs.Platform.Application.Contracts/DeveloperKit/DynamicServiceDtos.cs index b950e70d..2844d6f2 100644 --- a/api/src/Kurs.Platform.Application.Contracts/DeveloperKit/DynamicServiceDtos.cs +++ b/api/src/Kurs.Platform.Application.Contracts/DeveloperKit/DynamicServiceDtos.cs @@ -68,6 +68,12 @@ public class CompileResultDto public bool HasWarnings { get; set; } [JsonPropertyName("warnings")] public List Warnings { get; set; } + + /// + /// Yüklenen assembly (sadece server-side kullanım için, JSON'a serialize edilmez) + /// + [System.Text.Json.Serialization.JsonIgnore] + public System.Reflection.Assembly LoadedAssembly { get; set; } } /// diff --git a/api/src/Kurs.Platform.Application/DeveloperKit/DynamicServiceAppService.cs b/api/src/Kurs.Platform.Application/DeveloperKit/DynamicServiceAppService.cs index 0d6dab5f..02d23868 100644 --- a/api/src/Kurs.Platform.Application/DeveloperKit/DynamicServiceAppService.cs +++ b/api/src/Kurs.Platform.Application/DeveloperKit/DynamicServiceAppService.cs @@ -30,16 +30,9 @@ public class DynamicAppServiceAppService : PlatformAppService, IDynamicServiceAp [Authorize(AppCodes.DeveloperKits.DynamicServices.TestCompile)] public virtual async Task TestCompileAsync(TestCompileRequestDto request) { - Logger.LogInformation("Test compile başlatıldı. Tenant: {TenantId}", CurrentTenant.Id); - try { - var result = await _compiler.CompileAndValidateAsync(request.Code, CurrentTenant.Id); - - Logger.LogInformation("Test compile tamamlandı. Başarılı: {Success}, Tenant: {TenantId}", - result.Success, CurrentTenant.Id); - - return result; + return await _compiler.CompileAndValidateAsync(request.Code, CurrentTenant.Id); } catch (Exception ex) { @@ -51,9 +44,6 @@ public class DynamicAppServiceAppService : PlatformAppService, IDynamicServiceAp [Authorize(AppCodes.DeveloperKits.DynamicServices.Publish)] public virtual async Task PublishAsync(PublishAppServiceRequestDto request) { - Logger.LogInformation("AppService yayınlama başlatıldı. Ad: {Name}, Tenant: {TenantId}", - request.Name, CurrentTenant.Id); - try { // Önce kodu test compile et @@ -82,11 +72,9 @@ public class DynamicAppServiceAppService : PlatformAppService, IDynamicServiceAp existingService.PrimaryEntityType = request.PrimaryEntityType; appService = await _dynamicAppServiceRepository.UpdateAsync(existingService); - Logger.LogInformation("Mevcut AppService güncellendi. ID: {Id}", appService.Id); } else { - // Yeni servis oluştur appService = new DynamicService( GuidGenerator.Create(), request.Name, @@ -100,11 +88,9 @@ public class DynamicAppServiceAppService : PlatformAppService, IDynamicServiceAp }; appService = await _dynamicAppServiceRepository.InsertAsync(appService); - Logger.LogInformation("Yeni AppService oluşturuldu. ID: {Id}", appService.Id); } - // Runtime'da derle ve yükle - var assemblyName = $"DynamicAppService_{appService.Id}_{appService.Version}"; + var assemblyName = $"{appService.Name}_{appService.Version}"; var loadResult = await _compiler.CompileAndRegisterForTenantAsync( CurrentTenant.Id ?? Guid.Empty, request.Code, @@ -112,23 +98,21 @@ public class DynamicAppServiceAppService : PlatformAppService, IDynamicServiceAp if (loadResult.Success) { - // Başarılı derleme durumunu kaydet appService.MarkCompilationSuccess(); await _dynamicAppServiceRepository.UpdateAsync(appService); - var result = new PublishResultDto + var endpoints = loadResult.LoadedAssembly != null + ? _compiler.ExtractEndpointsFromAssembly(loadResult.LoadedAssembly, request.Name) + : new List(); + + return new PublishResultDto { Success = true, AppServiceId = appService.Id, ControllerName = appService.ControllerName, 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 { @@ -178,59 +162,38 @@ public class DynamicAppServiceAppService : PlatformAppService, IDynamicServiceAp var dtos = ObjectMapper.Map, List>(items); - Logger.LogDebug("AppService listesi döndürüldü. Toplam: {Total}, Dönen: {Count}", - totalCount, dtos.Count); - return new PagedResultDto(totalCount, dtos); } [Authorize(AppCodes.DeveloperKits.DynamicServices.ViewCode)] public virtual async Task GetAsync(Guid id) { - Logger.LogDebug("AppService detayı istendi. ID: {Id}, Tenant: {TenantId}", id, CurrentTenant.Id); - var appService = await _dynamicAppServiceRepository.GetAsync(id); - var dto = ObjectMapper.Map(appService); - - return dto; + return ObjectMapper.Map(appService); } [Authorize(AppCodes.DeveloperKits.DynamicServices.Delete)] 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); - + // TODO: Runtime'dan assembly'yi kaldırma işlemi // (AssemblyLoadContext.Unload() çağrısı) await _dynamicAppServiceRepository.DeleteAsync(id); - - Logger.LogInformation("AppService silindi. ID: {Id}", id); } [Authorize(AppCodes.DeveloperKits.DynamicServices.Manage)] 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); appService.IsActive = isActive; - await _dynamicAppServiceRepository.UpdateAsync(appService); - - Logger.LogInformation("AppService aktiflik durumu güncellendi. ID: {Id}, Aktif: {Active}", - id, isActive); } [Authorize(AppCodes.DeveloperKits.DynamicServices.Manage)] public virtual async Task ReloadAllActiveServicesAsync() { - Logger.LogInformation("Tüm aktif AppService'ler yeniden yükleniyor. Tenant: {TenantId}", - CurrentTenant.Id); var activeServices = await _dynamicAppServiceRepository .GetListAsync(x => x.IsActive && x.CompilationStatus == CompilationStatus.Success); @@ -242,7 +205,8 @@ public class DynamicAppServiceAppService : PlatformAppService, IDynamicServiceAp { 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( CurrentTenant.Id ?? Guid.Empty, service.Code, @@ -251,17 +215,12 @@ public class DynamicAppServiceAppService : PlatformAppService, IDynamicServiceAp if (result.Success) { successCount++; - Logger.LogDebug("AppService yeniden yüklendi. ID: {Id}, Ad: {Name}", - service.Id, service.Name); } else { errorCount++; service.MarkCompilationError(result.ErrorMessage); await _dynamicAppServiceRepository.UpdateAsync(service); - - Logger.LogWarning("AppService yeniden yüklenemedi. ID: {Id}, Hata: {Error}", - service.Id, result.ErrorMessage); } } catch (Exception ex) @@ -273,19 +232,14 @@ public class DynamicAppServiceAppService : PlatformAppService, IDynamicServiceAp 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)] public virtual async Task RecompileAsync(Guid id) { - Logger.LogInformation("AppService yeniden derleniyor. ID: {Id}", 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( CurrentTenant.Id ?? Guid.Empty, appService.Code, @@ -303,9 +257,6 @@ public class DynamicAppServiceAppService : PlatformAppService, IDynamicServiceAp await _dynamicAppServiceRepository.UpdateAsync(appService); - Logger.LogInformation("AppService yeniden derleme tamamlandı. ID: {Id}, Başarılı: {Success}", - id, result.Success); - return result; } @@ -324,16 +275,4 @@ public class DynamicAppServiceAppService : PlatformAppService, IDynamicServiceAp return controllerName; } - - private List GenerateEndpointList(string serviceName) - { - var controllerName = GenerateControllerName(serviceName); - - return - [ - $"/api/app/{controllerName.ToLowerInvariant()}", - $"/api/app/{controllerName.ToLowerInvariant()}/{{id}}", - $"/api/app/{controllerName.ToLowerInvariant()}/list" - ]; - } } diff --git a/api/src/Kurs.Platform.Application/DeveloperKit/DynamicServiceCompiler.cs b/api/src/Kurs.Platform.Application/DeveloperKit/DynamicServiceCompiler.cs index fbe791bc..3f288457 100644 --- a/api/src/Kurs.Platform.Application/DeveloperKit/DynamicServiceCompiler.cs +++ b/api/src/Kurs.Platform.Application/DeveloperKit/DynamicServiceCompiler.cs @@ -178,30 +178,21 @@ public class DynamicServiceCompiler : ITransientDependency var assembly = loadContext.LoadFromStream(ms); - // ApplicationService türevlerini bul var appServiceTypes = assembly.GetTypes() .Where(t => IsApplicationServiceType(t)) .ToList(); - _logger.LogInformation("Tenant {TenantId} için {Count} ApplicationService bulundu", - tenantId, appServiceTypes.Count); - - // Tenant assembly'leri listesine ekle _tenantAssemblies.AddOrUpdate(tenantId, new List { assembly }, (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); - _logger.LogInformation("Tenant {TenantId} için assembly başarıyla yüklendi: {AssemblyName}", - tenantId, assemblyName); - return new CompileResultDto { Success = true, - CompilationTimeMs = validateResult.CompilationTimeMs + CompilationTimeMs = validateResult.CompilationTimeMs, + LoadedAssembly = assembly }; } catch (Exception ex) @@ -331,12 +322,9 @@ public class DynamicServiceCompiler : ITransientDependency { 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; @@ -355,9 +343,8 @@ public class DynamicServiceCompiler : ITransientDependency (type.Name.EndsWith("AppService") || type.Name.EndsWith("ApplicationService")) && HasApplicationServiceBase(type); } - catch (Exception ex) + catch { - _logger.LogWarning(ex, "Tip kontrolü sırasında hata: {Type}", type?.FullName); return false; } } @@ -394,4 +381,109 @@ public class DynamicServiceCompiler : ITransientDependency } return 1; } + + /// + /// Assembly içindeki ApplicationService'lerden endpoint listesini çıkarır + /// + public List ExtractEndpointsFromAssembly(Assembly assembly, string serviceName) + { + var endpoints = new List(); + + 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; + } + + /// + /// PascalCase'i kebab-case'e çevirir (DynamicCustomer -> dynamic-customer) + /// + 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(); + } + + /// + /// Method'un HTTP verb'ünü belirler (ABP convention'a göre) + /// + 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"; + } } diff --git a/api/src/Kurs.Platform.HttpApi.Host/DynamicServices/ActionDescriptorChangeProvider.cs b/api/src/Kurs.Platform.HttpApi.Host/DynamicServices/ActionDescriptorChangeProvider.cs new file mode 100644 index 00000000..5f676952 --- /dev/null +++ b/api/src/Kurs.Platform.HttpApi.Host/DynamicServices/ActionDescriptorChangeProvider.cs @@ -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; + +/// +/// Provides a change token to notify MVC infrastructure (and Swagger/ApiExplorer) +/// that action descriptors have changed at runtime. +/// +public class ActionDescriptorChangeProvider : IActionDescriptorChangeProvider +{ + private readonly ILogger _logger; + private CancellationTokenSource _cts = new CancellationTokenSource(); + + public ActionDescriptorChangeProvider(ILogger logger) + { + _logger = logger; + } + + public IChangeToken GetChangeToken() + { + _logger.LogDebug("GetChangeToken çağrıldı"); + return new CancellationChangeToken(_cts.Token); + } + + /// + /// Call this method after adding/removing controllers at runtime to notify + /// MVC/Swagger that action descriptors must be refreshed. + /// + 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"); + } + } +} diff --git a/api/src/Kurs.Platform.HttpApi.Host/DynamicServices/DynamicAssemblyRegistrationService.cs b/api/src/Kurs.Platform.HttpApi.Host/DynamicServices/DynamicAssemblyRegistrationService.cs index ffd9301e..4a61403a 100644 --- a/api/src/Kurs.Platform.HttpApi.Host/DynamicServices/DynamicAssemblyRegistrationService.cs +++ b/api/src/Kurs.Platform.HttpApi.Host/DynamicServices/DynamicAssemblyRegistrationService.cs @@ -23,6 +23,7 @@ namespace Kurs.Platform.DynamicServices private readonly ILogger _logger; private readonly ApplicationPartManager _partManager; private readonly IOptions _mvcOptions; + private readonly ActionDescriptorChangeProvider _changeProvider; // Bekleyen assembly kayıt istekleri private static readonly Queue _pendingRegistrations = new(); @@ -32,12 +33,14 @@ namespace Kurs.Platform.DynamicServices IServiceProvider serviceProvider, ILogger logger, ApplicationPartManager partManager, - IOptions mvcOptions) + IOptions mvcOptions, + ActionDescriptorChangeProvider changeProvider) { _serviceProvider = serviceProvider; _logger = logger; _partManager = partManager; _mvcOptions = mvcOptions; + _changeProvider = changeProvider; } /// @@ -59,15 +62,15 @@ namespace Kurs.Platform.DynamicServices 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) { try { await ProcessPendingRegistrations(); - - // 5 saniye bekle await Task.Delay(5000, stoppingToken); } catch (Exception ex) @@ -75,8 +78,44 @@ namespace Kurs.Platform.DynamicServices _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>(); + var compiler = scope.ServiceProvider.GetService(); + + 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() @@ -94,8 +133,6 @@ namespace Kurs.Platform.DynamicServices if (requests.Count == 0) return; - _logger.LogDebug("İşlenecek {Count} assembly kaydı var", requests.Count); - foreach (var request in requests) { try @@ -112,43 +149,86 @@ namespace Kurs.Platform.DynamicServices private async Task RegisterAssembly(AssemblyRegistrationRequest request) { - _logger.LogInformation("Assembly kaydediliyor. Tenant: {TenantId}, Assembly: {Assembly}", - request.TenantId, request.AssemblyName); + var lastUnderscoreIndex = request.AssemblyName.LastIndexOf('_'); + if (lastUnderscoreIndex > 0) + { + var servicePrefix = request.AssemblyName.Substring(0, lastUnderscoreIndex + 1); + + var oldParts = _partManager.ApplicationParts + .OfType() + .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() .Where(t => IsApplicationServiceType(t)) .ToList(); if (appServiceTypes.Count == 0) { - _logger.LogWarning("Assembly'de ApplicationService bulunamadı: {Assembly}", request.AssemblyName); return; } - // Dependency Injection Container'a ekle - using (var scope = _serviceProvider.CreateScope()) + foreach (var serviceType in appServiceTypes) { - var services = scope.ServiceProvider.GetRequiredService(); - - foreach (var serviceType in appServiceTypes) - { - // Transient olarak kaydet - services.AddTransient(serviceType); - _logger.LogDebug("DI'ya eklendi: {ServiceType}", serviceType.FullName); - } + DynamicServiceTypeRegistry.RegisterType(serviceType); } - // MVC Application Parts'a ekle (Controllers için) - var assemblyPart = new AssemblyPart(request.Assembly); - _partManager.ApplicationParts.Add(assemblyPart); + var existingPart = _partManager.ApplicationParts + .OfType() + .FirstOrDefault(p => p.Assembly == request.Assembly); + + if (existingPart == null) + { + var assemblyPart = new AssemblyPart(request.Assembly); + _partManager.ApplicationParts.Add(assemblyPart); + } - // ABP Conventional Controllers'a ekle - _mvcOptions.Value.ConventionalControllers.Create(request.Assembly); - - _logger.LogInformation("Assembly başarıyla kaydedildi. Tenant: {TenantId}, Assembly: {Assembly}, Service Count: {Count}", - request.TenantId, request.AssemblyName, appServiceTypes.Count); + try + { + var isConfigured = _mvcOptions.Value.ConventionalControllers.ConventionalControllerSettings + .Any(s => s.Assembly == request.Assembly); + + 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; } @@ -162,9 +242,8 @@ namespace Kurs.Platform.DynamicServices (type.Name.EndsWith("AppService") || type.Name.EndsWith("ApplicationService")) && HasApplicationServiceBase(type); } - catch (Exception ex) + catch { - _logger.LogWarning(ex, "Tip kontrolü sırasında hata: {Type}", type?.FullName); return false; } } diff --git a/api/src/Kurs.Platform.HttpApi.Host/DynamicServices/DynamicControllerActivator.cs b/api/src/Kurs.Platform.HttpApi.Host/DynamicServices/DynamicControllerActivator.cs new file mode 100644 index 00000000..66b2c964 --- /dev/null +++ b/api/src/Kurs.Platform.HttpApi.Host/DynamicServices/DynamicControllerActivator.cs @@ -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; + +/// +/// Registry for dynamically loaded service types +/// +public static class DynamicServiceTypeRegistry +{ + private static readonly ConcurrentBag _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 GetAllTypes() + { + return _registeredTypes.ToList(); + } +} + +/// +/// Custom controller activator that can create instances of dynamically loaded controllers +/// +public class DynamicControllerActivator : IControllerActivator +{ + private readonly IServiceProvider _serviceProvider; + private readonly ILogger _logger; + + public DynamicControllerActivator( + IServiceProvider serviceProvider, + ILogger 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(); + } + } +} diff --git a/api/src/Kurs.Platform.HttpApi.Host/PlatformHttpApiHostModule.cs b/api/src/Kurs.Platform.HttpApi.Host/PlatformHttpApiHostModule.cs index 761abbd2..9bc0635e 100644 --- a/api/src/Kurs.Platform.HttpApi.Host/PlatformHttpApiHostModule.cs +++ b/api/src/Kurs.Platform.HttpApi.Host/PlatformHttpApiHostModule.cs @@ -14,6 +14,7 @@ using Kurs.Platform.FileManagement; using Kurs.Platform.Identity; using Kurs.Platform.Localization; using Kurs.Settings; +using Microsoft.AspNetCore.Mvc.Infrastructure; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Cors; using Microsoft.AspNetCore.Extensions.DependencyInjection; @@ -50,6 +51,7 @@ using Volo.Abp.Swashbuckle; using Volo.Abp.UI.Navigation.Urls; using Volo.Abp.VirtualFileSystem; using Kurs.Platform.DynamicServices; +using Kurs.Platform.DeveloperKit; using static Kurs.Platform.PlatformConsts; using static Kurs.Settings.SettingsConsts; @@ -360,8 +362,15 @@ public class PlatformHttpApiHostModule : AbpModule // Dynamic AppService Background Service context.Services.AddHostedService(); - // Roslyn Compiler Servisleri + // Roslyn Compiler context.Services.AddSingleton(); + + // Action descriptor change provider for runtime controller registration + context.Services.AddSingleton(); + context.Services.AddSingleton(sp => sp.GetRequiredService()); + + // Custom controller activator for dynamic dependency injection + context.Services.AddSingleton(); } public override void OnApplicationInitialization(ApplicationInitializationContext context) @@ -369,6 +378,12 @@ public class PlatformHttpApiHostModule : AbpModule var app = context.GetApplicationBuilder(); var env = context.GetEnvironment(); + // Setup delegate for dynamic service registration + DynamicServiceCompiler.NotifyAssemblyRegistration = (tenantId, assembly, assemblyName) => + { + DynamicAssemblyRegistrationService.RequestAssemblyRegistration(tenantId, assembly, assemblyName); + }; + if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); diff --git a/ui/src/components/developerKit/DynamicAppServiceEditor.tsx b/ui/src/components/developerKit/DynamicAppServiceEditor.tsx index a2909d3a..6480f234 100644 --- a/ui/src/components/developerKit/DynamicAppServiceEditor.tsx +++ b/ui/src/components/developerKit/DynamicAppServiceEditor.tsx @@ -167,10 +167,8 @@ namespace DynamicServices description: description, primaryEntityType: primaryEntityType, } - console.log('Publish request data:', requestData) const result = await dynamicServiceService.publish(requestData) - setPublishResult(result) if (result.success) { @@ -548,9 +546,6 @@ namespace DynamicServices {publishResult.success && (
-

- AppService başarıyla yayınlandı ve Swagger'a eklendi. -

{publishResult.controllerName && (

Controller:{' '} @@ -574,15 +569,6 @@ namespace DynamicServices

)} -
- -
)}