Ошибка Ninject в WebAPI 2.1 - Убедитесь, что контроллер имеет беззадачный публичный конструктор

У меня есть следующие пакеты и их зависимости, установленные в моем проекте WebAPI:

Ninject.Web.WebApi Ninject.Web.WebApi.OwinHost

Я использую это как проект web-api. Нет MVC.

Когда я запускаю свое приложение и отправляю POST в действие RegisterController Register, я получаю следующую ошибку:

{
"message":"An error has occurred.",
"exceptionMessage":"An error occurred when trying to create a controller of type 'AccountController'. Make sure that the controller has a parameterless public constructor.",
"exceptionType":"System.InvalidOperationException",
"stackTrace":"   at System.Web.Http.Dispatcher.DefaultHttpControllerActivator.Create(HttpRequestMessage request, HttpControllerDescriptor controllerDescriptor, Type controllerType)\r\n   at System.Web.Http.Controllers.HttpControllerDescriptor.CreateController(HttpRequestMessage request)\r\n   at System.Web.Http.Dispatcher.HttpControllerDispatcher.SendAsyncCore(HttpRequestMessage request, CancellationToken cancellationToken)\r\n   at System.Web.Http.Dispatcher.HttpControllerDispatcher.<SendAsync>d__0.MoveNext()",
"innerException":{
"message":"An error has occurred.",
"exceptionMessage":"Type 'RPT.Api.Controllers.AccountController' does not have a default constructor",
"exceptionType":"System.ArgumentException",
"stackTrace":"   at System.Linq.Expressions.Expression.New(Type type)\r\n   at System.Web.Http.Dispatcher.DefaultHttpControllerActivator.GetInstanceOrActivator(HttpRequestMessage request, Type controllerType, Func`1& activator)\r\n   at System.Web.Http.Dispatcher.DefaultHttpControllerActivator.Create(HttpRequestMessage request, HttpControllerDescriptor controllerDescriptor, Type controllerType)"
}
}

Может ли кто-нибудь помочь мне, поскольку единственные детали, которые я могу найти в Google, похоже, с 2012 года.

Примечание. Я также попробовал AutoFac вместо Ninject и получил там ту же ошибку. Самое разочаровывающее.

Здесь мой NinjectWebCommon.cs:

using Microsoft.AspNet.Identity;
using Microsoft.AspNet.Identity.EntityFramework;
using RPT.Data;
using RPT.Services;
using RPT.Services.Interfaces;

[assembly: WebActivatorEx.PreApplicationStartMethod(typeof(RPT.Api.NinjectWebCommon), "Start")]
[assembly: WebActivatorEx.ApplicationShutdownMethodAttribute(typeof(RPT.Api.NinjectWebCommon), "Stop")]

namespace RPT.Api
{
    using System;
    using System.Web;

    using Microsoft.Web.Infrastructure.DynamicModuleHelper;

    using Ninject;
    using Ninject.Web.Common;

    public static class NinjectWebCommon
    {
        private static readonly Bootstrapper bootstrapper = new Bootstrapper();

        /// <summary>
        /// Starts the application
        /// </summary>
        public static void Start()
        {
            DynamicModuleUtility.RegisterModule(typeof(OnePerRequestHttpModule));
            DynamicModuleUtility.RegisterModule(typeof(NinjectHttpModule));
            bootstrapper.Initialize(CreateKernel);
        }

        /// <summary>
        /// Stops the application.
        /// </summary>
        public static void Stop()
        {
            bootstrapper.ShutDown();
        }

        /// <summary>
        /// Creates the kernel that will manage your application.
        /// </summary>
        /// <returns>The created kernel.</returns>
        private static IKernel CreateKernel()
        {
            var kernel = new StandardKernel();
            try
            {
                kernel.Bind<Func<IKernel>>().ToMethod(ctx => () => new Bootstrapper().Kernel);
                kernel.Bind<IHttpModule>().To<HttpApplicationInitializationHttpModule>();

                RegisterServices(kernel);


                return kernel;
            }
            catch
            {
                kernel.Dispose();
                throw;
            }
        }

        /// <summary>
        /// Load your modules or register your services here!
        /// </summary>
        /// <param name="kernel">The kernel.</param>
        private static void RegisterServices(IKernel kernel)
        {
            kernel.Bind<RptContext>().ToSelf();

            kernel.Bind<IUserStore<IdentityUser>>().To<UserStore<IdentityUser>>();
            kernel.Bind<UserManager<IdentityUser>>().ToSelf();

            kernel.Bind<IAccountService>().To<AccountService>();
        }
    }
}

Здесь мой AccountController:

using System.Threading.Tasks;
using System.Web.Http;
using System.Web.ModelBinding;
using Microsoft.AspNet.Identity;
using RPT.Api.Models;
using RPT.Services.Interfaces;

namespace RPT.Api.Controllers
{
    [RoutePrefix("api/account")]
    public class AccountController : ApiController
    {
        #region Initialisation

        private readonly IAccountService _accountService;

        public AccountController(IAccountService accountService) : base()
        {
            _accountService = accountService;
        }

        #endregion

        #region Actions

        [AllowAnonymous]
        [Route("register")]
        public async Task<IHttpActionResult> Register(UserRegistrationViewModel model)
        {
            if (!ModelState.IsValid)
            {
                return BadRequest(ModelState);
            }

            var result = await _accountService.RegisterUser(model.UserName, model.Password);

            var errorResult = GetErrorResult(result);

            return errorResult ?? Ok();
        }

        #endregion

        #region Internal

        protected override void Dispose(bool disposing)
        {
            if (disposing)
            {
                _accountService.Dispose();
            }

            base.Dispose(disposing);
        }
        private IHttpActionResult GetErrorResult(IdentityResult result)
        {
            if (result == null)
            {
                return InternalServerError();
            }

            if (result.Succeeded) return null;
            if (result.Errors != null)
            {
                foreach (var error in result.Errors)
                {
                    ModelState.AddModelError("", error);
                }
            }

            if (ModelState.IsValid)
            {
                // No ModelState errors are available to send, so just return an empty BadRequest.
                return BadRequest();
            }

            return BadRequest(ModelState);
        }

        #endregion
    }
}

Ответы

Ответ 1

Вы изменили свой класс OWIN Startup для вызова app.UseNinjectWebApi и app.UseNinjectMiddleware вместо вызова app.UseWebApi?

Startup.cs в примерах API-интерфейса Ninject делает это...

Ответ 2

В моем случае причина была в том, что resolver не смог найти отображение. Предположим, что HomeController имеет зависимость от IDumb, резольвер не смог найти конкретную реализацию Dumb с инструментами IDumb. Другими словами, сообщение об ошибке

**No parameterless constructor defined for this object
An error occurred when trying to create a controller of type 'ToDoListT1.WebApp.Controllers.HomeController'. Make sure that the controller has a parameterless public constructor**

полностью вводит в заблуждение. В моем случае я просто решил, добавив ссылку на проект класса Dumb. Это должно было быть что-то вроде "Нет сопоставления для IDumb можно найти.". Я не уверен, что проблема связана с NInject или MS. Что мне понадобилось, чтобы узнать об этом.

Ответ 3

Мое решение добавляет ключевое слово public к конструктору.

Ответ 4

Вам не нужен конвертер зависимостей, его действительно базовая реализация:

public class NinjectHttpDependencyResolver : IDependencyResolver, IDependencyScope
{
    private readonly IKernel _kernel;
    public NinjectHttpDependencyResolver(IKernel kernel)
    {
        _kernel = kernel;
    }
    public IDependencyScope BeginScope()
    {
        return this;
    }

    public void Dispose()
    {
        //Do nothing
    }

    public object GetService(Type serviceType)
    {
        return _kernel.TryGet(serviceType);
    }

    public IEnumerable<object> GetServices(Type serviceType)
    {
        return _kernel.GetAll(serviceType);
    }
}

Затем просто зарегистрируйте его при создании ядра:

var httpResolver = new NinjectHttpDependencyResolver(kernel);
GlobalConfiguration.Configuration.DependencyResolver = httpResolver;

Ответ 5

Убедитесь, что вы зарегистрировали все типы, используемые контроллером, вплоть до базы данных.

В моем случае я добавил только интерфейс, который использовал сам контроллер, но не интерфейс, используемый для фактического запроса к базе данных.

Обратите внимание, в коде ниже, как класс AddUserMaintenanceProcessor имеет зависимости, о которых контроллер не знает. Если вы опустите Unity (или какой-либо инструмент IoC, который вы используете), сопоставления типов для этих зависимостей, конструкция контроллера завершится неудачно.

В моем решении используется Unity, но точка, которую я пытаюсь сделать, заключается в том, что вам нужно создавать сопоставления типов для всех зависимостей.

Startup.cs

public void Configuration(IAppBuilder app)
{
    var config = new HttpConfiguration();

    // Configure Unity
    var resolver = new UnityDependencyResolver(UnityConfig.GetConfiguredContainer());
    GlobalConfiguration.Configuration.DependencyResolver = resolver;
    config.DependencyResolver = resolver;

    // Do Web API configuration
    WebApiConfig.Register(config);

    app.UseWebApi(config);
}

UnityConfig.cs

public class UnityConfig
{
    private static readonly Lazy<IUnityContainer> Container = new Lazy<IUnityContainer>(() =>
    {
        var container = new UnityContainer();
        RegisterTypes(container);
        return container;
    });

    // Gets the configured Unity container
    public static IUnityContainer GetConfiguredContainer()
    {
        return Container.Value;
    }

    // Register type mappings
    public static void RegisterTypes(IUnityContainer container)
    {
        // LogManagerAdapter wrapping e.g. log4net
        container.RegisterType<ILogManager, LogManagerAdapter>();

        // AutoMapperAdapter wrapping e.g. AutoMapper (configuration omitted)
        container.RegisterType<IAutoMapper, AutoMapperAdapter>();

        // Interface for persisting the user
        container.RegisterType<IAddUserQueryProcessor, AddUserQueryProcessor>();

        // Interface for doing application logic in regards to adding a user
        container.RegisterType<IAddUserMaintenanceProcessor, AddUserMaintenanceProcessor>();
    }
}

UsersController.cs

public class UsersController : ApiController
{
    private readonly IAddUserMaintenanceProcessor _addUserProcessor;

    public UsersV1Controller(IAddUserMaintenanceProcessor addUserProcessor)
    {
        _addUserProcessor = addUserProcessor;
    }

    public async Task<UserModel> Post(NewUser user)
    {
        return await _addUserProcessor.AddUserAsync(user);
    }

    // ...
}

AddUserMaintenanceProcessor.cs

public class AddUserMaintenanceProcessor : IAddUserMaintenanceProcessor
{
    private readonly IAddUserQueryProcessor _queryProcessor;
    private readonly ILog _logger;
    private readonly IAutoMapper _mapper;

    public AddUserMaintenanceProcessor(
        IAddUserQueryProcessor queryProcessor,
        ILogManager logManager,
        IAutoMapper mapper)
    {
        _queryProcessor = queryProcessor;
        _logger = logManager.GetLog(typeof(AddUserMaintenanceProcessor));
        _mapper = mapper;
    }

    public async Task<UserModel> AddUserAsync(NewUser newUser)
    {
        _logger.Info($"Adding new user {newUser.UserName}");

        // Map the NewUser object to a User object
        var user = _mapper.Map<User>(newUser);

        // Persist the user to a medium unknown to this class, using the query processor,
        // which in turn returns a User object
        var addedUser = await _queryProcessor.AddUserAsync(user);

        // Map the User object back to UserModel to return to requester
        var userModel = _mapper.Map<UserModel>(addedUser);

        _logger.Info($"User {userModel.UserName} added successfully");

        return userModel;
    }
}

Я пропустил интерфейсы для процессоров, поскольку они содержат только один метод (шаблон стратегии). Интерфейсы для ведения журнала и автоматического сопоставления не имеют отношения к этому вопросу.

Класс AddUserQueryProcessor просто сохраняет пользователя в базе данных. Еще раз не относится к этому вопросу.