请求匹配多个端点,但为什么呢? [英] The request matched multiple endpoints but why?

查看:63
本文介绍了请求匹配多个端点,但为什么呢?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有一个有多个路由的控制器.

我正在尝试调用一个声明为

的端点

GET:api/lookupent/2020-03-17T13:28:37.627691

但这会导致此错误

Microsoft.AspNetCore.Routing.Matching.AmbiguousMatchException:请求匹配多个端点.火柴: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)

为什么输入与这些端点匹配?

解决方案

您遇到的问题是您的控制器对于接收不同参数的 2 个不同方法具有相同的路由.让我用一个类似的例子来说明它,你可以有这样的两种方法:

Get(string entityName, long id)获取(字符串实体名称,字符串时间戳)

到目前为止这是有效的,至少 C# 没有给你一个错误,因为它是参数的重载.但是对于控制器,您会遇到问题,当 aspnet 收到额外的参数时,它不知道将您的请求重定向到哪里.您可以更改路由,这是一种解决方案.

此解决方案还使您能够将输入映射到复杂类型,否则请使用

这个是另一个:

 public IActionResult Get(IntDto a){返回新的 JsonResult(a);}

运行它并让我知道

I have a controller that has multiple routes.

I am trying to call an endpoint stated as

GET: api/lookupent/2020-03-17T13:28:37.627691

but this results in this error

Microsoft.AspNetCore.Routing.Matching.AmbiguousMatchException: The request matched multiple endpoints. Matches: 

Controllers.RecordController.Get (API)
Controllers.RecordController.GetRecordRegisteredAt (API)

but I am not sure I understand why this makes sense since this code

// 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)

why does the input match with these endpoints?

解决方案

The problem you have is that your controller has the same routing for 2 different methods receiving different parameters. Let me illustrate it with a similar example, you can have the 2 methods like this:

Get(string entityName, long id)
Get(string entityname, string timestamp)

So far this is valid, at least C# is not giving you an error because it is an overload of parameters. But with the controller, you have a problem, when aspnet receives the extra parameter it doesn't know where to redirect your request. You can change the routing which is one solution.

This solution gives you the ability to map your input to a complex type as well, otherwise use Route constraint for simple types

Normally I prefer to keep the same names and wrap the parameters on a DtoClass, IntDto and StringDto for example

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

but still, you have the error. In order to bind your input to the specific type on your methods, I create a ModelBinder, for this scenario, it is below(see that I am trying to parse the parameter from the query string but I am using a discriminator header which is used normally for content negotiation between the client and the server(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;
    }
}

Then you need to create a ModelBinderProvider (see that if I am receiving trying to bind one of these types, then I use MyModelBinder)

public IModelBinder GetBinder(ModelBinderProviderContext context)
        {
            if (context.Metadata.ModelType == typeof(IntDto) || context.Metadata.ModelType == typeof(StringDto))
                return new MyModelBinder();

            return null;
        }

and register it into the container

    public void ConfigureServices(IServiceCollection services)
    {
        services.AddControllers(options =>
        {
            options.ModelBinderProviders.Insert(0, new MyModelBinderProvider());
        });
    }

So far you didn't resolve the issue you have but we are close. In order to hit the controller actions now, you need to pass a header type on the request: application/json or application/myContentType.json. But in order to support conditional logic to determine whether or not an associated action method is valid or not to be selected for a given request, you can create your own ActionConstraint. Basically the idea here is to decorate your ActionMethod with this attribute to restrict the user to hit that action if he doesn't pass the correct media type. See below the code and how to use it

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

Here is your final change:

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

Now the error is gone if you run your app. But how you pass the parameters?: This one is going to hit this method:

public IActionResult Get(StringDto i)
        {
            return new JsonResult(i);
        }

And this one the other one:

 public IActionResult Get(IntDto a)
        {
            return new JsonResult(a);
        }

Run it and let me know

这篇关于请求匹配多个端点,但为什么呢?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆