.Net Core Multiple Get Methods with the Action Method Selector Attribute

Full source code available here.

In .Net Core Web Api it is not possible for the routing mechanism to distinguish between the following action methods.

public string GetSomething(int id, int something)
and
public string GetAnother(int id, int another)

But with the help of an ActionMethodSelectorAttribute we can tell the methods apart.

Step 1
Add a new class called QueryStringConstraint and inherit from ActionMethodSelectorAttribute.

public class QueryStringConstraint : ActionMethodSelectorAttribute
{
	public override bool IsValidForRequest(RouteContext routeContext, ActionDescriptor action)
	{
		IList<ParameterDescriptor> methodParameters = action.Parameters;

		ICollection<string> queryStringKeys = routeContext.HttpContext.Request.Query.Keys.Select(q => q.ToLower()).ToList();
		IList<string> methodParamNames = methodParameters.Select(mp => mp.Name.ToLower()).ToList();

		foreach (var methodParameter in methodParameters)
		{
			if (methodParameter.ParameterType.Name.Contains("Nullable")) 
			{
				//check if the query string has a parameter that is not in the method params
				foreach(var q in queryStringKeys)
				{
					if (methodParamNames.All(mp => mp != q))
					{
						return false;
					}
				}

				if(queryStringKeys.All(q => q != methodParameter.Name.ToLower()))
				{
					continue;
				}
			}
			else if (queryStringKeys.All(q => q != methodParameter.Name.ToLower()))
			{
				return false;
			}
		}
		return true;
	}
}

Step 2
Add the QueryStringConstraint to the action methods that need to be distinguished by query string parameters.

[QueryStringConstraint] 
public string GetAnother(int id, int another)
{
	return $"GetAnother {id} {another}";
}

// http://localhost:62922/api/values/?id=1&something=22
[QueryStringConstraint] 
public string GetSomething(int id, int something)
{
	return $"GetSomething {id} {something}";
} 

Full source code available here.

.NET Core Web Api Routing

Full source code available here.

Routing in .NET Core Web Api (1.1 and 2) is a little different than in earlier versions.

I’ve written about this a few times, you can find those posts here.

In this post I’m going to show some examples of routing that might help you if you are having problems, especially if you want to have multiple GET methods. In a later article I will show how to use the action method selector to choose between actions methods with the same signature.

The code and comments are fairly self explanatory.

[HttpGet("api/Person")]
public class PersonController : Controller
{
	// GET: api/Person
	[HttpGet]
	public IEnumerable<string> Get()
	{
		return new string[] { "Dave Smith", "Edward Temple" };
	}

	// http://localhost:27624/api/person/1
	[HttpGet("{id}")]
	public string Get(int id)
	{
		return $"Get by id:{id} - Dave Smith";
	}

	// http://localhost:62689/api/person/ByNumber/5
	[HttpGet("ByNumber/{number}")]
	public string GetByNumber(int number)
	{
		return $"Get by number:{number} - Tom Davis";
	}
	// http://localhost:62689/api/person/ByFirstName/Steve
	[HttpGet("ByFirstName")]
	public string GetByFristName(string firstName)
	{
		return $"Get by first name - {firstName} Smith";
	}

	// http://localhost:62689/api/person/ByLastName?lastname=Smith
	[HttpGet("ByLastName")]
	public string GetByLastName(string lastName)
	{
		return $"Get by last name - Dave {lastName}";
	}

	// http://localhost:62689/api/person/ByFirstAndLastName?firstname=Steve&lastname=Smith
	[HttpGet("ByFirstAndLastName")]
	public string GetByFirstAndLastName(string firstName, string lastName)
	{
		return $"Get by first and last name - {firstName} {lastName}";
	}
}

Here are some more examples with attribute routing and a query model.

[HttpGet("api/accounts/{AccountId}")]
public class AccountsController : Controller
{
	// http://localhost:62689/api/accounts/11
	public IActionResult Get(int accountId)
	{
		return Ok(accountId);
	}

	// http://localhost:62689/api/accounts/?accountId=11
	// http://localhost:62689/api/accounts/?accountId=11&AccountName=dave
	// http://localhost:62689/api/accounts/?accountId=11&AccountName=dave&managerid=15
	[HttpGet("/api/accounts/")]
	public IActionResult Get(QueryModel queryModel)
	{
		return Ok(queryModel);
	}

	// http://localhost:62689/api/accounts/11/manager/22
	[HttpGet("Manager/{ManagerId}")]
	public IActionResult Get(int accountId, int managerId)
	{
		return Ok($"accountId:{accountId}, managerId:{managerId}");
	}

	// http://localhost:62689/api/accounts/11/manager/22/role/33
	[HttpGet("/api/accounts/{AccountId}/Manager/{ManagerId}/Role/{RoleId}")]
	public IActionResult Get(int accountId, int managerId, int roleId)
	{
		return Ok($"accountId:{accountId}, managerId:{managerId}, roleId:{roleId}");
	}
}

public class QueryModel
{
	public int AccountId { get; set; }
	public string AccountName { get; set; }
	public int ManagerId { get; set; }
}

Full source code available here.

Web API 2 Controller with multiple GET methods – part 2

I have two other posts on multiple GET methods, one for ASP.NET 5 Web Api, and another for Web Api 2.

Download full source code.

A few months ago I wrote a post explaining how to create a controller with multiple GET methods.
In that I used the combination of [RoutePrefix..] and [Route...] to generate routes like

  • http://…/api/values/geta
  • http://…/api/values/getb

Attribute parameter names and type

In this post I will show a different way of achieving multiple GETs by matching the type of parameter passed to the controller with the appropriate method.

In the first example I will match a method to an int being passed in, note the [Route("{id:int}")], this specifies that the expected parameter is named “id” and is an int.

// GET api/values/7
[Route("{id:int}")]
public string Get(int id)
{
    return $"You entered an int - {id}";
}

It is no problem to add another GET method with a [Route("{id:Guid}")]. This works fine because there is no way and int and Guid can be confused.

// GET api/values/AAC1FB7B-978B-4C39-A90D-271A031BFE5D
[Route("{id:Guid}")]
public string Get(Guid id)
{
    return $"You entered a GUID - {id}";
}

Where we start having problems is when the attribute routing system can’t determine which method you were attempting to call because it can’t distinguish an int from a long (or a string, decimal etc.)

For example, if we had another GET method taking a long, it would cause many runtime errors.

// GET api/values/8 - this will not work because it could be an int or a long
// GET api/values/4147483647 - this works because it can ONLY be a long
[Route("{id:long}")]
public string Get(long id)
{
    return $"You entered an long - {id}";
}

If we called http://...api/values/8, the attribute routing system has no way of knowing whether the GET method for the int or the long should be called.

But if we called http://...api/values/4147483647, that number is larger than an int can hold, the attribute routing system knows only one method matches the call.

If you want to have longs and ints in the same controller one of the routes needs to be altered. Note the new route – [Route("large/{id:long}")].

To call it now use http://...api/values/large/8 or http://...api/values/large/4147483647

[Route("large/{id:long}")]
public string Get(long id)
{
    return $"You entered an long - {id}";
}

Download full source code.

Web API 2 Controller with multiple get methods

I have two other posts on multiple GET methods, one for ASP.NET 5 Web Api, and another for Web Api 2.

It seems that every version of Web API changes how it handles default routes or route prefixes or whatever the next name will be.

I’ve written previously on how to handle multiple get methods in Asp.Net 5 Web API.

It’s a little different in Web Api 2.

using System.Web.Http;

namespace WebApiMultipleGets.Controllers
{
    [RoutePrefix("api/values")]
    public class ValuesController : ApiController
    {
        public string Get()
        {
            return "simple get";
        }

        [Route("geta")]
        public string GetA()
        {
            return "A";
        }

        [Route("getb")]
        public string GetB()
        {
            return "B";
        }

        [Route("getc")]
        public string GetC()
        {
            return "C";
        }

        [Route("getd")]
        public string GetD()
        {
            return "D";
        }
    }
}

ASP.NET 5 Web Api Controller with multiple get methods

I have two other posts on multiple GET methods, both for Web Api 2, the first shows how to use routes like ‘http://…/api/values/geta’ and the second shows ‘http://…/api/values/22’ or ‘http://…/api/values/AAC1FB7B-978B-4C39-A90D-271A031BFE5D’.

I was recently working on a Asp.Net 5 Web.Api application and needed a controller with multiple get methods.
I wanted to have something like GetByAdminId(int adminId) and GetByMemberId(int memberId) (yes I know people will say that you should have two controllers and maybe even two webservices, but that is the scenario I was faced with).

Of course this should not be the most difficult problem, but it was not obvious either.

Here is the solution.

using Microsoft.AspNet.Mvc;

namespace ControllerWithMultipleGetMethods.Controllers
{
    [Route("api/[controller]")] /* this is the default prefix for all routes, see line 20 for overriding it */ 
    public class ValuesController : Controller
    {
        [HttpGet] // this api/Values
        public string Get()
        {
            return string.Format("Get: simple get");
        }

        [Route("GetByAdminId")] /* this route becomes api/[controller]/GetByAdminId */
        public string GetByAdminId([FromQuery] int adminId)
        {
            return $"GetByAdminId: You passed in {adminId}";
        }

        [Route("/someotherapi/[controller]/GetByMemberId")] /* note the / at the start, you need this to override the route at the controller level */
        public string GetByMemberId([FromQuery] int memberId)
        {
            return $"GetByMemberId: You passed in {memberId}";
        }

        [HttpGet]
        [Route("IsFirstNumberBigger")] /* this route becomes api/[controller]/IsFirstNumberBigger */
        public string IsFirstNumberBigger([FromQuery] int firstNum, int secondNum)
        {
            if (firstNum > secondNum)
            {
                return $"{firstNum} is bigger than {secondNum}";
            }
            return $"{firstNum} is NOT bigger than {secondNum}";
        }
    }
}