在 MVC 中,对于具有多个构造函数的控制器,我如何指示要调用哪个构造函数?

发布于 2024-11-27 21:12:19 字数 1697 浏览 0 评论 0原文

通常情况下,我希望在控制器中拥有多个构造函数,因为其中一个构造函数最适合在单元测试期间手动注入依赖项,而另一个构造函数最适合使用 IOC 容器注入依赖项。

在 MVC 3 中使用标准服务定位器或 DependencyResolver 时,是否有任何方法可以向控制器工厂指示在激活控制器时使用哪个构造函数?

我不想使用 IOC 框架特有的属性之一,因为我希望代码保持独立于 IOC 容器。

更新: 似乎大多数答案都表明拥有多个构造函数是不好的做法。我并不完全不同意这一点,但是,这种情况是由于试图找到映射/转换层的良好解决方案而出现的,我在 这个问题但从未得到很好的答案。

我的代码如下所示:

public class SomeController : Controller
{
    private ISomeService _SomeService;
    private IEntityConverter _EntityConverter;

    // this would be used during unit tests when mocking the entity converter 
    public SomeController(ISomeService someService, IEntityConverter entityConverter)
    {
        _SomeService = someService;
        _EntityConverter = entityConverter;
    }

    // this would be used when injecting via an IOC container, where it would be tedious
    // to always specify the implementations for the converters
    public SomeController(ISomeService someService)
        : this(someService, new EntityConverter())
    {
    }

    public ActionResult Index(SomeViewModel someViewModel)
    {
        if (!ModelState.IsValid)
            return View(someViewModel);
        else
        {
            ServiceInput serviceInput = _EntityConverter.ConvertToServiceInput(someViewModel);
            _SomeService.DoSomething(serviceInput);
            return View("Success");
        }
    }
}

在这个非常简单的示例中,我使用一个接口在视图模型和作为服务层输入的实体之间进行转换。

我更喜欢使用接口,因为可以模拟此转换,而不是使用静态方法。然而,我觉得总是必须在 IOC 容器中指定这些转换实现的实现是很乏味的,因为这些转换接口的实现就位于接口旁边。

这可能不是最好的解决方案。我对更好的选择持开放态度。

It's often the case where I'd like to have multiple constructors in my controllers, since one of the constructors is optimal for manually injecting dependencies during unit testing and the other is optimal for using a IOC container to inject dependencies.

When using the standard service locator or DependencyResolver in MVC 3, is there any way to indicate to the controller factory which constructor to use when activating the controller?

I'd prefer not to use one of the attributes specific to an IOC framework since I want the code to remain independent of the IOC container.

UPDATE:
It seems like the majority of the answers indicate its bad practice to have multiple constructors. I don't entirely disagree with this, however, the situation arises as a result of trying to find a good solution to the mapping/conversion layer, which I asked in this question but never got a good answer to.

My code would look like this:

public class SomeController : Controller
{
    private ISomeService _SomeService;
    private IEntityConverter _EntityConverter;

    // this would be used during unit tests when mocking the entity converter 
    public SomeController(ISomeService someService, IEntityConverter entityConverter)
    {
        _SomeService = someService;
        _EntityConverter = entityConverter;
    }

    // this would be used when injecting via an IOC container, where it would be tedious
    // to always specify the implementations for the converters
    public SomeController(ISomeService someService)
        : this(someService, new EntityConverter())
    {
    }

    public ActionResult Index(SomeViewModel someViewModel)
    {
        if (!ModelState.IsValid)
            return View(someViewModel);
        else
        {
            ServiceInput serviceInput = _EntityConverter.ConvertToServiceInput(someViewModel);
            _SomeService.DoSomething(serviceInput);
            return View("Success");
        }
    }
}

In this very simple example, I am using an interface to do conversions between my view models and the entities that are inputs to the service layer.

I'd prefer to use an interface because this conversion can be mocked, rather than if a static method was used. However, I feel like it would be tedious to always have to specify, in the IOC container, what the implementations are for these conversion implementations, since the implementations for these conversion interfaces sit right next to the interfaces.

This may not be the best solution. I'm open to better alternatives.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

羁〃客ぐ 2024-12-04 21:12:19

不要使用多个构造函数。您的类应该对它所需的依赖项有一个单一的定义。该定义位于构造函数中,因此它应该只有 1 个公共构造函数。

我必须说我不喜欢使用模拟框架。我发现我的应用程序和单元测试的良好设计使我不必一起使用模拟框架(当然里程可能会有所不同)。

对我有帮助的一个技巧是允许通过不验证构造函数参数来将 null 引用注入到构造函数中。如果被测类不使用依赖项,这对于单元测试来说非常方便,而在生产中,您仍然可以保证不注入 null,因为 IOC 容器永远不会将 null 注入到构造函数中(或者至少不会注入 null)。我所知道的容器)。

Don't use multiple constructors. Your classes should have a single definition of what dependencies it needs. That definition lies in the constructor and it should therefore only have 1 public constructor.

I must say I'm not a fan of using mocking frameworks. I found that a good design of my application and my unit tests prevents me from having to use mocking frameworks all together (of course mileage may vary) .

One trick that helps me is allowing null references to be injected into the constructor by not validating the constructor arguments. This is very convenient for unit tests in cases where the class under test does not use the dependency, while in production you would still have the guarantee that null is not injected, since IOC containers will never inject null into a constructor (or at least none of the containers that I know).

不美如何 2024-12-04 21:12:19

似乎有一种代码味道,您需要一个显式构造函数来进行测试。如果测试人员可以工作但生产人员不能工作怎么办?看起来就像是错误进入的一扇门。对于测试来说,尽可能使用与真实客户端相同的路径是理想的。

两者有何不同?

另外,如果您使用某种基于属性的 DI(例如 MEF),我不会太担心解耦。拥有额外的属性(以及相应的参考)应该不会有那么大的影响......除非您预见到未来 IOC 技术会发生变化。

Seems like a code-smell that you need an explicit constructor for testing. What if the testing ctor works but the production one doesn't? Seems like a door for bugs to creep in. It would be ideal for tests to use the same path as real clients as far as possible.

What is different between the two ?

Also if you're using some sort of attribute based DI (e.g. like MEF), I'd not be too worried about decoupling. Having an extra attribute (and the corresponding reference) should not be that big a hit... unless you foresee a change in IOC tech in the future.

回忆追雨的时光 2024-12-04 21:12:19

也许我误读了你的问题,但我不确定你为什么要测试不同的构造函数。测试应该测试实际将要使用的内容。

对于您的测试,您应该模拟您的依赖项,以便它们不需要 IoC 容器。

使用 Moq

控制器:

public SomeController(IService1 service1, IService2 service2)
{
    // Do some cool stuff
}

测试方法:

[Test]
public void Some_Action_In_SomeController_Should_Do_Something()
{
    var service1Mock = new Mock<IService1>();
    var service2Mock = new Mock<IService2>();
    var controller = new SomeController(service1Mock.Object, service2Mock.Object);

    controller.Action();

    // Assert stuff
}

Maybe I'm misreading your question, but I'm not sure why you would want to test different constructors. The tests should test what's actually going to be used.

For your tests, you ought to mock your dependencies so that they don't need an IoC container.

Using Moq:

Controller:

public SomeController(IService1 service1, IService2 service2)
{
    // Do some cool stuff
}

Test Method:

[Test]
public void Some_Action_In_SomeController_Should_Do_Something()
{
    var service1Mock = new Mock<IService1>();
    var service2Mock = new Mock<IService2>();
    var controller = new SomeController(service1Mock.Object, service2Mock.Object);

    controller.Action();

    // Assert stuff
}
酒废 2024-12-04 21:12:19

这一切都与您的服务定位器有关,您需要在注册类型的位置对其进行配置。

下面是使用 Unity 时使用默认无参数构造函数的示例。

请注意,默认情况下服务定位器将使用最具体的构造函数(参数最多的构造函数)

ServiceLocator.Current.RegisterType<ICache, HttpContextCache>(new InjectionConstructor()); //use default constructor

那么为什么我需要这个呢?确实,我可以创建一个受保护的虚拟方法来解析 HttpContextBase 并在测试中使用提取和重写,或者使用另一个接口,该接口具有创建可以注入到此类中的 HttpContextBase 的方法......或者我可以只是告诉 Unity 使用默认构造函数。

    private readonly HttpContextBase _httpContextBase;

    public HttpContextCache()
    {
        _httpContextBase = new HttpContextWrapper(HttpContext.Current);
    }

    public HttpContextCache(HttpContextBase httpContextBase)
    {
        _httpContextBase = httpContextBase;
    }

这是一种罕见的情况,项目中几乎所有其他类都只有一个构造函数

It's all to do with your service locator, you need to configure it where you register your types.

Here is an example of using the default parameterless constructor when using Unity.

Note that by default the service locator will use the most specific constructor (the one with the most parameters)

ServiceLocator.Current.RegisterType<ICache, HttpContextCache>(new InjectionConstructor()); //use default constructor

So why did I need this? It is true I could have made a protected virtual method to resolve HttpContextBase and use extract and override in the test, or otherwise yet another interface with a method that creates the HttpContextBase that can be injected into this class.... or I could just tell unity to use the default constructor.

    private readonly HttpContextBase _httpContextBase;

    public HttpContextCache()
    {
        _httpContextBase = new HttpContextWrapper(HttpContext.Current);
    }

    public HttpContextCache(HttpContextBase httpContextBase)
    {
        _httpContextBase = httpContextBase;
    }

This was a rare case and pretty much all other classes in the project have only one constructor

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文