我希望控制器具有由自定义代码生成的默认路由 URL 和 HTTP 方法,并且不需要任何
HttpPost|Get|etc
属性(路由操作将默认为 Post
)。我可以使用 Microsoft.AspNetCore.Mvc.ApplicationModels.IActionModelConvention
来做到这一点。
但是我无法在控制器上使用
[ApiController]
,因为没有显式属性路由 - 我收到错误:
Controller Action>没有属性route。用 ApiControllerAttribute 注释的控制器上的操作方法必须是属性路由
有没有办法动态添加默认路由和http方法到控制器操作并使用
[ApiController]
属性?
相关问题
ASP.NET Core API 中的传统路由 - 这很接近 - 但我想拥有
[ApiController]
的所有优点/默认值 - 即不必为参数设置 [FromBody]
等
核心要求
我想创建一个 OpenAPI 后端并用于前端的 JS SPA 客户端代码生成。由于 API 仅由 SPA 使用,并且客户端 API 将自动生成代码 - 我并不真正关心硬编码路由,甚至不关心使用“正确”的 HTTP 方法(一切都可能只是 POST)。所以我想摆脱控制器中的所有样板。
当前的想法/想法
在执行
I*Convention
验证之前,不会应用 Microsoft.AspNetCore.Mvc.ApplicationModels.ApiBehaviorApplicationModelProvider.EnsureActionIsAttributeRouted
。因此,即使我在约定中应用了 AttributeRouteModel
,它也不包含在验证中:
actionSelector.AttributeRouteModel = new AttributeRouteModel
{
Template = $"foo/bar/{Guid.NewGuid():N}",
};
我的下一个想法是实现某种
Microsoft.AspNetCore.Mvc.ApplicationModels.IApplicationModelProvider
(这就是 [ApiController]
似乎是如何验证的 - 请参阅:Microsoft.AspNetCore.Mvc.ApplicationModels.ApiBehaviorApplicationModelProvider
) - 并尝试确保它在 ApiBehaviorApplicationModelProvider
之前注册并执行?
如果这不起作用,那么似乎可以选择更深入地研究诸如
Microsoft.AspNetCore.Mvc.Abstractions.IActionDescriptorProvider
之类的东西 - 但这看起来非常可怕。
我最终使用了
IApplicationModelProvider
策略。我只需确保将 Order
属性设置为在 ApiBehaviorApplicationModelProvider
之前运行 - 所以我最终得到:
/// <summary>
/// [ApiController] attribute model provider uses <c>-1000 + 100</c> -
/// this needs to be applied first so that we get attribute routing.
/// <see cref="AuthorizationApplicationModelProvider"/> uses +10 - so we're trying
/// to split the difference here.
/// </summary>
public int Order => -1000 + 50;
而且,如果有人感兴趣,以下是我如何将路由和 HttpMethods 添加到操作中(它似乎有效......):
public void OnProvidersExecuting(ApplicationModelProviderContext context)
{
var actionSelectors = context.Result.Controllers
.SelectMany(
c => c.Actions.SelectMany(
a => a.Selectors.Select(
selector => (
controller: c,
action: a, selector))));
foreach (var actionSelector in actionSelectors)
{
var selectorModel = actionSelector.selector;
var actionDebugName = $"{actionSelector.controller.ControllerName}:{actionSelector.action.ActionName}";
if (selectorModel.AttributeRouteModel == null)
{
var endpointUrl = // ... magic here to create the URL
_logger.LogDebug(
"Endpoint has no attribute route - adding default ({Action}): {Url}",
actionDebugName,
endpointUrl);
selectorModel.AttributeRouteModel = new AttributeRouteModel
{
Template = endpointUrl,
};
}
else
{
_logger.LogInformation(
"{Endpoint}: Endpoint has existing attribute route - will not add a default",
actionDebugName);
}
/*
* Add a default HTTP Method to the action (POST) if not specified
*/
var httpMethodConstraint =
(HttpMethodActionConstraint?)selectorModel.ActionConstraints.FirstOrDefault(
c => c is HttpMethodActionConstraint);
/*
* Debug out the HTTP Methods
*/
var httpMethods = httpMethodConstraint == null
? "NONE"
: string.Join(",", httpMethodConstraint.HttpMethods);
_logger.LogDebug("Existing HTTP Methods: {HttpMethods}", httpMethods);
if (httpMethodConstraint == null)
{
_logger.LogDebug("{Endpoint}: Adding default http method", actionDebugName);
selectorModel.ActionConstraints.Add(
new HttpMethodActionConstraint(new[] { DefaultHttpMethod }));
}
else
{
_logger.LogInformation("{Endpoint}: already has http action", actionDebugName);
}
}
}