我如何同时绑定FromQuery和FromRoute参数?

时间:2016-08-02 22:59:23

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

我需要同时支持基于查询参数的路由(/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。任何一方的细节都可能类似,但我对其中任何一方都感兴趣。

3 个答案:

答案 0 :(得分:4)

我发现以下情况有效:

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

......关键主要是'?'。您不需要方法签名中的任何[FromX],这样做可以满足查询字符串和路由参数传递的需要。

不幸的是,Swagger用户界面并不喜欢它,并期望一些明确的参数能够开箱即用(https://github.com/domaindrivendev/Ahoy/issues/47https://github.com/domaindrivendev/Ahoy/issues/182),但那是另一个故事:)

答案 1 :(得分:1)

我有同样的问题。 没有适用于Web api核心的解决方案(针对wep api .net)。 如果我们设置[Route(“ {id}”)]而[Route(“”))不起作用;如果仅设置[Route(“ {id?}”)],则如果我使用querystring,则查询参数为空。

因此,我使用了一种工作环境。 我使用了[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);
    }
}

答案 2 :(得分:1)

理想情况下,在域设计中,如果您可以使用一种方法来实现一项特定功能,那就太好了。最近,我不得不忠实地实现旧版API,但我不能选择分解API的设计。

如果您在MVC6中遇到模棱两可的路由,并且需要在给定一个POST方法提供的特定QueryString的情况下区分唯一路由。然后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;
                }
            }
        }
    }

基于两个QueryString的存在,我都需要编写API的一个方法为单独的create + update函数提供服务:名称和版本。

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

MSDN class description

Example implementation - see Entropy github