Developer Kits düzenlemesi
This commit is contained in:
parent
ce84b3baac
commit
2ec7f085e4
8 changed files with 397 additions and 137 deletions
|
|
@ -68,6 +68,12 @@ public class CompileResultDto
|
|||
public bool HasWarnings { get; set; }
|
||||
[JsonPropertyName("warnings")]
|
||||
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>
|
||||
|
|
|
|||
|
|
@ -30,16 +30,9 @@ public class DynamicAppServiceAppService : PlatformAppService, IDynamicServiceAp
|
|||
[Authorize(AppCodes.DeveloperKits.DynamicServices.TestCompile)]
|
||||
public virtual async Task<CompileResultDto> 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<PublishResultDto> 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<string>();
|
||||
|
||||
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<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);
|
||||
}
|
||||
|
||||
[Authorize(AppCodes.DeveloperKits.DynamicServices.ViewCode)]
|
||||
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 dto = ObjectMapper.Map<DynamicService, DynamicServiceDto>(appService);
|
||||
|
||||
return dto;
|
||||
return ObjectMapper.Map<DynamicService, DynamicServiceDto>(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<CompileResultDto> 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<string> GenerateEndpointList(string serviceName)
|
||||
{
|
||||
var controllerName = GenerateControllerName(serviceName);
|
||||
|
||||
return
|
||||
[
|
||||
$"/api/app/{controllerName.ToLowerInvariant()}",
|
||||
$"/api/app/{controllerName.ToLowerInvariant()}/{{id}}",
|
||||
$"/api/app/{controllerName.ToLowerInvariant()}/list"
|
||||
];
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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> { 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;
|
||||
}
|
||||
|
||||
/// <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";
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -23,6 +23,7 @@ namespace Kurs.Platform.DynamicServices
|
|||
private readonly ILogger<DynamicAssemblyRegistrationService> _logger;
|
||||
private readonly ApplicationPartManager _partManager;
|
||||
private readonly IOptions<AbpAspNetCoreMvcOptions> _mvcOptions;
|
||||
private readonly ActionDescriptorChangeProvider _changeProvider;
|
||||
|
||||
// Bekleyen assembly kayıt istekleri
|
||||
private static readonly Queue<AssemblyRegistrationRequest> _pendingRegistrations = new();
|
||||
|
|
@ -32,12 +33,14 @@ namespace Kurs.Platform.DynamicServices
|
|||
IServiceProvider serviceProvider,
|
||||
ILogger<DynamicAssemblyRegistrationService> logger,
|
||||
ApplicationPartManager partManager,
|
||||
IOptions<AbpAspNetCoreMvcOptions> mvcOptions)
|
||||
IOptions<AbpAspNetCoreMvcOptions> mvcOptions,
|
||||
ActionDescriptorChangeProvider changeProvider)
|
||||
{
|
||||
_serviceProvider = serviceProvider;
|
||||
_logger = logger;
|
||||
_partManager = partManager;
|
||||
_mvcOptions = mvcOptions;
|
||||
_changeProvider = changeProvider;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -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<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()
|
||||
|
|
@ -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<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()
|
||||
.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<IServiceCollection>();
|
||||
|
||||
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<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
|
||||
_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;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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<DynamicAssemblyRegistrationService>();
|
||||
|
||||
// Roslyn Compiler Servisleri
|
||||
// Roslyn Compiler
|
||||
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)
|
||||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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 && (
|
||||
<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 && (
|
||||
<p className="text-sm text-gray-600">
|
||||
Controller:{' '}
|
||||
|
|
@ -574,15 +569,6 @@ namespace DynamicServices
|
|||
</ul>
|
||||
</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>
|
||||
)}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue