Open Closed

Dynamically added controllers dependency problem. #5081


User avatar
0
alirizaadiyahsi created

Here is my original question: https://support.abp.io/QA/Questions/4883/Dynamically-add-controller

This is working with a simple controller. But if I inject transient dependency classes, it is only working for the first request after that I am getting following error:

System.ObjectDisposedException: 'Instances cannot be resolved and nested lifetimes cannot be created from this LifetimeScope as it has already been disposed.

Here is my controller (as string):

public class DbProviderController : ControllerBase
{
    string connectionString = "some connection string";
    private readonly DaisyDatabaseProviderFactory _daisyDatabaseProviderFactory;

    public DbProviderController(DaisyDatabaseProviderFactory daisyDatabaseProviderFactory)
    {
        _daisyDatabaseProviderFactory = daisyDatabaseProviderFactory;
    }

    [HttpGet]
    public async Task<IEnumerable<Dictionary<string, object>>> GetPatientData()
    {
        return await _daisyDatabaseProviderFactory.Create(Daisy.Core.DatabaseProviders.DatabaseProviderTypes.MsSql).GetAllAsync(connectionString, "select * from some_table", 200);
    }
}

I am compiling this controller code string and adding as appPart like this:

public class DynamicApplicationPartManager : ITransientDependency
{
    private readonly ApplicationPartManager _partManager;
    private readonly IActionDescriptorCollectionProvider _actionDescriptorCollectionProvider;

    public DynamicApplicationPartManager(ApplicationPartManager partManager, IActionDescriptorCollectionProvider actionDescriptorCollectionProvider)
    {
        _partManager = partManager;
        _actionDescriptorCollectionProvider = actionDescriptorCollectionProvider;
    }

    public void Create(byte[] assembly)
    {
        var newAssembly = Assembly.Load(assembly);
        if (_partManager.ApplicationParts.Any(ap => ap.Name == newAssembly.GetName().Name)) return;

        _partManager.ApplicationParts.Add(new AssemblyPart(newAssembly));

        DaisyActionDescriptorChangeProvider.Instance.HasChanged = true;
        DaisyActionDescriptorChangeProvider.Instance.TokenSource.Cancel();
    }
    
    ...
}

DaisyDatabaseProviderFactory :

public class DaisyDatabaseProviderFactory : ITransientDependency
{
    private readonly IServiceProvider _serviceProvider;

    public DaisyDatabaseProviderFactory(IServiceProvider serviceProvider)
    {
        _serviceProvider = serviceProvider;
    }

    public IDaisyDatabaseProvider Create(DatabaseProviderTypes databaseProvider)
    {
        return databaseProvider switch
        {
            DatabaseProviderTypes.MsSql => _serviceProvider.GetRequiredService<MsSqlDatabaseProvider>(),
            DatabaseProviderTypes.MySql => _serviceProvider.GetRequiredService<MySqlDatabaseProvider>(),
            DatabaseProviderTypes.PostgreSql => _serviceProvider.GetRequiredService<PostgreSqlDatabaseProvider>(),
            DatabaseProviderTypes.MongoDb => _serviceProvider.GetRequiredService<MongoDatabaseProvider>(),
            DatabaseProviderTypes.None => throw new NotImplementedException($"The database provider '{databaseProvider}' is not implemented!"),
            DatabaseProviderTypes.Oracle => throw new NotImplementedException($"The database provider '{databaseProvider}' is not implemented!"),
            DatabaseProviderTypes.SqLite => throw new NotImplementedException($"The database provider '{databaseProvider}' is not implemented!"),
            _ => throw new NotImplementedException($"The database provider '{databaseProvider}' is not implemented!")
        };
    }
}

MsSqlDatabaseProvider

public class MsSqlDatabaseProvider : IDaisyDatabaseProvider
{
    public async Task<IEnumerable<Dictionary<string, object>>> GetAllAsync(string connectionString, string query, int maxResultCount = int.MaxValue, int skipCount = 0)
    {
        await using var connection = new SqlConnection(connectionString);
        query = $"SELECT * FROM ({query}) AS t order by 1 OFFSET {skipCount} ROWS FETCH NEXT {maxResultCount} ROWS ONLY";
        var rows = await connection.QueryAsync(query, commandTimeout: 60 * 3); 

        return rows.ToDictionaryRowsList();
    }
}

IDaisyDatabaseProvider

public interface IDaisyDatabaseProvider : ITransientDependency
{
    Task<IEnumerable<Dictionary<string, object>>> GetAllAsync(string connectionString, string query, int maxResultCount = int.MaxValue, int skipCount = 0);
}

ApiManagerServiceHttpApiModule

[DependsOn(
    typeof(ApiManagerServiceApplicationContractsModule),
    typeof(AbpAspNetCoreMvcModule),
    typeof(CoreHttpApiModule))]
public class ApiManagerServiceHttpApiModule : AbpModule
{
    public override void PreConfigureServices(ServiceConfigurationContext context)
    {
        PreConfigure<IMvcBuilder>(mvcBuilder => { mvcBuilder.AddApplicationPartIfNotExists(typeof(ApiManagerServiceHttpApiModule).Assembly); });
    }

    public override void ConfigureServices(ServiceConfigurationContext context)
    {
        context.Services.AddSingleton<IActionDescriptorChangeProvider>(DaisyActionDescriptorChangeProvider.Instance);
        context.Services.AddSingleton(DaisyActionDescriptorChangeProvider.Instance);

        context.Services.AddTransient<ServiceBasedControllerActivator>();
        context.Services.Replace(ServiceDescriptor.Transient<IControllerActivator, DaisyControllerActivator>());

        Configure<AbpLocalizationOptions>(options =>
        {
            options.Resources
                .Get<ApiManagerServiceResource>()
                .AddBaseTypes(typeof(AbpUiResource));
        });

        Configure<AbpAuditingOptions>(options =>
        {
            options.IsEnabledForGetRequests = true;
            options.HideErrors = false;
            options.IsEnabledForIntegrationServices = true;
        });
        
        Configure<AbpExceptionHandlingOptions>(options =>
        {
            options.SendExceptionsDetailsToClients = true;
            options.SendStackTraceToClients = false;
        });

        context.Services.ConfigureOpenTelemetry();
    }

    public override async Task OnPostApplicationInitializationAsync(ApplicationInitializationContext context)
    {
        await context.ServiceProvider
            .GetRequiredService<ApiManagerControllerRegistrar>()
            .RegisterAsync();
    }
}

And after that I can see it in swagger. For the first request it is working, other than that it is throwing the error that I mentioned above:

[api-manager-service_d2c3d4bb-2]: [09:16:49 ERR] ---------- RemoteServiceErrorInfo ----------
[api-manager-service_d2c3d4bb-2]: {
[api-manager-service_d2c3d4bb-2]: "code": null,
[api-manager-service_d2c3d4bb-2]: "message": "Instances cannot be resolved and nested lifetimes cannot be created from this LifetimeScope as it (or one of its parent scopes) has already been disposed.",
[api-manager-service_d2c3d4bb-2]: "details": "ObjectDisposedException: Instances cannot be resolved and nested lifetimes cannot be created from this LifetimeScope as it (or one of its parent scopes) has already been disposed.\r\n",
[api-manager-service_d2c3d4bb-2]: "data": {},
[api-manager-service_d2c3d4bb-2]: "validationErrors": null
[api-manager-service_d2c3d4bb-2]: }
[api-manager-service_d2c3d4bb-2]:
[api-manager-service_d2c3d4bb-2]: [09:16:49 ERR] Instances cannot be resolved and nested lifetimes cannot be created from this LifetimeScope as it (or one of its parent scopes) has already been disposed.
[api-manager-service_d2c3d4bb-2]: System.ObjectDisposedException: Instances cannot be resolved and nested lifetimes cannot be created from this LifetimeScope as it (or one of its parent scopes) has already been disposed.
[api-manager-service_d2c3d4bb-2]: at Autofac.Core.Lifetime.LifetimeScope.ThrowDisposedException()
[api-manager-service_d2c3d4bb-2]: at Autofac.Core.Lifetime.LifetimeScope.ResolveComponent(ResolveRequest request)
[api-manager-service_d2c3d4bb-2]: at Autofac.ResolutionExtensions.TryResolveService(IComponentContext context, Service service, IEnumerable`1 parameters, Object& instance)
[api-manager-service_d2c3d4bb-2]: at Autofac.ResolutionExtensions.ResolveService(IComponentContext context, Service service, IEnumerable`1 parameters)
[api-manager-service_d2c3d4bb-2]: at Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService[T](IServiceProvider provider)
[api-manager-service_d2c3d4bb-2]: at Daisy.Core.DataManagement.DatabaseProviders.DaisyDatabaseProviderFactory.Create(DatabaseProviderTypes databaseProvider) in C:\Users\aliriza\Documents\Projects\GitHub\Daisy\shared\Daisy.Core\src\Daisy.Core.Domain\DataManagement\DatabaseProviders\DaisyDatabaseProviderFactory.cs:line 25
[api-manager-service_d2c3d4bb-2]: at DaisyDataBaseProvider.DbProviderController.GetPatientData()
[api-manager-service_d2c3d4bb-2]: at lambda_method2045(Closure, Object)
[api-manager-service_d2c3d4bb-2]: at Microsoft.AspNetCore.Mvc.Infrastructure.ActionMethodExecutor.AwaitableObjectResultExecutor.Execute(ActionContext actionContext, IActionResultTypeMapper mapper, ObjectMethodExecutor executor, Object controller, Object[] arguments)
[api-manager-service_d2c3d4bb-2]: at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.<InvokeActionMethodAsync>g__Logged|12_1(ControllerActionInvoker invoker)
[api-manager-service_d2c3d4bb-2]: at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.<InvokeNextActionFilterAsync>g__Awaited|10_0(ControllerActionInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted)
[api-manager-service_d2c3d4bb-2]: at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.Rethrow(ActionExecutedContextSealed context)
[api-manager-service_d2c3d4bb-2]: at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.Next(State& next, Scope& scope, Object& state, Boolean& isCompleted)
[api-manager-service_d2c3d4bb-2]: at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.InvokeInnerFilterAsync()
[api-manager-service_d2c3d4bb-2]: --- End of stack trace from previous location ---
[api-manager-service_d2c3d4bb-2]: at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeNextExceptionFilterAsync>g__Awaited|26_0(ResourceInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted)

BTW, it is working if I write this controller hardcoded (traditional way).


7 Answer(s)
  • User Avatar
    1
    maliming created
    Support Team Fullstack Developer

    hi

    Please try to use IRootServiceProvider _serviceProvider; to repleace IServiceProvider _serviceProvider;

    Be careful to use the root service provider since there is no way to release/dispose objects resolved from the root service provider. So, always create a new scope if you need to resolve any service.

    If still not working, please share a minimal project to reproduce the problem. Thanks

    liming.ma@volosoft.com

  • User Avatar
    0
    alirizaadiyahsi created

    Okay it is working now. Could give more detailed explanation about this?

    Also, what is the best practice, should I always use IRootServiceProvider instead of IServiceProvider?

  • User Avatar
    0
    maliming created
    Support Team Fullstack Developer

    Can you share a minimal project so I can write the details reason?

  • User Avatar
    0
    alirizaadiyahsi created

    Actually I shared all the code I have in the question.

  • User Avatar
    0
    maliming created
    Support Team Fullstack Developer

    Instances cannot be resolved and nested lifetimes cannot be created from this LifetimeScope as it (or one of its parent scopes) has already been disposed.

    The _serviceProvider in DaisyDatabaseProviderFactory is from the http request(HttpContext.RequestServices). It will be released when the request ends.

    The error happened when the next request is coming.

    MyTypeActivatorCache is not applicable to your case, you can try to create a controller instance every time(without cache)

  • User Avatar
    0
    alirizaadiyahsi created

    @maliming, not it is clear. As you said problem is MyTypeActivatorCache . Now I removed it and I am creating controller every time. Here is the latest controller activator:

    public class DaisyControllerActivator : IControllerActivator
    {
        private readonly ServiceBasedControllerActivator _serviceBasedControllerActivator;
    
        public DaisyControllerActivator(ServiceBasedControllerActivator serviceBasedControllerActivator)
        {
            _serviceBasedControllerActivator = serviceBasedControllerActivator;
        }
    
        public object Create(ControllerContext context)
        {
            try
            {
                return _serviceBasedControllerActivator.Create(context);
            }
            catch (Exception ex)
            {
                // Create controller if not found.
                return ActivatorUtilities.CreateInstance(context.HttpContext.RequestServices, context.ActionDescriptor.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));
            }
            
            _serviceBasedControllerActivator.Release(context, controller);
        }
    }
    

    Thanks a lot for your patient/help.

  • User Avatar
    0
    maliming created
    Support Team Fullstack Developer

    : )

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