1

I am working on an API solution, split in three projects:

  • API
  • Domain
  • DataAccessLayer

I have created an interface, in the Domain project, ICustomerRepository, with a single method:

Domain.Customer GetCustomerById(int customerId)

--

The DataAccessLayer implements this interface with a FooCustomerRepository.

Foo is a application-specific DB provider. It provides an .NET assembly, which is referenced in the DataAccessLayer project.

--

The API controller only uses the interface, which is injected through Unity:

ApiController.cs

 public class ApiController : Controller
 {
    public ApiController(
         Domain.ICustomerRepository customerRepository)
    {
         ...
    }
}

UnityConfig.cs

public static void RegisterTypes(IUnityContainer container)
{
    container.RegisterType<Domain.ICustomerRepository, DataAccessLayer.FooCustomerRepository>();
}

In order to get it to work, I have to create a dependency to DataAccessLayer in my API project, and therefore to the Foo assembly.

--

I have been assigned an intern to work on this project. For red tape reasons, I will not be able to have Foo installed on his computer before a long while, but I would like him to work on the API project, and be able to compile it.

A fake repository returning dummy data is available in the API project.

How can I achieve this?

I have thought about creating a new branch, and removing anything to do with DataAccessLayer, including the UnityConfig and the references, but it seems like it would be a nightmare to merge back (since as you can imagine, the actual project is a bit more complex).

Is that the only way to go?

2
  • Is dependency injection used throughout your project? If that is the case, then you only need to worry about the setup. Jul 18 '19 at 12:40
  • Yes, all dependencies are resolved in the unity container setup Jul 18 '19 at 12:43
1

Probably the most reliable way to handle both the normal way and for development. That's to make use of configuration. The built in configuration APIs for Net Core/Standard allow you to merge environment specific configuration with standard configuration.

var environment = Environment.GetEnvironmentVariable("Environment");
var builder = new ConfigurationBuilder()
    .SetBasePath(Directory.GetCurrentDirectory())
    .AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
    .AddJsonFile($"appsettings.{environment}.json", optional: true, reloadOnChange: true)
    .AddEnvironmentVariables();
var config = builder.Build();

And then to get the value:

var dataAccessOptions = config.GetSection("My App");
 // Get value as string
string dataAccessType = dataAccessOptions.Items["DataAccess"];

// Get value as type, can convert enums
var dataAccess = dataAccessOptions.GetValue<DataAccessType>("DataAccess");

Once you have read the config value, you can now put an if statement in your RegisterTypes method. That allows you to conditionally override the default Foo DataAccess:

public static void RegisterTypes(IUnityContainer container)
{
    if(DataAccess == DataAccessType.Foo)
    {
        container.RegisterType<Domain.ICustomerRepository, 
            DataAccessLayer.FooCustomerRepository>();
    }
    else
    {
        container.RegisterType<Domain.ICustomerRepository, 
            DataAccessLayer.MockCustomerRepository>();
    }
}

This approach makes it so that you have even more options. For example, the ability to inject mocks for tests, etc. Just limit the configurable options to those things that you don't really have any control over. It adds complexity, but allows you to solve a problem in the generic sense while still allowing you to support less than ideal situations like this.


Alternative using reflection:

public static void RegisterTypes(IUnityContainer container)
{
    var typeName = (DataAccess == DataAccessType.Foo)
        ? "DataAccessLayer.FooCustomerRepository"
        : "DataAccessLayer.MockCustomerRepository";

    var repositoryType = Type.GetType(typeName);

    container.RegisterType(
        typeof(Domain.ICustomerRepository),
        repositoryType);
}

This may be required when you need to ensure there is no direct dependency on Foo. It really depends on how your projects are set up. The assumption here is that Unity will load all assemblies in your project directory, or a plugin directory to find Foo dependencies at runtime.

3
  • Wouldn't I need to have a reference to Foo on the computer doing the compiling, even with this? Jul 18 '19 at 15:01
  • If the Foo DLL doesn't even exist in this scenario, then you can use reflection. It's not pretty, but it will work. I'll be honest, the answer to whether you need Foo or not depends on how you structured your libraries. If the dependency is transitive (i.e. only required by a wrapper library) then you can absolutely get away without having Foo present. If the dependency is direct (i.e. in your project references) then you will always need Foo present even if you never use it. Jul 18 '19 at 15:47
  • @MaximeVisconte: In the end, your application will create a single web that connects all the dependencies. That's an unavoidable fact of life (if a dependency was not connected to the application, then it's not needed). As long as your interface does not expose Foo (or any other reference from that library), then your top level project does not need to actually have a direct reference to the Foo library. Dependency good practice is about direct references. Indirect references (through another layer) are inevitable.
    – Flater
    Dec 15 '19 at 16:34

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.