Как получить Web API / Castle Windsor для распознавания контроллера?

Я узнал, почему здесь вызывали местозаполнитель "Home Controller"

Я закомментировал ссылку на HomeController в RouteConfig.cs и добавил контроллер, который я хочу вместо этого вызывать:

public class RouteConfig
{
    public static void RegisterRoutes(RouteCollection routes)
    {
        routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
        //If get 404 error re: favicon, see http://docs.castleproject.org/(X(1)S(vv4c5o450lhlzb45p5wzrq45))/Windsor.Windsor-tutorial-part-two-plugging-Windsor-in.ashx or just uncomment the line below:
        //routes.IgnoreRoute("{*favicon}", new { favicon = @"(.*/)?favicon.ico(/.*)?" });

        //routes.MapRoute(
        //    name: "Default",
        //    url: "{controller}/{action}/{id}",
        //    defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
        //);

        routes.MapRoute(
            name: "Default",
            url: "{controller}/{action}/{id}",
            defaults: new { controller = "DepartmentsController", action = "GetCountOfDepartmentRecords", id = UrlParameter.Optional }
        );
    }
}

... но это дает мне ноль здесь в WindsorControllerFactory:

protected override IController GetControllerInstance(RequestContext requestContext, Type controllerType)
{
    if (controllerType == null)
    {
        throw new HttpException(404, string.Format("The controller for path '{0}' could not be found.", requestContext.HttpContext.Request.Path));
    }
    return (IController)_kernel.Resolve(controllerType);
}

... хотя GetCountOfDepartmentRecords() существует в DepartmentsController:

public int GetCountOfDepartmentRecords()
{
    return _deptsRepository.Get();
}

Так чего же мне не хватает в настройке или настройке или...???

Примечание: я также пробовал запись RouteConfig без слов "Контроллер":

defaults: new { controller = "Departments", action = "GetCountOfDepartmentRecords", id = UrlParameter.Optional }

... но это не имеет значения.

ОБНОВИТЬ

Вот что я имею в WindsorDependencyResolver:

public class ApiControllersInstaller : IWindsorInstaller
{
    public void Install(Castle.Windsor.IWindsorContainer container, Castle.MicroKernel.SubSystems.Configuration.IConfigurationStore store)
    {
        container.Register(Classes.FromThisAssembly()
         .BasedOn<ApiController>()
         .LifestylePerWebRequest());
    }
}

...Я делаю что-то неправильно?

ОБНОВЛЕНИЕ 2

Возможно, у меня слишком много классов, которые реализуют IWindsorInstaller в моем проекте?

&& ||, соответственно, я вызываю container.Register() слишком много раз / в слишком многих местах?

У меня есть вызовы container.Register() в трех местах:

0) WindsorDependencyResolver.cs:

public class ApiControllersInstaller : IWindsorInstaller
{
    public void Install(Castle.Windsor.IWindsorContainer container, Castle.MicroKernel.SubSystems.Configuration.IConfigurationStore store)
    {
        container.Register(Classes.FromThisAssembly()
         .BasedOn<ApiController>()
         .LifestylePerWebRequest());
    }
}

1) SomethingProviderInstaller.cs:

public class SomethingProviderInstaller : IWindsorInstaller
{
    public void Install(IWindsorContainer container, IConfigurationStore store)
    {
        container.Register(Classes.FromThisAssembly()
                               .BasedOn(typeof(ISomethingProvider))
                               .WithServiceAllInterfaces());
        container.AddFacility<TypedFactoryFacility>();
        container.Register(Component.For<IMyFirstFactory>().AsFactory()); 
    }
}

2) Репозитории Installer

public class RepositoriesInstaller : IWindsorInstaller
{
    public void Install(IWindsorContainer container, IConfigurationStore store)
    {
        container.Register(
                           Types.FromThisAssembly()
                                   .Where(type => type.Name.EndsWith("Repository"))
                                   .WithService.DefaultInterfaces()
                                   .Configure(c => c.LifestylePerWebRequest()));
    }
}

Это "слишком много хорошего"?

ОБНОВЛЕНИЕ 3

Я считаю, что это:

public class ApiControllersInstaller : IWindsorInstaller
{
    public void Install(Castle.Windsor.IWindsorContainer container, Castle.MicroKernel.SubSystems.Configuration.IConfigurationStore store)
    {
        container.Register(Classes.FromThisAssembly()
         .BasedOn<ApiController>()
         .LifestylePerWebRequest());
    }
}

... должен регистрировать все, что реализует интерфейс ApiController; Итак, DepartmentsController должен быть установлен.

public class DepartmentsController : ApiController

(как и все остальные контроллеры, так как все они реализуют интерфейс ApiController).

Пока этот:

public void Install(IWindsorContainer container, IConfigurationStore store)
{
    container.Register(Classes.FromThisAssembly()
                           .BasedOn(typeof(ISomethingProvider))
                           .WithServiceAllInterfaces());
    container.AddFacility<TypedFactoryFacility>();
    container.Register(Component.For<IMyFirstFactory>().AsFactory()); 
}

... зарегистрирует только один класс, который реализует ISomethingProvider.

И, наконец, этот:

container.Register (Types.FromThisAssembly ().Where (type => type.Name.EndsWith ("Controllers")).WithService.DefaultInterfaces ().Configure (c => c.LifestylePerWebRequest ()));

... зарегистрировать все, что называется контроллером.

Примечание: я заменил:

.Where(type => type.Name.EndsWith("Repository"))

... с тем, что выше (хранилище).

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

ОБНОВЛЕНИЕ 4

На основе приведенного здесь примера Summit Dishpanhands я закомментировал код, который был у меня в RepositoriesInstaller, и заменил его следующим:

container.Register(
  Component.For<IDepartmentRepository>().ImplementedBy<DepartmentRepository>()
);

... все еще нет радости в Mudville, хотя...

ОБНОВЛЕНИЕ 5

Используя код Summit здесь [ Внедрение зависимостей в WebAPI с Castle Windsor, я изменил код в Global.asax.cs из:

protected void Application_Start()
{
    AreaRegistration.RegisterAllAreas();
    GlobalConfiguration.Configure(WebApiConfig.Register);
    BootstrapContainer();
}

private static void BootstrapContainer()
{
    _container = new WindsorContainer().Install(FromAssembly.This());
    var controllerFactory = new WindsorControllerFactory(_container.Kernel);

    ControllerBuilder.Current.SetControllerFactory(controllerFactory);

    GlobalConfiguration.Configuration.Services.Replace(
        typeof(IHttpControllerActivator), new WindsorCompositionRoot(_container));
}

...к этому:

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

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

    ConfigureWindsor(GlobalConfiguration.Configuration);
}

public static void ConfigureWindsor(HttpConfiguration configuration)
{
    _container = new WindsorContainer();
    _container.Install(FromAssembly.This());
    _container.Kernel.Resolver.AddSubResolver(new CollectionResolver(_container.Kernel, true));
    var dependencyResolver = new WindsorDependencyResolver(_container);
    configuration.DependencyResolver = dependencyResolver;
}   

... и я, кажется, получил дальше. Теперь вместо исключения времени выполнения я получаю исключение времени просмотра, а именно HTTP Ошибка 403.14 - Запрещено:

Мне действительно нужно возиться с МКС или есть что-то еще, что решило бы эту проблему?

ОБНОВЛЕНИЕ 6

В ответ на Киран Чалла:

Когда я меняю его на MapHttpRoute, вот так:

routes.MapHttpRoute(
    name: "Departments",
    url: "{controller}/{action}/{id}",
    defaults: new { controller = "Departments", action = "GetCountOfDepartmentRecords", id = UrlParameter.Optional }
);

... Я получаю ошибку во время компиляции"System.Web.Routing.RouteCollection" не содержит определения для "MapHttpRoute" и нет метода расширения "MapHttpRoute", принимающего первый аргумент типа "System.Web.Routing.RouteCollection 'можно найти..."

ОБНОВЛЕНИЕ 7

Спасибо за вашу помощь, Киран.

У меня есть WebApiConfig.cs, и он на самом деле уже все это:

public static class WebApiConfig
{
    public static void Register(HttpConfiguration config)
    {
        // Web API configuration and services

        // Web API routes
        config.MapHttpAttributeRoutes();

        config.Routes.MapHttpRoute(
            name: "DefaultApi",
            routeTemplate: "api/{controller}/{id}",
            defaults: new { id = RouteParameter.Optional }
        );

        config.Routes.MapHttpRoute(
            name: "DefaultApiWithParameters",
            routeTemplate: "api/{controller}/{ID}/{CountToFetch}"
            //defaults: new { ID = RouteParameter.Optional, CountToFetch = RouteParameter.Optional }
        );

        config.Routes.MapHttpRoute(
            name: "DefaultApiWith3Parameters",
            routeTemplate: "api/{controller}/{ID}/{packSize}/{CountToFetch}"
            //defaults: new { ID = RouteParameter.Optional, packSize = RouteParameter.Optional, CountToFetch = RouteParameter.Optional }
        );

    }
}

... так что я думаю, что я должен просто закомментировать материал в RouteConfig.cs; кажется, у меня слишком много файлов; как будто мой шкаф забит легковоспламеняющимися предметами.

ОБНОВЛЕНИЕ 8

Для Адама Коннелли:

"Я предполагаю, что вы имеете в виду, что controllerType имеет значение null".

Да.

"Где у вас сейчас есть что-то вроде следующего:

var controllerFactory = new WindsorControllerFactory (container.Kernel); ControllerBuilder.Current.SetControllerFactory (ControllerFactory); Вам также нужно будет сделать что-то подобное (или вместо этого, если ваш проект использует только WebApi):

GlobalConfiguration.Configuration.Services.Replace (typeof (IHttpControllerActivator), new WindsorControllerActivator (container)); "

На самом деле, у меня были оба из них:

private static void BootstrapContainer()
{
    _container = new WindsorContainer().Install(FromAssembly.This());
    var controllerFactory = new WindsorControllerFactory(_container.Kernel);

    ControllerBuilder.Current.SetControllerFactory(controllerFactory);

    GlobalConfiguration.Configuration.Services.Replace(
        typeof(IHttpControllerActivator), new WindsorCompositionRoot(_container));
}

... но в настоящее время я не вызываю BootstrapContainer(), поэтому ни один из них не вызывается. Что заменило это:

private static IWindsorContainer _container;
. . .
protected void Application_Start()
{
    AreaRegistration.RegisterAllAreas();

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

    ConfigureWindsor(GlobalConfiguration.Configuration);
}

public static void ConfigureWindsor(HttpConfiguration configuration)
{
    _container = new WindsorContainer();
    _container.Install(FromAssembly.This());
    _container.Kernel.Resolver.AddSubResolver(new CollectionResolver(_container.Kernel, true));
    var dependencyResolver = new WindsorDependencyResolver(_container);
    configuration.DependencyResolver = dependencyResolver;
}   

ОБНОВЛЕНИЕ 9

Сердар, это кажется очень полезным, особенно метод ServiceInstaller: IWindsorInstaller.

Теперь я вижу, что у IRepository образ жизни (LifestyleTransient) другой, чем у остальных (LifestylePerWebRequest).

Является ли IRepository чем-то, на что я должен иметь ссылку (у меня нет, это неразрешимо), или это ваш пользовательский интерфейс, такой как все остальные (IUserService, IAppService и т. Д.)?

3 ответа

Решение

Хорошо, давайте разберемся с этим. Вы говорите, что получаете нулевое значение в WindsorControllerFactory.GetControllerInstance. Я предполагаю, что вы имеете в виду, что controllerType имеет значение null.

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

Как сказал Киран, не похоже, что вы фактически наметили какие-либо маршруты для WebApi, что, вероятно, является вашей проблемой.

Кроме того, WebApi не использует WindsorControllerFactory, который использует MVC, вам нужен отдельный класс, который реализует IHttpControllerActivator (см. Пост Сердара выше для ссылки с примером).

Где у вас сейчас есть что-то вроде следующего:

var controllerFactory = new WindsorControllerFactory(container.Kernel);
ControllerBuilder.Current.SetControllerFactory(controllerFactory);

Вам также нужно будет сделать что-то подобное (или вместо этого, если ваш проект использует только WebApi):

GlobalConfiguration.Configuration.Services.Replace(
    typeof (IHttpControllerActivator),
    new WindsorControllerActivator(container));

Итак, вот что вам нужно сделать:

  • Настройте маршруты WebApi таким образом, чтобы WebApi, а не MVC обрабатывал вызовы вашего API.
  • Реализуйте IHttpControllerActivator и зарегистрируйте новую реализацию в WebApi.

Обновить

Я взял ваш источник и исправил несколько проблем с ним. Я создал запрос извлечения по адресу https://github.com/bclayshannon/WebAPICastleWindsorExtravaganza/pull/1 с исправлениями в нем. Посмотрите на это для деталей того, что я изменил. Вот краткое изложение того, что я изменил:

  • Произошла двойная виндзорская регистрация IDepartmentsRepository, которая вызвала исключение при запуске.
  • Мне пришлось заменить вызов WebApiConfig.Register в Global.asax.cs на вызов GlobalConfiguration.Configure. Ранее я получал 404 (но это может быть просто разница между моей машиной и вашей).
  • Я удалил некоторые вызовы MapRoutes, из-за которых инфраструктура MVC обрабатывала маршруты WebApi.

Кроме того, я переместил все контроллеры API в папку api, чтобы упростить понимание, и использовал маршрутизацию атрибутов на DepartmentsController. Пример вызова - GET /api/Departments/Count. Использование атрибутной маршрутизации не обязательно, но обычно проще создать приличный API.

Я проверил, что теперь я могу сделать следующие запросы и войти в DepartmentsController:

  • GET / api / Отделы / Граф
  • GET /api/Departments?ID=123&CountToFetch=100

MapRoute расширение для контроллера MVC, тогда как DepartmentsController это контроллер Web API, реализующий IHttpController Интерфейс (пример: ApiController реализует этот интерфейс). Вы должны использовать MapHttpRoute расширение, а также вы можете использовать WebApiConfig.cs если у вас есть.

Проверьте эту ссылку, на ней установлены и контроллер, и apicontroller.

https://github.com/argeset/set-locale/blob/ca0ffa6a3b4a63ad3e1de901dbbdfca58bcf4bb6/src/client/SetLocale.Client.Web/Configurations/IocConfig.cs

маршруты обрабатываются в атрибутах

https://github.com/argeset/set-locale/commit/082b8956f9b9fe0ec343842dd897965b64a516b1

Другие вопросы по тегам