问题
I have a Data Access Layer library that I would like to make "portable". The reason I like it to be portable is because I want to work with SQL Azure & Azure File Storage (eg, data + pdf reports) as well as Sql Server 2008R2 and File System storage on a concrete server.
Based on spec the system is supposed to go live with the later implementation (sql + file system storage), while upon a meeting a certain scalability threshold we plan on moving to Azure.
The data access class I use implements IDataProvider interface (which I built) and it defines the methods that any data access concrete implementation should have. Consuming the data access layer is done via passing the IDataProvider interface and calling methods on it eg:
public Interface IDataProvider
{
public bool DoSomething();
}
public class AzureDataProvider : IDataProvider
{
private string ConnectionString;
public AzureDataProvider(string connectionString)
{
this.ConnectionString = connectionString;
}
public AzureDataProvider():this(
ConfigurationManager.ConnectionString["conn"].ConnectionString)
{
}
public bool DoSomething()
{
return false;
}
}
So now the issue is that the consumer class that would call methods on the IDataProvider interface has to do the following:
public class DataAccessConsumer
{
public void SomeOperation()
{
AzureDataProvider azureProvider = new AzureDataProvider();
IDataProvider dataProvider = (IDataProvider)azureProvider;
bool result = dataProvider.DoSomething();
}
}
So the issue with the above code is that the client still has to have knowledge of the concrete AzureDataProvider class. I want to have a method of providing the client with just the IDataProvider interface, without passing a connection string to every interface method, or looking up the connection string within every method via the ConfigurationManager.
Is that possible? Would Abstract Factory or some sort of Dependency Injection Container pattern do the trick? If so, I would appreciate code samples, or links to code samples.
回答1:
Firstly, the AzureDataProvider has a dependency on the ConfigurationManager. This should be injected in instead.
Secondly, this DataProvider should be injected into the DataAccessConsumer.
This means that a realistic application will have good dependency injection throughout, with no dependency on the container, however you will then need to the "wiring" - connecting all the dependencies together. This is a pain - use the DependencyInjectionContainer only at the main entry point to help resolve this wiring. (This allows you to use a more convenient declarative approach rather than an imperative approach, because you can ask the container "Get me the DataAccessConsumer", and the dependency injection framework will figure out the dependencies for you.
My favorite dependency injection framework for C# is NInject2.
回答2:
Well, I rolled out my own DI. If the goal is to achieve portability then I think I have come to a semi-acceptable solution. The disadvantages are that you cannot truly achieve a full DI, however in the context of my application it is good enough.
Connection Interface:
public interface IConnection
{
public string ConnectionString;
}
Concrete Connection Implementation
public class Connection: IConnection
{
public string ConnectionString{ get; set; }
public Connection(string connectionString)
{
this.ConnectionString = connectionString;
}
public Connection():this(ConfigurtionManager.ConnectionStrings["connection"].ConnectionString)
{
//Broke DI in the interest of usability.
}
}
Data Access Layer Interface
public interface IDataProvider
{
IConnection Connection;
public void Foo();
}
Concrete Data Access Layer Implementation
public class AzureProvider : IDataProvider
{
IConnection Connection { get; set; }
public AzureProvider(IConnection connection)
{
this.Connection = connection;
}
public void Foo()
{
}
}
DI Conainer / Factory (Singleton or Static Class)
public static class ProviderFactory
{
public static IDataProvider GetProvider() //I'd pass parameters if I had more than 1.
{
Connection connection = new Connection(); //this is why I broke DI.
IConnection iConnection = (IConnection)connection;
AzureProvider azureProvider = new AzureProvider(iConnection);
IDataProvider iDataProvider = (IDataProvider)azureProvider;
return iDataProvider;
}
}
Data Access Layer Consumer (in this sample it is a page):
public class SomePage : Page
{
protected void Page_Load(object sender, EventArgs e)
{
IDataProvider provider = ProviderFactory.GetProvider();
provider.Foo();
}
}
As you can see, the page doesn't need to know any of the implementation details of the data access layer. As long as the ProviderFactory can spit IDataProvider, the page is happy. Therefore, if we decide to change providers, say SqlStorageProvider, as long as it implements IDataProvider Interface, the Page's code will not have to be changed. This achieves true separation of concerns in terms of software architecture.
来源:https://stackoverflow.com/questions/4796051/dependency-injection-container