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

问题描述 投票:2回答:3

我需要同时支持基于查询参数的路由(/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
3个回答
4
投票

我发现以下工作:

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

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

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


1
投票

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

所以,我使用了workround。我使用[Route(“{id?}”)],但在函数内部我从Request.Query得到了param

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);
    }
}

1
投票

理想情况下,在域设计中,如果您可以使用一种方法为一个特定功能提供服最近我不得不忠实地实现遗留API,但我不能分解我的API设计。

如果您在MVC6中遇到模糊路由,并且需要根据在单个POST方法提供的特定QueryStrings来区分唯一路由。那么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中的这一个方法都基于一些QueryStrings的存在来提供单独的create + update函数:name和version。

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

MSDN class description

Example implementation - see Entropy github

© www.soinside.com 2019 - 2024. All rights reserved.