Many developers are familiar with your most common HTTP methods of GET and POST. Many of us do not realize that there is a world of other HTTP methods that can we can utilize for additional semantic meaning for our web requests. HTTP methods include GET, PUT, POST, DELETE, HEAD, OPTIONS, TRACE, PATCH, and CONNECT. We can use HTTP methods to include additional endpoints in our applications without bloating the URI interface we provide our users.

In this post, we’ll see how we can leverage the spectrum of HTTP methods in our ASP.NET Core MVC applications. We’ll be enhancing our HTML forms to support all possible HTTP methods.

HttpMethodOverrideMiddleware

All HTML forms (<form>) have an action attribute, with only two valid values: GET and POST. The restricted action values limit the possible ways our front end can communicate with our back end. Luckily for us, ASP.NET Core ships with HttpMethodOverrideMiddleware, which allows our application to route a GET or a POST to an ASP.NET Core endpoint that has a different HTTP method.

For example, let’s take an endpoint with an HttpDelete attribute.

[HttpDelete, Route("{id:int}")]
public async Task<IActionResult> Delete(int id)
{
    var script = await db.Scripts.FindAsync(id);
    
    if (script != null)
    {
        db.Scripts.Remove(script);
        await db.SaveChangesAsync();
    }

    return RedirectToPage("/Index");
}

Technically, an HTML form could never call this endpoint, since the DELETE method is not supported. To get it working, we’ll need to register the HttpMethodOverrideMiddleware int our Startup class.

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
    if (env.IsDevelopment())
    {
        app.UseDeveloperExceptionPage();
    }
    else
    {
        app.UseExceptionHandler("/Error");
        app.UseHsts();
    }

    // allow the frontend to call with more HTTP methods
    app.UseHttpMethodOverride(new HttpMethodOverrideOptions
    {
        FormFieldName = 
            HtmlHelperExtensions.HttpMethodOverrideFormName
    });
    
    app.UseHttpsRedirection();
    app.UseStaticFiles();

    app.UseRouting();

    app.UseAuthorization();

    app.UseEndpoints(endpoints =>
    {
        endpoints.MapControllers();
        endpoints.MapRazorPages();
    });
}

Note, that the middleware requires an HttpMethodOverrideOptions instance that sets the FormFieldName. We’ll use the name later in our extension method. First, let’s look at the implementation HttpMethodOverrideMiddleware.

// HttpMethodOverrideMiddleware
public async Task Invoke(HttpContext context)
{
    if (string.Equals(context.Request.Method, "POST", StringComparison.OrdinalIgnoreCase))
    {
        if (_options.FormFieldName != null)
        {
            if (context.Request.HasFormContentType)
            {
                var form = await context.Request.ReadFormAsync();
                var methodType = form[_options.FormFieldName];
                if (!string.IsNullOrEmpty(methodType))
                {
                    context.Request.Method = methodType;
                }
            }
        }
        else
        {
            var xHttpMethodOverrideValue = context.Request.Headers[xHttpMethodOverride];
            if (!string.IsNullOrEmpty(xHttpMethodOverrideValue))
            {
                context.Request.Method = xHttpMethodOverrideValue;
            }
        }
    }
    await _next(context);
}

We notice that the middleware will only use our form name when the request meets two criteria:

  1. The HTTP request has an original HTTP Method of POST.
  2. That we set the form name in the registration of the middleware.
  3. The HTTP request has a form content type, which most POST requests do.

If our request does not meet those criteria, then the middleware will fallback to looking for a request header of X-Http-Method-Override.

HtmlHelper Extension For Overrides

To take advantage of the middleware, we need to create an HTML helper extension.

using Microsoft.AspNetCore.Mvc.Rendering;
using Microsoft.AspNetCore.Html;
public static class HtmlHelperExtensions
{
    public const string HttpMethodOverrideFormName = "X-HTTP-Method-Override";
    
    public static IHtmlContent HttpMethodOverride(
        this IHtmlHelper helper,
        System.Net.Http.HttpMethod method, 
        string name = HttpMethodOverrideFormName)
    {
        return helper.Raw($"<input type=\"hidden\" name=\"{name}\" value=\"{method}\" />");
    }
}

Our Razor views can take advantage of this extension method.

<form method="post" 
    action="@Url.Action("Delete", "Scripts", new {@script.Id})">
    @Html.HttpMethodOverride(HttpMethod.Delete)
    <button type="submit">Delete</button>
</form>

Now, whenever the UI submits the HTML form, we will hit our ASP.NET Core controller action from previously. Our extension method creates a hidden input with the HTTP method we want ASP.NET Core to handle when routing our request. We use the System.Net.HttpMethods class to restrict our parameter choices.

Conclusion

HTML semantics are essential, and HTTP methods allow us to express intent without jamming more information into our requests. With the HttpMethodOverrideMiddleware we can use all the HTTP methods available to us with ease.

I hope this post is helpful, and please leave a comment.