ASP.Net Core Call a controller from another contro

2019-03-25 08:34发布

In my ASP.Net Core MVC 6 solution I have two sets of controllers. One set contains the webpages with their regular views. Another set contains the API controllers.

To avoid duplicating db logic the web controllers are using the API controllers. Currently I am creating an instance of the required controller manually by handing it a DbContext as constructor argument. This is the DbContext given to web controller by dependency injection.

But whenever I add another constructor parameter to the API controller I need to modify all web controllers that use this API controller.

How can I use the dependency injection system builtin to ASP.Net 5 to create an instance of the required API controller for me? Then it would fill in the required constructor parameters automatically.

One solution could be to move the db logic from the API controllers to a separate layer and call that from both API and web controllers. This would not solve my problem since the new layer would still need the same parameters and I'm not fan of the unnecessary wiring.

Another solution would be to have the web controllers access the API through a web call, but that just adds complexity to the app.

Today I am doing this:

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);
    }
}

And I am hoping for something like this: (This example does not work.)

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);
}

4条回答
叛逆
2楼-- · 2019-03-25 08:47

How can I use the dependency injection system builtin to ASP.Net 5 to create an instance of the required API controller for me?

In your Startup.cs can tell the MVC to register all your controllers as services.

services.AddMvc().AddControllersAsServices();

Then you can simply inject the desired controller in your other controller via the DI mechanism and invoke its action method.

查看更多
疯言疯语
3楼-- · 2019-03-25 08:51

Don't do it. Move that logic to another component that gets shared between the 2 controllers.

查看更多
劫难
4楼-- · 2019-03-25 08:57

Why would your new layer need wiring up? Why not take in an object into both controllers and call a method on that object. The DI container could resolve the dependencies of this new object without duplicated wiring couldn't it?

ie you could have this:

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);   
    }
}

//Repeat this for the API controller

public class SharedComponent
{
    public SharedComponent(DBContext dbContext, AppEnvironment appEnvironment, UserManager userManager, RoleManager roleManager, 
        EmailSender emailSender, SmsSender smsSender)
    {
       ...Store in fields for later usage
    }
}
查看更多
Ridiculous、
5楼-- · 2019-03-25 09:14

To be able to use a controller from another controller you need to:

  1. Register the controller in Startup.cs ConfigureServices: services.AddTransient <Areas.Api.Controllers.FoobarController, Areas.Api.Controllers.FoobarController>();
  2. You must pass the controller you want to access as a ctor parameter into the main controller.

If you need to access local properties in the controller such as User or Url there are two ways to do this.

The first way is to use DI to get an instance of IHttpContextAccessor to access User and IUrlHelper to access Url objects:

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;
    }
}

The second way is to set it in the calling controller:

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);
    }
}

The source code for ASP.Net Core MVC6 RC1 Controller can be found here. It is however undergoing heavy rewrite for RC2 and with it the properties that has to be copied to get access to User and Url will change.

查看更多
登录 后发表回答