0

I would like to prevent other developers from deviating from the application's architecture by limiting the classes in which the UnitOfWork class can be injected. Or at least make it more obvious that they're deviating from the accepted pattern.

We have a collection of services that all extend the abstract class of BaseService. The BaseService class contains the property for the UnitOfWork class which is injected via dependency injection.

public abstract class BaseService
{
    protected readonly IUnitOfWork UnitOfWork;

    protected BaseService(IUnitOfWork unitOfWork)
    {
        UnitOfWork = unitOfWork;
    }
}

This means if a class needs the UnitOfWork it'll need to extend the BaseService class, but nothing actually prevents a developer from just injecting the UnitOfWork into some other class that's not a service. If they did this, they could potentially be saving changes outside of the service, which is what we're trying to avoid.

Is there a way that we can throw a build error or warning based on the UnitOfWork being injected in some other class?

Roachy
  • 1
  • 2
  • 2
    how about putting it into a separate project, and marking the class as `internal`? – Franz Gleichmann Aug 13 '22 at 18:45
  • To create a build error/warning you'd need to create a custom analyzer and it's probably not worth if that's the only rule you want to enforce. https://learn.microsoft.com/en-us/dotnet/csharp/roslyn-sdk/tutorials/how-to-write-csharp-analyzer-code-fix – funatparties Aug 13 '22 at 22:49
  • You can write a unit test that goes through the classes in the solution using reflection to check the architectural rule is violated. Or similarly, you can apply the checks to your container registrations at startup (depending on the DI library you use). – Steven Aug 14 '22 at 10:26
  • Which DI library are you using? – Steven Aug 14 '22 at 10:28
  • I'm using the built-in dependency injection in ASP.NET Core. @Steven – Roachy Aug 14 '22 at 20:03

1 Answers1

0

Two possible solutions come into my mind: One is based on using DI framework features to make the registration only visible in a specific scope of the code. The other one is based on using a public and an internal interface, while the implementation is only registered for the internal interface.

Solution based on DI framework

I best know Autofac, so I name the solution for this DI framework here. But I think other DI frameworks have similar features, so a bit of searching could give you the corresponding feature in your DI framework:

In case that your dependency injection framework is Autofac you can work with lifetime scopes to make specific registrations only available in a limited "area" (scope) of your code. (See also at the official Autofac documentation for an explaination about the concept of liftime scopes: https://autofac.readthedocs.io/en/latest/lifetime/index.html)

Solution via splitting the interface

This approach is to split IUnitOfWork into two interfaces - one which is public and one which is internal and therefore not accessible from other projects. The implementation is only registered for the internal interface, so that other projects cannot inject anything, as the interface is not known to them.

public interface IUnitOfWork
{
    // ...
}

internal interface IInjectableUnitOfWork : IUnitOfWork
{
    // no content
}

public abstract class BaseService
{
    protected readonly IUnitOfWork UnitOfWork;

    protected BaseService(IInjectableUnitOfWork unitOfWork)
    {
        UnitOfWork = unitOfWork; // hint: The assignment works, because IInjectableUnitOfWork inherits from IUnitOfWork.
    }
}
Chris
  • 26
  • 4
  • 1
    One more hint: In the case that the question is not about legacy code (where changes to the current pattern might be difficult), I would propose to avoid such inheritance structure, as it tends to be hard to change in future. (see also the rule of thumb "prefer composition over inheritance" - or ). – Chris Aug 13 '22 at 23:25