Запрос соответствует нескольким конечным точкам, но почему? - PullRequest
0 голосов
/ 17 марта 2020

У меня есть контроллер с несколькими маршрутами.

Я пытаюсь вызвать конечную точку, указанную как

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)

почему вход совпадает с этими конечными точками?

Ответы [ 2 ]

1 голос
/ 17 марта 2020

Проблема в том, что ваш контроллер имеет одинаковую маршрутизацию для 2 разных методов, получающих разные параметры. Позвольте мне проиллюстрировать это на похожем примере, у вас может быть 2 метода, подобных этому:

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

Пока это верно, по крайней мере, C# не дает вам ошибки, потому что это перегрузка параметры. Но с контроллером у вас есть проблема, когда pnet получает дополнительный параметр, он не знает, куда перенаправить ваш запрос. Вы можете изменить маршрут, который является одним из решений.

Это решение дает вам также возможность сопоставить ваш ввод со сложным типом, в противном случае используйте Ограничение маршрута для простых типов

Обычно я предпочитаю сохранять одинаковые имена и переносить параметры в 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);
    }
}

, но, тем не менее, у вас есть ошибка. Чтобы связать ваш ввод с указанным типом c в ваших методах, я создаю ModelBinder, для этого сценария он ниже (см., Что я пытаюсь проанализировать параметр из строки запроса, но я использую заголовок дискриминатора который обычно используется для согласования содержимого между клиентом и сервером ( согласование содержимого ):

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. Но для того, чтобы Поддерживая условную логику c, чтобы определить, является ли связанный метод действия действительным или нет для выбора для данного запроса, вы можете создать свой собственный ActionConstraint. Basica Идея здесь заключается в том, чтобы украсить ваш 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);
        }

application/myContentType.json

А этот другой:

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

application/json

Запустите его и дайте мне знать

0 голосов
/ 17 марта 2020

Вы можете исправить это, используя ограничения маршрута.

Взгляните на https://docs.microsoft.com/en-us/aspnet/web-api/overview/web-api-routing-and-actions/attribute-routing-in-web-api-2

Вот их пример:

[Route("users/{id:int}")]
public User GetUserById(int id) { ... }

[Route("users/{name}")]
public User GetUserByName(string name) { ... }
...