【问题标题】:ASP.net MVC Controller - Constructor usageASP.net MVC 控制器 - 构造函数用法
【发布时间】:2011-05-27 16:48:01
【问题描述】:

我正在开发一个 ASP.net MVC 应用程序,我有一个关于为我的控制器使用构造函数的问题。

我正在使用 Entity Framework 和 linq to Entities 来处理我的所有数据事务。我需要为几乎所有控制器操作访问我的实体模型。当我第一次开始编写应用程序时,我在每个 Action 方法的开头创建一个实体对象,执行我需要的任何工作,然后返回我的结果。

我意识到我为每个操作方法一遍又一遍地创建相同的对象,因此我为实体对象创建了一个私有成员变量,并开始在每个控制器的构造函数中实例化它。现在每个方法只引用该私有成员变量来完成它的工作。

我仍在质疑自己哪种方式是正确的。我想知道 A.) 哪种方法最合适? B.)在构造方法中,这些对象存在多长时间? C.) 构造函数方法是否存在性能/完整性问题?

【问题讨论】:

  • 你需要这个对象只用于一个控制器吗?
  • 也许你应该使用带有私有静态实例的单例模式和实例的延迟加载获取属性。

标签: asp.net-mvc asp.net-mvc-2 c#-4.0 constructor


【解决方案1】:

你在问正确的问题。

A.在每个动作方法中创建这种依赖关系绝对不合适。 MVC 的主要特性之一是能够分离关注点。通过使用这些依赖项加载您的控制器,您正在使控制器变得更厚。这些应该被注入到控制器中。依赖注入 (DI) 有多种选择。通常,这些类型的对象可以注入到构造函数或属性中。我的偏好是构造函数注入。

B.这些对象的生命周期将由垃圾收集器决定。 GC 不是确定性的。因此,如果您的对象与资源受限的服务(数据库连接)有连接,那么您可能需要确保自己关闭这些连接(而不是依赖 dispose)。很多时候,“生命周期”关注点被分离到控制反转 (IOC) 容器中。那里有很多。我的偏好是 Ninject。

C.实例化成本可能是最小的。数据库事务成本是您可能要关注的地方。您可能需要研究一个名为“工作单元”的概念。从本质上讲,数据库可以处理大于一个保存/更新操作的事务。增加事务大小可以带来更好的数据库性能。

希望能帮助您入门。

【讨论】:

  • 尝试使用 NINJECT 进行依赖注入!
  • “确保你自己关闭这些连接(而不是依赖 dispose)”——我强烈反对。 IDisposable 控制器由 ControllerFactory 处理,而不是由 GC 处理,它们的处理是确定性的。详情请见this question
【解决方案2】:

RCraven 有一些出色的见解。我想告诉你如何实施他的建议。

最好先为数据访问类定义一个接口来实现:

public interface IPostRepository 
{
    IEnumerable<Post> GetMostRecentPosts(int blogId);
}

然后实现一个数据类。实体框架上下文的构建成本很低,如果不处置它们,您可能会得到不一致的行为,因此我发现通常最好将您想要的数据拉入内存,然后处置上下文。

public class PostRepository : IPostRepository
{
    public IEnumerable<Post> GetMostRecentPosts(int blogId)
    {
        // A using statement makes sure the context is disposed quickly.
        using(var context = new BlogContext())
        {
            return context.Posts
                .Where(p => p.UserId == userId)
                .OrderByDescending(p => p.TimeStamp)
                .Take(10)
                // ToList ensures the values are in memory before disposing the context
                .ToList(); 
        }
    }
}

现在您的控制器可以接受这些存储库之一作为构造函数参数:

public class BlogController : Controller
{
    private IPostRepository _postRepository;
    public BlogController(IPostRepository postRepository)
    {
        _postRepository = postRepository;
    }

    public ActionResult Index(int blogId)
    {
        var posts = _postRepository.GetMostRecentPosts(blogId);
        var model = new PostsModel { Posts = posts };
        if(!posts.Any()) {model.Message = "This blog doesn't have any posts yet";}
        return View("Posts", model);
    }

}

MVC 允许您使用自己的控制器工厂代替默认设置,因此您可以指定您的 IoC 框架(如 Ninject)决定如何创建控制器。您可以设置您的注入框架,以知道当您请求 IPostRepository 时,它应该创建一个 PostRepository 对象。

这种方法的一大优势是它使您的控制器可进行单元测试。例如,如果您想确保您的模型在没有帖子时收到消息,您可以使用像 Moq 这样的模拟框架来设置您的存储库不返回帖子的场景:

var repositoryMock = new Mock<IPostRepository>();
repositoryMock.Setup(r => r.GetMostRecentPosts(1))
    .Returns(Enumerable.Empty<Post>());
var controller = new BlogController(repositoryMock.Object);
var result = (ViewResult)controller.Index(1);
Assert.IsFalse(string.IsNullOrEmpty(result.Model.Message));

这使您可以轻松地测试您期望从控制器操作中获得的特定行为,而无需设置您的数据库或任何类似的特殊操作。像这样的单元测试很容易编写,具有确定性(它们的通过/失败状态基于代码,而不是数据库内容),而且速度很快(您通常可以在一秒钟内运行一千个)。

【讨论】:

  • 太好了。谢谢您的帮助。一段时间以来,我一直在想一个类似的模型 (DI),但我仍在学习 MVC 的来龙去脉。我认为是时候回去为更可持续的控制器构建了。非常感谢。
  • 如何向构造函数发送参数?我也是这样做的,只是在构造函数中我只有myRepository = new MyRepository()
  • @DoctorOreo:正如我在帖子中所说,MVC 允许您指定控制器工厂。您可以创建一个控制器工厂,该工厂在创建控制器时知道要传入存储库。或者您可以使用现有的依赖注入框架,设置存储库的绑定,并让 MVC 要求 DI 框架创建控制器。
  • @StriplingWarrior:当且仅当 IoC 到位时,您的示例是否有效,或者当我们要求 IPostRepository 时,MVC 框架是否会自动计算 PostRepository 的实例。
  • @user203687:您需要使用某种知道如何创建控制器的自定义工厂来设置 ASP.NET MVC——默认情况下,它只会调用默认构造函数。您可以编写自己的控制器工厂来定义如何构造每个控制器。但是大多数 DI 框架都有插件,可以让你轻松地将它们与 ASP.NET 连接起来,所以通常你只会看到连接了 IoC 容器的人使用这种模式。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-09-29
  • 1970-01-01
  • 1970-01-01
  • 2018-06-05
  • 1970-01-01
相关资源
最近更新 更多