Simple Injector не может внедрить зависимости в контроллеры веб-API

Я пытаюсь создать базовый конструктор DI с помощью Simple Injector, и мне кажется, что он не может разрешить зависимости для контроллеров веб-API.

  • У меня есть контроллер API в папке «API», которая находится за пределами папки «Контроллеры».
  • Я также попытался поместить его в папку «Контроллеры», но это, похоже, не имело большого значения. Полученная мной трассировка стека аналогична той, которая представлена ​​в этот вопрос.
  • Я использую новую установку пакета NuGet "Simple Injector MVC Integration Quick Start" (v. 2.1.0).
  • У меня есть база SimpleInjectorWebApiDependencyResolver из документации, которая также совпадает с найденной здесь.
  • Я использую Entity Framework и просмотрел ветку обсуждения об изменениях для правильной загрузки контекста.

Кажется, это не проблема, но я все равно получаю следующую ошибку:

Тип MyProject.API.ArticleController не имеет конструктора по умолчанию.

System.ArgumentException в

System.Linq.Expressions.Expression.New (Тип типа) в System.Web.Http.Internal.TypeActivator.Create [TBase] (Тип instanceType) в System.Web.Http.Dispatcher.DefaultHttpControllerActivator.GetInstanceOrActivatorMessageType request, TypeReportMessage controller , Func`1 и активатор) в System.Web.Http.Dispatcher.DefaultHttpControllerActivator.Create (запрос HttpRequestMessage, HttpControllerDescriptor controllerDescriptor, тип controllerType)

Был бы признателен, если бы кто-нибудь мог предложить мне несколько предложений о том, следует ли что-либо изменить из его текущего состояния / порядка вызовов.

ArticleController (базовая структура):

public class ArticleController : ApiController
{
    private readonly IArticleRepository articleRepository;
    private readonly IUserRepository userRepository;
    private readonly IReleaseRepository releaseRepository;

    public ArticleController(IArticleRepository articleRepository, IUserRepository userRepository, IReleaseRepository releaseRepository)
    {
        this.articleRepository = articleRepository;
        this.userRepository = userRepository;
        this.releaseRepository = releaseRepository;
    }

    // GET api/Article
    public IEnumerable<Article> GetArticles(){ // code }

    // GET api/Article/5
    public Article GetArticle(int id){ // code }

    // PUT api/Article/5
    public HttpResponseMessage PutArticle(int id, Article article){ // code }

    // POST api/Article
    public HttpResponseMessage PostArticle(ArticleModel article){ // code }

    // DELETE api/Article/5
    public HttpResponseMessage DeleteArticle(int id){ // code }
}

SimpleInjectorInitializer:

public static class SimpleInjectorInitializer
{
    public static void Initialize()
    {
        var container = new Container();
        InitializeContainer(container);
        container.RegisterMvcControllers(Assembly.GetExecutingAssembly());
        container.RegisterMvcAttributeFilterProvider();
        container.Verify();

        DependencyResolver.SetResolver(new SimpleInjectorDependencyResolver(container));
    }

    private static void InitializeContainer(Container container)
    {
        container.Register<IArticleRepository, ArticleRepository>();
        container.Register<IUserRepository, UserRepository>();
        container.Register<IReleaseRepository, ReleaseRepository>();
    }
}

Global.asax.cs:

public class WebApiApplication : System.Web.HttpApplication
{
    private void ConfigureApi()
    {
        // Create the container as usual.
        var container = new Container();

        // Verify the container configuration
        // container.Verify();

        // Register the dependency resolver.
        GlobalConfiguration.Configuration.DependencyResolver =
                new SimpleInjectorWebApiDependencyResolver(container);
    }

    protected void Application_Start()
    {
        AreaRegistration.RegisterAllAreas();
        ConfigureApi();

        WebApiConfig.Register(GlobalConfiguration.Configuration);
        FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
        RouteConfig.RegisterRoutes(RouteTable.Routes);
        BundleConfig.RegisterBundles(BundleTable.Bundles);
    }
}

person user1417835    schedule 09.04.2013    source источник
comment
Вы уверены, что ConfigureApi метод действительно запущен? Вы установили в нем точку останова?   -  person Steven    schedule 09.04.2013
comment
Есть ли причина, по которой вы создаете отдельный Container экземпляр для конфигурации веб-API?   -  person Steven    schedule 09.04.2013
comment
Да, я уверен, что он запущен. У меня установлена ​​точка останова в строке, которая устанавливает DependencyResolver в GlobalConfiguration. Если вы имеете в виду Global.asax.cs, то я следовал этому руководству (asp.net/web-api/overview/extensibility/), чтобы узнать, решит ли это мою проблему. Однако, похоже, это не так.   -  person user1417835    schedule 09.04.2013


Ответы (2)


TLTR: проблема вызвана неявным способом, которым веб-API обрабатывает определение типов контроллеров; явно зарегистрируйте контроллеры веб-API, и вы увидите, в чем проблема.

Вот пошаговое описание того, что происходит под прикрытием:

  1. System.Web.Http.DefaultHttpControllerActivator вызывает SimpleInjectorWebApiDependencyResolver и запрашивает создание контроллера API.
  2. SimpleInjectorWebApiDependencyResolver перенаправляет этот вызов экземпляру SimpleInjector.Container.
  3. Однако этот экземпляр Container не имеет явных регистраций для этого контроллера API (поскольку вы предоставили преобразователю пустой контейнер).
  4. Поскольку явной регистрации нет, контейнер пытается выполнить регистрацию в последнюю минуту для этого типа.
  5. Однако этот тип контроллера зависит от интерфейсов, которые не могут быть разрешены, потому что они не зарегистрированы в контейнере (помните, ваш контейнер пуст).
  6. Хотя контейнер обычно генерирует исключение, в этом случае возвращается значение null, поскольку тип запрашивается с помощью метода IServiceProvider.GetService, а тип не был зарегистрирован явно.
  7. Метод GetService SimpleInjectorWebApiDependencyResolver также вернет null, поскольку он по определению должен возвращать значение null; Он должен возвращать значение null, если регистрации не существует (что имеет место в настоящее время).
  8. Поскольку DependencyResolver вернул значение null, DefaultHttpControllerActivator вернется к своему поведению по умолчанию, что означает создание самого этого типа, но для этого требуется, чтобы у контроллера был конструктор по умолчанию.

Короче говоря, проблема вызвана неявным способом, которым веб-API обрабатывает определение типов контроллеров.

Итак, решение здесь:

  1. В вашем веб-приложении должен быть только один Container. Это предотвратит всевозможные проблемы и усложнение вашей конфигурации.
  2. Явно зарегистрируйте все контроллеры веб-API в контейнере. Явная регистрация контроллеров гарантирует, что Simple Injector выдаст исключение, когда контроллер не может быть разрешен. Кроме того, это позволяет вам вызвать container.Verify(), что приведет к сбою приложения во время запуска, когда конфигурация недействительна (a важна проверяемая конфигурация). И это также позволяет вам диагностировать конфигурацию, что дает вам еще больше уверенности в правильности вашей конфигурации.

Мой совет: разместить MVC и веб-API в собственный проект. Это значительно упростит задачу.

Зарегистрировать все контроллеры веб-API можно с помощью следующего кода:

container.RegisterWebApiControllers(GlobalConfiguration.Configuration);

ОБНОВЛЕНИЕ:

Поскольку эта ошибка очень распространена, более новые версии класса SimpleInjectorWebApiDependencyResolver просто никогда не возвращают null при запросе типа контроллера. Вместо этого будет выдана описательная ошибка. Из-за этого вы больше никогда не увидите ошибки, пока используете официальный SimpleInjectorWebApiDependencyResolver.

person Steven    schedule 09.04.2013
comment
Спасибо за развернутый ответ! Где именно разместить этот метод? Подразумевается ли, что я должен создать новый класс и реализовать его как метод расширения Контейнера? - person user1417835; 10.04.2013
comment
Это метод расширения. Вы можете поместить его в любой статический класс, чтобы вы могли делать container.RegisterApiControllers() из корня композиции. - person Steven; 10.04.2013
comment
Было бы целесообразно установить GlobalConfiguration DependencyResolver в SimpleInitializer? Или это следует оставить в Global.asax.cs? В документации (simpleinjector.codeplex.com/) предполагается, что создание контейнера и регистрация службы / ресурса должна выполняться в Application_Start() в Global.asax.cs, но комментарии в InitializeContainer() также, кажется, предполагают, что регистрация должна происходить там. - person user1417835; 10.04.2013
comment
Нет ничего плохого в том, чтобы поместить это в SimpleInjectorInitiazer. - person Steven; 10.04.2013
comment
Это то же самое, что использовать GlobalConfiguration.Configuration.Services.GetHttpControllerTypeResolver (). GetControllerTypes ()? (вместо поиска сборки с помощью кода LINQ, который вы использовали? - person diegohb; 27.09.2013
comment
@diegohb: Хороший улов. Было бы лучше использовать HttpControllerTypeResolve. Я обновил свой ответ. - person Steven; 27.09.2013
comment
@ Стивен, могу я обратить ваше внимание на этот вопрос, пожалуйста? stackoverflow.com/questions/41499668/ - person Edukondalu Thaviti; 06.01.2017

У меня работают следующие настройки:

1) включить Unity.WebAPI из https://www.nuget.org/packages/Unity.WebAPI/ 2) в UnityConfig

public static class UnityConfig
    {
        public static void RegisterComponents()
        {
            var container = new UnityContainer();
            // **** Important note -----
            // register all your components with the container here
            // e.g. container.RegisterType<ITestService, TestService>();


            DependencyResolver.SetResolver(new Unity.Mvc5.UnityDependencyResolver(container));

            GlobalConfiguration.Configuration.DependencyResolver = new Unity.WebApi.UnityDependencyResolver(container);
        }
    }

3) в файле Global.asax

 public class MvcApplication : System.Web.HttpApplication
        {
            protected void Application_Start()
            {
                AreaRegistration.RegisterAllAreas();
                UnityConfig.RegisterComponents();
                GlobalConfiguration.Configure(WebApiConfig.Register);
                FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
                RouteConfig.RegisterRoutes(RouteTable.Routes);
                BundleConfig.RegisterBundles(BundleTable.Bundles);
            }
        }

Начало работы с Unity.WebAPI

Для начала просто добавьте вызов UnityConfig.RegisterComponents () в метод Application_Start файла Global.asax.cs, и тогда инфраструктура веб-API будет использовать Unity.WebAPI DependencyResolver для разрешения ваших компонентов.

e.g.

public class WebApiApplication : System.Web.HttpApplication
{
  protected void Application_Start()
  {
    AreaRegistration.RegisterAllAreas();
    UnityConfig.RegisterComponents();                           // <----- Add this line
    GlobalConfiguration.Configure(WebApiConfig.Register);
    FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
    RouteConfig.RegisterRoutes(RouteTable.Routes);
    BundleConfig.RegisterBundles(BundleTable.Bundles);
  }           
}  

Добавьте свои регистрации Unity в метод RegisterComponents класса UnityConfig. Все компоненты, реализующие IDisposable, должны быть зарегистрированы в HierarchicalLifetimeManager, чтобы гарантировать их правильное удаление в конце запроса.

person Muhammad Awais    schedule 18.10.2019