如何配置 Web Api 2 以查找独立项目中的控制器?(就像我在 Web Api 中所做的那样)

49

以前我会把控制器放到Mvc Web Api的一个单独的类库项目中。我会在Web Api项目的global.asax文件中添加以下行来寻找单独项目中的控制器:


I used to place my controllers into a separate Class Library project in Mvc Web Api. I used to add the following line in my web api project's global.asax to look for controllers in the separate project:
ControllerBuilder.Current.DefaultNamespaces.Add("MyClassLibraryProject.Controllers");

除了添加上述行之外,我从未进行其他配置。这对我一直很有效。

然而,我无法在WebApi2中使用上述方法来实现相同的功能。它只是无法工作。WebApi2项目仍然尝试在其自己项目的控制器文件夹中查找控制器。

-- 两个月后的小结更新(因为我在此发布了赏金):

我创建了一个WebApiOne解决方案,其中有2个项目,第一个是WebApi项目,第二个是用于控制器的类库。如果我将控制器类库项目的引用添加到WebApi项目中,则所有工作都正常。即,如果我转到 http://mydevdomain.com/api/values,我可以看到正确的输出。

我现在创建了一个名为WebApiTwo的第二个项目,其中有2个项目,第一个是WebApi2项目,第二个是用于控制器的类库。如果我将控制器类库项目的引用添加到WebApi2项目中,它不会按预期工作。也就是说,如果我转到http://mydevdomain.com/api/values,我会收到“找不到与名称为'values'的控制器匹配的类型”。

对于第一个项目,我根本不进行任何自定义设置,我没有:

ControllerBuilder.Current.DefaultNamespaces.Add("MyClassLibraryProject.Controllers");

在我的global.asax中,我没有实施StrathWeb在他的两篇博客文章中提出的任何自定义解决方案,因为我认为它已经不再适用了; 因为只需将控制器项目的引用添加到WebApi项目中,所有工作都可以正常进行。

因此,我预期WebApi2的所有工作方式都应该相同...但事实并非如此。有人真的在WebAPi2中尝试过这样做吗?


5
你展示的代码与MVC控制器相关,与Web API无关。只要已加载具有Web API控制器的程序集,控制器就会被找到。 - Darrel Miller
支持 Darrel 所提到的。 - Kiran
这可能会给你一些关于你正在尝试做的事情的指示:http://www.strathweb.com/2013/08/customizing-controller-discovery-in-asp-net-web-api/ - Tom Hall
@DarrelMiller ... 谢谢...那就意味着它在MVC4中可以工作,而在MVC5中不能工作...我刚刚创建了两个临时项目来证明这一点,MVC4与WebApi肯定可以工作,但是MVC5与WebApi2不行... - M. Ali Iftikhar
3
WebApi1和WebApi2都从所有已加载的程序集中加载控制器。除了与MVC一起捆绑在模板中之外,Web API核心对MVC没有任何依赖。您需要找出为什么您的控制器程序集未被加载。 - Darrel Miller
显示剩余4条评论
8个回答

30

我刚确认这个问题已经解决。需要检查以下几点:

引用: 你的主Web API项目是否引用了外部类库?

路由: 你是否设置了任何可能会干扰外部控制器的路由?

保护级别: 外部类库中的控制器是public吗?

继承: 外部类库中的控制器是否继承自ApiController

版本控制: 你的Web API项目和类库是否都使用相同版本的Web API库?

如果需要,我可以打包我的测试解决方案并提供给你。还有一点需要注意,你不需要在Global.asax中添加你所添加的代码行以告诉Web API找到控制器,只要你已经引用它们,系统会自动找到控制器。


我的问题与版本控制有关,我通过Nuget将Odata添加到了我的ClassLib中,并且Nuget安装了“Assembly System.Web.Http.OData.dll, v5.7.0.0”,但是我注意到我的WebSite项目正在使用旧版本。因此,我打开了WebSite项目的Nuget管理器并更新了Odata包。然后它就可以工作了。感谢您指出这一点 ;) - Yasin Kilicdere
@DavidG 我知道这个话题有点老了,但我真的很想看到一个示例,因为我正在尝试实现类似的东西,使用分离的项目中的控制器,虽然我的解决方案依赖于 OWIN 自托管而不是 ASP.NET IIS。 - grmihel
1
@grmihel 在IIS上,我只需要将Microsoft.AspNet.WebApi(包括依赖项Client/Core/WebHost)NuGet包添加到库中,并创建一个公共类,该类继承自ApiController。出乎我的意料,这很好用。不过我不知道OWIN。你确定你的RouteConfig没问题吗? - Sören Kuklau

15

应该可以直接工作。检查清单:

  • 继承ApiController
  • 控制器名称以Controller结尾,例如:ValuesController
  • 确保WebApi项目和类库项目引用相同的WebApi程序集
  • 尝试使用属性路由强制路由
  • 清理解决方案,手动删除bin文件夹并重新构建
  • 删除Temporary ASP.NET Files文件夹。WebApi和MVC会缓存控制器查找结果
  • 调用`config.MapHttpAttributeRoutes();以确保框架考虑属性路由
  • 确保您调用的方法能够处理正确的HTTP谓词(如果是GET网络方法,可以通过浏览器URL调用,如果是POST,则必须通过其他方式构建Web请求)

这个控制器:

[RoutePrefix("MyValues")]
public class AbcController : ApiController
{
    [HttpGet]
    [Route("Get")]
    public string Get()
    {
        return "Ok!";
    }
}

匹配此网址:

http://localhost/MyValues/Get(请注意,在路由中没有/ api / ,因为它没有在 RoutePrefix 中指定。


控制器查找缓存:这是默认控制器解析器。您将在源代码中看到它缓存了查找结果。

/// <summary>
/// Returns a list of controllers available for the application.
/// </summary>
/// <returns>An <see cref="ICollection{Type}" /> of controllers.</returns>
public override ICollection<Type> GetControllerTypes(IAssembliesResolver assembliesResolver)
{
    HttpControllerTypeCacheSerializer serializer = new HttpControllerTypeCacheSerializer();

    // First, try reading from the cache on disk
    List<Type> matchingTypes = ReadTypesFromCache(TypeCacheName, IsControllerTypePredicate, serializer);
    if (matchingTypes != null)
    {
        return matchingTypes;
    }
...
}

4
为什么类名必须以Controller结尾?仅从ApiController继承不足以满足需求吗? - To Ka
在我尝试设置像/api/cars这样的路由时,一个名为CarApiController的外部程序集中的控制器并且被装饰了[RoutePrefix("/api/cars")],但它却无法工作。但是只需将类名更改为CarsController即可使其正常工作。很奇怪,但也许有人可以解释一下。 - Umar Farooq Khawaja
@UmarFarooqKhawaja 你能让这个工作在WebApi 2上吗?我正在尝试将控制器分离到自己的项目中,并且有一个控制台项目应该使用这个控制器项目,但是我得到的是:{"Message":"No HTTP resource was found that matches the request URI 'http://localhost:9000/api/values/5'.","MessageDetail":"No type was found that matches the controller named 'values'."} 使用OWIN self-host。 - grmihel
@grmihel:清单应该也适用于WebApi 2。我现在添加了最后两项。 - Nikola Radosavljević
1
@grmihel 它应该可以正常工作。MVC/Web API 2只需要在应用程序域中加载控制器,才能将控制器路由到已定义的路由。您确定使用正确的动词命中了终点吗?尝试使用Postman。您可以在其中选择HTTP动词。我确信这就是您找不到端点的原因。如果您想让我查看,请将您的代码放在Bitbucket或Github上的存储库中,并让我拥有它的链接。 - Umar Farooq Khawaja

10

我遇到了相同的情况,@justmara帮我找到了正确的方法。以下是如何根据@justmara的答案强制加载依赖程序集的方法:

1)覆盖DefaultAssembliesResolver类

public class MyNewAssembliesResolver : DefaultAssembliesResolver
{
    public override ICollection<Assembly> GetAssemblies()
    {

        ICollection<Assembly> baseAssemblies = base.GetAssemblies();
        List<Assembly> assemblies = new List<Assembly>(baseAssemblies);
        var controllersAssembly = Assembly.LoadFrom(@"Path_to_Controller_DLL");
        baseAssemblies.Add(controllersAssembly);
        return baseAssemblies;

    }
}

2) 在配置部分,用新实现替换默认设置

config.Services.Replace(typeof(IAssembliesResolver), new MyNewAssembliesResolver());

我从这篇博客中汲取了一些指示,拼凑出了这个句法:

http://www.strathweb.com/2013/08/customizing-controller-discovery-in-asp-net-web-api/

就像其他人所说的那样,如果你通过直接引用来强制加载控制器并遇到此问题,你就会知道。另一种方法是检查 CurrentDomain.GetAssemblies() 的结果,并查看您的程序集是否在列表中。

还有:如果你正在使用 OWIN 组件进行自托管,你会遇到这个问题。请记住,在提交第一个 WebAPI 请求之前,默认的 DefaultAssembliesResolver 不会生效(我花了一段时间才意识到这一点)。


你需要这一行代码 List<Assembly> assemblies = new List<Assembly>(baseAssemblies); 吗?看起来似乎assemblies没有做任何事情。 - RossD
1
此外,该方法不应该是public virtual而应该是public override吧? - RossD
4
很遗憾,您所参考的原始代码存在打字错误,而您的版本也不正确。除非更正签名 (virtual -> override),否则它将无法按预期运行。如果您这样做,该代码将能够正常工作,但是如前所述,assemblies 没有被使用。删除该赋值语句,或者更正其使用方式,即将调用 baseAssemblies.Add 改为 assemblies.Add,然后返回 assemblies,而不是 baseAssemblies - Oskar Lindberg

8

您确定在调用IAssembliesResolver服务之前已加载了所需引用的程序集吗? 尝试在应用程序中插入一些虚拟代码,例如:

var a = new MyClassLibraryProject.Controllers.MyClass();

在配置方法中(但不要忘记,如果“a”从未使用,则编译器可能会“优化”此代码并完全删除它)。 我曾遇到过类似的程序集加载顺序问题。最终采取了在启动时强制加载依赖程序集的方法。


6
您需要告诉WebAPI/MVC加载您引用的程序集。您可以通过web.config中的compilation/assemblies部分来实现此操作。
<compilation debug="true" targetFramework="4.5.2">
  <assemblies>
    <add assembly="XYZ.SomeAssembly" />
  </assemblies>
</compilation>

就是这么简单。你可以使用@user1821052建议的代码来实现,但是使用web.config版本也会有相同的效果。


补充一下:我更喜欢这种方式,因为您无需重新编译任何代码即可添加或删除已加载的程序集。这将在应用程序初始化期间(在其启动之前)被读取和处理,因此每个应用程序池生命周期只会发生一次。 - Ryan Mann
除了将程序集添加到web.config之外,我还需要做一件事。由于主程序集没有其他对附加程序集的使用,因此我必须添加一个引用,并将其设置为“复制本地”。 - WiSeeker
是的,Copy Local = True 意味着在构建时该引用将复制到 bin 文件夹中。dll 必须位于 Bin 文件夹中,以便在应用程序池加载时被 .net 加载器捕获。默认情况下,新引用应始终设置 copy local = true,除非您以前由于某种原因将其设置为 false。 - Ryan Mann
我的评论并不是关于Copy Local=True。而是我需要添加对主项目的引用。 - WiSeeker

2
除了之前提到的内容之外: 确保您没有在不同命名空间中具有相同名称的两个控制器。 我曾经遇到这样一种情况,其中一个控制器(foo.UserApiController)应该部分迁移到一个新的命名空间(bar.UserApiController)和URI。旧的控制器按照约定映射到/userapi,新的控制器通过RoutePrefix["api/users"]属性路由。直到我将其重命名为bar.UserFooApiController,新的控制器才能正常工作。

1
使用AttributeRouting时,很容易忘记为方法添加属性,特别是当您在控制器类上使用属性时。似乎您的控制器程序集没有被Web API管道捕获。

0
如果您的类库是使用EF构建的,请确保在类库项目的App.config中指定了连接字符串,并且在您的Web API MVC项目的Web.config中也指定了连接字符串。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接