Taking Your API to the Next Level: Leveraging OPTIONS and HEAD Requests in ASP.NET Web API

While most developers are familiar with the basic HTTP verbs such as GET, POST, PUT, PATCH, and DELETE, there are two less popular HTTP verbs that are also important in RESTful APIs: OPTIONS and HEAD requests. These requests are used to retrieve metadata about resources and to determine the communication options available for a target resource. In this blog post, we will explore how to work with OPTIONS and HEAD requests in ASP.NET Web API.

OPTIONS Request

An OPTIONS request is an HTTP method that is used to retrieve information about the communication options available for a target resource. This request is typically used to determine whether a server supports a particular HTTP method or to retrieve a list of available methods for a resource (e.g., POST, PUT, or DELETE). The response to an OPTIONS request includes a list of supported methods as a comma separated list, allowed headers, and other metadata.

To handle an OPTIONS request in ASP.NET Web API, you can create a method in your controller that returns an HttpResponseMessage object. The following code shows an example of an OPTIONS request handler:

[AllowAnonymous]
[AcceptVerbs("OPTIONS")]
public IActionResult Options()
{
	Response.Headers.Add("Access-Control-Allow-Origin", "*");
	Response.Headers.Add("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS");
	Response.Headers.Add("Access-Control-Allow-Headers", "Content-Type, Authorization");

	return Ok();
}

In this example, the AllowAnonymous attribute is used to indicate that the method can be accessed without authentication. The AcceptVerbs attribute is used to specify that the method should handle OPTIONS requests. Alternatively, you can decorate the action method with the [HttpOptions] attribute and that will allow this action method to respond to OPTIONS requests, as well. The method adds the appropriate headers to the response to enable cross-origin requests (CORS). The example above allows all origins to make API calls against this API. In real usage, you should restrict this to just your approved list of consumers.

Test this call in your favorite API testing tool. You should get a response similar to what’s shown below:

An API testing tool showing the HTTP response to the OPTIONS call spec'd out in the previous code block.

Since the HTTP response body is empty, for compliance, the API must return a content-length header with a value of zero along with the response. You’ll notice that asp.net has automatically added this in for you without you having to specify it manually.

HEAD Request

A HEAD request is similar to a GET request, but it only retrieves the headers of a response, not the body. This request is typically used to retrieve metadata about a resource, such as its size or last modification date, or if you have implemented pagination information in the headers – then, those, all without actually retrieving the resource itself. The response to a HEAD request includes the same headers that would be returned in a GET request, but without the body content.

Take a look at the action method shown below:

[HttpHead]
[HttpGet(Name = "GetWeatherForecast")]
public IEnumerable<WeatherForecast> Get()
{
	return Enumerable.Range(1, 5).Select(index => new WeatherForecast
	{
		Date = DateOnly.FromDateTime(DateTime.Now.AddDays(index)),
		TemperatureC = Random.Shared.Next(-20, 55),
		Summary = Summaries[Random.Shared.Next(Summaries.Length)]
	})
	.ToArray();
}

You’ll recognize this as the default GET method that comes with the built-in dotnet new webapi template, except for one minor tweak: you’ll see that I’ve decorated the method with an [HttpHead] attribute, in addition to the HttpGet one. Yes, you don’t have to duplicate your GET methods to handle HEAD requests but all you need to do is simply add it to your existing GET requests that you also want to have respond to HEAD requests.

Let’s look at the response for a call of this action method:

Sample response of an HTTP HEAD call

But wait, where’s the content-length header? Why is it not set to 0 as was the case in the OPTIONS request described previously?

Well, the HTTP specifications doesn’t require it for HEAD requests: RFC 9110: HTTP Semantics (rfc-editor.org).

Previously, the spec intimated that we must specify the actual length of the GET response but that didn’t make sense as this would:

  • Force the server to execute the actual GET request just to get the content length and then throw away the actual response. HEAD is usually requested for efficiency purposes and doing the actual work would negate such gains.
  • The response may be dynamic and may vary between different invocations of the API. In such cases, the server cannot pin down the actual content-length of a call that hasn’t happened yet.

So, in summary – you must specify a content-length of zero on OPTIONS requests and you may omit this on HEAD requests.

Conclusion

Browsers and API consuming applications use OPTIONS and HEAD requests as security measures and/or for use-cases associated with performance enhancements. Supporting these lesser used verbs will increase the ergonomics of your API, taking it to the next level.

Leave a Comment

Your email address will not be published. Required fields are marked *