您可以通过遵循最佳实践来编写更好的控制器。 所谓的“瘦”控制器(即,具有更少代码和更少责任的控制器)更易于阅读和维护。 而且一旦控制器变薄,您可能就不需要对其进行大量测试。 相反,您可以专注于测试业务逻辑和数据访问代码。 瘦型或瘦型控制器的另一个优点是,随着时间的推移,维护多个版本的控制器变得更加容易。
本文讨论了使控制器变胖的不良习惯,然后探讨了使控制器变瘦和易于管理的方法。 我在编写控制器方面的最佳实践列表可能并不全面,但是我已经在适用的情况下讨论了最重要的一些相关源代码。 在接下来的部分中,我们将研究什么是胖控制器,为什么它是代码气味,什么是瘦控制器,为什么有益,以及如何使控制器变薄,简单,可测试和可管理。
从控制器中删除数据访问代码
编写控制器时,您应遵守“ 单一责任原则” ,这意味着控制器应承担“一项责任”或“一个且仅有一个变更理由”。 换句话说,您希望将将控制器代码更改的原因降至最低。 考虑以下代码,该代码显示了具有数据访问逻辑的典型控制器。
public class AuthorController : Controller
{
private AuthorContext dataContext = new AuthorContext();
public ActionResult Index(int authorId)
{
var authors = dataContext.Authors
.OrderByDescending(x=>x.JoiningDate)
.Where(x=>x.AuthorId == authorId)
.ToList();
return View(authors);
}
//Other action methods
}
请注意,使用数据上下文实例在action方法内部读取数据。 这违反了“单一责任原则”,并使您的控制器因本不应驻留在其中的代码而肿。 在此示例中,我们使用DataContext(假设我们使用Entity Framework Core)连接到数据库中的数据并使用该数据。
明天,如果您决定更改数据访问技术(出于更好的性能或可能的原因),则也必须更改控制器。 例如,如果我想使用Dapper连接到基础数据库怎么办? 更好的方法是使用存储库类来封装数据访问逻辑(尽管我不是存储库模式的忠实拥护者)。 让我们用以下代码更新AuthorController。
public class AuthorController : Controller
{
private AuthorRepository authorRepository = new AuthorRepository();
public ActionResult Index(int authorId)
{
var authors = authorRepository.GetAuthor(authorId);
return View(authors);
}
//Other action methods
}
控制器现在看起来更苗条。 这是编写此控制器的最佳方法吗? 并不是的。 如果您的控制器正在访问数据访问组件,则它做的事情太多,因此违反了“单一责任原则”。 您的控制器绝对不能具有直接访问数据访问组件的数据访问逻辑或代码。 这是AuthorController类的改进版本。
public class AuthorController : Controller
{
private AuthorService authorService = new AuthorService();
public ActionResult Index(int authorId)
{
var authors = authorService.GetAuthor(authorId);
return View(authors);
}
//Other action methods
}
AuthorService类利用AuthorRepository类来执行CRUD操作。
public class AuthorService
{
private AuthorRepository authorRepository = new AuthorRepository();
public Author GetAuthor (int authorId)
{
return authorRepository.GetAuthor(authorId);
}
//Other methods
}
避免编写样板代码来映射对象
您通常需要将数据传输对象(DTO)与域对象映射,反之亦然。 请参考下面给出的代码片段,其中显示了控制器方法中的映射逻辑。
public IActionResult GetAuthor(int authorId)
{
var author = authorService.GetAuthor(authorId);
var authorDTO = new AuthorDTO();
authorDTO.AuthorId = author.AuthorId;
authorDTO.FirstName = author.FirstName;
authorDTO.LastName = author.LastName;
authorDTO.JoiningDate = author.JoiningDate;
//Other code
......
}
您不应该在控制器内部编写这样的映射逻辑,因为它会膨胀控制器并增加额外的责任。 如果要编写映射逻辑,则可以利用诸如AutoMapper之类的对象映射器工具来避免编写大量样板代码。
下面的代码片段显示了如何配置AutoMapper。
public class AutoMapping
{
public static void Initialize()
{
Mapper.Initialize(cfg =>
{
cfg.CreateMap<Author, AuthorDTO>();
//Other code
});
}
}
接下来,可以在Global.asax中调用Initialize()方法,如下所示。
protected void Application_Start()
{
AutoMapping.Initialize();
}
最后,您应该将映射逻辑移到我们之前创建的服务类中。 注意如何使用AutoMapper映射两个不兼容的类型Author和AuthorDTO。
public class AuthorService
{
private AuthorRepository authorRepository = new AuthorRepository();
public AuthorDTO GetAuthor (int authorId)
{
var author = authorRepository.GetAuthor(authorId);
return Mapper.Map<AuthorDTO>(author);
}
//Other methods
}
有关更多详细信息,请参见我有关AutoMapper的文章 。
避免在控制器中编写业务逻辑代码
您不应在控制器中编写业务逻辑或验证逻辑。 控制器应仅接受请求,然后委派下一个动作-别无其他。 所有业务逻辑代码都应移入其他类(例如我们之前创建的AuthorService类)。 您可以通过多种方式在请求管道中设置验证器,但不要在控制器内部编写验证逻辑。 这会使您的控制器不必要地发胖,并使它负责不应该执行的任务。
优先依赖注入而不是合成
您应该更喜欢在控制器中使用依赖项注入来管理依赖项。 依赖注入是控制反转(IoC)原理的子集。 通过使这些依赖项可以从外部注入,它可用于从实现中删除内部依赖项。 在我以前的文章中,您可以阅读有关依赖注入原理的更多信息, 可以在ASP.Net Core中 使用依赖注入,在ASP.Net Web窗体中使用依赖注入 。
通过利用依赖注入,您无需担心对象的实例化,初始化等。您可以拥有一个工厂,该工厂返回所需类型的实例,然后可以通过使用构造函数注入注入该实例。 以下代码段说明了如何使用构造函数注入将IAuthorService类型的实例注入到AuthorController中。 (假设IAuthorService是AuthorService类扩展的接口。)
public class AuthorController : Controller
{
private IAuthorService authorService = new AuthorService();
public AuthorController(IAuthorService authorService)
{
this.authorService = authorService;
}
// Action methods
}
使用动作过滤器消除重复的代码
您可以使用ASP.Net Core中的操作筛选器在请求管道中的特定位置执行自定义代码。 例如,您可以使用动作过滤器在执行动作方法之前和之后执行自定义代码。 您可以从控制器的操作方法中删除验证逻辑,然后将其写入操作过滤器中,而不是在控制器内部编写验证逻辑并不必要地使其膨胀。 以下代码片段显示了如何实现此目的。
[ValidateModelState]
[HttpPost]
public ActionResult Create(AuthorRequest request)
{
AuthorService authorService = new AuthorService();
authorService.Save(request);
return RedirectToAction("Home");
}
如果您为控制器分配了多个职责,则控制器也会有多种变更原因。 因此,这违反了单一责任原则,该原则规定一类应该只有一个并且只有一个改变的理由。 您可以在上一篇文章中阅读有关“单一责任原则”的更多信息 。
From: https://www.infoworld.com/article/3404472/how-to-write-efficient-controllers-in-aspnet-core.html