WebAPI 中具有默认根参数的属性路由

Attribute routing with default root parameter in WebAPI

我有一个 API,其中所有方法都需要一个固定参数 {customer} :

/cust/{customerId}/purchases
/cust/{customerId}/invoices
/cust/{customerId}/whatever*

如何映射所有控制器以默认以可重用的方式接收此参数,例如:

endpoints.MapControllerRoute(name: "Default", pattern: "/cust/{customerId:int}/{controller}*"

我在启动时使用 .net core 3.0 和新的 .useEndpoints 方法。

您可以创建 ControllerModelConvention to custom the attribute route behavior. For more details, see official docs 的实现。

例如,假设您想将属性路由约定(如/cust/{customerId:int}/[controller])与全局现有属性组合,只需创建一个约定如下:

public class FixedCustomIdControllerConvention : IControllerModelConvention
{
    public void Apply(ControllerModel controller)
    {
        var customerRouteModel= new AttributeRouteModel(){
            Template="/cust/{customerId:int}",
        };
        var isApiController= controller.ControllerType.CustomAttributes.Select(c => c.AttributeType)
            .Any(a => a == typeof(ApiControllerAttribute));
        foreach (var selector in controller.Selectors)
        {
            if(!isApiController)
            {
                var oldAttributeRouteModel=selector.AttributeRouteModel;
                var newAttributeRouteModel= oldAttributeRouteModel;
                if(oldAttributeRouteModel != null){
                    newAttributeRouteModel= AttributeRouteModel.CombineAttributeRouteModel(customerRouteModel, oldAttributeRouteModel);
                }
                selector.AttributeRouteModel=newAttributeRouteModel;
            } else{
                // ApiController won't honor the by-convention route
                // so I just replace the template
                var oldTemplate = selector.AttributeRouteModel.Template;
                if(! oldTemplate.StartsWith("/") ){
                    selector.AttributeRouteModel.Template= customerRouteModel.Template + "/" + oldTemplate;
                }
            }
        }
    }
}

然后在Startup中注册:

services.AddControllersWithViews(opts =>{
    opts.Conventions.Add(new FixedCustomIdControllerConvention());
});

演示

假设我们有一个 ValuesController:

[Route("[controller]")]
public class ValuesController : Controller
{

    [HttpGet]
    public IActionResult Get(int customerId)
    {
        return Json(new {customerId});
    }

    [HttpPost("opq")]
    public IActionResult Post(int customerId)
    {
        return Json(new {customerId});
    }

    [HttpPost("/rst")]
    public IActionResult PostRst(int customerId)
    {
        return Json(new {customerId});
    }
}

注册以上FixedCustomIdControllerConvention后,路由行为为:

  1. HTTP 请求 GET https://localhost:5001/cust/123/values 将匹配 Get(int customerId) 方法。
  2. HTTP 请求 POST https://localhost:5001/cust/123/values/opq 将匹配 Post(int customerId) 方法
  3. 因为我们有意在 /rst 中放置了一个前导斜杠,所以忽略了全局约定。结果,POST https://localhost:5001/rst 将匹配 PostRst(int customerId) 方法(customId=0)

如果您使用的是带有 [ApiController] 注释的控制器:

[ApiController]
[Route("[controller]")]
public class ApiValuesController : ControllerBase
{

    [HttpGet]
    public IActionResult Get([FromRoute]int customerId)
    {
        return new JsonResult(new {customerId});
    }

    [HttpPost("opq")]
    public IActionResult Post([FromRoute]int customerId)
    {
        return new JsonResult(new {customerId});
    }

    [HttpPost("/apirst")]
    public IActionResult PostRst([FromRoute]int customerId)
    {
        return new JsonResult(new {customerId});
    }
}

您可能需要使用 [FromRoute].

修饰路由参数