25

У меня есть базовый контроллер в моем MVC 5 проекте, который реализует некоторые общие функции. Эта функциональность требует определенных зависимостей. Я использую Unity 3, чтобы внедрить эти реализации в мои контроллеры, шаблон, который работал нормально, пока я не переключил свои контроллеры на наследование с этого базового контроллера. Теперь я бегу в следующем выпуске:Встраивание конструктора базового контроллера в ASP.NET MVC с Unity

public class BaseController : Controller 
{ 
    private readonly IService _service; 

    public BaseController(IService service) 
    { 
     _service = service; 
    } 
} 

public class ChildController : BaseController 
{ 
    private readonly IDifferentService _differentService; 

    public ChildController(IDifferentService differentService) 
    { 
     _differentService = differentService; 
    } 
} 

Это, по понятным причинам бросает ошибку в 'BaseController' does not contain a constructor that takes 0 arguments. Unity не разрешает построение BaseController, поэтому он не может вставлять в него зависимостей. Я вижу два очевидных пути решения этой проблемы:

1.) Явный вызвать BaseController CTOR и имеют каждый ChildController т е р инъекционных зависимости в BaseController в

public class ChildController : BaseController 
{ 
    private readonly IDifferentService _differentService; 

    public ChildController(IDifferentService differentService, 
          IService baseService) 
     : base(baseService) 
    { 
     _differentService = differentService; 
    } 
} 

мне не нравится этот подход по нескольким причинам : один из них, потому что ChildControllers не используют дополнительные зависимости (поэтому он вызывает размытие конструктора в дочерних контроллерах без причины), и что более важно, если я когда-либо изменяю подпись конструктора базового контроллера, я должен изменить конструкторские подписи каждого дочернего контроллера.

2.) Реализовать зависимости в BaseController с помощью инъекции собственности

public class BaseController : Controller 
{ 
    [Dependency] 
    public IService Service { get; set; } 

    public BaseController() { } 
} 

Мне нравится этот подход лучше - я не использую любого из зависимостей в конструкторе коде BaseController - но она делает инъекции зависимостей стратегия кода непоследовательная, что тоже не идеально.

Существует, вероятно, еще лучший подход, который включает в себя некоторую функцию разрешения зависимостей BaseController, которая вызывает контейнер Unity для сортировки подписи метода ctor, но прежде чем я начну писать что-либо слишком, я задавался вопросом, разрешил ли кто-либо эта проблема раньше? Я нашел несколько решений, плавающих в Интернете, но они были обходными решениями, такими как Service Locator, которые я не хочу использовать.

Спасибо!

ответ

26

Первое, что вам нужно понять, это то, что вы не создаете базовый контроллер. Вы создаете дочерний контроллер, который наследует интерфейс и функциональность базового контроллера. Это важное различие. Когда вы говорите: «ChildControllers не используют дополнительные зависимости», то вы абсолютно ошибаетесь. Потому что ChildController IS BaseController также. Создано не два разных класса. Только один класс, который реализует обе функции.

Итак, поскольку ChildController IS A BaseController, нет ничего плохого или странного в передаче параметров в конструкторе дочерних контроллеров, который вызывает конструктор базовых классов. Так оно и должно быть сделано.

Если вы измените базовый класс, вам, вероятно, придется менять свои дочерние классы. Невозможно использовать инсталляцию конструктора для ввода зависимостей базового класса, которые не включены в дочерний класс.

Я не рекомендую впрыскивание свойств, так как это означает, что ваши объекты могут быть созданы без правильной инициализации, и вы должны помнить, что правильно их настроить.

BTW, правильные термины являются подклассом и суперклассом. «Ребенок» является подклассом, родителем является «суперкласс».

+1

Вы правы! Я добавил базовый контроллер в качестве решения, требующего совместной работы без полного учета последствий OO. Я думаю, что я сделаю немного рефакторинг, чтобы гарантировать, что базовые зависимости хороши и аккуратны, и передавайте их через дочерние конструкторы, как вы предлагаете. Спасибо Эрик! – NWard

+0

Возможно, это правильный ответ здесь, но, к сожалению, хотя я могу заключить инициализацию кода в базовом классе, каждый производный класс должен реализовать правильный конструктор. Я должен помнить, чтобы вводить тот же самый конструктор для каждого производного класса. И любое изменение данных, которые я передаю конструктору, требует изменения каждого класса, который вытекает из него. Было бы гораздо лучше иметь возможность поддерживать это только из базового класса. –

+0

@JonathanWood - этот аргумент не является особенно убедительным, поскольку конструкторы существуют по какой-то причине ... Вы хотите инициализировать объект конкретными данными. Если вы «забыли» включить правый конструктор, то задача, которую вы хотите выполнить, не будет работать, потому что не будет конструктора, который вам нужен, и вы быстро заметите свою ошибку. Это похоже на то, что вы должны «помнить», чтобы добавить свойства, которые вам нужны для класса ... вы не получите очень далеко, если не помните. –

7

С ASP.Net 5 и он построен в DI

public class BaseController : Controller 
{ 
    protected ISomeType SomeMember { get; set; } 

    public BaseController(IServiceProvider serviceProvider) 
    { 
     //Init all properties you need 
     SomeMember = (SomeMember)serviceProvider.GetService(typeof(ISomeMember)); 
    } 
} 

public class MyController : BaseController 
{ 
public MyController(/*Any other injections goes here*/, 
         IServiceProvider serviceProvider) : 
base(serviceProvider) 
{} 
} 

UPDATE

Существует также метод расширения в Microsoft.Extensions.DependencyInjection, чтобы сделать его короче

SomeMember = serviceProvider.GetRequiredService<ISomeMember>(); 
+0

Это отличное дополнение. Возможно ли реализовать базовый контроллер без DI в ASP.NET 4.5? – JoshYates1980

+0

Не уверен, что вы имеете в виду. Вы можете использовать Factory + Singleton вместо DI в BaseController. – Vitaly

+0

да, пренебрежение. Я оставил контроллеры пространства имен. Этот проект, над которым я работаю, не имеет влияния на DI. – JoshYates1980

0

Я принял несколько иной (но для меня, довольно очевидный и, вероятно, общий) подход. Это работает для меня, но могут быть ошибки, о которых я не знаю.

Я создал общие свойства сервиса в моем классе BaseController, который используется большинством моих контроллеров. Они получают экземпляр, когда они необходимы/указаны.

Если есть служба, которая нуждается в определенном контроллере, которая менее используется, я ввожу ее в конструктор этого контроллера, как обычно.

using JIS.Context; 
using JIS.Managers; 
using JIS.Models; 
using JIS.Services; 
using System; 
using System.Collections.Generic; 
using System.Web; 
using System.Web.Mvc; 

namespace JIS.Controllers 
{ 
    public class BaseController : Controller 
    { 
     public BaseController() 
     { 
      ViewBag.User = UserManager?.User; 
     } 

     private IUserManager userManager; 
     public IUserManager UserManager 
     { 
      get 
      { 
       if (userManager == null) 
       { 
        userManager = DependencyResolver.Current.GetService<IUserManager>(); 
       } 
       return userManager; 
      } 
      set 
      { 
       userManager = value; 
      } 
     } 

     private ILoggingService loggingService; 
     public ILoggingService LoggingService 
     { 
      get 
      { 
       if (loggingService == null) 
       { 
        loggingService = DependencyResolver.Current.GetService<ILoggingService>(); 
       } 
       return loggingService; 
      } 
      set { loggingService = value; } 
     } 

     private IUserDirectory userDirectory; 
     public IUserDirectory UserDirectory 
     { 
      get 
      { 
       if (userDirectory == null) 
       { 
        userDirectory = DependencyResolver.Current.GetService<IUserDirectory>(); 
       } 
       return userDirectory; 
      } 
      set { userDirectory = value; } 
     } 

     private ApplicationDbContext appDb; 
     public ApplicationDbContext AppDb 
     { 
      get 
      { 
       if (appDb == null) 
       { 
        appDb = new ApplicationDbContext(); 
       } 
       return appDb; 
      } 
      set 
      { 
       appDb = value; 
      } 
     } 

     protected override void Dispose(bool disposing) 
     { 
      if (disposing && appDb != null) 
      { 
       appDb.Dispose(); 
      } 
      base.Dispose(disposing); 
     } 
    } 
} 

В мой контроллер я просто подкласс от этого BaseController:

public class UsersController : BaseController 
{ 
    // and any other services I need are here: 
    private readonly IAnotherService svc; 
    public UsersController(IAnotherService svc) 
    { 
     this.svc = svc; 
    } 
... 
} 

Таким образом, общие службы генерируются на лету, когда они необходимы, и доступны для моих контроллеров, не много шаблонный.

Смежные вопросы