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)
-
1
hi
Please try to use
IRootServiceProvider _serviceProvider;
to repleaceIServiceProvider _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
-
0
Okay it is working now. Could give more detailed explanation about this?
Also, what is the best practice, should I always use
IRootServiceProvider
instead ofIServiceProvider
? -
0
Can you share a minimal project so I can write the details reason?
-
0
Actually I shared all the code I have in the question.
-
0
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
inDaisyDatabaseProviderFactory
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) -
0
@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.
-
0
: )