Using AsParameters

::: warning When you use [AsParameters], you can read HTTP form data or deserialize a request body as JSON, but not both at the same time and Wolverine will happily throw an exception telling you so if you try to do this. :::

::: tip Use Wolverine’s pre-generated code to understand exactly how Wolverine is processing any model object decorated with [AsParameters] :::

Wolverine supports the ASP.Net Core AsParameters attribute usage for complex binding of a mixed bag of HTTP information including headers, form data elements, route arguments, the request body, IoC services to a single input model using the ASP.Net Core [AsParameters] attribute as a marker.

See the Microsoft documentation on AsParameters for more background.

Below is a sample from our test suite showing what is possible for query string and header values:

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; } = null!;
    [FromForm]
    public string StringFromForm { get; set; } = null!;
    public string StringNotUsed { get; set; } = null!;
    [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; } = null!;
 
    [FromHeader(Name = "x-number")] public int NumberHeader { get; set; } = 5;
    
    [FromHeader(Name = "x-nullable-number")]
    public int? NullableHeader { get; set; }
}

snippet source | anchor

And the corresponding test case for utilizing this:

var result = await Host.Scenario(x => x
    .Post
    .FormData(new Dictionary<string, string>
    {
        { "EnumFromForm", "east" },
        { "StringFromForm", "string2" },
        { "IntegerFromForm", "2" },
        { "FloatFromForm", "2.2" },
        { "BooleanFromForm", "true" },
        { "StringNotUsed", "string3" }
    }).QueryString("EnumFromQuery", "west")
    .QueryString("StringFromQuery", "string1")
    .QueryString("IntegerFromQuery", "1")
    .QueryString("FloatFromQuery", "1.1")
    .QueryString("BooleanFromQuery", "true")
    .QueryString("IntegerNotUsed", "3")
    .ToUrl("/api/asparameters1")
);
var response = await result.ReadAsJsonAsync<AsParametersQuery>();
response.EnumFromForm.ShouldBe(Direction.East);
response.StringFromForm.ShouldBe("string2");
response.IntegerFromForm.ShouldBe(2);
response.FloatFromForm.ShouldBe(2.2f);
response.BooleanFromForm.ShouldBeTrue();
response.EnumFromQuery.ShouldBe(Direction.West);
response.StringFromQuery.ShouldBe("string1");
response.IntegerFromQuery.ShouldBe(1);
response.FloatFromQuery.ShouldBe(1.1f);
response.BooleanFromQuery.ShouldBeTrue();
response.EnumNotUsed.ShouldBe(default);
response.StringNotUsed.ShouldBe(default);
response.IntegerNotUsed.ShouldBe(default);
response.FloatNotUsed.ShouldBe(default);
response.BooleanNotUsed.ShouldBe(default);

snippet source | anchor

Wolverine.HTTP is also able to support [FromServices], [FromBody], and [FromRoute] bindings as well as shown in this sample from the tests:

public class AsParameterBody
{
    public string Name { get; set; } = null!;
    public Direction Direction { get; set; }
    public int Distance { get; set; }
}
 
public class AsParametersQuery2
{
    // We do a check inside of an HTTP endpoint that this works correctly
    [FromServices, JsonIgnore]
    public IDocumentStore Store { get; set; } = null!;
 
    [FromBody]
    public AsParameterBody Body { get; set; } = null!;
 
    [FromRoute]
    public string Id { get; set; } = null!;
    
    [FromRoute]
    public int Number { get; set; }
}
 
public static class AsParametersEndpoints2{
    [WolverinePost("/asp2/{id}/{number}")]
    public static AsParametersQuery2 Post([AsParameters] AsParametersQuery2 query)
    {
        // Just proving the service binding works
        query.Store.ShouldBeOfType<DocumentStore>();
        return query;
    }
}

snippet source | anchor

And lastly, you can use C# records or really just any constructor function as well and decorate parameters like so:

public record AsParameterRecord(
    [FromRoute] string Id,
    [FromQuery] int Number,
    [FromHeader(Name = "x-direction")] Direction Direction,
    [FromForm(Name = "test")] bool IsTrue);
 
public static class AsParameterRecordEndpoint
{
    [WolverinePost("/asparameterrecord/{Id}")]
    public static AsParameterRecord Post([AsParameters] AsParameterRecord input) => input;
}

snippet source | anchor

The Fluent Validation middleware for Wolverine.HTTP is able to validate against request types bound with [AsParameters]:

public static class ValidatedAsParametersEndpoint
{
    [WolverineGet("/asparameters/validated")]
    public static string Get([AsParameters] ValidatedQuery query)
    {
        return $"{query.Name} is {query.Age}";
    }
}
 
public class ValidatedQuery
{
    [FromQuery]
    public string? Name { get; set; }
    
    public int Age { get; set; }
 
    public class ValidatedQueryValidator : AbstractValidator<ValidatedQuery>
    {
        public ValidatedQueryValidator()
        {
            RuleFor(x => x.Name).NotNull();
        }
    }
}

snippet source | anchor



url: /guide/messaging/transports/azureservicebus.md