Search code examples
c#dependency-injectionunity-containerdependency-resolver

Unity On-Demand Resolve of Interfce Properties


I have an interface IRDFReport and a base class BaseReport which implements that. The View and Report properties are heavy objects and should be resolving only when the report is actually being requested. I used two simple string suffix for finding named mappings of View and Report properties of the objects.

I'd like to use Unity to resolve heavy objects on demand while being able to resolve all reports to have a list of them. Is this kind of resolving in get methods the best I can do for this?

public interface IRDFReport
{
    UserControl View { get; }
    string ReportName { get; }
    string Name { get; set; }
    Task<bool> GenerateReport(SynchronizationContext context);
    DevExpress.XtraReports.IReport Report { get; set; }
}

BaseReport which implements this interface:

public class BaseReport : IRDFReport
{

    public DevX.IReport Report
    {
        get
        {
            return ReportManager.myContainer.Resolve<IReport>(ReportName + ReportManager.Report_Suffix) as XtraReport;
        }
    }

    public UserControl View
    {
        get
        {
            return ReportManager.myContainer.Resolve<UserControl>(ReportName + ReportManager.View_Suffix);
        }
    }

    ///
    /// other members
    ///
}       

And in my report manager I register them like this:

public const string View_Suffix = ".View";
public const string Report_Suffix = ".XtraReport";

Reports = new List<IRDFReport>();

myContainer.RegisterType<IReport, BalanceSheetXtraReport>(nameof(BalanceSheetReport) + Report_Suffix, new ContainerControlledLifetimeManager());
myContainer.RegisterType<UserControl, BalanceSheetView>(nameof(BalanceSheetReport) + View_Suffix, new ContainerControlledLifetimeManager());

  ///
  /// registering other reports inherited from BaseReport
  ///

myContainer.RegisterTypes(
    AllClasses.FromLoadedAssemblies()
    .Where(type => typeof(IRDFReport).IsAssignableFrom(type)),
    WithMappings.FromAllInterfaces,
    WithName.TypeName);

var reports = myContainer.ResolveAll<IRDFReport>().Where(x => !string.IsNullOrEmpty(x.Name)).ToList();
Reports.AddRange(reports);

Solution

  • What you are doing is called Service Location and is considered an anti-pattern.

    I am going to suggest a different way to obtain your dependency. Please note that I am going to provide some generic code just as an example. And also I am going to speak about IReport only. The other dependency can be treated similarly.

    What I am suggesting is to use Constructor Injection. Your BaseReport has a dependency on IReport, but it want to be able to obtain it (and to have it constructed) only later on demand.

    One way to do this is to use an abstract factory.

    Here is some example:

    public interface IReportFactory
    {
        IReport Create(); //this can also take parameters
    }
    

    You can then create an implementation of this factory and inject it to the constructor of BaseReport. This will enable BaseReport to request the IReport dependency on demand.

    Another solution is to use the .NET Lazy class. This class allows you to create the dependency on the first time your try to use it. Unity has native support for the Lazy class (see this reference).

    Here is an example on how you would use it:

    You can inject Lazy<IReport> into your BaseReport class like this:

    public class BaseReport
    {
        private readonly Lazy<IReport> m_LazyReport;
    
        public BaseReport(Lazy<IReport> lazy_report)
        {
            m_LazyReport = lazy_report;
        }
    
        public IReport Report
        {
            get { return m_LazyReport.Value; }
        }
    }
    

    In the Composition Root (the place where you use the DI container), do the following:

    UnityContainer container = new UnityContainer();
    
    container.RegisterType<IReport, Report>("name");
    
    container.RegisterType<BaseReport>(
        new InjectionConstructor(
            new ResolvedParameter<Lazy<IReport>>("name")));
    

    It is enough to just register IReport, and then Unity can resolve Lazy<IReport> without any problem, and it knows to make it work in such a way that only when the Lazy object value is accessed that it goes ahead and creates the Report object.