Dependency Injection in Model classes (entities)

Viewed 19337

I am building an ASP.NET Core MVC application with Entity Framework Code-First. I implemented a simple repository pattern, providing basic CRUD operations for all the model classes I have created. I chose to follow all the recommendations provided in docs and DI is one of these.

In ~~.NET 5~~ (6 years later update: .net 5 was the alpha name of .net core 1.0) dependency injection works very well for any class that we do not directly instantiate (e.g.: controllers, data repositories, ...).

We simply inject them via the constructor, and register the mappings in the Startup class of the application :

// Some repository class
public class MyRepository : IMyRepository
{
    private readonly IMyDependency _myDependency;
    public MyRepository(IMyDependency myDependency)
    {
        _myDependency = myDependency;
    }
}

// In startup.cs :
services.AddScoped<IMyDependency, MyDependency>();
services.AddScoped<IMyRepository, MyRepository>();

The problem is that in some of my model classes, I would like to inject some of the dependencies I have declared.

But I think that I cannot use the constructor injection pattern because model classes are often explicitly instantiated. Therefore, I would need to provide myself with the dependencies, which I can't.

So my question is: is there another way than constructor injection to inject dependencies, and how? I was for example thinking of an attribute pattern or something like that.

7 Answers

I know my answer is late and may not exactly what you're asking for, but I wanted to share how I do it.

First of all: If you want to have a static class that resolves your dependencies this is a ServiceLocator and it's Antipattern so try not to use it as you can. In my case I needed it to call MediatR inside of my DomainModel to implement the DomainEvents logic.

Anyway, I had to find a way to call a static class in my DomainModel to get an instance of some registered service from DI.

So I've decided to use the HttpContext to access the IServiceProvider but I needed to access it from a static method without mention it in my domain model.

Let's do it:

1- I've created an interface to wrap the IServiceProvider

public interface IServiceProviderProxy
{
    T GetService<T>();
    IEnumerable<T> GetServices<T>();
    object GetService(Type type);
    IEnumerable<object> GetServices(Type type);
}

2- Then I've created a static class to be my ServiceLocator access point

public static class ServiceLocator
{
    private static IServiceProviderProxy diProxy;

    public static IServiceProviderProxy ServiceProvider => diProxy ?? throw new Exception("You should Initialize the ServiceProvider before using it.");

    public static void Initialize(IServiceProviderProxy proxy)
    {
        diProxy = proxy;
    }
}

3- I've created an implementation for the IServiceProviderProxy which use internally the IHttpContextAccessor

public class HttpContextServiceProviderProxy : IServiceProviderProxy
{
    private readonly IHttpContextAccessor contextAccessor;

    public HttpContextServiceProviderProxy(IHttpContextAccessor contextAccessor)
    {
        this.contextAccessor = contextAccessor;
    }

    public T GetService<T>()
    {
        return contextAccessor.HttpContext.RequestServices.GetService<T>();
    }

    public IEnumerable<T> GetServices<T>()
    {
        return contextAccessor.HttpContext.RequestServices.GetServices<T>();
    }

    public object GetService(Type type)
    {
        return contextAccessor.HttpContext.RequestServices.GetService(type);
    }

    public IEnumerable<object> GetServices(Type type)
    {
        return contextAccessor.HttpContext.RequestServices.GetServices(type);
    }
}

4- I should register the IServiceProviderProxy in the DI like this

public void ConfigureServices(IServiceCollection services)
{
    services.AddHttpContextAccessor();
    services.AddSingleton<IServiceProviderProxy, HttpContextServiceProviderProxy>();
    .......
}

5- Final step is to initialize the ServiceLocator with an instance of IServiceProviderProxy at the Application startup

public void Configure(IApplicationBuilder app, IHostingEnvironment env,IServiceProvider sp)
{
    ServiceLocator.Initialize(sp.GetService<IServiceProviderProxy>());
}

As a result now you can call the ServiceLocator in your DomainModel classes "Or and needed place" and resolve the dependencies that you need.

public class FakeModel
{
    public FakeModel(Guid id, string value)
    {
        Id = id;
        Value = value;
    }

    public Guid Id { get; }
    public string Value { get; private set; }

    public async Task UpdateAsync(string value)
    {
        Value = value;
        var mediator = ServiceLocator.ServiceProvider.GetService<IMediator>();
        await mediator.Send(new FakeModelUpdated(this));
    }
}

I'm simply adding some supplemental information here to the answers provided that can help.

IServiceProvider was provided in the accepted answer, but not the important IServiceProvider.CreateScope() method. You can use it to create scopes as necessary that you added through ConfigureServices.

I'm not sure if IServiceProvider is actually a Service Locator pattern behind the scenes or not, but it's how you create scopes as far as I know. At least in the case if it is a Service Locator pattern, it's the official one for today in .NET, and so it's not compounded by the problems of writing your own Service Locator, which I also agree is anti-pattern.

Example, Startup.cs/ConfigureServices and Configure:

    public void ConfigureServices(IServiceCollection services)
    {
        services.AddDbContext<SomeDbContext>(options =>
        {
            options.UseSqlServer(Configuration.GetSection("Databases").GetSection("SomeDb")["ConnectionString"]);
            options.UseQueryTrackingBehavior(QueryTrackingBehavior.NoTracking);
        }, ServiceLifetime.Scoped);

        services.AddMvcCore().AddNewtonsoftJson();
        services.AddControllersWithViews();
    }

    public async void Configure(IApplicationBuilder app, IWebHostEnvironment env, IServiceProvider provider)
    {
        ...
        IServiceScope scope = provider.CreateScope();
        SomeDbContext context = scope.ServiceProvider.GetRequiredService<SomeDbContext>();
        SomeModelProxyClass example = new SomeModelProxyClass(context);
        await example.BuildDefaults(
            Configuration.GetSection("ProfileDefaults").GetSection("Something"),
            Configuration.GetSection("ProfileDefaults").GetSection("SomethingSomething"));
        scope.Dispose();
    }

The above is for doing some default interactions on Startup, maybe if you need to build some default records in your database on a first usage, just as an example.

Ok so let's get to your repository and dependency though, will they work?

Yep!

Here's a test in my own CRUD project, I made a simple minimalist implementation of your IMyDependency and IMyRepository like so, then added them scoped as you did to Startup/ConfigureServices:

public interface IMyRepository
{
    string WriteMessage(string input);
}

public interface IMyDependency
{
    string GetTimeStamp();
}

public class MyDependency : IMyDependency
{
    public MyDependency()
    {

    }

    public string GetTimeStamp()
    {
        return DateTime.Now.ToLongDateString() + " " + DateTime.Now.ToLongTimeString();
    }
}

public class MyRepository : IMyRepository
{
    private readonly IMyDependency _myDependency;
    public MyRepository(IMyDependency myDependency)
    {
        _myDependency = myDependency;
    }

    public string WriteMessage(string input)
    {
        return input + " - " + _myDependency.GetTimeStamp();
    }
}

Here ContextCRUD is a Model class from my own project not derived from Scaffold-DbContext tooling like my other database classes, it's a container of logic from those scaffold Model classes, and so I put it in the namespace Models.ProxyModels to hold its own business logic for doing CRUD operations so that the Controllers are not gummed up with logic that should be in the Model:

    public ContextCRUD(DbContext context, IServiceProvider provider)
    {
        Context = context;
        Provider = provider;

        var scope = provider.CreateScope();
        var dep1 = scope.ServiceProvider.GetService<IMyRepository>();
        string msg = dep1.WriteMessage("Current Time:");
        scope.Dispose();
    }

Debugging I get back the expected results in msg, so it all checks out.

The calling code from the Controller for reference, just so you can see how IServiceProvider is passed from upstream by constructor injection in the Controller:

[Route("api/[controller]")]
public class GenericController<T> : Controller where T: DbContext
{
    T Context { get; set; }
    ContextCRUD CRUD { get; set; }
    IConfiguration Configuration { get; set; }

    public GenericController(T context, IConfiguration configuration, IServiceProvider provider)
    {
        Context = context;
        CRUD = new ContextCRUD(context, provider);
        Configuration = configuration;
    }
    ...

You can do it, check out [InjectionMethod] and container.BuildUp(instance);

Example:

Typical DI constructor (NOT NEEDED IF YOU USE InjectionMethod) public ClassConstructor(DeviceHead pDeviceHead) { this.DeviceHead = pDeviceHead; }

This attribute causes this method to be called to setup DI. [InjectionMethod] public void Initialize(DeviceHead pDeviceHead) { this.DeviceHead = pDeviceHead; }

Related