问题
I am building a generic piece of middleware software (a windows service) that will perform many jobs
.. such as:
- Sync Inventory
- Import Orders
- etc
I am using C# with a Windows Service, and Quartz.NET to schedule the jobs to run. I am also using AutoFac.
From what I understand, AutoFac dependencies should be built at the composition root. This is fine..however, I have certain services that get injected which require runtime parameters (configuration values that come from the database).
For example:
- Some jobs will connect to an SFTP server whereby the connection details are stored as key value pairs in the database
- Some jobs will connect to a remote API whereby those API authentication details are stored against the job in the database.
I've done some research on this and some alternatives basically suggest removing the constructor of some of these services (such as an SFTP client) and passing these as configuration methods..
Instead of the client have a constructor such as
SftpClient(string host, string username, string password, int timeoutInSeconds)
It would have a default constructor, and a configure method that you pass these in.
I don't like this at all- it goes against what I've learnt in that you should try and configure your object so it is in a consistent state through the constructor.
What are the best options?
My JobFactory
method currently takes on a dependancy on IComponentContext
.
I've seen there are ways to pass parameters to AutoFac to construct the object, but things I've read suggest that it is not ideal.
Is it better to just use my Factory to
回答1:
The simplest solution is to inject a factory instead of an instance. Do not inject the AutoFac container itself (that hides dependencies). Just write a simple class.
class SftpClientFactory : ISftpClientFactory
{
public SftpClient GetClient(string host, string userName, string password, int timeout)
{
return new SftpClient(host, userName, password, timeout);
}
}
Then register it like this:
container.RegisterType<ISftpFactory, SftpFactory>();
And in your class
class Example
{
private readonly ISftpClientFactory _clientFactory;
public Example(ISftpClientFactory injectedFactory)
{
_clientFactory = injectedFactory;
}
public void DoTheWork()
{
var client = _clientFactory.GetClient(host, userName, password, timeout);
}
}
As a bonus, you now have complete control of the object life cycle, which sounds like it could be important with an sftp client, which might hold a non-managed resource and be Disposable
:
public void DoTheWork()
{
using (var client = _clientFactory.GetClient(host, userName, password, timeout))
{
client.DownloadFile();
}
}
This scheme preserves the inversion of control, you still keep a single composition root, the client can still be stubbed by your unit test project, and you still get compile-time resolution of dependencies. The only down side is it is slightly more work to write the stub, since you have to write a stub factory too.
回答2:
You could take advantage of delegate factories to pass custom parameters in runtime. In that case you do not have to create any custom factories and it would be quite easy to mock in your tests as well.
You cannot use built-in Func<X,Y,T>
delegate, because parameters you have to pass are of the same type. But you could introduce your own delegate. If you have a separate interface for SmtpClient
, it would look like this
public delegate ISmtpClient SmtpClientFactory(string host, string username, string password, int timeout);
Then, instead of injecting ISmtpClient
, you should inject SmptClientFactory
. No additional registration is needed there.
If you do not have ISmtpClient
interface it would look pretty much the same, although it would be harder to test later.
来源:https://stackoverflow.com/questions/45427236/what-is-the-best-way-to-pass-runtime-constructor-parameters-to-autofac