简体   繁体   中英

How to register Service Factory Pattern using base interface with Castle Windsor

I am running into an issue and i can't figure out how to solve for it. I basically have a factory pattern set up as such:

This is my base interface

public interface IReportedIssueManager
{
    Task<BaseManagerResponse> ManageRequest(ReportedIssueRequest request);
}

And then I have a factory class which will handle which service to return based on a certain type (createReportedXIssueManager, or createReportedYIssueManager, or createReportedZIssueManager). In my controller I would create an instance of this class and call this method and pass through a type:

public class ReportedIssueFactory : IReportedIssueFactory
    {
        private readonly ICreateReportedXIssueManager createReportedXIssueManager;
        private readonly ICreateReportedYIssueManager createReportedYIssueManager;
        private readonly ICreateReportedZIssueManager createReportedZIssueManager;

        public ReportedIssueFactory(
            ICreateReportedXIssueManager createReportedXIssueManager,
            ICreateReportedYIssueManager createReportedYIssueManager,
            ICreateReportedZIssueManager createReportedZIssueManager)
        {
            this.createReportedXIssueManager = createReportedXIssueManager;
            this.createReportedYIssueManager= createReportedYIssueManager;
            this.createReportedZIssueManager= createReportedZIssueManager;
        }

        public async Task<IReportedIssueManager> ReportIssue(int issueTypeId)
        {
            var issueType = (IssueType)issueTypeId;
            switch(issueType)
            {
                case IssueType.Listing:
                    return createReportedXIssueManager;
                case IssueType.Post:
                    return createReportedYIssueManager;
                case IssueType.User:
                    return createReportedZIssueManager;
                default:
                    throw new ValidationException(ReportedIssuesServiceResources.UnknownIssueType);
            }
        }
    }

and each of those services are set up as such:

public interface ICreateReportedXIssueManager : IReportedIssueManager
    {
        Task<BaseManagerResponse> ManageRequest(CreateReportedXIssueRequest request);
    }

and 

public interface ICreateReportedYIssueManager : IReportedIssueManager
    {
        Task<BaseManagerResponse> ManageRequest(CreateReportedYIssueRequest request);
    }

and 

public interface ICreateReportedZIssueManager : IReportedIssueManager
    {
        Task<BaseManagerResponse> ManageRequest(CreateReportedZIssueRequest request);
    }

and finally in my controller i would call something like this:

var manager = await _reportedIssueFactory.ReportIssue(IssueTypeId);
var response = await manager.ManageRequest(request);

My problem is that since this is a factory pattern, i think Windsor expects a service of the same name as the base interface?

I get the following error:

No component for supporting the service System.Threading.Tasks.Task`1[[.....ReportedIssues.IReportedIssueManager]]

Does anyone know how to properly register to Windsor in this case? Any help greatly appreciated, thanks!

Based on the error message, you're trying to inject Task<IReportedIssueManager> instead of your factory. That might be all you need to know.

You also don't need to have a "base interface" and then another interface for each implementation. The point of the one interface is that it can have multiple implementations. If we have multiple implementations of the one interface, then declaring inherited interfaces, one per implementation, is redundant.

In other words, whatever your factory returns will be an implementation of IReportedIssueManager cast as IReportedIssueManager . Downstream code won't know whether it also implements ICreateReportedXIssueManager , ICreateReportedYIssueManager , or ICreateReportedZIssueManager . There might be no reason at all for those interfaces to exist. Even if there is a reason, it's not relevant here if the caller expects and receives IReportedIssueManager .


Windsor provides a way to create a factory. It's not much more concise, but it does result in resolving everything from the container each time it's created.

First, regardless of how you implement the factory there's no reason for it to be asynchronous. It's just creating an object. Async is for longer-running I/O processes where the thread can be freed up to do something else while some response is pending. Here's a synchronous interface:

public interface IReportedIssueFactory
{
    // I used the enum here, but you could use the int instead.
    IReportedIssueManager CreateIssueManager(IssueType issueType);
}

In your dependency registration, add the TypedFactoryFacility to your container:

container.AddFacility<TypedFactoryFacility>();

Then register all of the implementations of IReportedIssueManager , giving them names (which will be important later.) Since the point is that they all implement IReportedIssueManager , for the purpose of this factory it doesn't matter if those classes implement other interfaces. Register them according to their concrete types.

For this example I'm using the names of types to name the dependencies just because it avoids adding a "magic string" constant. The actual names can be whatever you choose, but they're not important except that you use them elsewhere.

container.Register(
    Component.For<IReportedIssueManager, CreateReportedXIssueManager>()
        .Named(typeof(CreateReportedXIssueManager).FullName),
    Component.For<IReportedIssueManager, CreateReportedYIssueManager>()
        .Named(typeof(CreateReportedYIssueManager).FullName),
    Component.For<IReportedIssueManager, CreateReportedZIssueManager>()
        .Named(typeof(CreateReportedZIssueManager).FullName)
);

Next, create a class which will take an input (in this case the issueTypeId ) and return the name of the correct dependency:

public class IssueManagerSelector : DefaultTypedFactoryComponentSelector
{

    protected override string GetComponentName(MethodInfo method, object[] arguments)
    {
        var issueType = (IssueType)arguments[0];
        switch (issueType)
        {
            case IssueType.Listing:
                {
                    return typeof(CreateReportedXIssueManager).FullName;
                }
            case IssueType.Post:
                {
                    return typeof(CreateReportedYIssueManager).FullName;
                }
            case IssueType.User:
                {
                    return typeof(CreateReportedZIssueManager).FullName;
                }
            default:
                // So I didn't have to create an exception type as I tested.
                throw new Exception("Unknown type");
        }
    }
}

Finally, register the following with your container:

container.Register(Component.For<IReportedIssueFactory>()
    .AsFactory(new IssueManagerSelector()));

You don't need to actually create an implementation of IReportedIssueFactory . You can just inject the interface and Windsor supplies the implementation.

It will do the following: - When you call CreateIssueManager it will pass your IssueType argument to the GetComponentName method of IssueManagerSelector . - That method will select a component name and return it. - The factory will then resolve the component with that name and return it.

The factory works by convention. Windsor assumes that a factory interface method that returns something is the "create" method. That's why we didn't need to give it a particular name.

Here's a unit test to ensure that it works as expected:

[TestMethod]
public void WindsorFactoryTest()
{
    var container = new WindsorContainer();
    container.AddFacility<TypedFactoryFacility>();
    container.Register(
        Component.For<IReportedIssueManager, CreateReportedXIssueManager>()
            .Named(typeof(CreateReportedXIssueManager).FullName),
        Component.For<IReportedIssueManager, CreateReportedYIssueManager>()
            .Named(typeof(CreateReportedYIssueManager).FullName),
        Component.For<IReportedIssueManager, CreateReportedZIssueManager>()
            .Named(typeof(CreateReportedZIssueManager).FullName)
    );
    container.Register(Component.For<IReportedIssueFactory>()
        .AsFactory(new IssueManagerSelector()));

    var factory = container.Resolve<IReportedIssueFactory>();
    Assert.IsInstanceOfType(factory.CreateIssueManager(IssueType.Listing), typeof(CreateReportedXIssueManager));
    Assert.IsInstanceOfType(factory.CreateIssueManager(IssueType.Post), typeof(CreateReportedYIssueManager));
    Assert.IsInstanceOfType(factory.CreateIssueManager(IssueType.User), typeof(CreateReportedZIssueManager));

}

Finally, here is Windsor's documentation .

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