c# - Error de Ninject en WebAPI 2.1: asegúrese de que el controlador tenga un constructor público sin parámetros
asp.net-web-api dependency-injection (6)
¿Modificó su clase de Startup
OWIN para llamar a app.UseNinjectWebApi
y app.UseNinjectMiddleware
lugar de llamar a app.UseWebApi
?
Startup.cs en los ejemplos de la API web de Ninject hace esto ...
Tengo los siguientes paquetes y sus dependencias instaladas en mi proyecto WebAPI:
Ninject.Web.WebApi
Ninject.Web.WebApi.OwinHost
Estoy ejecutando esto puramente como un proyecto web-api. No MVC.
Cuando ejecuto mi aplicación y envío un POST
a la acción de registro del controlador de cuenta, recibo el siguiente error:
{
"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)"
}
}
Alguien me puede ayudar ya que los detalles que puedo encontrar en Google parecen ser a partir de 2012.
Nota: También probé AutoFac en lugar de Ninject y obtengo el mismo error allí también. Más frustrante.
Aquí está mi 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>();
}
}
}
Aquí está mi 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
}
}
Asegúrese de haber registrado todos los tipos utilizados por el controlador, hasta la base de datos.
En mi caso, solo había agregado la interfaz que usaba el controlador, pero no la interfaz utilizada para consultar la base de datos.
Observe en el código a continuación cómo la clase AddUserMaintenanceProcessor tiene dependencias que el controlador no conoce. Si omite las asignaciones de tipo Unity (o la herramienta IoC que usa) para estas dependencias, la construcción del controlador fallará.
Mi solución utiliza Unity, pero el punto que intento señalar es que necesita crear asignaciones de tipo para todas las dependencias .
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;
}
}
He omitido las interfaces para los procesadores, ya que solo contienen un método (patrón de estrategia). Las interfaces para el registro y la asignación automática son irrelevantes para esta pregunta.
La clase AddUserQueryProcessor simplemente persiste al usuario a la base de datos. Una vez más irrelevante para esta pregunta.
En mi caso, la razón fue resolver no pudo encontrar un mapeo. Suponiendo que HomeController depende de IDumb, el resolvedor no pudo encontrar una implementación concreta de Dumb con los implementos IDumb. En otras palabras, el mensaje de error.
**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**
es completamente engañoso En mi caso, lo resolví agregando una referencia al proyecto de la clase Dumb. Debería haber sido algo así como "No se pudo encontrar ningún mapeo para IDumb". No estoy seguro de que el problema sea con NInject o MS. Lo que nunca me tomó horas para descubrir esto.
Mi solución es agregar una palabra clave "pública" al constructor.
Para mí, lo que causó este error fue que para la interfaz que estaba pasando al constructor, tenía un enlace con nombre y lo llamé con el nombre incorrecto. Como puede ver a continuación, el enlace dice que se llama "Almacén", pero me confundí y puse "DataWarehouse" en el constructor. El hecho de corregir este problema provocó que el error sobre un constructor sin parámetros desapareciera.
private static void RegisterServices(IKernel kernel)
{
kernel.Bind<IVisitProcessor>().To<VisitProcessor>().InThreadScope();
kernel.Bind<IPatientProcessor>().To<PatientProcessor>().InThreadScope();
kernel.Bind<IDbConnectionFactory>().To<SqlConnectionFactory>().Named("Warehouse").WithConstructorArgument("connectionString", ConfigurationManager.ConnectionStrings["DataWarehouse"].ConnectionString);
}
constructor:
//WRONG NAME
public VisitProcessor([Named("DataWarehouse")] IDbConnectionFactory connection)
{
_database = connection;
}
Te falta una resolución de dependencias, es una implementación muy básica:
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);
}
}
Luego simplemente regístrelo cuando cree el kernel:
var httpResolver = new NinjectHttpDependencyResolver(kernel);
GlobalConfiguration.Configuration.DependencyResolver = httpResolver;