If you don't want the references between projects you could look into factories/abstract factories.
Your UI knows about your business layer, so you want to define a factory in your business layer which knows how to use the data layer. Then you handle all your DI in your composition root (the UI project in this example).
A simple example below using a console app as the UI, sticking to the references you stated in your question
Data layer
public interface IDataAccess
{
string GetData();
}
public class XmlDataAccess : IDataAccess
{
public string GetData()
{
return "some data";
}
}
Business layer
public interface IDataAccessFactory
{
IDataAccess GetDataAccess();
}
public class XmlDataAccessFactory : IDataAccessFactory
{
public IDataAccess GetDataAccess()
{
return new XmlDataAccess();
}
}
public class BusinessLogic
{
IDataAccessFactory dataAccessFactory;
public BusinessLogic(IDataAccessFactory dataAccessFactory)
{
this.dataAccessFactory = dataAccessFactory;
}
public void DoSomethingWithData()
{
IDataAccess dataAccess = dataAccessFactory.GetDataAccess();
Console.WriteLine(dataAccess.GetData());
}
public string GetSomeData()
{
IDataAccess dataAccess = dataAccessFactory.GetDataAccess();
return dataAccess.GetData();
}
}
UI
static void Main(string[] args)
{
IUnityContainer container = new UnityContainer();
container.RegisterType<IDataAccessFactory, XmlDataAccessFactory>();
var logic = container.Resolve<BusinessLogic>();
logic.DoSomethingWithData();
string useDataInUI = logic.GetSomeData();
Console.WriteLine("UI " + useDataInUI);
Console.ReadKey();
}
It's a contrived example so it looks like abstraction for nothing, but with a real world example it would make more sense.
e.g. you might have a bunch of different data access classes in your data layer database, xml files, etc. so you might define a factory for each in your business layer.
Using abstract factories
The factory could contain a lot more logic about the nitty gritty of the data layer, or as an abstract factory provide a set of individual factories to the business logic layer.
Business layer
You might instead have an abstract factory in the business layer such as
public interface IPlatformFactory
{
IDataAccessFactory GetDataAccessFactory();
IPricingFactory GetPricingFactory(); // might be in the business project, or another project referenced by it
}
with a concrete factory
public class WebPlatformFactory : IPlatformFactory
{
IDataAccessFactory GetDataAccessFactory()
{
return new XmlDataAccessFactory();
}
IPricingFactory GetPricingFactory()
{
return new WebPricingFactory(); // not shown in the example
}
}
(You might have additional concrete factories such as RetailPlatformFactory
, etc.)
Your BusinessLogic
class would now look something like
public class BusinessLogic
{
IPlatformFactory platformFactory;
public BusinessLogic(IPlatformFactory platformFactory)
{
this.platformFactory = platformFactory;
}
public void DoSomethingWithData()
{
IDataAccessFactory dataAccessFactory = platformFactory.GetDataAccessFactory();
IDataAccess dataAccess = dataAccessFactory.GetDataAccess();
Console.WriteLine(dataAccess.GetData());
}
public string GetSomeData()
{
IDataAccessFactory dataAccessFactory = platformFactory.GetDataAccessFactory();
IDataAccess dataAccess = dataAccessFactory.GetDataAccess();
return dataAccess.GetData();
}
}
Data layer
Your business layer no longer needs to provide an IDataAccessFactory
to your UI so you can move it into your data layer in this example. So the data layer classes would be
public interface IDataAccess
{
string GetData();
}
public class XmlDataAccess : IDataAccess
{
public string GetData()
{
return "some data";
}
}
public interface IDataAccessFactory
{
IDataAccess GetDataAccess();
}
public class XmlDataAccessFactory : IDataAccessFactory
{
public IDataAccess GetDataAccess()
{
return new XmlDataAccess();
}
}
UI
Now you'd in the UI you'd configure the container and perform similar actions as
static void Main(string[] args)
{
IUnityContainer container = new UnityContainer();
container.RegisterType<IPlatformFactory, WebPlatformFactory>();
var logic = container.Resolve<BusinessLogic>();
logic.DoSomethingWithData();
string useDataInUI = logic.GetSomeData();
Console.WriteLine("UI " + useDataInUI);
Console.ReadKey();
}
The UI then knows nothing about the data layer/access, it's just handing off the factory creation to the business layer, which holds the data (and pricing) references.
Some recommended reading:
Composition Root
Implementing an Abstract Factory
Compose object graphs with confidence