4

I have the following scenario: I got a service ICompanyDocumentsService with a single implementation CompanyDocumentsServicewhich I register in my Startup class:

 public void ConfigureServices(IServiceCollection services)
        {
            services.AddScoped<ICompanyDocumentService, CompanyDocumentService>();
        }

I need this service in many places, and it doesn't bother me using DI in Constructor. However, there is one place where I need it injected in a Method (or probably in a property would be even better):

            public abstract class CompanyDocumentBase
            {
                public abstract object GetAllProperties(Employee employee, string properties, 
                      CompanyDocumentReferenceData documentReferenceData);
                // blah, blah, lots of Formatting Methods


                private CompanyDocumentService CompanyDocumentService { get; set; } // **inject service here**

                public string GetFormattedEmployeeIndividualEmploymentAgreementNumber(Employee employee, 
                    ICompanyDocumentService companyDocumentService = null) // **optional because 
//inherited class doesn't need to know anything about this service, it concerns only it's result**
                {
                    companyDocumentService = CompanyDocumentService;
                    var test  = 
                       companyDocumentService.GetEmloyeeIndividualEmploymentAgreementNumber(employee.Id);


                    return string.Empty;
                }

            }

There are many classes inheriting CompanyDocumentBase which are only concerned in it's method results, as mentioned above, that's why that parameter is optional, and that's why I don't need injecting DI in constructor, thus the inheriting classes won't be needing that.

 public class JobDescriptionCompanyDocument : CompanyDocumentBase
    {
        public override object GetAllProperties(Employee employee,
            string properties, CompanyDocumentReferenceData documentReferenceData)
        {
            var document = JsonConvert.DeserializeObject<JobDescriptionModel>(properties);

            document.IndividualEmploymentAgreementNumber = GetEmployeeIndividualEmploymentAgreementNumber(employee);

            return document;
        }
    }

Is there any simple way to achieve this? Preferable without needing to install a separate library like Unity or Autofac. Ideal it would be to somehow get the instance of CompanyDocumentsService directly into that property, something like:

private CompanyDocumentService CompanyDocumentService => Startup.Services.blah that instance
3
  • Have you tried FromServices? Commented Mar 16, 2020 at 12:43
  • @user743414, as far as I know that works in Controllers only. Also since the calling function (GetAllProperties in this case) doesn't need to know anything about my services, the called function should in this case have a nullable parameter, something like: GetEmployeeIndividualEmploymentAgreementNumber(employee, [FromServices]CompanyDocumentsService = null). However, I'll give it a try soon and come back with a feedback. Commented Mar 17, 2020 at 10:32
  • 1
    @user743414, I tried. Well first of all [FromServices] Attribute doesn't work if it's optional parameter. I tried with non-optional parameter, however, in the calling method, I can't provide anything but a null value, so no success with this approach :( Commented Mar 17, 2020 at 10:57

1 Answer 1

3

One hack way (personally I wouldn’t recommend it), is after your container is built, you could resolve an instance of IHttpContextAccessor and set it to static class, e.g. IoC

Then you could do private CompanyDocumentService CompanyDocumentService => IoC.HttpContextAccessor.HttpContext.RequestServices.GetService().

https://learn.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.http.httpcontext.requestservices?view=aspnetcore-3.1

The interface is a singleton, and provides access to scoped services from a static context.

Note you might have to explicitly register HttpContextAccessor: https://learn.microsoft.com/en-us/aspnet/core/fundamentals/http-context?view=aspnetcore-3.1

UPDATE

What I'd recommend

If you are open to object factories, and changing the way how DocumentBase is instantiated, try make a factory, and whenever you need an instance of DocumentBase, only use the factory to create it:

public abstract class CompanyDocumentBase
{
    // Use internal so that access only limited to friendly assembly
    internal CompanyDocumentService CompanyDocumentService { get; set; }
}

// Inject this class to where you need to create an instance of CompanyDocumentBase
public class CompanyDocumentFactory<T> where T : CompanyDocumentBase
{
    private readonly IServiceProvider _services;

    // DI contaiener has implicit IServiceProvider support, when you do this on constructor, it injects service provider of *current* scope
    // If this factory is registered as singleton, you need to use IHttpContextAccessor to use request's service provider in Create method
    // because your DocumentService is scoped.
    public CompanyDocumentFactory(IServiceProvider services)
    {
        _services = services;
    }

    public T Create()
    {
        // Create an instance of document use your current method.
        var instance = new T();
        instance.CompanyDocumentService = _services.GetRequiredService<ICompanyDocumentService>();
        return instance;
    }
}
Sign up to request clarification or add additional context in comments.

3 Comments

thank you for your suggestion. While missing an "official solution" I've gone with this workaround, which at the moment seems to be the best and cleanest solution :D
@FMR No worries. You could make it sort of "official" by using a ServiceLocator like this nuget.org/packages/CommonServiceLocator not your own static class. The problem is you might have people saying service locator is anti-pattern etc in your code review :) If you are open to an object factory see my update.
Or just DI IServiceProvider services in the constructor, and call _services.GetRequiredService<ICompanyDocumentService>() in your method.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.