简体   繁体   中英

Dependecy Injection : Cannot resolve scoped service

I get the following exception:

System.InvalidOperationException: 'Cannot resolve scoped service 'OSPC.Shared.Core.Data.IRepository`1[OSPC.Configuration.Objects.SignalMetaData]' from root provider.' on the line ActivatorUtilities.CreateInstance in the CommandDispatcher class.

WHen investigating the parameter serviceProvider which is injected in the CommandDispatcher class I can see 122 items in the ResolvedServices collection (all microsoft related services) but none of my services which are registered in the ConfigureServices method are in the collection.

Why are my own registered services not appearing?

public class Startup
{
    public void ConfigureServices(IServiceCollection services)
    {
        services.AddSingleton<ILogManager, LogManager>();
        services.AddScoped<IDbContext>(c => new OSPCContext(Configuration.GetConnectionString("OSPCConnectionstring")));
        services.AddScoped(typeof(IRepository<>), typeof(EfRepository<>));
        services.AddSingleton<ICommandDispatcher, CommandDispatcher>();
    }
}



public class CommandDispatcher : ICommandDispatcher
{
    private readonly IServiceProvider _serviceProvider;
    private readonly Dictionary<string, Type> _registeredCommands = new Dictionary<string, Type>();

    public CommandDispatcher(IServiceProvider serviceProvider)
    {
        _serviceProvider = serviceProvider;
        _registeredCommands.Add("CreateSignalMetaData", typeof(CreateSignalMetaDataHandler));
        _registeredCommands.Add("CreatePDCQueryConfig", typeof(CreatePDCQueryConfigHandler));
    }

    public object Dispatch(string json)
    {
        JObject jObject = JObject.Parse(json);
        JToken jToken = jObject["type"];
        if (jToken == null)
        {
            throw  new Exception("The parameter type is missing in the JSON string (Pay attention: it is casse sensitive).");

        }
        Type typeCommandHandler = _registeredCommands[jToken.ToString()];
        dynamic commandHandler = ActivatorUtilities.CreateInstance(_serviceProvider, typeCommandHandler);
        dynamic o = jObject.ToObject(commandHandler.InputType);
        return commandHandler.Handle(o);
    }
}



[Route("api/[controller]")]
[ApiController]
public class ConfigurationController : ControllerBase
{
    private readonly ILog _logger;
    private readonly IConfigurationService _configurationService;
    private readonly ICommandDispatcher _commandDispatcher;

    public ConfigurationController(ICommandDispatcher commandDispatcher)
    {
        this._commandDispatcher = commandDispatcher;
    }

    // POST api/configuration
    [HttpPost]
    public IActionResult Post([FromBody] dynamic  json)
    {
        var result = _commandDispatcher.Dispatch(json.ToString());                
        return Ok(result);
    }
}

using OSPC.Configuration.Msg;
using OSPC.Configuration.Objects;
using OSPC.Shared.Core.Commands;
using OSPC.Shared.Core.Data;
using System.Collections.Generic;

namespace OSPC.Configuration.CommandHandler
{
    public class CreateSignalMetaDataHandler : CommandHandlerBase<CreateSignalMetaDataRequest, CreateSignalMetaDataResponse>
    {
        private readonly IRepository<SignalMetaData> _signalMetaDataRepository;

        public CreateSignalMetaDataHandler(IRepository<SignalMetaData> signalMetaDataRepository)
        {
            _signalMetaDataRepository = signalMetaDataRepository;
        }

        public override CreateSignalMetaDataResponse Handle()
        {            
            Output.Success = false;

            var result = new CreateSignalMetaDataResponse
            {
                SignalMetaDatas = new List<CreateSignalMetaDataResponse.SignalMetaData>()
            };

            foreach (var item in Input.Payload.SignalMetaDatas)
            {
                var signalMetaData = new Objects.SignalMetaData()
                {
                    SignalName = item.SignalName,
                    Unit = item.Unit,
                    SignalDescription = item.SignalDescription,
                    Source = item.Source
                };
                _signalMetaDataRepository.Insert(signalMetaData);

                result.SignalMetaDatas.Add(new CreateSignalMetaDataResponse.SignalMetaData()
                {
                    Id = signalMetaData.Id,
                    SignalName = signalMetaData.SignalName,
                    Unit = signalMetaData.Unit,
                    SignalDescription = signalMetaData.SignalDescription,
                    Source = signalMetaData.Source
                });
            }

            Output.Success = true;        

            return result;
        }
    }
}

For IRepository<> , it is scoped, and for ICommandDispatcher , it is singleton. For ActivatorUtilities.CreateInstance(_serviceProvider, typeCommandHandler); , the _serviceProvider is root provider which could not resove the scoped service.

Try code below to create a new scoped provider.

dynamic commandHandler = ActivatorUtilities.CreateInstance(
    _serviceProvider.CreateScope().ServiceProvider, typeCommandHandler);

The solution of Tao Zhou is working but I don't know if this solution also solves the captive dependency problem?

I've solved it in the following way: In the startup class I've changed it to AddScoped in place of AddSingleton

  services.AddScoped<ICommandDispatcher, CommandDispatcher>();

Otherwise I've the captive dependency problem ( https://andrewlock.net/the-dangers-and-gotchas-of-using-scoped-services-when-configuring-options-in-asp-net-core/ ).

We can NOT define it is as Singleton (CommandDispatcher) because otherwise we have a captive dependency problem because the CommandDispatcher will eventually instantiate a CommandHandler (fe CreateSignalMetaDataHandler) and in the constructor one or more EFRepositories must be injected which on their turn must have a DbContext injected. The purpose is that the instantiation of DbContext must be scoped. So each HTTP request must have one and only one instance of DbContext injected. So if we are using different repositories they must all share the same DbContext. And each HTTP request has its own DbContext and a different HTTP request will have a different DbContext. Because DbContext must be scoped, the CommandDispatcher all the way up must also be scoped to avoid the captive dependency problem. So CommandDispatcher can not be defined as Singleton.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM