【问题标题】:Handle multiple endpoints in .NET Core 3.1 Web API by Query Params通过查询参数处理 .NET Core 3.1 Web API 中的多个端点
【发布时间】:2020-10-13 11:23:05
【问题描述】:

我正在将控制器从 .NET Framework 迁移到 .NET Core,并且我希望与以前版本的 API 调用兼容。我在处理来自查询参数的多个路由时遇到问题。

我的示例控制器:

[Route("/api/[controller]")]
[Route("/api/[controller]/[action]")]
public class StaticFileController : ControllerBase
{
    [HttpGet("{name}")]
    public HttpResponseMessage GetByName(string name)
    {
    }

    [HttpGet]
    public IActionResult Get()
    {
    }
}

调用api/StaticFile?name=someFunnyName 将引导我执行Get() 操作而不是预期 GetByName(string name)

我想要达到的目标:

  • 调用 GET api/StaticFile -> 转到 Get() 操作
  • 调用 GET api/StaticFile?name=someFunnyName -> 转到GetByName() 操作

我的app.UseEndpoints() 来自Startup.cs 只有这些行:

endpoints.MapControllers();
endpoints.MapDefaultControllerRoute();

如果我在任何地方都使用[HttpGet] 并添加([FromQuery] string name) 它会得到我AmbiguousMatchException: The request matched multiple endpoints

感谢您抽出时间帮助我(也许还有其他人)

【问题讨论】:

    标签: c# asp.net .net asp.net-core asp.net-core-webapi


    【解决方案1】:

    我想要达到的目标:

    • 调用 GET api/StaticFile -> 转到 Get() 操作
    • 调用 GET api/StaticFile?name=someFunnyName -> 转到 GetByName() 操作

    要实现上述基于查询字符串将请求与预期操作匹配的要求,您可以尝试实现自定义ActionMethodSelectorAttribute 并将其应用于您的操作,如下所示。

    [AttributeUsage(AttributeTargets.Method, AllowMultiple = true)]
    public class QueryStringConstraintAttribute : ActionMethodSelectorAttribute
    {
        public string QueryStingName { get; set; }
        public bool CanPass { get; set; }
        public QueryStringConstraintAttribute(string qname, bool canpass)
        {
            QueryStingName = qname;
            CanPass = canpass;
        }
        public override bool IsValidForRequest(RouteContext routeContext, ActionDescriptor action)
        {
            StringValues value;
    
            routeContext.HttpContext.Request.Query.TryGetValue(QueryStingName, out value);
    
            if (QueryStingName == "" && CanPass)
            {
                return true;
            }
            else
            {
                if (CanPass)
                {
                    return !StringValues.IsNullOrEmpty(value);
                }
    
                return StringValues.IsNullOrEmpty(value);
            }
        }
    }
    

    应用于操作

    [Route("api/[controller]")]
    [ApiController]
    public class StaticFileController : ControllerBase
    {
        [HttpGet]
        [QueryStringConstraint("name", true)]
        [QueryStringConstraint("", false)]
        public IActionResult GetByName(string name)
        {
            return Ok("From `GetByName` Action");
        }
    
        [HttpGet]
        [QueryStringConstraint("name", false)]
        [QueryStringConstraint("", true)]
        public IActionResult Get()
        {
            return Ok("From `Get` Action");
        }
    }
    

    测试结果

    【讨论】:

    【解决方案2】:

    HttpGet 的参数设置路由,而不是查询字符串参数名称。

    你应该为动作参数添加FromQuery属性并使用HttpGet而不使用"{name}"

    [HttpGet]
    public HttpResponseMessage GetByName([FromQuery] string name)
    {
        // ...
    }
    

    您还可以为查询参数设置不同的名称:

    [HttpGet]
    public HttpResponseMessage GetByName([FromQuery(Name = "your_query_parameter_name")] string name)
    {
        // ...
    }
    

    但是现在你有两个动作匹配相同的路由,所以你会得到异常。仅基于查询字符串部分(路径相同)执行不同逻辑的唯一方法是检查操作中的查询字符串:

    [HttpGet]
    public IActionResult Get([FromQuery] string name)
    {
        if (name == null)
        {
            // execute code when there is not name in query string
        }
        else
        {
            // execute code when name is in query string
        }
    }
    

    因此,您只有一个操作可以使用相同的路线处理这两种情况。

    【讨论】:

    • AmbiguousMatchException: The request matched multiple endpoints 并匹配 GetByNameGet。如果我同时拥有[HttpGet]GetByName(string name) 也是如此
    • @Saibamen,是的,因为您有两个匹配相同路由的操作(即使查询字符串不同,路由也相同)。您应该只使用一个动作来处理这个问题,或者为其中一个动作指定不同的路线
    • 没有办法解决吗?喜欢为 .NET Core 路由机制添加一些选项?我真的需要向后兼容新的 .NET Core 项目中的 .NET Framework 调用——有 100 多个控制器。谢谢您的回答。我还使用 API 调用编辑了我的主要问题,以便从一开始就更具可读性。
    • @Saibamen,不可能有两个动作匹配相同的路由(查询字符串不用于区分路由)。您可以使用一个操作并检查请求 url 中是否有查询字符串 - 如果查询字符串不为空,则继续执行一个逻辑,如果为空,则继续另一个登录。这是仅基于查询字符串(当路由相同时)执行不同登录的唯一可能方法。查看编辑后的答案
    【解决方案3】:

    我的解决方案来自https://www.strathweb.com/2016/09/required-query-string-parameters-in-asp-net-core-mvc/

    public class RequiredFromQueryAttribute : FromQueryAttribute, IParameterModelConvention
    {
        public void Apply(ParameterModel parameter)
        {
            if (parameter.Action.Selectors != null && parameter.Action.Selectors.Any())
            {
                parameter.Action.Selectors.Last().ActionConstraints.Add(new RequiredFromQueryActionConstraint(parameter.BindingInfo?.BinderModelName ?? parameter.ParameterName));
            }
        }
    }
    
    public class RequiredFromQueryActionConstraint : IActionConstraint
    {
        private readonly string _parameter;
    
        public RequiredFromQueryActionConstraint(string parameter)
        {
            _parameter = parameter;
        }
    
        public int Order => 999;
    
        public bool Accept(ActionConstraintContext context)
        {
            if (!context.RouteContext.HttpContext.Request.Query.ContainsKey(_parameter))
            {
                return false;
            }
    
            return true;
        }
    }
    

    例如,如果在StaticFileController 中使用[RequiredFromQuery],我们可以调用/api/StaticFile?name=withoutAction/api/StaticFile/GetByName?name=wAction,但不能调用/api/StaticFile/someFunnyName(?name= 和/)

    解决方案是创建单独的控制器操作来处理此类请求

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-03-15
      • 2021-11-20
      • 2020-01-21
      • 2015-10-23
      相关资源
      最近更新 更多