我有一个具有多个路由的控制器。
我正在尝试将端点声明为
GET: api/lookupent/2020-03-17T13:28:37.627691
但这会导致此错误
Microsoft.AspNetCore.Routing.Matching.AmbiguousMatchException: The request matched multiple endpoints. Matches:
Controllers.RecordController.Get (API)
Controllers.RecordController.GetRecordRegisteredAt (API)
但是我不确定自从这段代码以来为什么这很有意义
// GET: api/{RecordName}/{id}
[HttpGet("{RecordName}/{id}", Name = "GetRecord")]
public ActionResult Get(string RecordName, long id)
// GET: api/{RecordName}/{timestamp}
[HttpGet("{RecordName}/{timestamp}", Name = "GetRecordRegisteredAt")]
public ActionResult GetRecordRegisteredAt(string RecordName, string timestamp)
为什么输入与这些端点匹配?
答案 0 :(得分:5)
您可以使用路线约束来解决此问题。
这是他们的例子:
[Route("users/{id:int}")]
public User GetUserById(int id) { ... }
[Route("users/{name}")]
public User GetUserByName(string name) { ... }
答案 1 :(得分:1)
您遇到的问题是,对于具有不同参数的2种不同方法,您的控制器具有相同的路由。 让我用一个类似的示例进行说明,您可以使用以下两种方法:
Get(string entityName, long id)
Get(string entityname, string timestamp)
到目前为止,这是有效的,至少C#没有给您错误,因为它是参数的重载。但是对于控制器,您有一个问题,当aspnet收到额外的参数时,它不知道将请求重定向到何处。 您可以更改路由,这是一种解决方案。
该解决方案还使您能够将输入映射到复杂类型,否则对简单类型使用Route constraint
通常,我更喜欢保持相同的名称,并将参数包装在DtoClass,IntDto和StringDto上
public class IntDto
{
public int i { get; set; }
}
public class StringDto
{
public string i { get; set; }
}
[ApiController]
[Route("[controller]")]
public class WeatherForecastController : ControllerBase
{
[HttpGet]
public IActionResult Get(IntDto a)
{
return new JsonResult(a);
}
[HttpGet]
public IActionResult Get(StringDto i)
{
return new JsonResult(i);
}
}
但是仍然有错误。为了将您的输入绑定到方法上的特定类型,我创建了一个ModelBinder,在这种情况下,如下所示(请参阅我试图从查询字符串中解析参数,但我使用的是鉴别符头通常用于客户端和服务器之间的内容协商(Content negotiation):
public class MyModelBinder : IModelBinder
{
public Task BindModelAsync(ModelBindingContext bindingContext)
{
if (bindingContext == null)
throw new ArgumentNullException(nameof(bindingContext));
dynamic model = null;
string contentType = bindingContext.HttpContext.Request.Headers.FirstOrDefault(x => x.Key == HeaderNames.Accept).Value;
var val = bindingContext.HttpContext.Request.QueryString.Value.Trim('?').Split('=')[1];
if (contentType == "application/myContentType.json")
{
model = new StringDto{i = val};
}
else model = new IntDto{ i = int.Parse(val)};
bindingContext.Result = ModelBindingResult.Success(model);
return Task.CompletedTask;
}
}
然后,您需要创建一个ModelBinderProvider(请参见,如果我收到试图绑定这些类型之一的文件,那么我将使用MyModelBinder)
public IModelBinder GetBinder(ModelBinderProviderContext context)
{
if (context.Metadata.ModelType == typeof(IntDto) || context.Metadata.ModelType == typeof(StringDto))
return new MyModelBinder();
return null;
}
并将其注册到容器中
public void ConfigureServices(IServiceCollection services)
{
services.AddControllers(options =>
{
options.ModelBinderProviders.Insert(0, new MyModelBinderProvider());
});
}
到目前为止,您尚未解决所遇到的问题,但我们已经结束。为了立即执行控制器操作,您需要在请求上传递标头类型: application / json 或 application / myContentType.json 。但是,为了支持条件逻辑来确定关联的操作方法对于给定的请求是否有效,您可以创建自己的ActionConstraint
。基本上,这里的想法是用此属性装饰您的ActionMethod,以限制用户在未传递正确媒体类型的情况下执行该操作。参见下面的代码及其用法
[AttributeUsage(AttributeTargets.All, Inherited = true, AllowMultiple = true)]
public class RequestHeaderMatchesMediaTypeAttribute : Attribute, IActionConstraint
{
private readonly string[] _mediaTypes;
private readonly string _requestHeaderToMatch;
public RequestHeaderMatchesMediaTypeAttribute(string requestHeaderToMatch,
string[] mediaTypes)
{
_requestHeaderToMatch = requestHeaderToMatch;
_mediaTypes = mediaTypes;
}
public RequestHeaderMatchesMediaTypeAttribute(string requestHeaderToMatch,
string[] mediaTypes, int order)
{
_requestHeaderToMatch = requestHeaderToMatch;
_mediaTypes = mediaTypes;
Order = order;
}
public int Order { get; set; }
public bool Accept(ActionConstraintContext context)
{
var requestHeaders = context.RouteContext.HttpContext.Request.Headers;
if (!requestHeaders.ContainsKey(_requestHeaderToMatch))
{
return false;
}
// if one of the media types matches, return true
foreach (var mediaType in _mediaTypes)
{
var mediaTypeMatches = string.Equals(requestHeaders[_requestHeaderToMatch].ToString(),
mediaType, StringComparison.OrdinalIgnoreCase);
if (mediaTypeMatches)
{
return true;
}
}
return false;
}
}
这是您的最终更改:
[ApiController]
[Route("[controller]")]
public class WeatherForecastController : ControllerBase
{
[HttpGet]
[RequestHeaderMatchesMediaTypeAttribute("Accept", new[] { "application/json" })]
public IActionResult Get(IntDto a)
{
return new JsonResult(a);
}
[RequestHeaderMatchesMediaTypeAttribute("Accept", new[] { "application/myContentType.json" })]
[HttpGet]
public IActionResult Get(StringDto i)
{
return new JsonResult(i);
}
}
现在,如果您运行应用程序,该错误将消失。但是如何传递参数?: 这个将要使用这种方法:
public IActionResult Get(StringDto i)
{
return new JsonResult(i);
}
这个又一个:
public IActionResult Get(IntDto a)
{
return new JsonResult(a);
}
运行它,让我知道
答案 2 :(得分:0)
这两种方法的问题相同:
[HttpPost]
public async Task<IActionResult> PostFoos(IEnumerable<FooModelPostDTO> requests)
[HttpPost]
public async Task<IActionResult> GetFoos(GetRequestDTO request)
第一个用于获取实体(使用Post
),第二个用于在DB中发布新实体(再次使用Post
)。
一种可能的解决方案是通过具有../[action]
属性的方法名称(Route
)来区分它们:
[Route("api/[controller]/[action]")]
[ApiController]
public class FoosController : ControllerBase