如何获取Web API / Castle Windsor来识别控制器? [英] How do I get Web API / Castle Windsor to recognize a Controller?

查看:88
本文介绍了如何获取Web API / Castle Windsor来识别控制器?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我发现了为什么占位符 Home Controller被称为似乎非常有用,尤其是ServiceInstaller:IWindsorInstaller方法。



现在,我看到IRepository与其他生活方式(LifestylePerWebRequest)具有不同的生活方式(LifestyleTransient) )。



IRepository是我应该引用的东西(我不是,这是无法解决的),还是它是您的自定义界面,例如所有其他似乎是(IUserService,IAppService等)?

解决方案

好的,所以让我们分解一下。您说的是WindsorControllerFactory.GetControllerInstance中的值为空。我假设您的意思是controllerType为null。



这意味着您的问题与Windsor无关,因此您可以避免很多混乱现在暂时忽略您的Windsor配置。问题是您的WebApi路由设置不正确,因此WebApi无法根据您提供的路由找到合适的控制器。



像Kiran说,看起来您实际上没有为WebApi映射任何路由,这可能是您的问题所在。



此外,WebApi不使用MVC使用的WindsorControllerFactory,您需要一个单独的类来实现IHttpControllerActivator(有关示例的链接,请参见上面的Serdar帖子)。



当前您具有以下内容:

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

您还需要执行类似的操作(或者,如果您的项目仅使用WebApi) :

  GlobalConfiguration.Configuration.Services.Replace(
typeof(IHttpControllerActivator),
新的WindsorControllerActivator(容器));

因此,您需要执行以下操作:




  • 设置WebApi路由,以便WebApi而不是MVC处理对API的调用。

  • 实施IHttpControllerActivator,并向WebApi注册新的实现。



更新



我抓住了您的来源,修复了一些问题。我在 https://github.com/bclayshannon/WebAPICastleWindsorExtravaganza/pull/1包含修复程序。请看一下有关我所更改内容的详细信息。以下是我所做更改的简要摘要:




  • IDspartmentsRepository的Windsor注册重复,这导致启动期间引发异常。

  • 我必须将Global.asax.cs中的WebApiConfig.Register调用替换为对GlobalConfiguration.Configure的调用。以前我得到的是404(但是可能与您的机器之间有区别)。

  • 我删除了一些MapRoutes调用,这些调用会导致MVC框架处理WebApi。



另外,我将所有API控制器都移到了api文件夹中,以使内容更易于理解,并且在DepartmentsController。一个示例调用是GET / api / Departments / Count。
不需要使用属性路由,但通常可以使用它来创建一个体面的API。



我已经确认我现在可以发出以下请求并进入DepartmentsController:




  • 获取/ api /部门/计数

  • 获取/ api /部门?ID = 123& CountToFetch = 100


I found out why the placeholder "Home Controller" was getting called here

I commented out the reference to HomeController in RouteConfig.cs and added a Controller that I want it to call instead:

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 }
        );
    }
}

...but that gives me a null here in 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);
}

...even though GetCountOfDepartmentRecords() does exist in DepartmentsController:

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

So what am I missing as far as setup or configuration or ... ???

Note: I also tried the RouteConfig entry sans the "Controller" verbiage:

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

...but it makes no difference.

UPDATE

This is what I have in 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());
    }
}

...am I doing something wrong?

UPDATE 2

Perhaps I have too many classes that implement IWindsorInstaller in my project?

&& ||, relatedly, I'm calling container.Register() too many times/in too many places?

I've got calls to container.Register() in three spots:

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) RepositoriesInstaller

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()));
    }
}

Is this "too much of a good thing"?

UPDATE 3

My take on it is that this:

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

...should register everything that implements the ApiController interface; so, DepartmentsController should be installed.

public class DepartmentsController : ApiController

(as should all of the other Controllers, as they all implement the ApiController interface).

While this one:

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

...would only register the single class that implements ISomethingProvider.

And finally this one:

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

...would register everything named Controller.

Note: I replaced:

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

...with what it is above (Repository).

I put breakpoints in them all, and they were all hit following were hit, in reverse order from how they are shown above. So I don't know if only one call to container.Register() is allowed, or why I'm getting a null Controller...

UPDATE 4

Based on the example here by Summit Dishpanhands, I commented out the code I had in RepositoriesInstaller and replaced it with this:

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

...still no joy in Mudville, though...

UPDATE 5

Using Summit's code here [Dependency Injection in WebAPI with Castle Windsor, I changed the code in Global.asax.cs from:

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));
}

...to this:

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;
}   

...and I seem to have gotten further. Now, instead of a runtime exception, I get a browsetime exception, namely HTTP Error 403.14 - Forbidden:

Do I really need to mess with ISS, or is there something else that would solve this conundrum?

UPDATE 6

In response to Kiran Challa:

When I change it to MapHttpRoute like so:

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

...I get the compile-time error "'System.Web.Routing.RouteCollection' does not contain a definition for 'MapHttpRoute' and no extension method 'MapHttpRoute' accepting a first argument of type 'System.Web.Routing.RouteCollection' could be found..."

UPDATE 7

Thanks for your help, Kiran.

I do have a WebApiConfig.cs, and it actually already has all this:

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 }
        );

    }
}

...so I guess I should just comment out the stuff in RouteConfig.cs; it seems I've got too many files; it's as if my closet is chockfull of flammables.

UPDATE 8

For Adam Connelly:

"I'm assuming you mean that controllerType is null."

Yes.

"Where you currently have something like the following:

var controllerFactory = new WindsorControllerFactory(container.Kernel); ControllerBuilder.Current.SetControllerFactory(controllerFactory); You'll need to do something like this as well (or instead if your project only uses WebApi):

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

Actually, I did have both of them:

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));
}

...but am currently not calling BootstrapContainer(), so neither is called. What replaced that is:

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;
}   

UPDATE 9

Serdar, this seems very helpful, especially the ServiceInstaller : IWindsorInstaller method.

Now in that, I see that IRepository has a different lifestyle (LifestyleTransient) than the rest (LifestylePerWebRequest).

Is IRepository something I should have a reference to (I don't, it's unresolvable), or is it a custom interface of yours, such as all the rest appear to be (IUserService, IAppService, etc.)?

解决方案

Ok, so let's break this down. You're saying that you're getting a null in WindsorControllerFactory.GetControllerInstance. I'm assuming you mean that controllerType is null.

What this means is that your problem is nothing to do with Windsor, so you can save yourself a lot of confusion by ignoring your Windsor configuration for now. The problem is that your WebApi routing isn't setup correctly, so WebApi isn't able to find the appropriate controller based on the route you've supplied.

Like Kiran said, it doesn't look like you've actually mapped any routes for the WebApi, which is probably what your problem is.

Additionally, WebApi doesn't use the WindsorControllerFactory that MVC uses, you need a separate class that implements IHttpControllerActivator (see Serdar's post above for a link with an example).

Where you currently have something like the following:

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

You'll need to do something like this as well (or instead if your project only uses WebApi):

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

So here's what you need to do:

  • Setup your WebApi routes so that WebApi rather than MVC handles calls to your API.
  • Implement IHttpControllerActivator, and register your new implementation with WebApi.

Update

I've grabbed your source and fixed a few problems with it. I created a pull request at https://github.com/bclayshannon/WebAPICastleWindsorExtravaganza/pull/1 with the fixes in it. Take a look at that for the details of what I changed. Here's a quick summary of what I changed:

  • There was a duplicate Windsor registration of IDepartmentsRepository which was causing an exception to be thrown during startup.
  • I had to replace the WebApiConfig.Register call in Global.asax.cs with a call to GlobalConfiguration.Configure. Previously I was getting a 404 (but that could just be a difference between my machine and yours).
  • I removed some of the MapRoutes calls that would have caused the MVC framework to handle the WebApi routes.

Additionally, I moved all the API controllers into an api folder to make things easier to understand, and I used attribute routing on the DepartmentsController. An example call is GET /api/Departments/Count. Using attribute routing isn't necessary, but it's normally easier to create a decent API with.

I've verified that I can now make the following requests and get into the DepartmentsController:

  • GET /api/Departments/Count
  • GET /api/Departments?ID=123&CountToFetch=100

这篇关于如何获取Web API / Castle Windsor来识别控制器?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆