Les packages suivants et leurs dépendances sont installés dans mon projet WebAPI:
Ninject.Web.WebApi
Ninject.Web.WebApi.OwinHost
Je lance ceci purement comme un projet web-api. Pas de MVC.
Lorsque j'exécute mon application et que j'envoie une POST
à l'action Register du AccountController, l'erreur suivante est renvoyée:
{
"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)"
}
}
Quelqu'un peut-il m'aider car les seuls détails que je peux trouver sur Google semblent être de 2012.
Note: J'ai aussi essayé AutoFac au lieu de Ninject et j'y ai aussi la même erreur. Le plus frustrant.
Voici mon 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>();
}
}
}
Voici mon 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
}
}
Avez-vous modifié votre classe OWIN Startup
pour appeler app.UseNinjectWebApi
et app.UseNinjectMiddleware
plutôt que app.UseWebApi
?
Startup.cs dans les exemples d'API Web de Ninject, procédez comme suit ...
Dans mon cas, la raison était que le résolveur n'a pas pu trouver de mappage. Supposons que HomeController dépende d’IDumb, le résolveur n’a pas pu trouver une implémentation concrète de Dumb avec les implémentations IDumb. En d'autres termes, le message d'erreur
**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**
est complètement trompeur. Dans mon cas, je viens de résoudre en ajoutant une référence au projet de la classe Dumb. Cela aurait dû être quelque chose comme "Aucune cartographie pour IDumb n'a pu être trouvée". Je ne suis pas sûr que le problème est avec NInject ou MS. Quoi qu'il en soit, cela m'a pris des heures pour le découvrir.
Ma solution est d'ajouter le mot clé "public" au constructeur.
Pour moi, la cause de cette erreur est que pour l'interface que je passais dans le constructeur, j'avais une liaison nommée et je l'appelais avec un nom incorrect. Comme vous pouvez le voir ci-dessous, la liaison indique qu'elle s'appelle "Warehouse", mais j'ai tout mélangé et j'ai mis "DataWarehouse" dans le constructeur. Le redressement de cette erreur a entraîné la disparition de l’erreur liée à un constructeur sans paramètre.
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);
}
constructeur:
//WRONG NAME
public VisitProcessor([Named("DataWarehouse")] IDbConnectionFactory connection)
{
_database = connection;
}
Assurez-vous d’avoir enregistré tous les types utilisés par le contrôleur, jusqu’à la base de données.
Dans mon cas, je n'avais ajouté que l'interface utilisée par le contrôleur lui-même, mais pas l'interface utilisée pour interroger la base de données.
Dans le code ci-dessous, notez comment la classe AddUserMaintenanceProcessor a des dépendances inconnues du contrôleur. Si vous omettez les mappages de type Unity (ou l'outil IoC que vous utilisez) pour ces dépendances, la construction du contrôleur échouera.
Ma solution utilise Unity, mais ce que j'essaie de dire, c'est que vous devez créer des mappages de types pour toutes les dépendances.
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;
}
}
J'ai omis les interfaces pour les processeurs car elles ne contiennent qu'une seule méthode (modèle de stratégie). Les interfaces pour la journalisation et le mappage automatique ne sont pas pertinentes pour cette question.
La classe AddUserQueryProcessor persiste simplement dans la base de données. Encore une fois sans rapport avec cette question.
Il vous manque un résolveur de dépendance, c'est une implémentation vraiment basique:
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);
}
}
Ensuite, enregistrez-le lorsque vous créez le noyau:
var httpResolver = new NinjectHttpDependencyResolver(kernel);
GlobalConfiguration.Configuration.DependencyResolver = httpResolver;