控制器在单独库中的动态路由前缀



我正在一个单独的类库中开发一个MVC API。API 方法使用属性路由。该 API 将由其他 MVC 应用程序使用(不是由我构建的)。

主 MVC 应用程序将引用我的库程序集,并在它自己的启动类中调用AddMvc()/UseMvc()。它将能够动态地为我的 API 库设置根 API URL(从配置或选项设置委托),以便它可以确保与自己的路由没有冲突,这些路由可以使用属性路由或集中路由。

因此,假设我的 API 库有一个product/{id}路由。主应用程序应该能够选择任何路由前缀,例如api/product/{id}some/other/prefix/product/{id}

启动时,MVC 将发现所有引用程序集中的所有控制器/路由,并且还将发现并注册我的 API 库路由,但仅在没有任何前缀的硬编码product/{id}路由上。

我一直在尝试让 MVC 注册带有前缀的路由,但到目前为止还没有成功。主应用程序将调用自定义AddMyApi()/UseMyApi()配置方法,因此我可以为我的库进行配置/设置。我尝试过的一些事情:

映射

app.Map("/custom-prefix", api =>
{
api.UseMvc();
});

这将导致custom-prefix/product/{id}product/{id}的重复路由。

路线约定

基于 http://www.strathweb.com/2016/06/global-route-prefix-with-asp-net-core-mvc-revisited/

services.AddMvc(options =>
{
options.Conventions.Insert(0, new RouteConvention(new RouteAttribute("custom-prefix")));
});

看起来这不起作用,因为这些选项将被主应用程序对AddMvc()的调用覆盖,或者相反,具体取决于首先调用哪个。

自定义路由属性

基于 Controller 类IRouteTemplateProvider的自定义路由属性将不起作用,因为我需要从选项类注入前缀,并且属性不支持构造函数注入。

推迟发现路线

基于 http://www.strathweb.com/2015/04/asp-net-mvc-6-discovers-controllers/

我已向库控制器添加了[NonController],以防止在主应用程序的启动时发现它们。但是我以后无法添加它们,而且我想我会遇到主应用程序再次覆盖 MVC 选项的相同问题。

地区

我不能使用区域,因为主应用程序可能决定从根目录(不带前缀)运行 API。

所以我对如何解决这个问题感到困惑。任何帮助,不胜感激。

我相信约定是正确的方法,您缺少的一点只是为您的库提供正确的扩展方法以在 MVC 中注册。

首先创建一个约定,该约定将向传递特定选择器的所有控制器添加前缀。

  • 它基于我为添加区域性前缀而写的,但这个想法与您链接的文章非常相似。
  • 基本上,它将更新任何现有AttributeRouteModel,如果未找到,则会添加新。

这将是此类约定的一个例子:

public class ApiPrefixConvention: IApplicationModelConvention
{
private readonly string prefix;
private readonly Func<ControllerModel, bool> controllerSelector;
private readonly AttributeRouteModel onlyPrefixRoute;
private readonly AttributeRouteModel fullRoute;
public ApiPrefixConvention(string prefix, Func<ControllerModel, bool> controllerSelector)
{
this.prefix = prefix;
this.controllerSelector = controllerSelector;            
// Prepare AttributeRouteModel local instances, ready to be added to the controllers
//  This one is meant to be combined with existing route attributes
onlyPrefixRoute = new AttributeRouteModel(new RouteAttribute(prefix));
//  This one is meant to be added as the route for api controllers that do not specify any route attribute
fullRoute = new AttributeRouteModel(
new RouteAttribute("api/[controller]"));
}
public void Apply(ApplicationModel application)
{
// Loop through any controller matching our selector
foreach (var controller in application.Controllers.Where(controllerSelector))
{
// Either update existing route attributes or add a new one
if (controller.Selectors.Any(x => x.AttributeRouteModel != null))
{
AddPrefixesToExistingRoutes(controller);
}
else
{
AddNewRoute(controller);
}
}
}        
private void AddPrefixesToExistingRoutes(ControllerModel controller)
{
foreach (var selectorModel in controller.Selectors.Where(x => x.AttributeRouteModel != null).ToList())
{
// Merge existing route models with the api prefix
var originalAttributeRoute = selectorModel.AttributeRouteModel;                
selectorModel.AttributeRouteModel =
AttributeRouteModel.CombineAttributeRouteModel(onlyPrefixRoute, originalAttributeRoute);
}
}
private void AddNewRoute(ControllerModel controller)
{
// The controller has no route attributes, lets add a default api convention 
var defaultSelector = controller.Selectors.First(s => s.AttributeRouteModel == null);
defaultSelector.AttributeRouteModel = fullRoute;
}
} 

现在,如果这都是您正在编写的应用程序而不是库的一部分,您只需将其注册为:

services.AddMvc(opts =>
{
var prefixConvention = new ApiPrefixConvention("api/", (c) => c.ControllerType.Namespace == "WebApplication2.Controllers.Api");
opts.Conventions.Insert(0, prefixConvention);
});

但是,由于您正在提供库,因此您需要提供一种扩展方法(如AddMyLibrary("some/prefix")),该方法将负责添加此约定和任何其他设置,例如注册所需的服务。

因此,您可以编写用于IMvcBuilder的扩展方法,并更新该方法中的MvcOptions。好处是,由于是IMvcBuilder的扩展,它将始终在默认AddMvc()之后调用:

public static IMvcBuilder AddMyLibrary(this IMvcBuilder builder, string prefix = "api/")
{
// instantiate the convention with the right selector for your library.
// Check for namespace, marker attribute, name pattern, whatever your prefer
var prefixConvention = new ApiPrefixConvention(prefix, (c) => c.ControllerType.Namespace == "WebApplication2.Controllers.Api");
// Insert the convention within the MVC options
builder.Services.Configure<MvcOptions>(opts => opts.Conventions.Insert(0, prefixConvention));
// perform any extra setup required by your library, like registering services
// return builder so it can be chained
return builder;
}

然后,您可以要求库的用户将其包含在他们的应用程序中,如下所示:

services.AddMvc().AddMyLibrary("my/api/prefix/");

//尝试此参考 在此处输入链接说明

public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
app.UsePathBase("/Api/v/00");
app.Map("/api/v/0", api =>
{
api.UseMvc();
});        
}

最新更新