【问题标题】:How might I simultaneously bind FromQuery and FromRoute parameter?我如何同时绑定 FromQuery 和 FromRoute 参数?
【发布时间】:2016-12-08 11:27:51
【问题描述】:

我需要同时支持基于查询参数的路由 (/api/models?id=1) 和基于路由的路由 (/api/models/1),同时仍允许明确访问模型集合 (/api/models)?

我的控制器看起来(有点)像这样:

[Route("/api/{controller}")]
public class ModelsController : Controller
{
    [HttpGet]
    public Models[] GetModels([FromQuery]QueryOptions queryOptions)
    {
        //...
    }    

    [HttpGet("{id:int}")]
    public Model Get([FromRoute] int id)
    {
        //...
    }

    [HttpGet("?{id:int}")]
    public Model Get2Try1([FromQuery] int id)
    {
       //Fails with ": The literal section '?' is invalid. 
       //Literal sections cannot contain the '?' character."
       //Which makes sense after some reading...
    }

    [HttpGet]
    public Model Get2Try2([FromQuery] int id)
    {
       //Fails with "AmbiguousActionException: Multiple actions matched. 
       //The following actions matched route data and had all constraints satisfied: 
       //GetModels and Get2Try2"
       //Which I think I understand as well...the absence of optional params
       //means ambiguous routing...
    }

    [HttpGet] //What here?
    public Model Get2Try3([FromQuery] int id) //and/or here?
    {

    }
}

我觉得应该有某种方法(使用声明性路由)来实现这一点。有没有人做过类似的事情?

此外,当前的代码库是 ASP.NET Core (RC1),不久将升级到 RTM/1.0。双方的细节可能相似,但对其中一个/两个都感兴趣。

【问题讨论】:

    标签: asp.net-mvc asp.net-mvc-routing model-binding .net-core


    【解决方案1】:

    我发现以下工作:

    [HttpGet, Route("{id?}")]
    

    ...关键是'?'。您不需要在方法签名中添加任何 [FromX],这可以解决问题并同时满足查询字符串和路由参数传递的需求。

    不幸的是,Swagger UI 不喜欢它,并希望某些显式参数可以开箱即用(https://github.com/domaindrivendev/Ahoy/issues/47https://github.com/domaindrivendev/Ahoy/issues/182),但这是另一回事 :)

    【讨论】:

    • 这对我不起作用。它作为 [FromRoute] 工作,而不作为 [FromQuery] 工作
    【解决方案2】:

    我遇到了同样的问题。 没有适用于 web api 核心的解决方案(针对 wep api .net)。 如果我们设置 [Route("{id}")] 并且 [Route("")] 不起作用;如果我们只设置 [Route("{id?}")] 如果我使用查询字符串,则查询参数为空。

    所以,我使用了一种解决方法。 我使用了 [Route("{id?}")],但在函数内部我从 Request.Query 获取参数

    例子

    public T Cast<T>(string input)
    {
            T output = default(T);
            if (string.IsNullOrWhiteSpace(input))
                return output;
    
            input = input.Trim();
            try
            {
                Type typeToCastTo = typeof(T);
    
                if (typeof(T).IsGenericType)
                    typeToCastTo = typeToCastTo.GenericTypeArguments[0];
    
                if (typeToCastTo.IsEnum)
                {
                    if (Enum.IsDefined(typeToCastTo, input))
                        return (T)Enum.Parse(typeToCastTo, input);
                    return output;
                }
    
    
                object value = Convert.ChangeType(input, typeToCastTo, CultureInfo.InvariantCulture);
                return (value == null) ? output : (T)value;
            }
            catch
            {
                return output;
            }
    }
    
    public void MapQuerystringParams<T>(ref T param, string name)
    {
            var q = Request.Query[name].FirstOrDefault();
            if (q != null)
            {
                var cast = Cast<T>(q);
                if (!cast.Equals(default(T)))
                    param = cast;
            }
    
    }
    
    [Route("api/[controller]/[action]")]    
    [ApiController]
    public class ActivityController : ControllerBase
    {
        //examples of call 
        //https://localhost:44345/api/Activity/GetActivityByCode/7000
        //https://localhost:44345/api/Activity/GetActivityByCode/?Id=7000
    
        [HttpGet]
        [Route("{Id?}")]
        public IActionResult GetActivityByCode(int Id)
        {
            MapQuerystringParams(ref Id, "Id"); //this take param from querystring if exists
    
            ActivityBusiness business = new ActivityBusiness(new BusinessInitializer { config = configuration });
    
            ActivityDTOModel activity = business.GetActivityByCode(Id);
    
            return Ok(activity);
        }
    }
    

    【讨论】:

      【解决方案3】:

      理想情况下,在域设计中,如果您可以使用一种方法服务于一种特定功能,那就太好了。最近,我不得不忠实地实现一个遗留 API,我无法选择分解我的 API 设计。

      如果您在 MVC6 中遇到不明确的路由,并且需要在给定特定 QueryString 的情况下区分唯一路由,这些 QueryString 已在一个 POST 方法中提供。那么 IActionConstraint 可以提供帮助!这是我使用它的一些示例代码:

          using System;
          using Microsoft.AspNetCore.Mvc.ActionConstraints;
      
          namespace Automation.Api.Service.Attributes
          {
              public class RoutingSpecificAttribute : Attribute, IActionConstraint
              {
                  private string _keyParam;
      
                  public RoutingSpecificAttribute(string routingParameter)
                  {
                      this._keyParam = routingParameter;
                  }
      
      
                  public int Order
                  {
                      get
                      {
                          return 0;
                      }
                  }
      
                  public bool Accept(ActionConstraintContext context)
                  {
                      if (this._keyParam == null) { return true; }
      
                      switch (this._keyParam)
                      {
                          case "name": return context.RouteContext.HttpContext.Request.Query.ContainsKey(this._keyParam);
                          case "noquerystring": return context.RouteContext.HttpContext.Request.Query.Count == 0;
                          default:
                              return false;
                      }
                  }
              }
          }
      

      我需要编写的 API 中的这个方法都基于两个 QueryString 的存在提供了单独的创建 + 更新函数:名称和版本。

      因此,为了帮助消除歧义,您可以在所述控制器类 [RoutingSpecific("noquerystring")] 或 [RoutingSpecific("name")] 中清楚地装饰控制器中的每个方法,以帮助区分。

      MSDN class description

      Example implementation - see Entropy github

      【讨论】:

      【解决方案4】:

      对于任何碰巧像我一样偶然发现的人,

      使用 .Net Core 3.1 可以实现以下工作:

      网页控制器方法

      [HttpGet("something/{id}")]
      public IActionResult Get([FromRoute] id, [FromQuery] OptionalParams optionalParams) 
      {
          // do stuff
      }
      

      查询参数容器

      public class OptionalParams 
      {
          [FromQuery(Name = "colour_of_thing")]
          public string Colour { get; set; }
      
          [FromQuery(Name = "shape_of_thing")]
          public string Shape { get; set; }
      
          [FromQuery(Name = "some_other_filter")]
          public string SomeOtherFilter { get; set; }
      }
      

      网址

      var id = Guid.NewGuid();
      var colour = "red";
      var shape = "circle";
      
      var url = $"Http://localhost:5000/something/{id}?colour_of_thing={colour}&shape_of_thing={shape}";
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2019-03-16
        • 1970-01-01
        • 2019-09-21
        • 2021-10-08
        • 2018-08-21
        • 2018-11-02
        • 2021-06-11
        • 1970-01-01
        相关资源
        最近更新 更多