我终于让我的代码按预期工作但我无法弄清楚为什么以前我设置它的方法不起作用。我一直得到一个空引用异常,具体来说,“对象引用未设置为对象的实例。我想要做的是传入一个只读的BlogDb模型并在整个控制器中使用LINQ查询,但似乎每个控制器动作我必须传入BlogDb模型。
private readonly BlogDb model;
public PostsController(BlogDb model)
{
this.model = model;
}
public ActionResult Index()
{
return View();
}
[ValidateInput(false)]
public ActionResult Update(int? id, string title, string body, DateTime date, string tags)
{
var _db = new BlogDb();
if (!IsAdmin)
{
RedirectToAction("Index");
}
if (ModelState.IsValid)
{
Post post = GetPost(id);
post.Title = title;
post.Body = body;
post.Date = date;
post.Tags.Clear();
tags = tags ?? string.Empty;
string[] tagNames = tags.Split(new char[] { ' ' }, StringSplitOptions.RemoveEmptyEntries);
foreach (string tagName in tagNames)
{
post.Tags.Add(GetTag(tagName));
}
if (!id.HasValue || id == 0)
{
_db.Posts.Add(post);
}
_db.SaveChanges();
return RedirectToAction("Details", new { id = post.Id });
}
return View();
}
public ActionResult Edit(int? id)
{
Post post = GetPost(id);
var tagList = new StringBuilder();
foreach (Tag tag in post.Tags)
{
tagList.AppendFormat("{0}", tag.Name);
}
ViewBag.Tags = tagList.ToString();
return View(post);
}
public Tag GetTag(string tagName)
{
//return model.Tags.FirstOrDefault(x => x.Name == tagName) ?? new Tag() { Name = tagName };
return new Tag() { Name = tagName };
}
private Post GetPost(int? id)
{
if (id.HasValue && id != 0)
{
return model.Posts.FirstOrDefault(x => x.Id == id);
}
return new Post();
}
当我尝试使用SaveChanges时,当我有下面的代码片段时,它会不断抛出一个对象实例异常。
if (!id.HasValue || id == 0)
{
model.Posts.Add(post);
}
model.SaveChanges();
所以我不得不最终抛出模型的本地实例并以这种方式使用它。
var _db = new BlogDb();
进一步向下
if (!id.HasValue || id == 0)
{
_db.Posts.Add(post);
}
_db.SaveChanges();
我只是误解了如何使用数据库上下文? 在模型中传递构造函数的目的是什么?
答案 0 :(得分:7)
private readonly BlogDb model; public PostsController(BlogDb model) { this.model = model; } public PostsController() { }
您有2个构造函数 - 其中只有一个设置了model
字段。 默认控制器工厂将只调用默认的无参数构造函数,使参数化的一个未使用(因此,model
字段仍为null
)。
这就是您访问NullReferenceException
时获得model
的原因:该字段的引用从未分配过!
此:
var _db = new BlogDb();
是一个草率的修复。相反,链你的构造函数:
public PostsController()
: this(new BlogDb())
{
}
public PostsController(BlogDb model)
{
this.model = model;
}
这样,无论使用哪个构造函数创建控制器,都将分配model
字段。
请注意,这称为 bastard injection ,dependency-injection反模式。或者你是DI,或者你没有 - 换句话说,如果你打算使用默认的控制器工厂,那就改为:
public PostsController()
{
this.model = new BlogDb();
}
甚至:
private readonly BlogDb model = new BlogDb();
然后你可以删除所有构造函数......并且你有一个紧密耦合的代码。
关于这个主题的一个好的(优秀的)读物是Dependency Injection in .NET,Mark Seemann。
<强> IDisposable的强>
您的BlogDb
继承了EF DbContext
,它实现了IDisposable
界面,这意味着您需要考虑如何在该实例上调用Dispose()
。
通过使其成为实例级私有字段[而不是正确的依赖注入],您的生活变得非常复杂。
您需要每个请求实例 - 因为只要需要,您就会希望该资源在范围内,这意味着每个{{1}的新实例} 方法;像这样的东西:
ActionResult
IEnumerable<Post> posts;
using (var context = new BlogDb())
{
posts = context.Posts.ToList();
return View(posts);
}
阻止确保在using
上调用IDisposable.Dispose()
。 唯一的其他选项,是手动调用context
;如果context.Dispose()
是某个context
字段,那么事情只有在再次使用已处理的上下文之后才会顺利进行。
作为一般经验法则,负责创建 private readonly BlockDb _context;
实例的对象也负责处理它。
构造函数注入
现在,如果您实现自己的 IControllerFactory 使用IoC容器,并在IDisposable
中连接它,那就不同了故事。
现在,负责实例化一次性上下文的对象也负责处理它,并且可以每次请求一次,即注入一个新实例,通过构造函数,在每个请求 - 然后处理它。
当你有一个IoC容器的实例级global.asax
构造函数注入时 - 因为你知道无论谁给你那个实例,正确处理它(因为你已经设置好了)。
您将不再需要默认构造函数,因此使用单个构造函数保持简单,该构造函数通过构造函数签名最终静态记录类具有的每个依赖项 :
private readonly BlogDb _context;
依赖注入的基础,是依赖于抽象,而不是实现。通过这样做......
private readonly BlogDb _context;
public PostsController(BlogDb context)
{
_context = context;
}
您现在可以自由地注入private readonly IPostsService _service;
public PostsController(IPostsService service)
{
_service = service;
}
(给定BlogDb
)...或其他任何实现该界面的内容,可能是模拟实现,可以编写单元测试,可以覆盖所有控制器操作,而无需访问数据库。
通过直接依赖BlogDb : IBlogService
,控制器与该类的特定实现细节相关联;它紧密耦合。通过依赖抽象,松散耦合是可以实现的。
答案 1 :(得分:2)
model
字段将是null
,因为您没有告诉MVC它应该如何处理具有非默认构造函数的控制器。 MVC使用称为控制器工厂的东西,在创建控制器时,默认情况下会选择无参数构造函数(如果存在)。如果删除无参数构造函数,则可能会出现异常。
如果您希望能够将“参数”注入控制器,则需要执行以下操作之一:
IControllerFactory
界面并使用ControllerBuilder.Current.SetControllerFactory
方法进行设置。IDependencyResolver
界面并使用DependencyResolver.SetResolver
方法设置它。通常使用IoC容器来实现这些接口。现有最流行的实现,你可以插入即可。我建议您阅读有关MVC and Dependency Injection的更多信息。