Open Closed

Dynamically add controller. #4883


User avatar
0
alirizaadiyahsi created
  • ABP Framework version: v7.1.0
  • UI type: Angular
  • DB provider: EF Core
  • Tiered (MVC) or Identity Server Separated (Angular): Microservice

I have following code to add controller dynamically.

    [HttpPost]
    public async Task<AppPartDto> CreateAsync(CreateAppPartInput input)
    {
        var dto = await _appPartsAppService.CreateAsync(input);
        if (!dto.IsRunning) return dto;

        var assembly = Assembly.Load(dto.CompiledCode);
        if (_partManager.ApplicationParts.Any(ap => ap.Name == assembly.GetName().Name)) return dto;
        _partManager.ApplicationParts.Add(new AssemblyPart(assembly));
        DaisyActionDescriptorChangeProvider.Instance.HasChanged = true;
        DaisyActionDescriptorChangeProvider.Instance.TokenSource.Cancel();

        return dto;
    }

And I can see the controller is added in swagger:

But when I want to call it, I am getting following error.

[api-manager-service_87ede53f-7]: [19:18:00 ERR] ---------- RemoteServiceErrorInfo ----------
[api-manager-service_87ede53f-7]: {
[api-manager-service_87ede53f-7]: "code": null,
[api-manager-service_87ede53f-7]: "message": "An internal error occurred during your request!",
[api-manager-service_87ede53f-7]: "details": null,
[api-manager-service_87ede53f-7]: "data": {},
[api-manager-service_87ede53f-7]: "validationErrors": null
[api-manager-service_87ede53f-7]: }
[api-manager-service_87ede53f-7]:
[api-manager-service_87ede53f-7]: [19:18:00 ERR] The requested service 'Daisy.ApiManagerService.Customers.CustomersController' has not been registered. To avoid this exception, either register a component to provide the service, check for service registration using IsRegistered(), or use the ResolveOptional() method to resolve an optional dependency.
[api-manager-service_87ede53f-7]: Autofac.Core.Registration.ComponentNotRegisteredException: The requested service 'Daisy.ApiManagerService.Customers.CustomersController' has not been registered. To avoid this exception, either register a component to provide the service, check for service registration using IsRegistered(), or use the ResolveOptional() method to resolve an optional dependency.
[api-manager-service_87ede53f-7]: at Autofac.ResolutionExtensions.ResolveService(IComponentContext context, Service service, IEnumerable`1 parameters)
[api-manager-service_87ede53f-7]: at Microsoft.AspNetCore.Mvc.Controllers.ControllerFactoryProvider.&lt;&gt;c__DisplayClass6_0.&lt;CreateControllerFactory&gt;g__CreateController|0(ControllerContext controllerContext)
[api-manager-service_87ede53f-7]: at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.Next(State& next, Scope& scope, Object& state, Boolean& isCompleted)
[api-manager-service_87ede53f-7]: at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.InvokeInnerFilterAsync()
[api-manager-service_87ede53f-7]: --- End of stack trace from previous location ---
[api-manager-service_87ede53f-7]: at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.&lt;InvokeNextExceptionFilterAsync&gt;g__Awaited|26_0(ResourceInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted)
[api-manager-service_87ede53f-7]: [19:18:00 INF] Executing ObjectResult, writing value of type 'Volo.Abp.Http.RemoteServiceErrorResponse'.
[api-manager-service_87ede53f-7]: [19:18:00 INF] Executed action Daisy.ApiManagerService.Customers.CustomersController.Get (OnTheFlyCompilationAssembly) in 237.6787ms
[api-manager-service_87ede53f-7]: [19:18:00 INF] Executed endpoint 'Daisy.ApiManagerService.Customers.CustomersController.Get (OnTheFlyCompilationAssembly)'
[api-manager-service_87ede53f-7]: [19:18:00 DBG] Added 0 entity changes to the current audit log
[api-manager-service_87ede53f-7]: [19:18:00 INF] Request finished HTTP/2 GET https://localhost:44996/api/api-manager-service/customers - - - 500 - application/json;+charset=utf-8 240.8945ms

Maybe there is an elegant way to add controller dynamically?

I can do this in an dotnet api project (which is not related to ABP) and it is working. But with ABP I think I should register the assembly(controller) to dependency container dynamically.

Any suggestion would be appropriate. Thanks.


2 Answer(s)
  • User Avatar
    0
    liangshiwei created
    Support Team Fullstack Developer

    Hi,

    Because ABP uses controller as a service: https://andrewlock.net/controller-activation-and-dependency-injection-in-asp-net-core-mvc/

    But you can't add service after the application start.

    You can try this.

    The MyControllerActivator will try to create from DI and manually create an instance if it does not exist.

    public sealed class MyTypeActivatorCache : ISingletonDependency
    {
    
    #nullable disable
        private readonly Func<Type, ObjectFactory> _createFactory = (Func<Type, ObjectFactory>) (type => ActivatorUtilities.CreateFactory(type, Type.EmptyTypes));
        private readonly ConcurrentDictionary<Type, ObjectFactory> _typeActivatorCache = new ConcurrentDictionary<Type, ObjectFactory>();
        
        public TInstance CreateInstance<TInstance>(
            IServiceProvider serviceProvider,
            Type implementationType)
        {
            if (serviceProvider == null)
                throw new ArgumentNullException(nameof (serviceProvider));
            if (implementationType == (Type) null)
                throw new ArgumentNullException(nameof (implementationType));
            return (TInstance) this._typeActivatorCache.GetOrAdd(implementationType, this._createFactory)(serviceProvider, (object[]) null);
        }
        
        public bool IsCreated(Type implementationType)
        {
            return this._typeActivatorCache.ContainsKey(implementationType);
        }
    }
    
    public class MyControllerActivator : IControllerActivator
    {
        private readonly ServiceBasedControllerActivator _serviceBasedControllerActivator;
        private readonly MyTypeActivatorCache _typeActivatorCache;
        public MyControllerActivator(ServiceBasedControllerActivator serviceBasedControllerActivator,
            MyTypeActivatorCache typeActivatorCache)
        {
            _serviceBasedControllerActivator = serviceBasedControllerActivator;
            _typeActivatorCache = typeActivatorCache;
        }
    
        public object Create(ControllerContext context)
        {
            try
            {
                return _serviceBasedControllerActivator.Create(context);
            }
            catch (Exception e)
            {
                if (context == null)
                {
                    throw new ArgumentNullException(nameof(context));
                }
            
                var controllerTypeInfo = context.ActionDescriptor.ControllerTypeInfo;
    
                if (controllerTypeInfo == null)
                {
                    throw new ArgumentException(nameof(context.ActionDescriptor.ControllerTypeInfo));
                }
    
                var serviceProvider = context.HttpContext.RequestServices;
                return _typeActivatorCache.CreateInstance<object>(serviceProvider, controllerTypeInfo.AsType());
            }
        }
    
        public void Release(ControllerContext context, object controller)
        {
            if (context == null)
            {
                throw new ArgumentNullException(nameof(context));
            }
    
            if (controller == null)
            {
                throw new ArgumentNullException(nameof(controller));
            }
    
            if (_typeActivatorCache.IsCreated(controller.GetType()))
            {
                if (controller is IDisposable disposable)
                {
                    disposable.Dispose();
                }
            }
        }
    }
    
    
    context.Services.AddTransient<ServiceBasedControllerActivator>();
    context.Services.Replace(ServiceDescriptor.Transient<IControllerActivator, MyControllerActivator>());
    
  • User Avatar
    0
    alirizaadiyahsi created

    Hi @liangshiwei, this is the good point. This fixes my issue.

    Thanks a lot.

Made with ❤️ on ABP v8.2.0-preview Updated on March 25, 2024, 15:11