Prisoner ZERO
Prisoner ZERO

Reputation: 14166

Configure a Property of a Concrete Class using StructureMap

I have a common set of email classes used in an Application instance. They all share the same interface, but I need to be able to (optionally) "shim-in" an instance of another "common" type as a property.

START HERE: Given this starting point...
Notice, MyApplication starts-off with all 3 properties. And, since DistributionListProvider is the only IInstanceProvider configured...no issues arise.

public class MyApplication : IMyApplication
{       
    #region notifications

    [SetterProperty]
    public IEmailNotification RequestToFlowEmailNotification { get; set; }

    [SetterProperty]
    public IEmailNotification ApprovalToFlowEmailNotification{ get; set; }

    #endregion

    #region instance providers

    [SetterProperty]
    public IInstanceProvider DistributionListProvider { get; set; }

    #endregion
}

public class RequestToFlowEmailNotification : IEmailNotification
{
    #region <Properties>

    [SetterProperty]
    public IInstanceProvider DistributionListProvider { get; set; }

    #endregion
}

public class ApprovalToFlowEmailNotification : IEmailNotification
{

}

public class DistributionListProvider : ComponentProviderBase, IInstanceProvider
{

}

LETS ADD: Now lets create another IInstanceProvider & add it to the APPLICATION...
But first, notice that IEmailNotification & IInstanceProvider are common types & not "generic types"...meaning they are not EmailNotificationFor<T> types. This makes a difference in how you configure them in the Registry.

// NOW...LETS ADD THIS !!
public class AuthorizationToFlowMeterDocumentRecallProvider : ComponentProviderBase, IInstanceProvider
{

}

public class MyApplication : IMyApplication
{       
    #region notifications

    [SetterProperty]
    public IEmailNotification RequestToFlowEmailNotification { get; set; }

    [SetterProperty]
    public IEmailNotification ApprovalToFlowEmailNotification{ get; set; }

    #endregion

    #region instance providers

    [SetterProperty]
    public IInstanceProvider DistributionListProvider { get; set; }

    [SetterProperty]
    public IInstanceProvider AuthorizationToFlowMeterDocumentRecallProvider{ get; set; }

    #endregion
}

At this point StructureMap NO LONGER UNDERSTANDS how to fill...

Q: How do I update the Registry to force StructureMap to fill correctly?
Below is my entire Registry...

NOTE:
I do NOT wish to set a default...is there a way to do this without setting a DEFAULT?

public ContainerRegistry()
{
    Scan(
        scan =>
        {
            scan.TheCallingAssembly();
            scan.WithDefaultConventions();
            scan.LookForRegistries();
            scan.SingleImplementationsOfInterface();
        });

    // ------------
    // UNIT OF WORK
    // ------------
    // DbContext
    For<DbContext>().Use<MeasurementContractsDbContext>();

    // UnitOfWork
    For<IMeasurementContractsUnitOfWork>().Use<MeasurementContractsUnitOfWork>();

    // GenericRepository
    For(typeof(ICompositeRepository<>)).Use(typeof(GenericRepository<>));

    // --------
    // HELPERS
    // --------
    For<IWindowsIdentityHelper>().Use<WindowsIdentityHelper>();

    For<ISmtpClientHelper>().Use<SmtpClientHelper>()
        .Ctor<ISmtpClient>().Is(new SmtpClient());

    For<IPdfConverterHelper>().Use<PdfConverterHelper>()
        .Ctor<IPdfConverterClient>().Is(new SelectPdfUrlConverterClient());

    // WARNING: Do not remove without replacing it with "some kind of" IConstructorSelector, so that, Unit Testing can be done
    For<IDataServiceFor<EmployeeData>>()
        .Use(x => new EmployeeDataService(new ODataProxyV4())); //<-- uses this constructor

    // --------
    // WORKFLOW
    // ---------
    For<IWorkflowProvider>().Use<WorkflowProvider>()
        .Ctor<Assembly>().Is(Assembly.GetExecutingAssembly());

    // --------
    // MANAGERS
    // --------
    For<IManager<Device>>().Use<DeviceManager>();
    For<IManager<Favorite>>().Use<FavoritesManager>();
    For<IManager<User>>().Use<UserManager>();

    // --------
    // DEPENDENCY MANAGERS
    // --------
    For<IDocumentDependency>().Use<DeviceAffinityProvider>();

    // --------
    // TRANSFORMERS
    // --------
    For<IXmlTransformerFor<AuthorizationToFlowMeterDocumentXmlDataSet>>().Use<AuthorizationToFlowMeterDocumentXmlTransformer>();
    For<IXmlTransformerFor<FirstDeliveryNoticeDocumentXmlDataSet>>().Use<FirstDeliveryNoticeDocumentXmlTransformer>();
    For<IXmlTransformerFor<RequestToFlowMeterDocumentXmlDataSet>>().Use<RequestToFlowMeterDocumentXmlTransformer>();

    // --------
    // PROVIDERS: DataItem Providers
    // --------
    For<IDataItemProviderFor<EmployeeData>>().Use<EmployeeDataProvider>();

    // --------
    // PROVIDERS: Document Providers
    // --------
    For<IDataItemProviderFor<RequestToFlowMeterDocument>>().Use<RequestToFlowMeterDocumentProvider>();
    For<IDataItemProviderFor<AuthorizationToFlowMeterDocument>>().Use<AuthorizationToFlowMeterDocumentProvider>();
    For<IDataItemProviderFor<FirstDeliveryNoticeDocument>>().Use<FirstDeliveryNoticeDocumentProvider>();

    // --------
    // COMMANDS
    // --------
    For<IAdminUpdateCommandFor<AuthorizationToFlowMeterDocument>>().Use<AuthorizationToFlowMeterDocumentAdminUpdateCommand>();

    // -----------
    // APPLICATION
    // -----------
    For<IMyApplication>().Use<MyApplication>()

        // Instance Provider
        .Setter(x => x.DistributionListProvider).Is<DistributionListProvider>()
        .Setter(x => x.AuthorizationToFlowMeterDocumentRecallProvider).Is<AuthorizationToFlowMeterDocumentRecallProvider>()

        // Email Notifications
        .Setter(x => x.ApprovalToFlowEmailNotification).Is<ApprovalToFlowEmailNotification>()
        .Setter(x => x.DenialToFlowEmailNotification).Is<DenialToFlowEmailNotification>()
        .Setter(x => x.ApprovalToFlowRecalledEmailNotification).Is<ApprovalToFlowRecalledEmailNotification>()
        .Setter(x => x.RequestToFlowEmailNotification).Is<RequestToFlowEmailNotification>()
        .Setter(x => x.FirstDeliveryNoticeEmailNotification).Is<FirstDeliveryNoticeEmailNotification>();
}

Upvotes: 1

Views: 311

Answers (1)

Nkosi
Nkosi

Reputation: 247098

With the [SetterProperty] attribute, StructureMap will try to build and attach a value for the property as part of object construction.

However it is the setter attribute that requires a default implementation as it cannot determine which implementation is intended to be used when there are multiple implementations or especially if a default implementation was not added.

With RequestToFlowEmailNotification.DistributionListProvider the container did not know which implementation to use.

To override this behavior, use inline setter configuration for RequestToFlowEmailNotification.DistributionListProvider

//...
.Setter(x => x.RequestToFlowEmailNotification)
    .Is<RequestToFlowEmailNotification>(_ => 
        _.Setter(d => d.DistributionListProvider)
            .Is<DistributionListProvider>()); //<<-- change to which ever one is needed

Note the chaining of the Setter configuration in order to set up RequestToFlowEmailNotification and then the inner DistributionListProvider

Upvotes: 2

Related Questions