简体   繁体   中英

Selecting a constructor in StructureMap that has non-configured arguments

I appreciate the question title might be a bit ambiguous. I'm working on trying to abstract an email provider using my own functionally-inspired class ResourceFactory<T> . So the way ResourceFactory<T> works is that it's constructor takes a Func<T> which is a factory to create an instance of T . ResourceFactory<T> then exposes a method called Using<T>(Action<T>) , which will take an Action<T> , create a new T object by invoking the Func<T> passed to it in the constructor, invoke the action on a pre-defined scheduler (such as the thread pool) and then dispose of the created T when the function has finished.

So, my email provider is expecting an instance of ResourceFactory<SmtpClient> in it's constructor. It can also be overloaded to accept a string called from , which is the from address of the e-mail. Code is below:

public class DotNetEmailProvider : IEmailProvider
{
    // TODO: Don't hard-code the CC
    private readonly ResourceFactory<SmtpClient> _smtpFactory;
    private readonly MailAddress _from;

    /// <summary>
    /// Create the DotNetEmailProvider.
    /// </summary>
    /// <param name="factory"></param>
    public DotNetEmailProvider(ResourceFactory<SmtpClient> factory) : this(factory, "support@rumm.co.uk") { }

    /// <summary>
    /// Create the DotNetEmailProvider.
    /// </summary>
    /// <param name="factory"></param>
    /// <param name="from"></param>
    public DotNetEmailProvider(ResourceFactory<SmtpClient> factory, string from)
    {
        _smtpFactory = factory;
        _from = new MailAddress(from);
    }


    public void SendEmail(string to, string subject, string body)
    {
        // Build the e-mail
        var email = new MailMessage
        {
            Subject = subject,
            Body = body,
            IsBodyHtml = true,
            From = _from
        };

        email.CC.Add(_from);

        _smtpFactory.Invoke(client => client.Send(email));
    }
}

ResourceFactory<T> just basically abstracts away Observable.Using , like so:

public class ResourceFactory<T> 
    where T : IDisposable
{
    private readonly Func<T> _factory;
    private readonly IScheduler _scheduler;

    /// <summary>
    /// Create the given resource with a factory that will create instances of the resource.
    /// </summary>
    /// <param name="factory"></param>
    public ResourceFactory(Func<T> factory, IScheduler scheduler)
    {
        _factory = factory;
        _scheduler = scheduler;
    }

    /// <summary>
    /// Invoke the given action by creating a new instance of the resource.
    /// </summary>
    /// <param name="invocation"></param>
    public IObservable<Unit> Invoke(Action<T> invocation)
    {
        return Observable.Using(_factory, (t) => Observable.Start(() => invocation(t), _scheduler));
    }
}

My issue comes when trying to set this up using StructureMap. First of all, StructureMap kept trying to use the overloaded DotNetEmailProvider constructor with the from argument. At this point I didn't care about that, so I investigated a little bit to find out how to 'select' a constructor in StructureMap and ended up with this code:

x.SelectConstructor<DotNetEmailProvider>(() => new DotNetEmailProvider(null));
x.ForConcreteType<DotNetEmailProvider>()
    .Configure
        .Ctor<ResourceFactory<SmtpClient>>().Is(new ResourceFactory<SmtpClient>(() => new SmtpClient(), TaskPoolScheduler.Default));

x.For<IEmailProvider>()
    .Use<DotNetEmailProvider>();

My problem now is that now I'm getting this error thrown:

{"StructureMap Exception Code:  202\nNo Default Instance defined for PluginFamily System.Reactive.Concurrency.IScheduler, System.Reactive.Interfaces, Version=2.2.4.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"}

Which to me doesn't make sense because, yes, the ResourceFactory<T> constructor does take an IScheduler instance as it's second argument, however I'm already passing that in in my configuration ( TaskPoolScheduler.Default ), and I don't necessarily want every IScheduler instance to be the same throughout the application (so I dont' want to do x.For<IScheduler>().Is(...)

What do?

EDIT: It should be noted I am aware that my ResourceFactory<T>.Invoke method won't "do" anything until the observable is subscribed to, however my issue still remains

The exception is thrown when we do ask for interface IEmailProvider like:

var provider = ObjectFactory.GetInstance<IEmailProvider>();

So, we should configure the IEmailProvider a bit more precise way

x.For<IEmailProvider>()
    .Use<DotNetEmailProvider>()
    // plus this
    .Ctor<ResourceFactory<SmtpClient>>().Is(new ResourceFactory<SmtpClient>(
          () => new SmtpClient(), TaskPoolScheduler.Default));

Now StructureMap knows enough to return instantiated IEmailProvider

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM