在我的ASP.Net Core MVC 6解决方案中,我有两套控制器。一组包含具有常规视图的网页。另一组包含API控制器。
为避免重复数据库逻辑,Web控制器正在使用API控制器。目前,我通过将DbContext作为构造函数参数手动创建所需控制器的实例。这是通过依赖注入为Web控制器提供的DbContext。
但每当我向API控制器添加另一个构造函数参数时,我需要修改所有使用此API控制器的Web控制器。
如何使用内置于ASP.Net 5的依赖注入系统为我创建所需API控制器的实例?然后它会自动填充所需的构造函数参数。
一种解决方案可能是将db逻辑从API控制器移动到单独的层,并从API和Web控制器调用它。这不能解决我的问题,因为新图层仍然需要相同的参数,而且我不喜欢不必要的布线。
另一种解决方案是让网络控制器通过网络电话访问API,但这只会增加应用程序的复杂性。
今天我这样做:
public IActionResult Index()
{
using (var foobarController = new Areas.Api.Controllers.FoobarController(
// All of these has to be in the constructor of this controller so they can be passed on to the ctor of api controller
_dbContext, _appEnvironment,
_userManager, _roleManager,
_emailSender, _smsSender))
{
var model = new IndexViewModel();
model.Foo = foobarController.List(new FoobarRequest() { Foo = true, Bar = false });
model.Bar = foobarController.List(new FoobarRequest() { Foo = false, Bar = true });
return View(model);
}
}
我希望这样的事情: (这个例子不起作用。)
using (var foobarController = CallContextServiceLocator.Locator.ServiceProvider.GetService<Areas.Api.Controllers.FoobarController>())
{
var model = new IndexViewModel();
model.Foo = foobarController.List(new FoobarRequest() { Foo = true, Bar = false });
model.Bar = foobarController.List(new FoobarRequest() { Foo = false, Bar = true });
return View(model);
}
答案 0 :(得分:18)
如何使用内置于ASP.Net 5的依赖注入系统为我创建所需API控制器的实例?
在Startup.cs
中,您可以告诉MVC注册所有controllers as services。
services.AddMvc().AddControllersAsServices();
然后,您只需通过DI机制将所需的控制器注入其他控制器并调用其操作方法。
答案 1 :(得分:9)
为了能够使用来自另一个控制器的控制器,您需要:
services.AddTransient <Areas.Api.Controllers.FoobarController, Areas.Api.Controllers.FoobarController>();
如果您需要访问控制器中的本地属性,例如User
或Url
,则有两种方法可以执行此操作。
第一种方法是使用DI获取IHttpContextAccessor
的实例来访问User
和IUrlHelper
来访问Url
个对象:
public class FoobarController : Controller
{
private readonly ApplicationDbContext _dbContext;
private readonly IHttpContextAccessor _httpContextAccessor;
private readonly IUrlHelper _urlHelper;
public FoobarController(ApplicationDbContext dbContext, IHttpContextAccessor httpContextAccessor, IUrlHelper _urlHelper, [...])
{
_dbContext = dbContext;
_httpContextAccessor = httpContextAccessor;
_urlHelper = urlHelper;
}
public FoobarResponse List(FoobarRequest request)
{
var userId = _httpContextAccessor.HttpContext.User.GetUserId();
var response = new FoobarResponse();
response.List = _dbContext.Foobars.Where(f => f.UserId == userId).ToList();
response.Thumb =
return response;
}
}
第二种方法是在呼叫控制器中设置它:
public class HomeController : Controller
{
private Areas.Api.Controllers.FoobarController _foobarController;
public HomeController(Areas.Api.Controllers.FoobarController foobarController)
{
_foobarController = foobarController;
}
private void InitControllers()
{
// We can't set this at Ctor because we don't have our local copy yet
// Access to Url
_foobarController.Url = Url;
// Access to User
_foobarController.ActionContext = ActionContext;
// For more references see https://github.com/aspnet/Mvc/blob/6.0.0-rc1/src/Microsoft.AspNet.Mvc.ViewFeatures/Controller.cs
// Note: This will change in RC2
}
public IActionResult Index()
{
InitControllers();
var model = new IndexViewModel();
model.Foo = _foobarController.List(new FoobarRequest() { Foo = true, Bar = false });
model.Bar = _foobarController.List(new FoobarRequest() { Foo = false, Bar = true });
return View(model);
}
}
可以找到ASP.Net Core MVC6 RC1 Controller的源代码here。然而,对RC2进行了大量重写,并且必须复制以便访问User和Url的属性才会发生变化。
答案 2 :(得分:5)
不要这样做。将该逻辑移动到在2个控制器之间共享的另一个组件。作为HTTP调用的结果,框架将调度控制器,而不是您的公共API表面。通常,您的控制器应该用作将HTTP请求转换为业务对象的位置。这些对象的操作应该委托给另一个层(特别是如果需要在应用程序中的多个位置使用它)。
答案 3 :(得分:0)
为什么你的新图层需要接线?为什么不将对象引入两个控制器并在该对象上调用方法。 DI容器可以解决这个新对象的依赖关系而不需要重复连接吗?
即你可以拥有:
public class MvcController
{
SharedComponent sharedComponent;
public MvcController(SharedComponent sharedComponent)
{
this.sharedComponent = sharedComponent;
}
public IActionResult Index()
{
var model = new IndexViewModel();
model.Foo = shredComponent.List(new FoobarRequest() { Foo = true, Bar = false });
model.Bar = shredComponent.List(new FoobarRequest() { Foo = false, Bar = true });
return View(model);
}
}
//对API控制器重复此操作
public class SharedComponent
{
public SharedComponent(DBContext dbContext, AppEnvironment appEnvironment, UserManager userManager, RoleManager roleManager,
EmailSender emailSender, SmsSender smsSender)
{
...Store in fields for later usage
}
}
答案 4 :(得分:0)
我必须与其他人达成共识,注入控制器可能不是最佳途径。主要是因为它将业务逻辑与ASP.Net结合在一起,而不是像我认为的那样将其像IO设备一样对待。
假设我们有一个如下所示的界面:
public interface ICalculator {
int Add(int left, int right);
}
并且我们有一个存储业务逻辑的实现:
public class MyCalculator : ICalculator {
public int Add(int left, int right) => left + right;
}
此实现可以用作后台服务,在与WPF应用程序相同的过程中,也可以用作ASP.NET WebAPI控制器。看起来像这样:
[ApiController]
[Route("api/{controller}")]
public void CalculatorController : Controller, ICalculator {
private readonly ICalculator _calculator;
public CalculatorController(ICalculator calc) => _calculator = calc;
[Route("Add")]
public int Add(int left, int right) => _calculator.Add(left, right);
}
如果该控制器对存储库具有依赖性,那么您也可以注入该接口。我个人喜欢定义存储库的集合(例如IUserRepository),并且只注入所需的内容,而不是整个DbContext。
public CalculatorController(ICalculator calculator, IDbContext db) { }
控制器没有什么不对的,这取决于它所装饰的东西。只要确保您拥有一组可以断言各种事情的测试即可。例如,您可以断言,当调用特定的控制器方法时,另一个接口上的特定方法也被调用。
我个人认为这种方法更合适。可以使用某些技术,但应与业务规则保持一定距离。开发人员应该能够采用管理代码特定部分的业务规则,并轻松地从WCF服务切换到ASP.NET WebAPI。
我个人曾经是几个项目的一部分,在这个项目中,我们不得不从一种数据库技术切换到另一种数据库技术(从SQL Server到CouchDB),并且其中的微服务需要作为静态Web API服务而不是Windows运行。服务。如果以这种方式设计事物,那么与通常的事物构成相比,这些类型的项目就变得相对琐碎了。