
In the Wolverine 3.13 release yesterday, the Wolverine.HTTP library got some new, long requested, probably overdue capabilities for working with HTTP Form posts and support for ASP.Net Core Minimal API’s [AsParameters] feature.
First, let’s look at Wolverine’s new support for working with HTTP Form posts using the ASP.Net Core [FromForm] attribute as a marker. First, if you want to inject a single Form data element from the request into a Wolverine HTTP endpoint method, you can do just this:
[WolverinePost("/form/string")]
public static string UsingForm([FromForm]string name) // name is from form data
{
return name.IsEmpty() ? "Name is missing" : $"Name is {name}";
}
I should point out here that Wolverine is capable of dealing with any common .NET types like numbers, date types, enumeration values, booleans, and Guid values for the Form data and not just strings.
Next, let’s have the entire contents of a form post from the client bound to a single input object like this:
[WolverinePost("/api/fromformbigquery")]
public static BigQuery Post([FromForm] BigQuery query) => query;
Where BigQuery is this type:
public class BigQuery
{
public string Name { get; set; }
public int Number { get; set; }
public Direction Direction { get; set; }
public string[] Values { get; set; }
public int[] Numbers { get; set; }
public bool Flag { get; set; }
public int? NullableNumber { get; set; }
public Direction? NullableDirection { get; set; }
public bool? NullableFlag { get; set; }
[FromQuery(Name = "aliased")]
[FromForm(Name = "aliased")]
public string? ValueWithAlias { get; set; }
}
In the code above, every publicly “writeable” property of BigQuery will be bound to a form data element in the HTTP request if one exists and can be parsed to the value type of that property. And if you’re curious about how this works, Wolverine is generating C# code behind the scenes to do all the ugly type coercion and property setting. There’s no reflection happening at runtime.
Now, switching to the larger [AsParameters] support, Wolverine does that now too as shown below:
public static class AsParametersEndpoints{
[WolverinePost("/api/asparameters1")]
public static AsParametersQuery Post([AsParameters] AsParametersQuery query)
{
return query;
}
}
public class AsParametersQuery{
[FromQuery]
public Direction EnumFromQuery{ get; set; }
[FromForm]
public Direction EnumFromForm{ get; set; }
public Direction EnumNotUsed{get;set;}
[FromQuery]
public string StringFromQuery { get; set; }
[FromForm]
public string StringFromForm { get; set; }
public string StringNotUsed { get; set; }
[FromQuery]
public int IntegerFromQuery { get; set; }
[FromForm]
public int IntegerFromForm { get; set; }
public int IntegerNotUsed { get; set; }
[FromQuery]
public float FloatFromQuery { get; set; }
[FromForm]
public float FloatFromForm { get; set; }
public float FloatNotUsed { get; set; }
[FromQuery]
public bool BooleanFromQuery { get; set; }
[FromForm]
public bool BooleanFromForm { get; set; }
public bool BooleanNotUsed { get; set; }
[FromHeader(Name = "x-string")]
public string StringHeader { get; set; }
[FromHeader(Name = "x-number")] public int NumberHeader { get; set; } = 5;
[FromHeader(Name = "x-nullable-number")]
public int? NullableHeader { get; set; }
}
Wolverine.HTTP also supports a mix of [FromBody], [FromServices], and [FromRoute] support as well, but we think the [FromServices] support is going to have some limitations, and Wolverine.HTTP already supports “method injection” of IoC services being passed into endpoint methods as parameters anyway.
In a way, this is coming full circle from Wolverine’s antecedent project FubuMVC where we had a (grossly inefficient) model binding capability that could bind HTTP form data, query string values, route data, and header data to one input argument in FubuMVC’s “one model in, one model out” philosophy. Fast forward to now, and I think Wolverine’s [AsParameters] support is more usable, if higher code ceremony, just because it’s more clear where the data elements are actually coming from.
Lastly, Wolverine is able to glean OpenAPI metadata from the attribute usage on the input types.