How do I specify the class instance I want my Controller's constructor to receive (when working with Web API, DI, and Castle Windsor)?

不想你离开。 提交于 2019-12-25 04:33:31

问题


Perhaps my questions here and here are not clear enough, so I'll try to decrease the verbosity while not reducing the clarity.

Say my Controller uses DI (you can verbalize it "in your head" - you don't have to utter it out loud); so it could look like this:

private readonly IDepartmentRepository _deptsRepository;

    public DepartmentsController(IDepartmentRepository deptsRepository)
    {
        if (deptsRepository == null)
        {
            throw new ArgumentNullException("deptsRepository is null");
        }
        _deptsRepository = deptsRepository;
    }

That way, I can intercept the Web API routing mechanism, having it pass a particular class that implements IDepartmentRepository to the Controller's constructor. So, if my interface is:

public interface IDepartmentRepository
{
    int Get();
    IEnumerable<Department> Get(int ID, int CountToFetch);
    Department Add(Department item);
    void Post(Department dept);
    void PostDepartment(int accountid, string name);
    void Put(Department dept);
    void Delete(int Id);
}

...I could have classes like this:

public class DepartmentRepositoryProductionData : IDepartmentRepository
{
    private readonly List<Department> departments = new List<Department>();

    public DepartmentRepository()
    {
        using (var conn = new OleDbConnection(
            @"Provider=Microsoft.ACE.OLEDB.12.0;User ID=BlaBlaBla..."))
        {
...                        

...and this:

public class DepartmentRepositoryTestData : IDepartmentRepository
{
    private readonly List<Department> departments = new List<Department>();

    public DepartmentRepository()
    {
        // Load test data from an XML file (or text file, or whatever)
...                        

Now, that's just dandy, but how/where do I specify that I want an instance of DepartmentRepositoryTestData (or DepartmentRepositoryProductionData) to be the one the Controller instantiates? I heartily embrace the concept of delaying the identification of the class until runtime, but even after reading quite a bit about DI, I'm missing the part about how and where one specifies which class (that implements the required interface) the Controller will receive in its constructor arg.

Is it specified by the Client, IOW by what's passed in the URI? I would think not, but I am a bit baffled so "anything is possible."

UPDATE

Since this is apparently not clear enough yet, I'll add this:

For this abstraction (interface arg for the Controller constructor) to be of any value, though, there has to be multiple classes that implement that interface, right? This could be a "test data" class and a "production data" class, or it could be a "California Pizza" class and a "New York Pizza" class, or California, New York, Chicago, and Italy classes that all implement IPizzaPie.

But somebody somewhere has to be the traffic cop that says: "Instantiate the California class" or "Instantiate the New York class" etc.

That's my question: who/where is this traffic cop (class instance decider), and how do we tell it which class to pass?

UPDATE 2

It would seem that possibly the place where the class type is being specified is when this constructor (in my "WindsorCompositionRoot.cs") is called:

public IHttpController Create(
    HttpRequestMessage request,
    HttpControllerDescriptor controllerDescriptor,
    Type controllerType)
{
    var controller =
        (IHttpController)this.container.Resolve(controllerType);

    request.RegisterForDispose(
        new Release(
            () => this.container.Release(controller)));

    return controller;
}

...but I still don't know who calls this (IHttpController)...

UPDATE 3

This helps, and so does this


回答1:


In general, if you are doing some form of TDD, and wanted to write isolated tests, I would recommend against wiring up a container to do your testing. You would want to eliminate the container from your unit tests completely.

So if you wanted to test the controller, you would do something like:

var sut = new DepartmentsController(new DepartmentRepositoryTestData());

This assumes you do want to have a concrete test class for IDepartmentRepository in lieu of using some sort of mocking framework.

If this is more of an integration test, and you DO want to wire up a container to test interaction between classes, then this would be the responsibility of your container registration code. You should have separate container registration for a testing story vs. a production story, so that services would resolve to different concrete instances based on context.

Edit

I think you are actually asking two questions here: one about routing and parameters, and the other about DI. I'm going to answer from the perspective of DI.

You said this:

For this abstraction (interface arg for the Controller constructor) to be of any value, though, there has to be multiple classes that implement that interface, right?

You are saying an interface/abstraction is only useful if there is more than one implementation? I think this is a matter of opinion, but many people who use DI create interfaces to promote loose coupling, even if there is only one (production) implementation. Often the second or subsequent implementation is a mock.

This could be a "test data" class and a "production data" class, or it could be a "California Pizza" class and a "New York Pizza" class, or California, New York, Chicago, and Italy classes that all implement IPizzaPie.

But somebody somewhere has to be the traffic cop that says: "Instantiate the California class" or "Instantiate the New York class" etc.

That's my question: who/where is this traffic cop (class instance decider), and how do we tell it which class to pass?

I think what you are looking for here is called an Abstract Factory. You can register multiple instances of an interface, and then create a factory that chooses the implemention based on some criteria.

Good article on the subject: http://blog.ploeh.dk/2012/03/15/ImplementinganAbstractFactory/

Edit #2

The code you posted in your last update is responsible for resolving the right controller for ASP.Net MVC. This is simply required plumbing for MVC to work using DI.

You are trying to return a specific implementation of a service based on some sort of context. This is generally done with an abstract factory, as I noted above. You would create a factory interface and inject it into the controller via the constructor, and then call it like this in a controller method or constructor:

IDepartmentRepository repo = _departmentRepoFactory.Create(myContext);

Any way you look at it, some code somewhere has to request that the container supply an implementation based on something (a string, an enum, an integer, etc.), and this is where the abstract factory comes in.



来源:https://stackoverflow.com/questions/20860732/how-do-i-specify-the-class-instance-i-want-my-controllers-constructor-to-receiv

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!