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

Fluent Validation in ASP.NET Core

Full source code available here.

I have written about Fluent Validation a couple of times. There is a new library available for .Net Core.

How to return to validation messages back to the caller is not immediately obvious. You might see the validators running, but the responses are missing! Here is how to solve that problem.

Step 1

Firstly add the package to your Web Api project.
Install-Package FluentValidation.AspNetCore.
If you are going to keep your validators in the Web Api project that is all you need to add.

But if you put the validators in a different project you need to add the FluentValidation package to that projecty
Install-Package FluentValidation.

Step 2

In startup.cs add –

public void ConfigureServices(IServiceCollection services)
{
    services.AddMvc(options =>
        {
            options.Filters.Add(typeof(ValidateModelAttribute));
        })
        .AddFluentValidation(fv => fv.RegisterValidatorsFromAssemblyContaining<PersonModelValidator>());
}

Note the line options.Filters.Add(typeof(ValidateModelAttribute)), we need to add that filter.

Step 3

Add the validation filter –

public class ValidateModelAttribute : ActionFilterAttribute
{	
    public override void OnActionExecuting(ActionExecutingContext context)	
    {	
        if (!context.ModelState.IsValid)	
        {	
            context.Result = new BadRequestObjectResult(context.ModelState);
        }
    }
}
Step 4

Add a model and a validator.

public class PersonModel
{
    public string FirstName { get; set; }
    public string LastName { get; set; }
}

public class PersonModelValidator : AbstractValidator<PersonModel>
{
    public PersonModelValidator()
    {
        RuleFor(p => p.FirstName).NotEmpty();
        RuleFor(p => p.LastName).Length(5);
    }
}

Full source code available here.