web-dev-qa-db-fra.com

Comment résoudre le problème auquel la demande correspondait à plusieurs points de terminaison dans .Net Core Web Api

Je remarque qu'il y a un tas de questions similaires sur ce sujet.

J'obtiens cette erreur lorsque j'appelle l'une des méthodes ci-dessous.

Microsoft.AspNetCore.Routing.Matching.AmbiguousMatchException: la demande correspond à plusieurs points de terminaison.

Je ne peux cependant pas déterminer quelle est la meilleure pratique pour résoudre le problème. Jusqu'à présent, je n'ai pas configuré de middleware de routage spécifique.

// api/menus/{menuId}/menuitems
[HttpGet("{menuId}/menuitems")]
public IActionResult GetAllMenuItemsByMenuId(int menuId)
{            
    ....
}

// api/menus/{menuId}/menuitems?userId={userId}
[HttpGet("{menuId}/menuitems")]
public IActionResult GetMenuItemsByMenuAndUser(int menuId, int userId)
{
    ...
}
5
Magnus Wallström

Vous avez le même itinéraire dans votre attribut HttpGet

Changez pour quelque chose comme ceci:

    // api/menus/{menuId}/menuitems
    [HttpGet("{menuId}/getAllMenusItems")]
    public IActionResult GetAllMenuItemsByMenuId(int menuId)
    {            
        ....
    }

    // api/menus/{menuId}/menuitems?userId={userId}
    [HttpGet("{menuId}/getMenuItemsFiltered")]
    public IActionResult GetMenuItemsByMenuAndUser(int menuId, int userId)
    {
        ...
    }
1
Laphaze

Voici une autre solution que vous pouvez utiliser pour ce type de scénario:

Solution 1 et plus complexe, utilisant IActionConstrain et ModelBinders (cela vous donne la flexibilité de lier votre entrée à un DTO spécifique):

Le problème que vous avez est que votre contrôleur a le même routage pour 2 méthodes différentes recevant des paramètres différents. Permettez-moi de l'illustrer avec un exemple similaire, vous pouvez avoir les 2 méthodes comme ceci:

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

Jusqu'à présent, cela est valide, au moins C # ne vous donne pas d'erreur car il s'agit d'une surcharge de paramètres. Mais avec le contrôleur, vous avez un problème, lorsque aspnet reçoit le paramètre supplémentaire, il ne sait pas où rediriger votre demande. Vous pouvez changer le routage qui est une solution.

Normalement, je préfère garder les mêmes noms et encapsuler les paramètres sur un DtoClass, IntDto et StringDto par exemple

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

mais vous avez quand même l'erreur. Afin de lier votre entrée au type spécifique de vos méthodes, je crée un ModelBinder, pour ce scénario, il est ci-dessous (voir que j'essaie d'analyser le paramètre de la chaîne de requête mais j'utilise un en-tête discriminateur qui est utilisé normalement pour la négociation de contenu entre le client et le serveur ( négociation de conten ):

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

Ensuite, vous devez créer un ModelBinderProvider (voir que si je reçois en essayant de lier l'un de ces types, alors j'utilise MyModelBinder)

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

            return null;
        }

et l'enregistrer dans le conteneur

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

Jusqu'à présent, vous n'avez pas résolu le problème, mais nous sommes proches. Afin de frapper les actions du contrôleur maintenant, vous devez passer un type d'en-tête sur la demande: application/json ou application/myContentType.json . Mais afin de prendre en charge la logique conditionnelle pour déterminer si une méthode d'action associée est valide ou non à sélectionner pour une demande donnée, vous pouvez créer votre propre ActionConstraint. Fondamentalement, l'idée ici est de décorer votre ActionMethod avec cet attribut pour restreindre l'utilisateur à frapper cette action s'il ne passe pas le type de média correct. Voir ci-dessous le code et comment l'utiliser

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

Voici votre dernier changement:

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

Maintenant, l'erreur a disparu si vous exécutez votre application. Mais comment passez-vous les paramètres?: Celui-ci va frapper cette méthode:

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

application/myContentType.json

Et celui-ci l'autre:

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

application/json

Solution 2: contraintes de routes

[ApiController]
[Route("[controller]")]
public class WeatherForecastController : ControllerBase
{
    [HttpGet("{i:int}")]
    public IActionResult Get(int i)
    {
        return new JsonResult(i);
    }

    [HttpGet("{i}")]
    public IActionResult Get(string i)
    {
        return new JsonResult(i);
    }
}

Ceci est une sorte de test car j'utilise le routage par défaut:

https://localhost:44374/weatherforecast/"test"  should go to the one that receives the string parameter

https://localhost:44374/weatherforecast/1 devrait aller à celui qui reçoit un paramètre int

0
Zinov