Required query string parameters in ASP.NET Core MVC

Today let’s have a look at two extensibility points in ASP.NET Core MVC – IActionConstraint and IParameterModelConvention. We’ll see how we can utilize them to solve a problem, that is not handled out of the box by the framework – creating an MVC action that has mandatory query string parameters.

Let’s have a look.

IActionConstraint extensibility point

ASP.NET Core MVC allows us to participate in the decision making regarding selecting an action suitable to handle the incoming HTTP request. We can do that through IActionConstraint extensibility point, which is a more powerful version of ActionMethodSelectorAttribute from “classic” ASP.NET MVC.

The interface is shown below.

So, when creating a custom IActionConstraint, you effectively just have to handle one method – Accept, which should return true when the action is suitable for handling the current request, or false, if it isn’t. Naturally, the ActionConstraintContext object would give you access to the current HttpContext.

The framework itself uses this mechanism to extend action selection based on HTTP verb or based on request media type.

Building your own IActionConstraint

Now, in our example, we’d like to constraint based on query string.

Imagine the following action:

In this case, the action takes 3 parameters – id, foo and bar. However, only id is mandatory – because it’s part of the route, the latter ones are optional. This means that all 4 of the following URLs would be valid and lead to our action:

  • GET api/values/5
  • GET api/values/5?foo=a
  • GET api/values/5?bar=b
  • GET api/values/5?foo=a&bar=b

Now, let’s restrict these URLs to only the last one – by making our query strings mandatory. MVC comes with [FromQuery] attribute, which restricts binding of the data to query string only, but it still treats them as optional if we use it, so the code shown below still wouldn’t work like we want; it would simply stop looking at other (non-query string) binding sources for our foo and bar parameters.

The solution is to implement our own attribute, which we will get to in a second.

But first let’s create an IActionConstraint. The constraint will be pretty simple – we will be creating a single instance of a constraint for each mandatory parameter, and if a matching parameter is not found on the current request (in the query string, naturally) then we will return false from the Accept method.

We chose order with a high value to make sure our constraint runs last, especially after the built in framework constraints (some of which have order of 200).

The final piece is to apply this constraint to specific parameters.

Stitching it together via IParameterModelConvention

We could subclass the existing FromQueryAttribute (the one we originally deemed unsuitable for us), since it will force the correct binding source for us, and make sure that the constraint is applied to the parameter if that parameter is decorated with our attribute. This is shown next:

We are able to achieve this via IParameterModelConvention – which gives us an option to add an extra constraint to each action by visiting all of the parameters of the discovered actions. There are also other ways of applying it – for example you could use IApplicationModelConvention too.

So now we could decorate our mandatory query string parameters with our new attribute, and voila!

Right now, only the following URL GET api/values/5?foo=a&bar=b would lead us into the action above – the other combinations of parameters would result in 404.