0

I have a classes tree having one being inheriting the other. All these base classes have different types of functionalities. So the inheriting class can choose which one to go for. Functionalities are move from base class to upwards. The base functionality of these classes requires IServiceProvider to be resolved.

The first solution which I don't like is passing IServiceProvider into the handler class which moves from one base to the next and so on.

The second one is what I am looking for. I looking for a workaround that I should be able to create instances of my handler classes without parameters and the base class itself should resolve IServiceProvider whenever required.

I could not find a solution to this till now.

Code example to make the question meaningful I am adding some code skeleton.

abstract class Root<ResultType, DbClass>
    where ResultType : class
    where DbClass : class
{
    private DbContext ent;
    abstract ResultType DefineHowToCreateResult(DbClass inputClass);

    IEnumerable<ResultType> GetAll()
    {
        // This class itself should be able to resolve the context. So everytime
        // it should not expect it to come all the way from child
        ent = ResolveContext<DbContext>();
        return DefineHowToCreateResult(ent.DbClass());
    }

    DbClass AddNew(DbClass inputPara)
    {
        // This class itself should be able to resolve the context. So everytime
        // it should not expect it to come all the way from child
        ent = ResolveContext<DbContext>();
        ent.AddNew(inputPara);
        return DbClass;
    }
}

Some functionalities come under the scope of this intermediate class. But it is never supposed to do GetAll() or AddNew() kind of functionalities. Because it is supposed to be handled by the Root class.

abstract class IntermediatRoot<ResultType, DbClass, InterMediatPara>
    : Root<ResultType, DbClass>
    where ResultType : class
    where DbClass : class
    where InterMediatPara : class
{
    ResultType PerformInterMediatWork()
    {
        var allData = GetAll();
        //------
        //------
        //------
        //------
        //------
        return ResultType;
    }
}

I simply want it to be a parameterless constructor and don't want to take IServiceProvider all the way down. Because ultimately it is required on Root class and I want that root class to be able to resolve the perform what I want. It would make that class a little bit independent and self-responsible for the scope of work given to it.

internal class HandlerClass : IntermediatRoot<H1, H2, H3>
{
    private override H1 DefineHowToCreateResult(H2 inputClass)
    {
        //------
        //------
        //------
        //------
    }
}
class H1 { }
class H2 { }
class H3 { }
Steven
  • 166,672
  • 24
  • 332
  • 435
  • 1
    Please be aware that the Service Locator pattern is the antithesis of Dependency Injection (DI). It is the *exact* opposite of doing DI and should, in general, be prevented, because of the large range of downsides that it exhibits. Unfortunately, your question contains to little details that allows us to give alternative solutions that effectively apply DI instead. Could you add some concrete code examples of the relevant code? – Steven Aug 15 '21 at 18:06
  • Thanks, @Steven Sorry if the question was not that clear earlier. I have tried to edit the question with some skeleton of requirements. So that you could actually find out the requirement in real. – Muhammad Irfan Aug 16 '21 at 18:47
  • You want to be able to resolve instances from the base class, but you don't want to have anything that allows you to resolve those instances from. Seems like a chicken-and-the-egg dilemma to me. Your question has no solution. That said, I think you should experiment with getting rid of the base-class hierarchy altogether. Base classes typically lead to more complexity while in the context of DI we prefer composition over inheritance. Instead of depending on behavior provided by a base class, try injecting that behavior instead. – Steven Aug 16 '21 at 20:06
  • Thanks @Steven, passing explicitly is the only what I am doing now. But my point was since in structure wise(asp.net) this kind of stuff is always cooked and ready state. So why explicitly pass these and not to consume what we already have. In my thought the current httpcontext, request, iServiceProvider etc. are platform managed elements and they are available throughout, within current scope, so why I need to follow an explicit passes and not just take what is already available as globally. – Muhammad Irfan Aug 17 '21 at 01:36
  • 2
    Hi Muhammad, a lot has been written about the considerate downsides of the pattern you are trying to apply, such as [here](https://blog.ploeh.dk/2010/02/03/ServiceLocatorisanAnti-Pattern/), [here](https://freecontent.manning.com/the-service-locator-anti-pattern/), and [DIPP&P](https://cuttingedge.it/book/) [devotes](https://livebook.manning.com/book/dependency-injection-principles-practices-patterns/chapter-5/136) eight pages on this topic. It would be good to read more on this to allow an informed decision on whether this is the way to go for you. – Steven Aug 17 '21 at 06:58
  • Related: https://stackoverflow.com/a/47844594/264697 – Steven Aug 17 '21 at 09:26

1 Answers1

1

Before anything furthur, i must say this first... what's so wrong with passing IServiceProvider along the child class in the hierarchy ? If the child class doesn't make use of it, then just less it pass through, otherwise, create a private readonly property should be fine. Since the DI will automatically provide the right scope of which IServiceProvider you should be use.

And if you want it so bad... create an instance which hold the service provider like

public class ServiceProviderContainer : IDisposable
    {
        private static IHttpContextAccessor _httpContextAccessor;
        private static readonly ConcurrentDictionary<string, IServiceProvider> ServiceProviderContainers = new();

        public ServiceProviderContainer(IServiceProvider serviceProvider, IHttpContextAccessor httpContextAccessor)
        {
            _httpContextAccessor = httpContextAccessor;
            if (!string.IsNullOrEmpty(httpContextAccessor.HttpContext?.TraceIdentifier))
            {
                ServiceProviderContainers.TryAdd(httpContextAccessor.HttpContext?.TraceIdentifier, serviceProvider);
            }
        }

        public static IServiceProvider ResolveCommonServiceProvider() =>
            _httpContextAccessor.HttpContext?.TraceIdentifier != null &&
            ServiceProviderContainers.TryGetValue(_httpContextAccessor.HttpContext.TraceIdentifier,
                out var serviceProvider)
                ? serviceProvider
                : throw new InvalidOperationException(
                    $"failed to get the Service Provider. Current trace Id: {_httpContextAccessor.HttpContext?.TraceIdentifier}");

        public void Dispose()
        {
            if (!string.IsNullOrEmpty(_httpContextAccessor.HttpContext?.TraceIdentifier))
            {
                ServiceProviderContainers.TryRemove(_httpContextAccessor.HttpContext.TraceIdentifier, out _);
            }
        }
    }

Register it as scope services.AddScoped<ServiceProviderContainer>(); - And this is the only life cycle you want to register something like Service provider!

If you already have a middleware upper your execution logic, put it there to ensure it get created every http request:

public class SomeUpperMiddleware
    {
        public SomeUpperMiddleware(ServiceProviderContainer container)
        {
        }
    }

Then now, feel free to use it anywhere in your application:

var container = ServiceProviderContainer.ResolveCommonServiceProvider();
var logger = container.GetRequiredService<ILogger<ProductController>>();

But let me insist again, this approach should be avoid cause it make the scope of IServiceProvider dusky.

Gordon Khanh Ng.
  • 1,352
  • 5
  • 12
  • Thanks, @Gordon Khanh Ng. I have added some code examples to make my requirement a little bit more understandable. – Muhammad Irfan Aug 16 '21 at 18:48
  • @MuhammadIrfan I just have some tuning on the solution to be more robust and accurate, it'll throw exception if something went wrong, you might want to checkout some – Gordon Khanh Ng. Aug 17 '21 at 04:30