1

I tried to convert ASP.NET WEB API to ASP.NET CORE WEB API and have errors

My code in ASP.NET WebAPI

public class TestController : ApiController
{
    // GET /test
    public object Get()
    {
        return "get";
    }

    // GET /test?id={id}
    public object Get(string id)
    {
        return id;
    }

    // GET /test?id={id}&anyParam={anyParam}
    public object Get(string id, string anyParam)
    {
        return id + anyParam;
    }
}

config.Routes.MapHttpRoute("Controller", "{controller}");

Try to convert it to ASP.NET Core 2.1 / 3.0

[ApiController]
[Route("{controller}")]
public class TestController : ControllerBase
{
    // GET /test
    public object Get()
    {
        return "get";
    }

    // GET /test?id={id}
    public object Get(string id)
    {
        return id;
    }

    // GET /test?id={id}&anyParam={anyParam}
    public object Get(string id, string anyParam)
    {
        return id + anyParam;
    }
}

services.AddControllers(); 
app.UseRouting();
app.UseEndpoints(endpoints => { endpoints.MapControllers(); });

And i have in ASP.NET Core

AmbiguousMatchException: The request matched multiple endpoints

TheBottle
  • 23
  • 7
  • Look at this `endpoints.MapControllerRoute` in https://learn.microsoft.com/cs-cz/aspnet/core/migration/22-to-30?view=aspnetcore-3.0&tabs=visual-studio – daremachine Nov 06 '19 at 23:25
  • @daremachine tried to `endpoints.MapControllerRoute("Controller", "{controller}");` same error **AmbiguousMatchException: The request matched multiple endpoints** – TheBottle Nov 06 '19 at 23:36
  • I wish they'd made that throw an exception in Web API, as multiple endpoints with the same names but different arguments aren't legal in Swagger definitions. The best way to fix this is to give each action a different name or move each to a new controller. – Stephen Kennedy Nov 06 '19 at 23:40
  • Shouldn't it be `[controller]` instead of `{controller}`? Just my quick impression. – Kit Nov 07 '19 at 01:29
  • @Kit As I found out, it doesn't matter – TheBottle Nov 07 '19 at 01:38
  • @Kit: You use square brackets when defining a route via an attribute, and you use curly brackets when adding a route table entry. I don't know why it is inconsistent like that. – Andrew Shepherd Nov 07 '19 at 03:10

4 Answers4

1

I look your generated urls on actions and they are both /test which cause AmbiguousMatchException because your parameters are GET and are optional.

I think you can have same names on actions but you need define different ROUTE attribute (diff urls) on actions.

Eg. you can not use default route with polymorphism on controller actions.

[Route("Home/About")]

MVC controllers Mapping of controllers now takes place inside UseEndpoints.

Add MapControllers if the app uses attribute routing.

Source

https://learn.microsoft.com/cs-cz/aspnet/core/mvc/controllers/routing?view=aspnetcore-3.0#attribute-routing

daremachine
  • 2,678
  • 2
  • 23
  • 34
  • Thank you, I will wait for the answer of others, suddenly my question has a solution to use polymorphism – TheBottle Nov 07 '19 at 00:29
  • Ok but your solution is have unique action name or set different route even route attribute. You need distinguish action endpoint with identical action name. – daremachine Nov 07 '19 at 01:23
1

Thanks to daremachine with his answer I was able to find information on Google

First step in ASP.NET Core we need class which inherit ActionMethodSelectorAttribute

public class RequireRequestValueAttribute : ActionMethodSelectorAttribute
{
    public RequireRequestValueAttribute(string name, string value = null)
    {
        Name = name;
        Value = value;
    }

    public string Name { get; }
    public string Value { get; }

    public StringComparison ComparisonType { get; } = StringComparison.OrdinalIgnoreCase;

    private bool ValueIsValid(object value)
    {
        return ValueIsValid(value?.ToString());
    }
    private bool ValueIsValid(string value)
    {
        if (Value == null)
        {
            return true;
        }

        return string.Equals(value, Value, ComparisonType);
    }

    public override bool IsValidForRequest(RouteContext routeContext, ActionDescriptor action)
    {
        var value = default(object);

        if (routeContext.RouteData.Values.TryGetValue(Name, out value) && ValueIsValid(value))
            return true;

        if (routeContext.RouteData.DataTokens.TryGetValue(Name, out value) && ValueIsValid(value))
            return true;

        if (routeContext.HttpContext.Request.Query.ContainsKey(Name))
        {
            var values = routeContext.HttpContext.Request.Query[Name];
            if (values.Count <= 0)
            {
                if (ValueIsValid(null))
                    return true;
            }
            else if (values.Any(v => ValueIsValid(v)))
                return true;
        }

        return false;
    }
}

Then we can add to question methods [RequireRequestValue("")], the controller will look like this

[ApiController]
[Route("{controller}")]
public class TestController : ControllerBase
{
    // GET /test
    public object Get()
    {
        return "get";
    }

    // GET /test?id={id}
    [RequireRequestValue("id")]
    public object Get(string id)
    {
        return id;
    }
}

But it can't polymorphism two similar fields, type id in my question

TheBottle
  • 23
  • 7
1

The sensible solution is just have one method that takes three parameters.

But, sensible solutions don't make for the most interesting stackoverflow answers, so here is how you can do this with two custom attributes, one which states the parameters that are required, and another which states which parameters are excluded:

public class RequireRequestParameterAttribute : ActionMethodSelectorAttribute
{
    private readonly string[] _requiredNames;
    public RequireRequestParameterAttribute(params string[] names)
    {
        this._requiredNames = names;
    }
    public override bool IsValidForRequest(
        RouteContext routeContext,
        ActionDescriptor action
    ) =>
        this._requiredNames
            .All(
                routeContext
                   .HttpContext
                   .Request
                   .Query
                   .ContainsKey
            );
}

public class DisallowRequestParameterAttribute : ActionMethodSelectorAttribute
{
    private readonly string[] _forbiddenNames;
    public DisallowRequestParameterAttribute(params string[] names)
    {
        this._forbiddenNames = names;
    }
    public override bool IsValidForRequest(
        RouteContext routeContext,
        ActionDescriptor action
    ) =>
        !(this._forbiddenNames
             .Any(
                 routeContext
                     .HttpContext
                     .Request
                     .Query
                     .ContainsKey
              )
        );
}

Now you can apply the attributes as follows:

[ApiController]
[Route("[controller]")]
public class TestController : ControllerBase
{
    // GET test
    public object Get()
    {
        return "Get";
    }


    // GET test?id={id}
    [RequireRequestParameter("id")]
    [DisallowRequestParameter("anyParam")]
    public object Get(string id)
    {
        return id;
    }

    // GET test?id={id}&anyParam={anyParam}
    [RequireRequestParameter("id", "anyParam")]
    public object Get(string id, string anyParam)
    {
        return $"{id}: {anyParam}";
    }
}

This means if you add another method with a third parameter, you have the maintenance burden of adding or modifying the DisallowRequestParameter attribute on the other methods.

Andrew Shepherd
  • 44,254
  • 30
  • 139
  • 205
  • Thank you, found a similar solution, but you have it works and compact. I do not use 3 parameters in only one method, for it will be ugly on the part of the query. I hope this helps someone else – TheBottle Nov 07 '19 at 01:36
0

For asp net core 2. If you try to implement the same logic as was in web api controllers then use Microsoft.AspNetCore.Mvc.WebApiCompatShim. This nuget package provides compatibility in ASP.NET Core MVC with ASP.NET Web API 2 to simplify migration of existing Web API implementations. Please check this answer. Starting with ASP.NET Core 3.0, the Microsoft.AspNetCore.Mvc.WebApiCompatShim package is no longer available.

alanextar
  • 1,094
  • 13
  • 16