问题
I have an interface
public interface ITrnsitReport
{
List<UserDefinedType> GetTransitReportData ();
}
And it has only one implementation which is
public class TransitReport : ITrnsitReport
{
private IValidateInput _inputValidation = null;
private readonly ITransitRepository _transitRepo = null;
public TransitReport (IValidateInput inputValidation,
ITransitRepository transitRepo)
{
_inputValidation = inputValidation;
_transitRepo = transitRepo;
}
public List<UserDefinedType> GetTransitReportData (string input1, string input2)
{
List<UserDefinedType> ppdcReportList = null;
bool isValid = _inputValidation.IsInputValid (input1, input2);
if (isValid)
{
ppdcReportList = _transitRepo.GetTransitData (input1, input2);
// do something with data
}
return ppdcReportList;
}
}
Now IValidateInput
has two implementations such as PPDCValidateInput
and PAIValidateInput
. Again for ITransitRepository
such as PPDCTransitRepository
and PAITransitRepository
. (where PAI and PPDC are business reports and each has different validation and repository)
I am using Unity Framework and have them registered them to UnityConfig.cs
.
New I am trying to do is
public TransitInfo : ITransitInfo
{
private ITrnsitReport _report = null;
public TransitInfo (ITrnsitReport report)
{
_report = report;
}
public List<UserDefinedType> GetReportData (string reportType, string input1, string input2)
{
if (reportType.Equals ("PAI"))
{
_report.GetTransitReportData (input1, input2); //inject PAIValidateInput and PAITransitRepository objects
}
if else (reportType.Equals ("PPDC"))
{
_report.GetTransitReportData (input1, input2); //inject PPDCValidateInput and PPDCTransitRepository objects
}
}
}
When its "PAI" how can I inject PAIValidateInput
and PAITransitRepository
objects to TransitReport
constructor and for "PPDC" PPDCValidateInput
and PPDCTransitRepository
objects.
回答1:
There are several ways to go.
1. Use named registrations.
container.RegisterType<IValidateInput, PPDCValidateInput>("PPDC");
container.RegisterType<IValidateInput, PAIValidateInput>("PAI");
And in your constructor:
public TransitReport ([Dependency("PPDC")]IValidateInput inputValidation,
ITransitRepository transitRepo)
{
_inputValidation = inputValidation;
_transitRepo = transitRepo;
}
Or in your registration:
container.Register<ITrnsitReport>(new InjectionConstructor(new ResolvedParameter<IValidateInput>("PPDC"));
The downside with this approach is that the consumer of the interface needs to know which implementation of the interface it wants, which kind of breaks the whole idéa of injecting an interface in the first place.
2. Use an Enum to define what it's validating.
public interface ITrnsitReport
{
List<UserDefinedType> GetTransitReportData ();
ValidateType ValidateType { get; }
}
public enum ValidateType
{
PPDC = 1,
PAI = 2
}
And then select it when you want to use it.
public TransitReport (IValidateInput[] inputValidation,
ITransitRepository transitRepo)
{
_inputValidation = inputValidation.FirstOrDefault(x => x.ValidateType == ValidateType.PPC);
_transitRepo = transitRepo;
}
The good part is that the logic is inside the interface itself, and not regarding the registration. However, it still requires the consumer of the interface to know what it wants.
3. Use different interfaces. (Probably the best option in my oppinion)
public interface IPAIValidateInput : IValidateInput
{
}
public interface IPPDCValidateInput : IValidateInput
{
}
And then register them separately in your container and inject the interface you actually want.
public TransitReport (IPAIValidateInput inputValidation,
ITransitRepository transitRepo)
{
_inputValidation = inputValidation;
_transitRepo = transitRepo;
}
Requires interfaces without no actual declaration. But it keeps the DI-logic more pure, in my oppinion.
4. Use a base class and combine them all.
First fix named registrations:
container.RegisterType<IValidateInput, PPDCValidateInput>("PPDC");
container.RegisterType<IValidateInput, PAIValidateInput>("PAI");
container.RegisterType<ITransitRepository, PPDCTransitRepository>("PPDC");
container.RegisterType<ITransitRepository, PAITransitRepository>("PAI");
container.RegisterType<ITransitReport, PAITransitReport>("PAI");
container.RegisterType<ITransitReport, PPDCTransitReport>("PPDC");
Then create a base class
public abstract class TransitReportBase : ITrnsitReport
{
private readonly IValidateInput _inputValidation;
private readonly ITransitRepository _transitRepo;
protected TransitReportBase(IValidateInput inputValidation,
ITransitRepository transitRepo)
{
_inputValidation = inputValidation;
_transitRepo = transitRepo;
}
public List<UserDefinedType> GetTransitReportData(string input1, string input2)
{
List<UserDefinedType> ppdcReportList = null;
bool isValid = _inputValidation.IsInputValid(input1, input2);
if (isValid)
{
ppdcReportList = _transitRepo.GetTransitData(input1, input2);
// do something with data
}
return ppdcReportList;
}
}
public class PAITransitReport : TransitReportBase
{
public PAITransitReport([Dependency("PAI")] IValidateInput inputValidation,
[Dependency("PAI")] ITransitRepository transitRepo) : base(inputValidation, transitRepo)
{
}
}
public class PPDCTransitReport : TransitReportBase
{
public PPDCTransitReport([Dependency("PPDC")] IValidateInput inputValidation,
[Dependency("PPDC")] ITransitRepository transitRepo) : base(inputValidation, transitRepo)
{
}
}
And use a Factory to resolve them:
public class TransitReportFactory : ITransitReportFactory
{
private readonly IUnityContainer _container;
public TransitReportFactory(IUnityContainer container) // container is injected automatically.
{
_container = container;
}
ITrnsitReport Create(string reportType)
{
return _container.Resolve<ITrnsitReport>(reportType);
}
}
Thanks to the concrete classes PPDCTransitReport
and PAITransitReport
we can make sure that the right dependency is injected to the base class.
How to use the factory:
First, register it with Unity.
container.RegisterType<ITransitReportFactory, TransitReportFactory>();
Then inject it in your TransitInfo
.
public TransitInfo : ITransitInfo
{
private ITransitReportFactory _transitReportFactory;
public TransitInfo (ITransitReportFactory transitReportFactory)
{
_transitReportFactory = transitReportFactory;
}
public List<UserDefinedType> GetReportData (string reportType, string input1, string input2)
{
// Create your transitreport object.
ITransitReport report = _transitReportFactory.Create(reportType);
var reportData = report.GetTransitReportData (input1, input2);
return reportData;
}
}
But I have to say that the factory itself doesn't add that much to the solution. If you don't mind the service locator-pattern you can inject IUnityContainer
directly to TransitInfo
.
回答2:
You have several options, the one I prefer is to use named registrations:
container.RegisterType<IValidateInput, PPDCValidateInput>("PPDC");
container.RegisterType<IValidateInput, PAIValidateInput>("PAI");
Then you need to resolve the ITransitInfo using overrides:
container.Resolve<ITransitInfo>(new InjectionConstructor(new ResolvedParameter<IValidateInput>("PPDC"), ...)
来源:https://stackoverflow.com/questions/37184286/inject-require-object-depends-on-condition-in-constructor-injection