我需要同时支持基于查询参数的路由(/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。任何一方的细节都可能类似,但我对其中任何一方都感兴趣。
答案 0 :(得分:4)
我发现以下情况有效:
[HttpGet, Route("{id?}")]
......关键主要是'?'。您不需要方法签名中的任何[FromX],这样做可以满足查询字符串和路由参数传递的需要。
不幸的是,Swagger用户界面并不喜欢它,并期望一些明确的参数能够开箱即用(https://github.com/domaindrivendev/Ahoy/issues/47或https://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”)]中明确装饰控制器中的每个方法,以帮助区分。