web-dev-qa-db-ja.com

リクエストが.Net Core Web APIの複数のエンドポイントと一致した問題を解決するにはどうすればよいですか?

このトピックについては、似たような質問がたくさんあることに気づきました。

以下のメソッドを呼び出すと、このエラーが発生します。

Microsoft.AspNetCore.Routing.Matching.AmbiguousMatchException:リクエストは複数のエンドポイントに一致しました。

ただし、問題を解決するためのベストプラクティスを整理することはできません。これまでのところ、特定のルーティングミドルウェアをセットアップしていません。

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

HttpGet属性に同じルートがあります

このようなものに変更してください:

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

これは、この種のシナリオに使用できる別のソリューションです。

IActionConstrainとModelBindersを使用したソリューション1以上の複雑な(これにより、入力を特定のDTOにバインドする柔軟性が得られます):

あなたが持っている問題は、コントローラーが異なるパラメーターを受け取る2つの異なるメソッドに対して同じルーティングを持っているということです。同様の例でそれを説明しましょう、あなたはこのような2つの方法を持つことができます:

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

これまでのところ、これは有効です。少なくともC#では、パラメータの過負荷であるため、エラーは発生しません。しかし、コントローラーでは問題があります。aspnetが追加のパラメーターを受け取ったときに、リクエストをリダイレクトする場所がわかりません。 1つの解決策であるルーティングを変更できます。

通常、私は同じ名前を保持し、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を作成します。このシナリオでは、以下のようになります(クエリ文字列からパラメーターを解析しようとしているが、使用される弁別ヘッダーを使用していることを確認してください)通常、クライアントとサーバー間のコンテンツネゴシエーション( コンテンツネゴシエーション ):

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

application/myContentType.json

そして、これは他のものです:

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

application/json

ソリューション2:ルート制約

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

デフォルトのルーティングを使用しているため、これは一種のテストです。

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

https://localhost:44374/weatherforecast/1は、intパラメータを受け取るものに移動する必要があります

0
Zinov