繁体   English   中英

通过查询参数处理 .NET Core 3.1 Web API 中的多个端点

[英]Handle multiple endpoints in .NET Core 3.1 Web API by Query Params

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

我的示例 controller:

[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()操作

我来自Startup.csapp.UseEndpoints()只有以下几行:

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

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

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

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

您应该为操作参数添加FromQuery属性并使用不带"{name}"HttpGet

[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
    }
}

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

我想要达到的目标:

  • 调用 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");
    }
}

测试结果

在此处输入图像描述

我的解决方案来自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= and /)

解决方法是创建单独的 controller 操作来处理此类请求

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM